Как отключить мнемонику для JavaFX MenuBar?
На моем этапе я вставил меню вверху, как обычно для программ. Я хочу дать клавише ALT (вместе с клавишами со стрелками) некоторую логику в другом контексте на сцене. Но каждый раз, когда я нажимаю ALT и стрелки, я непреднамеренно перемещаюсь по меню меню.
Я хочу избежать этого или лучше полностью отключить это мнемоническое поведение. Не удалось установить для свойств mnemonicParsing всех меню значение false. Я также попробовал этот подход без успеха:
menubar.addEventFilter(KeyEvent.ANY, e -> e.consume());
2 ответа
При нажатии ALT первое меню получает фокус, а когда меню имеют фокус, клавиши со стрелками вызывают навигацию между ними, независимо от того, нажата ли клавиша ALT или нет. Поэтому, чтобы предотвратить такое поведение, вам нужно предотвратить фокусировку первого меню при нажатии ALT.
Глядя на MenuBarSkin
Исходный код конструктора класса, дает нам решение:
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(); } }); }); ... }
Решение:
Как вы уже догадались, решение состоит в том, чтобы использовать событие, когда ALT не работает, но вам нужно добавить EventHandler в scene
не menubar
:
scene.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
// your desired behavior
if(event.isAltDown())
event.consume();
}
});
Или вы можете переписать скин MenuBar. Javafx сделал выбор (или это ошибка?), Чтобы фокусировался на MenuBar, когда клавиша ALT нажата, а не отпущена, что является стандартным поведением в Eclipse, Netbeans, .... Кроме того, фокус не должен быть отдан Строка меню, когда клавиша ALT_GRAPH нажата или отпущена.
Вот патч, который я предлагаю. Обратите внимание, что релевантны только первые различия, последняя разница только для кода, который нужно скомпилировать, когда у него нет доступа к коду. В основном я разделил "firstMenuRunnable" на 3 функции
firstMenuRunnable используется только при нажатии клавиши F10
deselectOnKeyPressed используется, когда menuBar имеет фокус и нажата клавиша ALT.
focusOnFirstMenuOnKeyReleased используется, когда панель меню не имеет фокуса и клавиша ALT отпущена
Следовательно, можно иметь стандартное поведение, позволяющее ускорителям использовать клавишу ALT без фокусировки на 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();
}
});
Ниже приведен полный код моего 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) {
};
}
}