How to disable mnemonic for JavaFX MenuBar?

寵の児 提交于 2021-01-27 05:27:10

问题


In my stage I have inserted a menubar at the top like usual for programs. I want to give the ALT key (together with arrow keys) some logic in another context within the stage. But everytime I press ALT and arrows I unintentionally navigate through the menus of the menubar, too.

I want to avoid that or better completely disable this mnemonic behavior. Setting the mnemonicParsing properties of all menus to false failed. I also tried this approach without success:

menubar.addEventFilter(KeyEvent.ANY, e -> e.consume());

回答1:


When ALT is pressed first menu gets focus and when menus have focus, arrow keys cause navigation among them whether ALT is pressed or not. So in order to prevent this behavior you need to prevent first menu getting focus when ALT is pressed.

Looking at the MenuBarSkin class' constructor source code, gives us the solution:

public MenuBarSkin(final MenuBar control) {
    ...
    Utils.executeOnceWhenPropertyIsNonNull(control.sceneProperty(), (Scene scene) -> {
        scene.getAccelerators().put(acceleratorKeyCombo, firstMenuRunnable);

        // put focus on the first menu when the alt key is pressed
        scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
            if (e.isAltDown()  && !e.isConsumed()) {
                firstMenuRunnable.run();
            }
        });
    });
    ...
}

Solution:

As you had already guessed, solution is to consume the event when ALT is down but you need to add the EventHandler to the scene not menubar:

scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
    @Override
    public void handle(KeyEvent event) {
        // your desired behavior
        if(event.isAltDown())
            event.consume();
    }
});



回答2:


Or you can rewrite MenuBar skin. Javafx has made the choice (or is it a bug ?) that focus is given to MenuBar when ALT key is pressed, not released, which is the standard behavior in Eclipse, Netbeans, .... Furthemore, focus should not be given to MenuBar when ALT_GRAPH key is pressed or released.

Here is the patch I propose. Note that only the first diffs are relevant, the last diff is just for the code to compile when one have not access to the code. Basically I have split the "firstMenuRunnable" in 3 functions

  • firstMenuRunnable is used only when F10 key is pressed

  • deselectOnKeyPressed is used when menuBar has focus and ALT key is pressed

  • focusOnFirstMenuOnKeyReleased is used when menuBar has not focus and ALT key is released

Hence, one can have the standard behavior, allowing accelerators using ALT key without focus being taken by MenuBar.

    --- com/sun/javafx/scene/control/skin/MenuBarSkin.java in C:\Program Files (x86)\Java\jdk1.8.0_131\javafx-src.zip
    +++ C:\Users\daniel\dev\xxx\Layout\src\com\stimulus\control\MenuBarSkin.java     
@@ -372,12 +491,21 @@
             scene.getAccelerators().put(acceleratorKeyCombo, firstMenuRunnable);

             // put focus on the first menu when the alt key is pressed
+            scene.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
+                altDown = false;
+            });
             scene.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
-                if (e.isAltDown()  && !e.isConsumed()) {
-                    firstMenuRunnable.run();
+                if (e.isAltDown() && !e.isConsumed() && e.getCode().equals(KeyCode.ALT)) {
+                    deselectMenusOnKeyPressed.run();
+                    altDown = true;
                 }
             });
+            scene.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
+                if (altDown) {
+                    focusOnFirstMenuOnKeyReleased.run();
+                }
         });
+        });

         ParentTraversalEngine engine = new ParentTraversalEngine(getSkinnable());
         engine.addTraverseListener(this);
    @@ -434,7 +453,50 @@
                 }
             };

    +    private boolean menuDeselectedOnKeyPressed = false;

    +    Runnable deselectMenusOnKeyPressed = new Runnable() {
    +        public void run() {
    +            /*
    +             ** check that this menubar's container has contents,
    +             ** and that the first item is a MenuButton....
    +             ** otherwise the transfer is off!
    +             */
    +            menuDeselectedOnKeyPressed = false;
    +            if (container.getChildren().size() > 0) {
    +                if (container.getChildren().get(0) instanceof MenuButton) {
    +//                        container.getChildren().get(0).requestFocus();
    +                    if (focusedMenuIndex >= 0) {
    +                        unSelectMenus();
    +                        menuDeselectedOnKeyPressed = true;
    +                    }
    +                }
    +            }
    +        }
    +    };
    +    Runnable focusOnFirstMenuOnKeyReleased = new Runnable() {
    +        public void run() {
    +            /*
    +             ** check that this menubar's container has contents,
    +             ** and that the first item is a MenuButton....
    +             ** otherwise the transfer is off!
    +             */
    +            if (container.getChildren().size() > 0) {
    +                if (container.getChildren().get(0) instanceof MenuButton) {
    +//                        container.getChildren().get(0).requestFocus();
    +                    if (focusedMenuIndex == -1 && !menuDeselectedOnKeyPressed) {
    +                        unSelectMenus();
    +                        menuModeStart(0);
    +                        openMenuButton = ((MenuBarButton) container.getChildren().get(0));
    +                        openMenu = getSkinnable().getMenus().get(0);
    +                        openMenuButton.setHover();
    +                    }
    +                }
    +            }
    +        }
    +    };
    +
         private boolean pendingDismiss = false;

         // For testing purpose only.
    @@ -650,9 +712,23 @@
                 menuButton.textProperty().bind(menu.textProperty());
                 menuButton.graphicProperty().bind(menu.graphicProperty());
                 menuButton.styleProperty().bind(menu.styleProperty());
    +            // patch because MenuButtonSkin.AUTOHIDE is private
    +            final String AUTOHIDE;
    +            {
    +                try {
    +                    Class<?> clazz = MenuButtonSkin.class;
    +//                    System.out.println("fields = " + Arrays.asList(clazz.getDeclaredFields()).toString());
    +                    Field field = clazz.getDeclaredField("AUTOHIDE");
    +                    field.setAccessible(true);
    +                    AUTOHIDE = (String) field.get(this);
    +                    field.setAccessible(false);
    +                } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException ex) {
    +                    throw new UnsupportedOperationException(ex);
    +                }
    +            }
                 menuButton.getProperties().addListener((MapChangeListener<Object, Object>) c -> {
    -                 if (c.wasAdded() && MenuButtonSkin.AUTOHIDE.equals(c.getKey())) {
    -                    menuButton.getProperties().remove(MenuButtonSkin.AUTOHIDE);
    +                if (c.wasAdded() && AUTOHIDE.equals(c.getKey())) {
    +                    menuButton.getProperties().remove(AUTOHIDE);
                         menu.hide();
                     }
                 });

Below is the complete code of my MenuBar:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.stimulus.control;

import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.Skin;

/**
 *
 * @author daniel
 */
public class CustomMenuBar extends MenuBar {

    public CustomMenuBar() {
    }

    public CustomMenuBar(Menu... menus) {
        super(menus);
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new MenuBarSkin(this) {

        };
    }

}


来源:https://stackoverflow.com/questions/36009964/how-to-disable-mnemonic-for-javafx-menubar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!