2012-06-29 16 views
3

当我打电话给component.requestFocusInWindow()时,Swing排队异步FOCUS_GAINEDFOCUS_LOST事件,而不是同步传输焦点。解决方法是,DefaultKeyboardFocusManager正在尝试模拟通过延迟键盘事件的调度来同步切换焦点,直到焦点事件完成调度。但它似乎没有正常工作。如何在Swing中同步请求焦点?

问:有没有什么办法可以在Swing中同步改变焦点?是DefaultKeyboardFocusManager真的试图模拟同步聚焦,它是严重的越野车吗?有没有一个焦点经理这样做是否正确?

动机:我有一个JTextField,它会在焦点满时自动转移焦点。在使用java.awt.Robot编写集成测试时,我需要它的行为是确定性的,而不是依赖于时序。

相关疑问并没有得到太多回应:How to grab focus now?

这里有一个演示。预期行为:当您按住某个键时,每个JTextField将包含一个字符。实际行为:焦点速度不够快,所以它们会得到多个字符。

更新:请注意,此行为不是特定于程序更改焦点。在第三个示例中,我取出了自定义焦点调用,而只是将文档更新延迟设置为500毫秒。然后我输入 标签标签标签标签标签标签标签TabTab 。正如你所看到的那样,这些数字已经正确地排队,但是标签按下了。

Screenshot Screenshot after holding down 1 button enter image description here

import java.awt.*; 
import java.awt.event.*; 
import java.text.DateFormat; 
import java.text.SimpleDateFormat; 
import java.util.ArrayList; 
import java.util.Date; 
import java.util.logging.*; 

import javax.swing.*; 
import javax.swing.GroupLayout.Group; 
import javax.swing.event.DocumentEvent; 
import javax.swing.event.DocumentListener; 

