java - 如何在Swing中同步请求焦点?

标签 java swing focus awt keyboardfocusmanager

当我调用 component.requestFocusInWindow() 时,Swing 将异步 FOCUS_GAINEDFOCUS_LOST 事件排入队列,而不是同步转移焦点。作为解决方法,DefaultKeyboardFocusManager 似乎正在尝试通过延迟键盘事件的分派(dispatch)直到焦点事件完成分派(dispatch)来模拟同步切换焦点。但它似乎无法正常工作。

问题:在Swing中有什么方法可以同步改变焦点吗? DefaultKeyboardFocusManager 真的在尝试模拟同步焦点吗?它有严重的错误吗?是否有可以正确执行此操作的焦点管理器?

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

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

这是一个演示。预期行为:当您按住一个键时,每个 JTextField 将包含一个字符。实际行为:焦点变化不够快,因此它们会出现多个字符。

更新:请注意,此行为并非特定于以编程方式更改焦点。在第三个示例中,我去掉了自定义焦点调用,而是简单地将文档更新延迟设置为 500 毫秒。然后我输入 1Tab2Tab3Tab4制表符5制表符6制表符< kbd>7制表符8制表符9制表符 0。如您所见,数字已正确排队,但 Tab 键按下被丢弃。

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();
    }
}

最佳答案

是的,Focus 是非常异步的,那么应该包装在 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();
            }
        });
    }
}

关于java - 如何在Swing中同步请求焦点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11268936/

相关文章:

java - 如何在Android上使用Intent获取文件夹路径

java - Integer.parseInt() 和 Integer.toString() 运行时

java - 如何在 android 中解决此错误 - java.lang.RuntimeException : Only one Looper may be created per thread

java - 设计——只让一个线程跨虚拟机运行

Java在netbeans中清除jFrame

java - 如何在java中实例化处理程序

javascript - (Jquery/Javascript) 事件句柄 - 失去焦点

java - 使 JLabel 消失

jQuery-:focus Triggering When Switching Windows/Tabs

javascript - ref.current.focus() 不是函数