ADDITIONAL SYSTEM INFORMATION :
Win 10 / jdk 14.0.1/ jfx 14.0.1
A DESCRIPTION OF THE PROBLEM :
When a menu is nested inside of another menu or ContextMenu that menu's items have their parentPopup set to the top level of the menu and not the actual context menu that they are within.
I think the problem is in the ContextMenu class. When it adds a MenuItem to the ContextMenu it overwrites the parentPopup of the MenuItem if there is a parentPopup already, which there's always one because it's nested inside of another menu already. So it just overwrites the MenuItem's parentPopup when it shouldn't.
Source code from jfx-sdk/14.0.1:
private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() {
@Override protected void onChanged(Change<MenuItem> c) {
while (c.next()) {
for (MenuItem item : c.getRemoved()) {
item.setParentPopup(null);
}
for (MenuItem item : c.getAddedSubList()) {
if (item.getParentPopup() != null) {
// we need to remove this item from its current parentPopup
// as a MenuItem should not exist in multiple parentPopup
// instances
item.getParentPopup().getItems().remove(item);
}
item.setParentPopup(ContextMenu.this);
}
}
}
};
My changes that I think would fix the problem, was unsuccessful in compiling jfx myself so I couldn't test this.
private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() {
@Override protected void onChanged(Change<MenuItem> c) {
while (c.next()) {
for (MenuItem item : c.getRemoved()) {
item.setParentPopup(null);
}
for (MenuItem item : c.getAddedSubList()) {
if (item.getParentPopup() != null) {
if(item.getParentMenu() != null) {
if(item.getParentMenu().getParentPopup() == this)
continue;
}
else {
// we need to remove this item from its current parentPopup
// as a MenuItem should not exist in multiple parentPopup
// instances
item.getParentPopup().getItems().remove(item);
}
}
item.setParentPopup(ContextMenu.this);
}
}
}
};
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Place a menu inside another menu and then compare the parentPopup of the nested menu and any of the nested menu's item's parentPopup.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The nested menu and the nested menu item's should have different a parentPopup because they are inside of different ContextMenus.
ACTUAL -
The nested menu and the nested menu item's have the same parentPopup. Thus you are unable to alter the nested menu's contextmenu because it's been orphaned.
---------- BEGIN SOURCE ----------
package application;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
MenuBar menuBar = new MenuBar();
Menu menu = new Menu("Menu");
Menu nestedMenu = new Menu("Nested");
MenuItem nestedMenuItem = new MenuItem("Nested Menu Item");
nestedMenu.getItems().add(nestedMenuItem);
menu.getItems().add(nestedMenu);
menuBar.getMenus().add(menu);
root.setTop(menuBar);
ContextMenu contextMenu = new ContextMenu();
Menu nestedMenu2 = new Menu("Nested Menu 2");
MenuItem nestedMenuItem2 = new MenuItem("Nested Menu Item 2");
nestedMenu2.getItems().add(nestedMenuItem2);
contextMenu.getItems().add(nestedMenu2);
scene.setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() {
@Override
public void handle(ContextMenuEvent event) {
contextMenu.show(root, event.getScreenX(), event.getScreenY());
}
});
primaryStage.setScene(scene);
primaryStage.show();
menu.getItems().forEach((i) -> {
System.out.println(i.getParentPopup());
System.out.println(((Menu) i).getItems().get(0).getParentPopup());
System.out.println(i.getParentPopup() == ((Menu) i).getItems().get(0).getParentPopup());
});
System.out.println(nestedMenu.getParentPopup());
System.out.println(nestedMenuItem.getParentPopup());
System.out.println(nestedMenu.getParentPopup() == nestedMenuItem.getParentPopup());
nestedMenu.getParentPopup().setStyle("-fx-background-color: green;");
nestedMenuItem.getParentPopup().setStyle("-fx-background-color: blue;");
nestedMenu2.getParentPopup().setStyle("-fx-background-color: green;");
nestedMenuItem2.getParentPopup().setStyle("-fx-background-color: blue;");
} catch(Exception e) {e.printStackTrace();}
}
public static void main(String[] args) {
launch(args);
}
}
---------- END SOURCE ----------
FREQUENCY : always
Win 10 / jdk 14.0.1/ jfx 14.0.1
A DESCRIPTION OF THE PROBLEM :
When a menu is nested inside of another menu or ContextMenu that menu's items have their parentPopup set to the top level of the menu and not the actual context menu that they are within.
I think the problem is in the ContextMenu class. When it adds a MenuItem to the ContextMenu it overwrites the parentPopup of the MenuItem if there is a parentPopup already, which there's always one because it's nested inside of another menu already. So it just overwrites the MenuItem's parentPopup when it shouldn't.
Source code from jfx-sdk/14.0.1:
private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() {
@Override protected void onChanged(Change<MenuItem> c) {
while (c.next()) {
for (MenuItem item : c.getRemoved()) {
item.setParentPopup(null);
}
for (MenuItem item : c.getAddedSubList()) {
if (item.getParentPopup() != null) {
// we need to remove this item from its current parentPopup
// as a MenuItem should not exist in multiple parentPopup
// instances
item.getParentPopup().getItems().remove(item);
}
item.setParentPopup(ContextMenu.this);
}
}
}
};
My changes that I think would fix the problem, was unsuccessful in compiling jfx myself so I couldn't test this.
private final ObservableList<MenuItem> items = new TrackableObservableList<MenuItem>() {
@Override protected void onChanged(Change<MenuItem> c) {
while (c.next()) {
for (MenuItem item : c.getRemoved()) {
item.setParentPopup(null);
}
for (MenuItem item : c.getAddedSubList()) {
if (item.getParentPopup() != null) {
if(item.getParentMenu() != null) {
if(item.getParentMenu().getParentPopup() == this)
continue;
}
else {
// we need to remove this item from its current parentPopup
// as a MenuItem should not exist in multiple parentPopup
// instances
item.getParentPopup().getItems().remove(item);
}
}
item.setParentPopup(ContextMenu.this);
}
}
}
};
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Place a menu inside another menu and then compare the parentPopup of the nested menu and any of the nested menu's item's parentPopup.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The nested menu and the nested menu item's should have different a parentPopup because they are inside of different ContextMenus.
ACTUAL -
The nested menu and the nested menu item's have the same parentPopup. Thus you are unable to alter the nested menu's contextmenu because it's been orphaned.
---------- BEGIN SOURCE ----------
package application;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.input.ContextMenuEvent;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
MenuBar menuBar = new MenuBar();
Menu menu = new Menu("Menu");
Menu nestedMenu = new Menu("Nested");
MenuItem nestedMenuItem = new MenuItem("Nested Menu Item");
nestedMenu.getItems().add(nestedMenuItem);
menu.getItems().add(nestedMenu);
menuBar.getMenus().add(menu);
root.setTop(menuBar);
ContextMenu contextMenu = new ContextMenu();
Menu nestedMenu2 = new Menu("Nested Menu 2");
MenuItem nestedMenuItem2 = new MenuItem("Nested Menu Item 2");
nestedMenu2.getItems().add(nestedMenuItem2);
contextMenu.getItems().add(nestedMenu2);
scene.setOnContextMenuRequested(new EventHandler<ContextMenuEvent>() {
@Override
public void handle(ContextMenuEvent event) {
contextMenu.show(root, event.getScreenX(), event.getScreenY());
}
});
primaryStage.setScene(scene);
primaryStage.show();
menu.getItems().forEach((i) -> {
System.out.println(i.getParentPopup());
System.out.println(((Menu) i).getItems().get(0).getParentPopup());
System.out.println(i.getParentPopup() == ((Menu) i).getItems().get(0).getParentPopup());
});
System.out.println(nestedMenu.getParentPopup());
System.out.println(nestedMenuItem.getParentPopup());
System.out.println(nestedMenu.getParentPopup() == nestedMenuItem.getParentPopup());
nestedMenu.getParentPopup().setStyle("-fx-background-color: green;");
nestedMenuItem.getParentPopup().setStyle("-fx-background-color: blue;");
nestedMenu2.getParentPopup().setStyle("-fx-background-color: green;");
nestedMenuItem2.getParentPopup().setStyle("-fx-background-color: blue;");
} catch(Exception e) {e.printStackTrace();}
}
public static void main(String[] args) {
launch(args);
}
}
---------- END SOURCE ----------
FREQUENCY : always