public class AwtEventListenerDemo { 
    public static final Logger logger = Logger.getLogger(AwtEventListenerDemo.class.getName()); 
    private static String keyEventToString(KeyEvent keyEvent) { 
     int id = keyEvent.getID(); 
     String eventName = 
      id == KeyEvent.KEY_PRESSED ? "key_pressed" : 
      id == KeyEvent.KEY_TYPED ? "key_typed" : 
      id == KeyEvent.KEY_RELEASED? "key_released" : "unknown " + id; 
     String what = id == KeyEvent.KEY_TYPED ? "" + keyEvent.getKeyChar() : "#" + keyEvent.getKeyCode(); 
     String componentString = keyEvent.getComponent().getName(); 
     if (componentString == null) componentString = keyEvent.getComponent().toString(); 
     return String.format("%12s %4s on %s", eventName, what, componentString); 
    } 
    private static String focusEventToString(FocusEvent focusEvent) { 
     int id = focusEvent.getID(); 
     String eventName = id == FocusEvent.FOCUS_GAINED ? "focus_gained" : 
      id == FocusEvent.FOCUS_LOST ? "focus_lost" : 
       null; 
     if (eventName == null) return focusEvent.toString(); 
     String componentString = focusEvent.getComponent().getName(); 
     if (componentString == null) componentString = focusEvent.getComponent().toString(); 
     return String.format("%12s on %s", eventName, componentString); 
    } 
    private final AWTEventListener loggingListener = new AWTEventListener() { 
     @Override public void eventDispatched(AWTEvent event) { 
      if (event instanceof KeyEvent) { 
       KeyEvent keyEvent = (KeyEvent) event; 
       int id = keyEvent.getID(); 
       if (id == KeyEvent.KEY_PRESSED && keyEvent.getComponent() instanceof JTextField && ((JTextField)keyEvent.getComponent()).getDocument().getLength() == 1) { 
        EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); 
        ArrayList<AWTEvent> inQueue = new ArrayList<AWTEvent>(); 
        int[] interestingIds = new int[] {KeyEvent.KEY_PRESSED, KeyEvent.KEY_TYPED, KeyEvent.KEY_RELEASED, FocusEvent.FOCUS_GAINED, FocusEvent.FOCUS_LOST}; 
        for (int i: interestingIds) { 
         AWTEvent peek = eventQueue.peekEvent(i); 
         if (peek != null) 
          inQueue.add(peek); 
        } 
        ArrayList<String> inQueueString = new ArrayList<String>(); 
        for (AWTEvent peek: inQueue) { 
         if (peek instanceof KeyEvent) { 
          inQueueString.add(keyEventToString((KeyEvent) peek)); 
         } else if (peek instanceof FocusEvent) { 
          inQueueString.add(focusEventToString((FocusEvent) peek)); 
         } 
        } 

        logger.info(String.format("Still in the queue (in no particular order): %s", inQueueString)); 
       } 
       logger.info(keyEventToString(keyEvent)); 
      } else { 
       logger.info(event.toString()); 
      } 
     } 
    }; 
    private JFrame jframe; 

    public void init() { 
     long mask = AWTEvent.KEY_EVENT_MASK; // AWTEvent.MOUSE_EVENT_MASK | 
     Toolkit.getDefaultToolkit().addAWTEventListener(loggingListener, mask); 
     if (jframe == null) jframe = new JFrame(AwtEventListenerDemo.class.getSimpleName()); 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override public void run() { 
       initUI(); 
      } 
     }); 
    } 
    public void cleanupForRestart() { 
     Toolkit.getDefaultToolkit().removeAWTEventListener(loggingListener); 
    } 
    public void destroy() { 
     cleanupForRestart(); 
     jframe.setVisible(false); 
     jframe = null; 
    } 

    public void initUI() { 
     GroupLayout groupLayout = new GroupLayout(jframe.getContentPane()); 
     jframe.getContentPane().removeAll(); 
     jframe.getContentPane().setLayout(groupLayout); 

     JButton jbutton = new JButton(new AbstractAction("Restart") { 
      private static final long serialVersionUID = 1L; 
      @Override public void actionPerformed(ActionEvent e) { 
       cleanupForRestart(); 
       init(); 
      } 
     }); 
     groupLayout.setAutoCreateGaps(true); 
     groupLayout.setAutoCreateContainerGaps(true); 
     Group verticalGroup = groupLayout.createSequentialGroup() 
       .addComponent(jbutton); 
     Group horizontalGroup = groupLayout.createParallelGroup() 
       .addComponent(jbutton); 
     groupLayout.setVerticalGroup(verticalGroup); 
     groupLayout.setHorizontalGroup(horizontalGroup); 
     for (int i = 0; i < 10; i++) { 
      final JTextField jtextfield = new JTextField(); 
      jtextfield.setName(String.format("JTextField %d", i)); 
      verticalGroup.addComponent(jtextfield); 
      horizontalGroup.addComponent(jtextfield); 
      boolean useDocumentListener = true; 
      final boolean useFocusRootAncestor = false; 
      if (useDocumentListener) { 
       DocumentListener listener = new DocumentListener() { 
        @Override public void removeUpdate(DocumentEvent e) { } 
        @Override public void insertUpdate(DocumentEvent e) { 

         // Simulate a slow event listener. When the listener is 
         // slow, the problems get worse. 
         try { 
          Thread.sleep(50); 
         } catch (InterruptedException e1) { 
          logger.warning(e1.toString()); 
         } 
         if (e.getDocument().getLength() > 0) { 
          // These two methods of transferring focus appear 
          // equivalent. 
          if (useFocusRootAncestor) { 
           Container focusRoot = jtextfield.getFocusCycleRootAncestor(); 
           FocusTraversalPolicy policy = focusRoot.getFocusTraversalPolicy(); 
           Component nextComponent = policy.getComponentAfter(focusRoot, jtextfield); 
           nextComponent.requestFocusInWindow(); 
          } else { 
           KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(jtextfield); 
          } 
         } 
        } 
        @Override public void changedUpdate(DocumentEvent e) { } 
       }; 
       jtextfield.getDocument().addDocumentListener(listener); 
      } 
     } 

     if (!jframe.isVisible()) { 
      jframe.pack(); 
      jframe.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 
      jframe.setVisible(true); 
     } 
    } 
    public static void main(String[] argv) { 
     // Use a single-line console log handler. 
     LogManager.getLogManager().reset(); 
     Formatter formatter = new Formatter() { 
      private SimpleDateFormat dateFormat = new SimpleDateFormat("kk:mm:ss.SSS"); 
      @Override 
      public String format(LogRecord logRecord) { 
       Date date = new Date(logRecord.getMillis()); 
       DateFormat.getTimeInstance().format(date); 
       return String.format("%s %s %s %s%n", 
         dateFormat.format(date), 
         logRecord.getLoggerName(), 
         logRecord.getLevel(), 
         logRecord.getMessage()); 
      } 
     }; 
     ConsoleHandler consoleHandler = new ConsoleHandler(); 
     consoleHandler.setFormatter(formatter); 
     consoleHandler.setLevel(Level.FINEST); 
     logger.addHandler(consoleHandler); 
     Logger focusLogger = Logger.getLogger("java.awt.focus.DefaultKeyboardFocusManager"); 
     focusLogger.setLevel(Level.FINEST); 
     focusLogger.addHandler(consoleHandler); 


     final AwtEventListenerDemo demo = new AwtEventListenerDemo(); 
     demo.init(); 
    } 
} 

