我的JDialog有时会从调用应用程序中收到多余的击键吗? (提供代码)

时间:2018-10-30 22:20:45

标签: java multithreading swing netbeans-platform

我基于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;
      }
   }
}

2 个答案:

答案 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教程以了解更多信息。