无需从 javafx 9 悬停鼠标即可突出显示上下文菜单中的第一个选项
First option in the context menu is highlighted without hovering the mouse from javafx 9
当我们右键单击上下文菜单时,列表中的第一个选项在没有悬停鼠标的情况下被突出显示。这仅在应用程序打开后第一次右击时发生。此行为是从 javafx-9 中观察到的。直到 javafx-8 它工作正常。
尝试使用示例代码:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class SampleContextMenu extends Application {
// labels
Label l;
public static void main(String args[]) {
// launch the application
launch(args);
}
// launch the application
public void start(Stage stage) {
// set title for the stage
stage.setTitle("creating contextMenu ");
// create a label
Label label1 = new Label("This is a ContextMenu example ");
// create a menu
ContextMenu contextMenu = new ContextMenu();
// create menuitems
MenuItem menuItem1 = new MenuItem("menu item 1");
MenuItem menuItem2 = new MenuItem("menu item 2");
MenuItem menuItem3 = new MenuItem("menu item 3");
// add menu items to menu
contextMenu.getItems().add(menuItem1);
contextMenu.getItems().add(menuItem2);
contextMenu.getItems().add(menuItem3);
// create a tilepane
TilePane tilePane = new TilePane(label1);
// setContextMenu to label
label1.setContextMenu(contextMenu);
// create a scene
Scene sc = new Scene(tilePane, 200, 200);
// set the scene
stage.setScene(sc);
stage.show();
}
}
经过一番挖掘后,发现罪魁祸首(可以这么说)是场景初始显示时的默认焦点遍历 - 即聚焦第一个可聚焦节点,在 contextMenu 的情况下是第一项。
首先尝试 hack-around 的:当项目获得焦点时请求焦点回到场景的根。步骤:
- 在 contextMenu 上注册一个 onShown 处理程序
- 在handler中,抓取包含contextMenu的场景:此时它的focusOwner还是null,所以我们需要在它的focusOwner上注册一个changeListener 属性
- 在侦听器中,第一次更改 focusOwner(旧值为 null)时,请求关注根并清理侦听器
注意:这还不够好,原来只是一个装饰性的黑客,评论中指出有几个小问题
- 请求焦点到场景根禁用键盘导航
- 第一项仍处于活动状态:按 enter 激活其操作
下一步尝试(现在真的很脏,需要访问 non-public classes 的隐藏实现细节!):替换首先尝试
- 抓取包含的 ContextMenuContent(com.sun.xx 中的内部 class),它是焦点项的祖父母
- 请求关注该内容以使突出显示消失
- 更新要注意的内容 no-item-focused(对私有字段的反射访问)
在代码中:
contextMenu.setOnShown(e -> {
Scene scene = contextMenu.getScene();
scene.focusOwnerProperty().addListener((src, ov, nv) -> {
// focusOwner set after first showing
if (ov == null) {
// transfer focus to root
// old hack (see the beware section) on why it doesn't work
// scene.getRoot().requestFocus();
// next try:
// grab the containing ContextMenuContainer and force the internal
// book-keeping into no-item-focused state
Parent parent = nv.getParent().getParent();
parent.requestFocus();
// reflective setting of private field, this is my utility method, use your own ;)
invokeSetFieldValue(ContextMenuContent.class, parent, "currentFocusedIndex", -1);
// cleanup
contextMenu.setOnShown(null);
}
});
});
为方便起见,这里是反射访问内部字段的实用方法(没有火箭科学,只是简单的 java ;)
public static void invokeSetFieldValue(Class<?> declaringClass, Object target, String name, Object value) {
try {
Field field = declaringClass.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
当我们右键单击上下文菜单时,列表中的第一个选项在没有悬停鼠标的情况下被突出显示。这仅在应用程序打开后第一次右击时发生。此行为是从 javafx-9 中观察到的。直到 javafx-8 它工作正常。
尝试使用示例代码:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class SampleContextMenu extends Application {
// labels
Label l;
public static void main(String args[]) {
// launch the application
launch(args);
}
// launch the application
public void start(Stage stage) {
// set title for the stage
stage.setTitle("creating contextMenu ");
// create a label
Label label1 = new Label("This is a ContextMenu example ");
// create a menu
ContextMenu contextMenu = new ContextMenu();
// create menuitems
MenuItem menuItem1 = new MenuItem("menu item 1");
MenuItem menuItem2 = new MenuItem("menu item 2");
MenuItem menuItem3 = new MenuItem("menu item 3");
// add menu items to menu
contextMenu.getItems().add(menuItem1);
contextMenu.getItems().add(menuItem2);
contextMenu.getItems().add(menuItem3);
// create a tilepane
TilePane tilePane = new TilePane(label1);
// setContextMenu to label
label1.setContextMenu(contextMenu);
// create a scene
Scene sc = new Scene(tilePane, 200, 200);
// set the scene
stage.setScene(sc);
stage.show();
}
}
经过一番挖掘后,发现罪魁祸首(可以这么说)是场景初始显示时的默认焦点遍历 - 即聚焦第一个可聚焦节点,在 contextMenu 的情况下是第一项。
首先尝试 hack-around 的:当项目获得焦点时请求焦点回到场景的根。步骤:
- 在 contextMenu 上注册一个 onShown 处理程序
- 在handler中,抓取包含contextMenu的场景:此时它的focusOwner还是null,所以我们需要在它的focusOwner上注册一个changeListener 属性
- 在侦听器中,第一次更改 focusOwner(旧值为 null)时,请求关注根并清理侦听器
注意:这还不够好,原来只是一个装饰性的黑客,评论中指出有几个小问题
- 请求焦点到场景根禁用键盘导航
- 第一项仍处于活动状态:按 enter 激活其操作
下一步尝试(现在真的很脏,需要访问 non-public classes 的隐藏实现细节!):替换首先尝试
- 抓取包含的 ContextMenuContent(com.sun.xx 中的内部 class),它是焦点项的祖父母
- 请求关注该内容以使突出显示消失
- 更新要注意的内容 no-item-focused(对私有字段的反射访问)
在代码中:
contextMenu.setOnShown(e -> {
Scene scene = contextMenu.getScene();
scene.focusOwnerProperty().addListener((src, ov, nv) -> {
// focusOwner set after first showing
if (ov == null) {
// transfer focus to root
// old hack (see the beware section) on why it doesn't work
// scene.getRoot().requestFocus();
// next try:
// grab the containing ContextMenuContainer and force the internal
// book-keeping into no-item-focused state
Parent parent = nv.getParent().getParent();
parent.requestFocus();
// reflective setting of private field, this is my utility method, use your own ;)
invokeSetFieldValue(ContextMenuContent.class, parent, "currentFocusedIndex", -1);
// cleanup
contextMenu.setOnShown(null);
}
});
});
为方便起见,这里是反射访问内部字段的实用方法(没有火箭科学,只是简单的 java ;)
public static void invokeSetFieldValue(Class<?> declaringClass, Object target, String name, Object value) {
try {
Field field = declaringClass.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}