Мой JDialog иногда получает избыточное нажатие клавиши от вызывающего приложения? (код предоставлен) - PullRequest
0 голосов
/ 31 октября 2018

Я разрабатываю сложное музыкальное приложение в java (8) на основе Netbeans RCP 8.2, и у меня возникает странная проблема, которая возникает случайно.

У меня есть JFrame с панелью, которая содержит много JComponents. Я использую InputMap / ActionMap панели для захвата нажатий клавиш «a», «b», ..., «g» и вызова действия.

Действие извлекает ключевой символ, затем показывает JDialog, который содержит JTextField, используемый для редактирования некоторых текстовых данных.

Перед отображением диалога с dialog.setVisible (true), действие вызывает dialog.prepare (ключ char), чтобы JDialog мог инициализировать себя перед отображением. На самом деле dialog.prepare (ключ char) добавляет только переданный символ (преобразованный в верхний регистр) в JTextField.

Это работает большую часть времени: я нажимаю, например, "c" в JFrame, затем JDialog появляется с "C" в конце JTextField.

Но иногда, может быть 1/20 раз, я получаю "Cc" в конце поля JTextfield!

Это похоже на то, что исходное событие нажатия клавиши (которое приходит из JComponent на панели JFrame и было обработано с использованием InputMap / ActionMap) также было избыточно обработано JDialog.

Я убедился, что это не аппаратная проблема клавиатуры. Я воспроизвел проблему на втором компьютере с Win8 (у меня Win10).

Я безуспешно пытался 1 / использовать KeyListener вместо InputMap / ActionMap и 2 / используйте java.awt.EventQueue.invokeLater () для добавления ключевого символа в JTextField.

Я создал небольшое независимое приложение (см. Ниже), чтобы воспроизвести проблему и облегчить отладку ... но это небольшое приложение работает нормально, я не мог воспроизвести проблему :-( Затем я сравнил снова с моим реальным кодом приложения, и это действительно тот же код, за исключением того, что настоящее приложение представляет собой законченное приложение RCP Netbeans.

Так может ли быть, что 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 голосов
/ 31 октября 2018

Я нашел проблему, хотя она все еще не логична для меня. Объяснение приветствуется!

Все компоненты Swing должны быть созданы и изменены в потоке диспетчеризации событий (EDT).

Да, это было так в моем коде, и все же он не работал ...

Чтобы попытаться понять, что происходит, я подключил KeyListener к JTextField JDialog.

Я обнаружил, что когда он работал (ключ не удваивается), мой KeyListener получал только событие keyReleased (). Когда это не работало (ключ удвоил «Cc»), мой KeyListener получил событие keyTyped (), затем keyReleased ().

Итак, я понимаю, что механизм обработчика событий AWT / Swing «отправляет» каждый KeyEvent в текущий фокусированный компонент (а не в компонент, из которого происходит KeyEvent). Когда я показываю диалог где-то в середине последовательности keyPressed / keyTyped / keyReleased, иногда keyTyped был «неверно» направлен в JTextField.

Чтобы решить эту проблему, я выполнил весь код actionPerformed (), используя SwingUtilities.invokeLater (), чтобы убедиться, что диалог отображается после обработки всех ожидающих событий EDT, и, похоже, он работает до сих пор ...

Я мог бы найти хорошую информацию в Связывание клавиш Java , но я не понимаю, что рекомендуется использовать InputMap / ActionMap, чтобы избежать всех проблем KeyListeners с изменениями фокуса и т. Д. Я использовал только InputMap / ActionMap и до сих пор не помогло ...

Так почему InputMap не реагирует только на событие keyTyped ()?

0 голосов
/ 31 октября 2018

Но иногда, может быть 1/20 раз, я получаю "Cc" в конце поля JTextfield!

Случайные проблемы обычно являются результатом проблем с потоками.

Все компоненты Swing должны быть созданы и изменены на Event Dispatch Thread (EDT).

Код из метода main () не выполняется в EDT, что может быть проблемой.

Код для создания GUI должен быть заключен в SwingUtilities.invokeLater(...).

Для получения дополнительной информации ознакомьтесь с руководством по Swing по Параллелизм .

...