If you change the graphic when a MenuItem is invoked with the mouse you can instigate an infinite loop. It looks like there is code in ContextMenuContainer that creates new child nodes whenever the graphic on a menuitem changes (see handlePropertyChanged(String) and createChildren()). This code adds a new MouseListener to the new children and never removes the old listener.
Run the provided test class and use your mouse to click on the MenuItem in the "Test" Menu. It will alter the label in the stage and its own appearance. If you keep toggling the MenuItem you will see in the console that the number of action events being fired increases each time.
If the code that alters the graphic is not in a "runLater" it will cause an infinite loop as the new child nodes register their mouse listener and then receive the same mouse event.
************************* Test Class *********************************************
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class MenuTest extends Application {
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final String labelOnString = "Feature On";
final String labelOffString = "Feature Off";
final Label label = new Label(labelOffString);
final Circle onCircle = new Circle(10, Color.RED);
final Circle offCircle = new Circle(10, Color.GREEN);
final String on = "Turn Off";
final String off = "Turn On";
final MenuItem testItem = new MenuItem(off, offCircle);
testItem.setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent event) {
System.out.println("firing menu item: " + testItem.getText());
if ( testItem.getText().equals(on) ) {
Platform.runLater(new Runnable() {
@Override public void run() {
label.setText(labelOffString);
testItem.setText(off);
// comment out the graphic change to see normal behaviour
testItem.setGraphic(offCircle);
}
});
} else {
Platform.runLater(new Runnable() {
@Override public void run() {
label.setText(labelOnString);
testItem.setText(on);
// comment out the graphic change to see normal behaviour
testItem.setGraphic(onCircle);
}
});
}
}
});
Menu menu = new Menu("Test");
menu.getItems().add(testItem);
MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(menu);
primaryStage.setScene(
new Scene( new BorderPane(label, menuBar, null, null , null) ) );
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
Run the provided test class and use your mouse to click on the MenuItem in the "Test" Menu. It will alter the label in the stage and its own appearance. If you keep toggling the MenuItem you will see in the console that the number of action events being fired increases each time.
If the code that alters the graphic is not in a "runLater" it will cause an infinite loop as the new child nodes register their mouse listener and then receive the same mouse event.
************************* Test Class *********************************************
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class MenuTest extends Application {
@Override public void start(final Stage primaryStage) throws Exception {
primaryStage.centerOnScreen();
primaryStage.setHeight(350);
primaryStage.setWidth(500);
final String labelOnString = "Feature On";
final String labelOffString = "Feature Off";
final Label label = new Label(labelOffString);
final Circle onCircle = new Circle(10, Color.RED);
final Circle offCircle = new Circle(10, Color.GREEN);
final String on = "Turn Off";
final String off = "Turn On";
final MenuItem testItem = new MenuItem(off, offCircle);
testItem.setOnAction(new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent event) {
System.out.println("firing menu item: " + testItem.getText());
if ( testItem.getText().equals(on) ) {
Platform.runLater(new Runnable() {
@Override public void run() {
label.setText(labelOffString);
testItem.setText(off);
// comment out the graphic change to see normal behaviour
testItem.setGraphic(offCircle);
}
});
} else {
Platform.runLater(new Runnable() {
@Override public void run() {
label.setText(labelOnString);
testItem.setText(on);
// comment out the graphic change to see normal behaviour
testItem.setGraphic(onCircle);
}
});
}
}
});
Menu menu = new Menu("Test");
menu.getItems().add(testItem);
MenuBar menuBar = new MenuBar();
menuBar.getMenus().add(menu);
primaryStage.setScene(
new Scene( new BorderPane(label, menuBar, null, null , null) ) );
primaryStage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}