Search code examples
javafxmenuwindow

Release mnemonic when application loses focus


Background

Due to a known bug, JavaFX applications that use a MenuBar will keep the mnemonic selected ("latched") when the user presses Alt+Tab to switch to another program. When the user returns to the JavaFX application, the framework retains the latch.

Problem

If the user then presses a letter that corresponds to the mnemonic, then the letter is consumed and that menu is opened.

This behaviour is not what users have come to expect from an application: it interrupts workflow. Rather, Alt+Tab should not put the application in a state whereby the menu can open. This is conflating Alt by itself to trigger the menu with Alt+Tab, a conceptually different operation.

Other questions seek to disable the mnemonic, but we want to clear the latch so that when the user returns to the application, pressing a letter will not trigger opening the menu.

Question

How do you instruct a JavaFX application to clear the latched mnemonics when Alt+Tab is pressed (i.e., the application focus is lost)?


Solution

  • There are a couple of parts to this solution: releasing the mnemonics and consuming the Alt key press. Be sure to implement both.

    Release mnemonics

    One way to work around the bug is to add a focus listener to the application's Stage that fires a key released event for all known mnemonics. Given a Stage instance, we can iterate over all the main menu mnemonics as follows:

    stage.focusedProperty().addListener( ( c, lost, show ) -> {
      if( lost ) {
        for( final var mnemonics : stage.getScene().getMnemonics().values() ) {
          for( final var mnemonic : mnemonics ) {
            mnemonic.getNode().fireEvent( keyUp( ALT, false ) );
          }
        }
      }
      else if( show ) {
        // Make sure the menu does not capture focus.
        stage.getScene().focusOwnerProperty().get().requestFocus();
      }
    } );
    

    We'll need a few helper methods to create the key release event:

    public static Event keyDown( final KeyCode code, final boolean shift ) {
      return keyEvent( KEY_PRESSED, code, shift );
    }
    
    public static Event keyUp( final KeyCode code, final boolean shift ) {
      return keyEvent( KEY_RELEASED, code, shift );
    }
    
    private static Event keyEvent(
      final EventType<KeyEvent> type, final KeyCode code, final boolean shift ) {
      return new KeyEvent(
        type, "", "", code, shift, false, false, false
      );
    }
    

    With those methods in place, cycling windows by pressing Alt+Tab no longer opens the menu upon returning to the JavaFX application followed by pressing a mnemonic key (such as "f" for the "File" menu).

    Consume event

    Additionally, make the scene consume the event:

    scene.addEventHandler( KEY_PRESSED, event -> {
      final var code = event.getCode();
    
      if( event.isAltDown() && (code == ALT_GRAPH || code == ALT) ) {
        event.consume();
      }
    } );