-
Bug
-
Resolution: Unresolved
-
P4
-
jfx11
Issue: modifying the items of a Menu (aka: submenu) while it is showing
does not update the items shown. For comparison: modifying the list of
direct items of the contextMenu is working as expected.
To reproduce, compile the example below, run:
A: see tab "subMenu"
- right click in button the show contextmenu
- move mouse over menu to open submenu and make sure it remains open during the steps below
- press f1
- expected: first item of open submenu visibly removed
- actual: no visual change
- to verify that items are removed from the items list, press f1 until message "empty" is printed
for comparison, see tab "direct" and do the same:
- as expected, items are visibly removed from the showing popup
The issue turned up in a question at SO: https://stackoverflow.com/q/54834206/203657
The culprit seems to be ContextMenuContent that is responsible for opening/hiding submenus - it does so by calling:
private void showSubmenu(Menu menu) {
openSubmenu = menu;
createSubmenu();
submenu.getItems().setAll(menu.getItems());
submenu.show(selectedBackground, Side.RIGHT, 0, 0);
}
note that the items of the subMenu (== ContextMenu that's showing the items of the menu) is set once to the items of the menu, without registering any listeners: so removing the items from Menu's items doesn't keep the items of the subMenu in sync.
A dirty (! and beware: not tested for side-effects) hack around is to
- grab the ContextMenuContent for the menu (that is the content that contains menu)
- reflectively access its private field subMenu
- when changing the items in menu, reset the items in subMenu to the same
public class MenuModifyItemsWhileShowing extends Application {
protected Node createTabContent(boolean asSubMenu) {
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
ContextMenu cmenu = new ContextMenu();
List<MenuItem> items;;
if (asSubMenu) {
//---------- submenu: bug
final Menu menu = new Menu("MENU");
cmenu.getItems().setAll(menu);
items = menu.getItems();
} else {
// for comparison: directly added - behaves as expected
items = cmenu.getItems();
}
options.stream().map(MenuItem::new).forEach(items::add);
Button button = new Button("for contextMenu");
button.setContextMenu(cmenu);
// on pressing f1, remove first menuItem until list empty
cmenu.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.F1) {
if (!items.isEmpty()) {
items.remove(0);
} else {
System.out.println("no more items");
}
}
});
return button;
}
private Parent createContent() {
TabPane pane = new TabPane();
pane.getTabs().addAll(
new Tab("subMenu", createTabContent(true)),
new Tab("direct", createTabContent(false)));
return pane;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
does not update the items shown. For comparison: modifying the list of
direct items of the contextMenu is working as expected.
To reproduce, compile the example below, run:
A: see tab "subMenu"
- right click in button the show contextmenu
- move mouse over menu to open submenu and make sure it remains open during the steps below
- press f1
- expected: first item of open submenu visibly removed
- actual: no visual change
- to verify that items are removed from the items list, press f1 until message "empty" is printed
for comparison, see tab "direct" and do the same:
- as expected, items are visibly removed from the showing popup
The issue turned up in a question at SO: https://stackoverflow.com/q/54834206/203657
The culprit seems to be ContextMenuContent that is responsible for opening/hiding submenus - it does so by calling:
private void showSubmenu(Menu menu) {
openSubmenu = menu;
createSubmenu();
submenu.getItems().setAll(menu.getItems());
submenu.show(selectedBackground, Side.RIGHT, 0, 0);
}
note that the items of the subMenu (== ContextMenu that's showing the items of the menu) is set once to the items of the menu, without registering any listeners: so removing the items from Menu's items doesn't keep the items of the subMenu in sync.
A dirty (! and beware: not tested for side-effects) hack around is to
- grab the ContextMenuContent for the menu (that is the content that contains menu)
- reflectively access its private field subMenu
- when changing the items in menu, reset the items in subMenu to the same
public class MenuModifyItemsWhileShowing extends Application {
protected Node createTabContent(boolean asSubMenu) {
final List<String> options = Arrays.asList(
"AbC",
"dfjksdljf",
"skdlfj",
"stackoverflow");
ContextMenu cmenu = new ContextMenu();
List<MenuItem> items;;
if (asSubMenu) {
//---------- submenu: bug
final Menu menu = new Menu("MENU");
cmenu.getItems().setAll(menu);
items = menu.getItems();
} else {
// for comparison: directly added - behaves as expected
items = cmenu.getItems();
}
options.stream().map(MenuItem::new).forEach(items::add);
Button button = new Button("for contextMenu");
button.setContextMenu(cmenu);
// on pressing f1, remove first menuItem until list empty
cmenu.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.F1) {
if (!items.isEmpty()) {
items.remove(0);
} else {
System.out.println("no more items");
}
}
});
return button;
}
private Parent createContent() {
TabPane pane = new TabPane();
pane.getTabs().addAll(
new Tab("subMenu", createTabContent(true)),
new Tab("direct", createTabContent(false)));
return pane;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}