When embedding a Menu in a Swing window through a JFXPanel
, I cannot close the menu by clicking on it. Sometimes it blinks, as if it closed and immediately reopened.
package testjavafx;
public class TestMenuJavaFX extends Application {
@Override
public void start(Stage primaryStage) {
MenuBar menuBar = new MenuBar(
new Menu("Menu 1", null,
new MenuItem("Menu item 1-1"),
new MenuItem("Menu item 1-2")),
new Menu("Menu 2", null,
new MenuItem("Menu item 2-1"),
new MenuItem("Menu item 2-2")),
new Menu("Menu 3", null,
new MenuItem("Menu item 3-1"),
new MenuItem("Menu item 3-1")));
menuBar.setPrefWidth(300);
Region root = new Pane(menuBar);
root.setPrefSize(300, 185);
useJFXPanel(root);
//usePrimaryStage(primaryStage, root);
}
private static void useJFXPanel(Region root) {
JFXPanel jfxPanel = new JFXPanel();
jfxPanel.setScene(new Scene(root));
JFrame jFrame = new JFrame("test menu JavaFX");
jFrame.setSize((int) root.getWidth(), (int) root.getHeight());
jFrame.add(jfxPanel);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
}
private static void usePrimaryStage(Stage primaryStage, Parent root) {
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(TestMenuJavaFX.class, args);
}
}
By using the method usePrimaryStage
I get the expected behavior (click on the menu to open it, click again to close it), but with useJFXPanel
the problem appears.
This is an event handling issue, the mouse click is first dispatched to the JFXPanel as a Swing mouse event then the JFXPanel
internally dispatches a JavaFX mouse event to its embedded Scene
.
It appears that, during the Swing part, the menu loses focus and closes, and when the event reaches the Menu instance it finds it closed and therefore opens it.
I tried to inherit the Menu
class to add a mouse click event handler to it, however it does not handle mouse clicks, and using the showing/shown and hiding/hidden events provided did not help it (because the problem happens earlier).
I also tried to subclass MenuBar
to add a mouse click event handler, but the handler is only called when clicking on the bar outside of a menu, so no luck here, and to subclass JFXPanel
to override processMouseEvent
and retrieve the MenuBarButton
instance through reflection black magic but i couldn't make it work.
This is a bug, right? And is there a (easy and clean, ideally) workaround to this issue?
I'm using OpenJDK 11.0.10.9 and JavaFX 17.0.0.1.
On my system, this wouldn't run, but hung on startup because the JFrame
is created and shown on the wrong thread. Correcting that did display the behavior you describe, which does appear to be a bug.
I found one workaround, which is to capture a ON_HIDING
event and schedule a call to hide the menu further down the event queue, using Platform.runLater(...)
. The resulting code looks like;
public class TestMenuJavaFX extends Application {
@Override
public void start(Stage primaryStage) {
MenuBar menuBar = new MenuBar(
new Menu("Menu 1", null,
new MenuItem("Menu item 1-1"),
new MenuItem("Menu item 1-2")),
new Menu("Menu 2", null,
new MenuItem("Menu item 2-1"),
new MenuItem("Menu item 2-2")),
new Menu("Menu 3", null,
new MenuItem("Menu item 3-1"),
new MenuItem("Menu item 3-1")));
menuBar.setPrefWidth(300);
menuBar.getMenus().forEach(menu -> {
menu.addEventHandler(Menu.ON_HIDING, e -> {
Platform.runLater(menu::hide);
});
});
Region root = new Pane(menuBar);
root.setPrefSize(300, 185);
useJFXPanel(root);
//usePrimaryStage(primaryStage, root);
}
private static void useJFXPanel(Region root) {
JFXPanel jfxPanel = new JFXPanel();
jfxPanel.setScene(new Scene(root));
SwingUtilities.invokeLater(() -> {
JFrame jFrame = new JFrame("test menu JavaFX");
jFrame.setSize((int) root.getWidth(), (int) root.getHeight());
jFrame.add(jfxPanel);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
});
}
private static void usePrimaryStage(Stage primaryStage, Parent root) {
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
public static void main(String[] args) {
launch(TestMenuJavaFX.class, args);
}
}