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) {
};
}
}