Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8211987

Menu bar gets input focus even if Alt-released event is consumed

XMLWordPrintable

    • b17
    • x86_64
    • windows_10

      ADDITIONAL SYSTEM INFORMATION :
      OS = Windows 10
      Java version = 1.8.0_141, 1.8.0_181 (latest 1.8.x), 9.x, 10.0.2.

      A DESCRIPTION OF THE PROBLEM :
      Pressing and releasing the Alt key inside a component like JTextArea/JTextField moves input focus to (activates) the menu bar even if there is a KeyListener on the text component that consumes the KeyEvent generated when releasing the Alt key.

      (I use this to trigger a special kind of selection in the text component when Alt is pressed, while dragging the mouse. After the mouse and Alt key are released, I do special processing, thus not needing to move focus to the menu, so the Alt-released event is consumed to avoid that.)

      Everything worked before, until Java 1.8.0_121. Starting with 1.8.0_141 (I didn't test with an update between 121 and 141) it doesn't work anymore (tested even with Java 9.x and 10.x).

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Create a JFrame containing a JMenuBar (with some menus inside) and a JTextArea.
      2. On the JTextArea register a KeyListener that consumes the KeyEvent received on the keyReleased(KeyEvent e) method when e.getKeyCode() == KeyEvent.VK_ALT (=> e.consume()).
      3. After displaying the JFrame, press and release (type) the Alt key inside the JTextArea.
      4. Focus should remain inside the JTextArea, but the menu is activated instead.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Input focus should be inside the JTextArea, allowing to type inside it.
      ACTUAL -
      The menu bar receives the input focus (the first menu item in the menu bar gets activated), although the generated key event was consumed.
      This indicates that although the event was consumed by the listener registered on the JTextArea, the event is still processed in a default manner after, its consumed status being ignored.

      ---------- BEGIN SOURCE ----------
      package test.java.swing;

      import java.awt.event.KeyAdapter;
      import java.awt.event.KeyEvent;
      import java.lang.reflect.InvocationTargetException;

      import javax.swing.JFrame;
      import javax.swing.JMenu;
      import javax.swing.JMenuBar;
      import javax.swing.JMenuItem;
      import javax.swing.JTextArea;
      import javax.swing.SwingUtilities;
      import javax.swing.UIManager;
      import javax.swing.UnsupportedLookAndFeelException;

      import junit.extensions.jfcunit.JFCTestCase;

      /**
       * Try to demonstrate the wrong behavior.
       */
      public class AltFocusIssueTest extends JFCTestCase {

        /**
         * Menu inside menu bar of the frame.
         */
        private JMenu menu;

        /**
         * Text area to test on.
         */
        private JTextArea ta;

        /**
         * Test that the text area loses input focus although Alt-released event is consumed.
         *
         * @throws InterruptedException
         * @throws InvocationTargetException
         */
        public void testAltEvents() throws InvocationTargetException, InterruptedException {
          SwingUtilities.invokeAndWait(() -> {
            try {
              createUI();
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
              e.printStackTrace();
            }
          });
          SwingUtilities.invokeLater(() -> ta.requestFocusInWindow());
          sleep(100);
          assertTrue("Text area should have input focus", ta.isFocusOwner());
          assertFalse(menu.isSelected());

          // Simulate an Alt-typed event
          SwingUtilities.invokeLater(
              () -> ta.dispatchEvent(
                  new KeyEvent(
                      ta,
                      KeyEvent.KEY_PRESSED,
                      System.currentTimeMillis(),
                      0,
                      KeyEvent.VK_ALT,
                      KeyEvent.CHAR_UNDEFINED,
                      KeyEvent.KEY_LOCATION_LEFT)));
          SwingUtilities.invokeLater(
              () -> ta.dispatchEvent(
                  new KeyEvent(
                      ta,
                      KeyEvent.KEY_RELEASED,
                      System.currentTimeMillis(),
                      0,
                      KeyEvent.VK_ALT,
                      KeyEvent.CHAR_UNDEFINED,
                      KeyEvent.KEY_LOCATION_LEFT)));
          sleep(100);
          // Since the event is consumed, I expect the input focus to be in the text area
          assertTrue("Text area should still have input focus.", ta.isFocusOwner());
          // OR
          assertFalse(
              "Focus should not be changed from the text area",
              SwingUtilities.getRootPane(ta).isFocusOwner());
          // OR
          assertFalse("Menu must not be selected", menu.isSelected());
        }

        /**
         * Builds UI to test.
         *
         * @throws ClassNotFoundException
         * @throws InstantiationException
         * @throws IllegalAccessException
         * @throws UnsupportedLookAndFeelException
         */
        private void createUI() throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException {
          // Install Windows L&F
          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

          JFrame frame = new JFrame();
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

          JMenuBar menuBar = new JMenuBar();
          frame.setJMenuBar(menuBar);

          menu = new JMenu("Menu");
          menu.add(new JMenuItem("Menu item"));
          menuBar.add(menu);
          
          ta = new JTextArea();
          frame.getContentPane().add(ta);

          ta.addKeyListener(
              new KeyAdapter() {
                /**
                 * @see java.awt.event.KeyAdapter#keyReleased(java.awt.event.KeyEvent)
                 */
                @Override
                public void keyReleased(KeyEvent e) {
                  if (e.getKeyCode() == KeyEvent.VK_ALT) {
                    /*
                     * This is where I need to do special handling of the Alt-released event.
                     * After, nobody else must react to this event, thus I consume it.
                     */
                    e.consume();
                  }
                }
              });

          frame.setSize(400, 300);
          frame.setVisible(true);
        }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      After investigating what changed between Java versions 1.8.0_121 and 1.8.0_141, I found out that the cause is in the WindowsRootPaneUI class.
      The nested AltProcessor class implementation changed: in the postProcessKeyEvent(KeyEvent ev) method, the first condition was changed from (1.8.0_121):

                  if(ev.isConsumed()) {
                      // do not manage consumed event
                      return false;
                  }

      to (1.8.0_141):

                  if(ev.isConsumed() && ev.getKeyCode() != KeyEvent.VK_ALT) {
                      // mnemonic combination, it's consumed, but we need
                      // set altKeyPressed to false, otherwise after selection
                      // component by mnemonic combination a menu will be open
                      altKeyPressed = false;
                      return false;
                  }

      FREQUENCY : always


            psadhukhan Prasanta Sadhukhan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: