我基于Netbeans RCP 8.2在Java(8)中开发了一个复杂的音乐应用程序,但我遇到了一个随机发生的奇怪问题。
我有一个JFrame,其面板包含许多JComponent。我使用面板的InputMap / ActionMap捕获按键“ a”,“ b”,...,“ g”并调用动作。
该操作检索键char,然后显示一个JDialog,其中包含用于编辑某些文本数据的JTextField。
在显示带有dialog.setVisible(true)的对话框之前,该动作将调用dialog.prepare(char键),以便JDialog可以在显示之前对其进行初始化。实际上,dialog.prepare(char键)仅在JTextField中附加传递的char(转换为大写)。
这在大多数情况下都是有效的:例如,我在JFrame中按下“ c”,然后JDialog在JTextField的末尾显示为“ C”。
但是有时候,也许是1/20次,我在JTextfield的末尾得到了“ Cc”!
就像原始的按键事件(来自JFrame面板中的JComponent并使用InputMap / ActionMap处理)一样,JDialog也对其进行了冗余处理。
我确认这不是键盘硬件问题。我在装有Win8的另一台计算机上重现了该问题(我的计算机是Win10)。
我尝试不成功1 /使用KeyListener代替了InputMap / ActionMap 和2 /使用java.awt.EventQueue.invokeLater()将键char附加到JTextField中。
我创建了一个小型独立应用程序(请参见下文)以重现该问题并方便调试...但是此小应用程序运行良好,无法重现该问题:-(然后,我再次将其与真实的应用程序代码进行比较,并且除了真正的应用程序是一个完整的Netbeans RCP应用程序之外,它的确是 相同的代码。
那么Netbeans RCP是否会影响Swing处理关键事件的方式?在我看来很奇怪...
我迷路了,任何提示/建议的测试将不胜感激!
/**
* Try to reproduce double key problem... Failed because this works OK !! :-(
*/
public class PbKeyDouble extends JFrame {
MyDialog dialog;
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
PbKeyDouble o = new PbKeyDouble();
o.setVisible(true);
}
});
}
public PbKeyDouble() {
// GUI INITIALIZATION
// Add a basic panel
JPanel panel = new JPanel();
getContentPane().add(panel);
panel.setPreferredSize(new Dimension(300, 200));
JButton button = new JButton("BUTTON");
panel.add(button);
// Button not used, it's only to simulate the real app where a component in the panel has the focus
button.requestFocusInWindow();
// If "A" or "B" key is pressed anywhere, MyAction.actionPerformed() will be called
panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("A"), "MyAction");
panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("B"), "MyAction");
panel.getActionMap().put("MyAction", new MyAction());
// Prepare JFrame
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
}
private class MyAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("EDT? " + SwingUtilities.isEventDispatchThread()); // Always prints TRUE
if (dialog == null) {
dialog = new MyDialog();
}
// Retrieve the key used to trigger the action
char c = e.getActionCommand().charAt(0);
// Prepare the dialog (insert the char)
dialog.prepare(c);
// Show dialog
dialog.setVisible(true);
}
}
private class MyDialog extends JDialog {
JTextField textfield;
/**
* A simple dialog with just a textfield.
*/
public MyDialog() {
textfield = new JTextField("Hello");
textfield.setColumns(100);
getContentPane().add(textfield);
pack();
setLocationRelativeTo(null);
}
/**
* Append the key (uppercased) at the end of the textfield
*/
public void prepare(char c) {
String text = textfield.getText();
textfield.setText(text + " " + Character.toUpperCase(c));
}
/**
* Overridden to add a global key binding on ESC key to exit the dialog.
* <p>
* This is only to facilitate the test where I need to try many times the process pressing "a" ESC "a" ESC etc.
*
* @return
*/
@Override
protected JRootPane createRootPane() {
JRootPane contentPane = new JRootPane();
contentPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("ESCAPE"), "actionCancel");
contentPane.getActionMap().put("actionCancel", new AbstractAction("Cancel") {
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
return contentPane;
}
}
}
答案 0 :(得分:1)
我发现了问题,尽管对我来说仍然不合逻辑。欢迎解释!
所有Swing组件都应在事件调度线程(EDT)上创建和修改。
是的,在我的代码中就是这种情况,但仍然无法正常工作...
为了尝试了解发生了什么,我将KeyListener附加到JDialog的JTextField。
我发现,当它工作时(密钥未加倍),我的KeyListener仅收到keyReleased()事件。当它不起作用(键“ Cc”加倍)时,我的KeyListener收到一个keyTyped()事件,然后收到keyReleased()。
因此,我了解AWT / Swing事件处理程序机制会将每个KeyEvent“发送”到当前关注的组件(而不是发送到KeyEvent源自的组件)。当我在keyPressed / keyTyped / keyReleased序列中间的某个位置显示Dialog时,有时keyTyped被“错误地”定向到JTextField。
为解决这个问题,我使用SwingUtilities.invokeLater()执行了完整的actionPerformed()代码,以确保在处理所有EDT待处理事件后显示Dialog,并且到目前为止看来仍然有效...
我可以在Java keybinding中找到一些很好的信息,但我不明白的是,建议使用InputMap / ActionMap来避免所有KeyListeners出现焦点更改等问题。我仅使用InputMap / ActionMap和仍然没有帮助...
那么,为什么InputMap不只对keyTyped()事件做出反应?
答案 1 :(得分:0)
但是有时候,也许是1/20次,我在JTextfield的末尾得到了“ Cc”!
随机问题通常是线程问题的结果。
应在Event Dispatch Thread (EDT)
上创建和修改所有Swing组件。
main()方法中的代码未在EDT上执行,这可能是问题所在。
用于创建GUI的代码应包装在SwingUtilities.invokeLater(...)
中。
查看Concurrency上的Swing教程以了解更多信息。