Jak synchronicznie żądać fokusa w Swing?

Kiedy dzwonięcomponent.requestFocusInWindow(), Swing kolejkuje asynchronicznieFOCUS_GAINED iFOCUS_LOST zdarzenia zamiast synchronicznie przenosić fokus. Jako obejście wydaje się, żeDefaultKeyboardFocusManager próbujesymulować synchroniczne przełączanie ostrości przez opóźnianie wysyłania zdarzeń klawiatury do czasu zakończenia wysyłania zdarzeń fokusowych. Ale wydaje się, że to nie działa prawidłowo.

Pytanie: Czy jest jakiś sposób na synchroniczną zmianę ostrości w Swing? JestDefaultKeyboardFocusManager naprawdę próbuję symulować synchroniczny fokus i czy jest on poważnie wadliwy? Czy istnieje menedżer fokusu, który robi to poprawnie?

Motywacja: mamJTextField który automatycznie przenosi fokus, gdy jest pełny. Pisząc testy integracyjne przy użyciujava.awt.Robot, Potrzebuję, aby jego zachowanie było deterministyczne i nie zależne od czasu.

Podobne pytanie, na które nie uzyskano odpowiedzi:Jak chwycić ostrośćteraz?

Oto demo. Oczekiwane zachowanie: po przytrzymaniu klawisza każdyJTextField będzie zawierać pojedynczy znak. Rzeczywiste zachowanie: fokus nie zmienia się wystarczająco szybko, aby uzyskać wiele znaków.

Aktualizacja: Zauważ, że to zachowanie nie jest specyficzne dla progresywnej zmiany ostrości. W trzecim przykładzie wykonałem niestandardowe wywołania fokusa i zamiast tego ustawiłem opóźnienie aktualizacji dokumentu na 500 ms. Potem wpisałem1Patka2Patka3Patka4Patka5Patka6Patka7Patka8Patka9Patka0. Jak widzisz, cyfry są ustawiane w kolejce prawidłowo, ale naciśnięcia klawiszy są odrzucane.

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

questionAnswers(1)

yourAnswerToTheQuestion