当我调用component.requestFocusInWindow()
时,Swing使异步FOCUS_GAINED
和FOCUS_LOST
事件入队,而不是同步转移焦点。作为一种解决方法,DefaultKeyboardFocusManager
似乎试图通过延迟键盘事件的分发直到焦点事件完成分发来模拟同步切换焦点。但这似乎无法正常工作。
问题:是否有任何方法可以在Swing中同步更改焦点? DefaultKeyboardFocusManager
真的是在尝试模拟同步焦点吗?它是否存在严重的错误?是否有可以正确执行此操作的焦点管理器?
动机:我有一个JTextField
,可以在焦点已满时自动转移焦点。在使用java.awt.Robot
编写集成测试时,我需要它的行为是确定性的并且不依赖于时序。
没有得到足够答复的相关问题:How to grab focus now?
这是一个演示。预期的行为:按住一个键时,每个JTextField
将包含一个字符。实际行为:焦点变化得不够快,因此它们会得到多个字符。
更新:请注意,此行为并非特定于以编程方式更改焦点。在第三个示例中,我删除了自定义焦点调用,而是简单地将文档更新延迟设置为500ms。然后我输入了1Tab2Tab3Tab4Tab5Tab6Tab7Tab8Tab9Tab0。如您所见,数字已正确排队,但制表符被删除。
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
是非常异步的,那么应该将其包装为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();
}
});
}
}