2012-06-29 20 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