Context menu for JavaFX tree

10,594

Solution 1

You should assign the context Menu to the TreeView instead of assigning it to the cell factory. Here is the code with the context menu working:

import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ContextMenuBuilder;
import javafx.scene.control.MenuItemBuilder;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Test extends Application
{
    List<Employee> employees = Arrays.<Employee>asList(
        new Employee("New Chassi", "New Datacenter"),
        new Employee("New Battery", "New Rack"),
        new Employee("New Chassi", "New Server"),
        new Employee("Anna Black", "Sales Department"),
        new Employee("Rodger York", "Sales Department"),
        new Employee("Susan Collins", "Sales Department"),
        new Employee("Mike Graham", "IT Support"),
        new Employee("Judy Mayer", "IT Support"),
        new Employee("Gregory Smith", "IT Support"),
        new Employee("Jacob Smith", "Accounts Department"),
        new Employee("Isabella Johnson", "Accounts Department"));
    TreeItem<String> rootNode = new TreeItem<>("MyCompany Human Resources");

    public static void main(String[] args)
    {
        Application.launch(args);
    }

    TreeView<String> treeView = new TreeView<>(rootNode);

    @Override
    public void start(Stage stage)
    {

        // instantiate the root context menu
        ContextMenu rootContextMenu
            = ContextMenuBuilder.create()
            .items(
                MenuItemBuilder.create()
                .text("Menu Item")
                .onAction(
                    new EventHandler<ActionEvent>()
                    {
                        @Override
                        public void handle(ActionEvent arg0)
                        {
                            System.out.println("Menu Item Clicked!");
                        }
                    }
                )
                .build()
            )
            .build();

        treeView.setContextMenu(rootContextMenu);

        rootNode.setExpanded(true);
        for (Employee employee : employees)
        {
            TreeItem<String> empLeaf = new TreeItem<>(employee.getName());
            boolean found = false;
            for (TreeItem<String> depNode : rootNode.getChildren())
            {
                if (depNode.getValue().contentEquals(employee.getDepartment()))
                {
                    depNode.getChildren().add(empLeaf);
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                TreeItem<String> depNode = new TreeItem<>(
                    employee.getDepartment()//,new ImageView(depIcon)   // Set picture
                );
                rootNode.getChildren().add(depNode);
                depNode.getChildren().add(empLeaf);
            }
        }

        stage.setTitle("Tree View Sample");
        VBox box = new VBox();
        final Scene scene = new Scene(box, 400, 300);
        scene.setFill(Color.LIGHTGRAY);


        box.getChildren().add(treeView);
        stage.setScene(scene);
        stage.show();
    }

    public static class Employee
    {

        private final SimpleStringProperty name;
        private final SimpleStringProperty department;

        private Employee(String name, String department)
        {
            this.name = new SimpleStringProperty(name);
            this.department = new SimpleStringProperty(department);
        }

        public String getName()
        {
            return name.get();
        }

        public void setName(String fName)
        {
            name.set(fName);
        }

        public String getDepartment()
        {
            return department.get();
        }

        public void setDepartment(String fName)
        {
            department.set(fName);
        }
    }


}

Solution 2

ContextMenuBuilder and MenuItemBuilder (which are used in the accepted answer) are deprecated in JavaFX 8 and will be removed in future releases.

Following an JavaFX 8 example with two items, one with icon and one without an icon:

MenuItem entry1 = new MenuItem("Test with Icon", graphicNode);
entry1.setOnAction(ae -> ...);
MenuItem entry2 = new MenuItem("Test without Icon");
entry2.setOnAction(ae -> ...);

myTreeView.setContextMenu(new ContextMenu(entry1, entry2));

Solution 3

I know this was 3 years ago but I hope my solution that I just found can help others like me when I was trying to find a simpler solution to this. What I found is you can just create a Label and set that as the graphic for the TreeItem. Then just set the context menu for that label, here is the code to make it work.

//The "" is important, without it the tree item would not appear at all
TreeItem<String> childItem = new TreeItem<>("");  

//Create a label with the string you would use for the tree item
childItem.setGraphic(new Label("TreeItem Label"));

//Add menu items for contextMenu
ContextMenu contextMenu = new ContextMenu();

childItem.getGraphic().setOnContextMenuRequested(e -> {
    //Sets the context menu for the label.
    contextMenu.show(childItem.getGraphic(), e.getScreenX(), e.getScreenY()));  
}

Solution 4

Minimal working example below without the unnecessary code and deprecated functions. The solution to .setContextMenu to the entire tree is not really usable since different TreeItems might have different Menus. The code below uses the standard way of using the implemented factory pattern in JavaFX and allows to use of all functions from the JavaFX API:

package javaapplication2;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.TextFieldTreeCell;
import javafx.stage.Stage;
import javafx.util.Callback;


public class JavaApplication2 extends Application {

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage stage) throws Exception {
    //Start a new TreeView
    TreeView<String> tv = new TreeView<>(new TreeItem<>("Root"));

    //Set the cell factory
    tv.setCellFactory(new Callback() {
        @Override
        public Object call(Object p) {
            return new TreeCellWithMenu();
        }
    });

    //Fill tree with some things
    tv.getRoot().getChildren().add(new TreeItem<>("1"));
    tv.getRoot().getChildren().add(new TreeItem<>("2"));
    tv.getRoot().getChildren().add(new TreeItem<>("3"));

    //Stage a new scene
    stage.setScene(new Scene(tv));

    //Show the stage
    stage.show();
}

public class TreeCellWithMenu extends TextFieldTreeCell<String> {

    ContextMenu men;

    public TreeCellWithMenu() {
        //ContextMenu with one entry
        men = new ContextMenu(new MenuItem("Right Click"));
    }

    @Override
    public void updateItem(String t, boolean bln) {
        //Call the super class so everything works as before
        super.updateItem(t, bln);
        //Check to show the context menu for this TreeItem
        if (showMenu(t, bln)) {
            setContextMenu(men);
        }else{
            //If no menu for this TreeItem is used, deactivate the menu
            setContextMenu(null);
        }
    }
    
    //Deccide if a menu should be shown or not
    private boolean showMenu(String t, boolean bln){
        if (t != null && !t.equals("Root")) {
            return true;
        }
        return false;
    }        

}

}
Share:
10,594
Peter Penzov
Author by