回答

4

是,Focus是相当异步的,那么应包INT invokeLater的,没办法

编辑

and nice workaround by @camickr

import javax.swing.*; 
import java.awt.*; 
import java.awt.event.*; 
//http://www.coderanch.com/t/342205/GUI/java/Tab-order-swing-components 
public class Testing { 

    private static final long serialVersionUID = 1L; 
    private Component[] focusList; 
    private int focusNumber = 0; 
    private JFrame frame; 

    public Testing() { 
     JTextField tf1 = new JTextField(5); 
     JTextField tf2 = new JTextField(5); 
     JTextField tf3 = new JTextField(5); 
     JButton b1 = new JButton("B1"); 
     JButton b2 = new JButton("B2"); 
     tf2.setEnabled(false); 
     focusList = new Component[]{tf1, b1, tf2, b2, tf3}; 
     JPanel panel = new JPanel(new GridLayout(5, 1)); 
     panel.add(tf1); 
     panel.add(b1); 
     panel.add(tf2); 
     panel.add(b2); 
     panel.add(tf3); 
     frame = new JFrame(); 
     frame.setFocusTraversalPolicy(new MyFocusTraversalPolicy()); 
     frame.add(panel); 
     frame.pack(); 
     frame.setLocation(150, 100); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     frame.setVisible(true); 
     KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() { 

      public boolean dispatchKeyEvent(KeyEvent ke) { 
       if (ke.getID() == KeyEvent.KEY_PRESSED) { 
        if (ke.getKeyCode() == KeyEvent.VK_TAB) { 
         Component comp = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); 
         if (comp.isEnabled() == false) { 
          if (ke.isShiftDown()) { 
           KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent(); 
          } else { 
           KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(); 
          } 
         } 
        } 
       } 
       return false; 
      } 
     }); 
    } 

    private class MyFocusTraversalPolicy extends FocusTraversalPolicy { 

     public Component getComponentAfter(Container focusCycleRoot, Component aComponent) { 
      focusNumber = (focusNumber + 1) % focusList.length; 
      return focusList[focusNumber]; 
     } 

     public Component getComponentBefore(Container focusCycleRoot, Component aComponent) { 
      focusNumber = (focusList.length + focusNumber - 1) % focusList.length; 
      return focusList[focusNumber]; 
     } 

     public Component getDefaultComponent(Container focusCycleRoot) { 
      return focusList[0]; 
     } 

     public Component getLastComponent(Container focusCycleRoot) { 
      return focusList[focusList.length - 1]; 
     } 

     public Component getFirstComponent(Container focusCycleRoot) { 
      return focusList[0]; 
     } 
    } 

    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 

      @Override 
      public void run() { 
       Testing testing = new Testing(); 
      } 
     }); 
    } 
} 
+0

+1我一直在想这个问题,认为Rob的'对话焦点',但没有时间回复。 –

+2

恐怕我不明白mKorbel的答案。 “那么应该包装int invokeLater,没办法”是什么意思? @ camickr's的链接文章告诉我们如何在模式对话框的setVisible后面调用'requestFocusInWindow'。代码示例显示如何跳过禁用的组件。但是他们都没有解决底层的问题,那就是requestFocusInWindow(有时)在调度已经排入队列的关键事件之前未能改变焦点。 – yonran