¿Cómo solicitar foco sincrónicamente en Swing?

Cuando llamocomponent.requestFocusInWindow(), Columpios en cola asíncronosFOCUS_GAINED yFOCUS_LOST eventos en lugar de transferir sincrónicamente el foco. Como solución, parece queDefaultKeyboardFocusManager está tratando desimular cambiar de forma sincrónica el enfoque retrasando el envío de eventos de teclado hasta que los eventos de enfoque hayan terminado de enviarse. Pero parece que esto no está funcionando correctamente.

Pregunta: ¿Hay alguna forma de cambiar el enfoque de forma sincrónica en Swing? EsDefaultKeyboardFocusManager ¿Realmente tratando de simular el enfoque sincrónico, y está realmente defectuoso? ¿Hay un administrador de enfoque que hace esto correctamente?

Motivación: tengo unJTextField que transfiere automáticamente el foco cuando está lleno. Al escribir pruebas de integración usandojava.awt.RobotNecesito que su comportamiento sea determinista y no dependiente del tiempo.

Pregunta relacionada que no obtuvo mucha respuesta:Cómo agarrar el focoahora?

Aquí hay una demo. Comportamiento esperado: cuando mantienes presionada una tecla, cadaJTextField contendrá un solo carácter. Comportamiento real: el enfoque no cambia lo suficientemente rápido para que obtengan múltiples personajes.

Actualizar: Tenga en cuenta que este comportamiento no es específico para cambiar de enfoque de manera progamática. En el tercer ejemplo, saqué las llamadas de enfoque personalizado y en su lugar simplemente configuré el retraso de actualización del documento a 500 ms. Entonces escribi1Lengüeta2Lengüeta3Lengüeta4Lengüeta5Lengüeta6Lengüeta7Lengüeta8Lengüeta9Lengüeta0. Como puede ver, los dígitos se colocan en la cola correctamente, pero las pulsaciones de las pestañas se caen.

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

Respuestas a la pregunta(1)

Su respuesta a la pregunta