Peter Penzov

Updated on June 14, 2022

Comments

  • Peter Penzov
    Peter Penzov almost 2 years

    I want to create context menu for JavaFX. This is the code that I tested. But for some reason there is no context menu when I right-click on the tree node. Can you help me to find my mistake.

    import java.util.Arrays;
    import java.util.List;
    import javafx.application.Application;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.scene.Scene;
    import javafx.scene.control.ContextMenu;
    import javafx.scene.control.ContextMenuBuilder;
    import javafx.scene.control.MenuItemBuilder;
    import javafx.scene.control.TreeCell;
    import javafx.scene.control.TreeItem;
    import javafx.scene.control.TreeView;
    import javafx.scene.control.cell.TextFieldTreeCell;
    import javafx.scene.layout.VBox;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    import javafx.util.Callback;
    
    public class MainApp extends Application
    {
        List<Employee> employees = Arrays.<Employee>asList(
            new Employee("New Chassi", "New Datacenter"),
            new Employee("New Battery", "New Rack"),
            new Employee("New Chassi", "New Server"),
            new Employee("Anna Black", "Sales Department"),
            new Employee("Rodger York", "Sales Department"),
            new Employee("Susan Collins", "Sales Department"),
            new Employee("Mike Graham", "IT Support"),
            new Employee("Judy Mayer", "IT Support"),
            new Employee("Gregory Smith", "IT Support"),
            new Employee("Jacob Smith", "Accounts Department"),
            new Employee("Isabella Johnson", "Accounts Department"));
        TreeItem<String> rootNode = new TreeItem<>("MyCompany Human Resources");
    
        public static void main(String[] args)
        {
            Application.launch(args);
        }
    
        TreeView<String> treeView = new TreeView<>(rootNode);
    
        @Override
        public void start(Stage stage)
        {
    
            rootNode.setExpanded(true);
            for (Employee employee : employees)
            {
                TreeItem<String> empLeaf = new TreeItem<>(employee.getName());
                boolean found = false;
                for (TreeItem<String> depNode : rootNode.getChildren())
                {
                    if (depNode.getValue().contentEquals(employee.getDepartment()))
                    {
                        depNode.getChildren().add(empLeaf);
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    TreeItem<String> depNode = new TreeItem<>(
                        employee.getDepartment()//,new ImageView(depIcon)   // Set picture
                    );
                    rootNode.getChildren().add(depNode);
                    depNode.getChildren().add(empLeaf);
                }
            }
    
            stage.setTitle("Tree View Sample");
            VBox box = new VBox();
            final Scene scene = new Scene(box, 400, 300);
            scene.setFill(Color.LIGHTGRAY);
    
            treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>()
            {
    
                @Override
                public TreeCell<String> call(TreeView<String> arg0)
                {
                    // custom tree cell that defines a context menu for the root tree item
                    return new MyTreeCell();
                }
            });
    
            box.getChildren().add(treeView);
            stage.setScene(scene);
            stage.show();
        }
    
        public static class Employee
        {
    
            private final SimpleStringProperty name;
            private final SimpleStringProperty department;
    
            private Employee(String name, String department)
            {
                this.name = new SimpleStringProperty(name);
                this.department = new SimpleStringProperty(department);
            }
    
            public String getName()
            {
                return name.get();
            }
    
            public void setName(String fName)
            {
                name.set(fName);
            }
    
            public String getDepartment()
            {
                return department.get();
            }
    
            public void setDepartment(String fName)
            {
                department.set(fName);
            }
        }
    
        class MyTreeCell extends TextFieldTreeCell<String>
        {
            private ContextMenu rootContextMenu;
    
            public MyTreeCell()
            {
                // instantiate the root context menu
                rootContextMenu
                    = ContextMenuBuilder.create()
                    .items(
                        MenuItemBuilder.create()
                        .text("Menu Item")
                        .onAction(
                            new EventHandler<ActionEvent>()
                            {
                                @Override
                                public void handle(ActionEvent arg0)
                                {
                                    System.out.println("Menu Item Clicked!");
                                }
                            }
                        )
                        .build()
                    )
                    .build();
            }
    
            @Override
            public void updateItem(String item, boolean empty)
            {
                super.updateItem(item, empty);
    
                // if the item is not empty and is a root...
                if (!empty && getTreeItem().getParent() == null)
                {
                    setContextMenu(rootContextMenu);
                }
            }
        }
    
    }