Search code examples
javajavafxtreetableview

TableMenuButton dose not show name of columns which are saved as Label, JavaFx


I turned my column names as suggested here to labels so that I can get tooltip on their names:

        for (Entry<String, String> ent : dc.getSortedAssignedOrg().entrySet()) {

        TreeTableColumn<String, ArrayList<String>> col = new TreeTableColumn<>();
        Label label = new Label(ent.getValue());
        col.setGraphic(label);
        col.setEditable(false);
        col.setSortable(false);
        label.setTooltip(new Tooltip(label.getText()));// tooltip for column
            .
            .
            .

Now the problem is my TableMenuButton does not show the column names, and clicking on the plus sign on the right corner of treetableview opens a list in which there are only the checked signs, which I can remove or add. But the name itself is not shown. How can I fix this?


Solution

  • You could create your own table menu. You're better off with a custom menu anyway, since the in-built menu e. g. closes each time you click on a button. Unfortunately there is no getter for the context menu, so you'll have to find access to it either via reflection or a lookup.

    I created a gist for a custom menu via the reflection and the lookup mechanism. Maybe it's of help for you.

    The relevant part for you would be

    CheckBox cb = new CheckBox(tableColumn.getText());
    

    where you set the menu item to the text of your preferrence, i. e. the text of your labels.


    Here's a lookup version for TreeTableView:

    CustomTreeTableMenuDemo.java

    import java.util.Arrays;
    import java.util.List;
    
    import javafx.application.Application;
    import javafx.beans.property.ReadOnlyStringWrapper;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.control.TreeItem;
    import javafx.scene.control.TreeTableColumn;
    import javafx.scene.control.TreeTableView;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    
    public class CustomTreeTableMenuDemo extends Application {
    
        List<Employee> employees = Arrays.<Employee> asList(new Employee(
                "Ethan Williams", "[email protected]"), new Employee(
                "Emma Jones", "[email protected]"), new Employee(
                "Michael Brown", "[email protected]"), new Employee(
                "Anna Black", "[email protected]"), new Employee(
                "Rodger York", "[email protected]"), new Employee(
                "Susan Collins", "[email protected]"));
    
        final TreeItem<Employee> root = new TreeItem<>(new Employee(
                "Sales Department", ""));
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
    
            stage.setTitle("Table Menu Demo");
            stage.setWidth(500);
            stage.setHeight(550);
    
            root.setExpanded(true);
            employees.stream().forEach((employee) -> {
                root.getChildren().add(new TreeItem<>(employee));
            });
            stage.setTitle("Tree Table View Sample");
            final Scene scene = new Scene(new Group(), 400, 400);
            scene.setFill(Color.LIGHTGRAY);
            Group sceneRoot = (Group) scene.getRoot();
    
            TreeTableColumn<Employee, String> empColumn = new TreeTableColumn<>(
                    "Employee");
            empColumn.setPrefWidth(150);
            empColumn
                    .setCellValueFactory((
                            TreeTableColumn.CellDataFeatures<Employee, String> param) -> new ReadOnlyStringWrapper(
                            param.getValue().getValue().getName()));
    
            TreeTableColumn<Employee, String> emailColumn = new TreeTableColumn<>(
                    "Email");
            emailColumn.setPrefWidth(190);
            emailColumn
                    .setCellValueFactory((
                            TreeTableColumn.CellDataFeatures<Employee, String> param) -> new ReadOnlyStringWrapper(
                            param.getValue().getValue().getEmail()));
    
            TreeTableView<Employee> treeTableView = new TreeTableView<>(root);
            treeTableView.getColumns().setAll(empColumn, emailColumn);
    
            sceneRoot.getChildren().add(treeTableView);
    
            stage.setScene(scene);
            stage.show();
    
            // enable table menu button and add a custom menu to it
            TreeTableUtils.addCustomTreeTableMenu(treeTableView);
        }
    
        public class Employee {
    
            private SimpleStringProperty name;
            private SimpleStringProperty email;
    
            public SimpleStringProperty nameProperty() {
                if (name == null) {
                    name = new SimpleStringProperty(this, "name");
                }
                return name;
            }
    
            public SimpleStringProperty emailProperty() {
                if (email == null) {
                    email = new SimpleStringProperty(this, "email");
                }
                return email;
            }
    
            private Employee(String name, String email) {
                this.name = new SimpleStringProperty(name);
                this.email = new SimpleStringProperty(email);
            }
    
            public String getName() {
                return name.get();
            }
    
            public void setName(String fName) {
                name.set(fName);
            }
    
            public String getEmail() {
                return email.get();
            }
    
            public void setEmail(String fName) {
                email.set(fName);
            }
        }
    }
    

    TreeTableUtils.java

    import javafx.collections.ObservableList;
    import javafx.event.EventHandler;
    import javafx.geometry.Side;
    import javafx.scene.Node;
    import javafx.scene.control.CheckBox;
    import javafx.scene.control.ContextMenu;
    import javafx.scene.control.CustomMenuItem;
    import javafx.scene.control.Label;
    import javafx.scene.control.SeparatorMenuItem;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TreeTableColumn;
    import javafx.scene.control.TreeTableView;
    import javafx.scene.input.MouseEvent;
    
    import com.sun.javafx.scene.control.skin.TableHeaderRow;
    import com.sun.javafx.scene.control.skin.TreeTableViewSkin;
    
    public class TreeTableUtils {
    
        /**
         * Make table menu button visible and replace the context menu with a custom context menu via reflection.
         * The preferred height is modified so that an empty header row remains visible. This is needed in case you remove all columns, so that the menu button won't disappear with the row header.
         * IMPORTANT: Modification is only possible AFTER the table has been made visible, otherwise you'd get a NullPointerException
         * @param treeTableView
         */
        public static void addCustomTreeTableMenu( TreeTableView treeTableView) {
    
            // enable table menu
            treeTableView.setTableMenuButtonVisible(true);
    
            // replace internal mouse listener with custom listener 
            setCustomContextMenu( treeTableView);
    
        }
    
        private static void setCustomContextMenu( TreeTableView treeTableView) {
    
            TreeTableViewSkin<?> treeTableViewSkin = (TreeTableViewSkin<?>) treeTableView.getSkin();
    
            // get all children of the skin
            ObservableList<Node> children = treeTableViewSkin.getChildren();
    
            // find the TableHeaderRow child
            for (int i = 0; i < children.size(); i++) {
    
                Node node = children.get(i);
    
                if (node instanceof TableHeaderRow) {
    
                    TableHeaderRow tableHeaderRow = (TableHeaderRow) node;
    
                    // setting the preferred height for the table header row
                    // if the preferred height isn't set, then the table header would disappear if there are no visible columns
                    // and with it the table menu button
                    // by setting the preferred height the header will always be visible
                    // note: this may need adjustments in case you have different heights in columns (eg when you use grouping)
                    double defaultHeight = tableHeaderRow.getHeight();
                    tableHeaderRow.setPrefHeight(defaultHeight);
    
                    for( Node child: tableHeaderRow.getChildren()) {
    
                        // child identified as cornerRegion in TableHeaderRow.java
                        if( child.getStyleClass().contains( "show-hide-columns-button")) {
    
                            // get the context menu
                            ContextMenu columnPopupMenu = createContextMenu( treeTableView);
    
                            // replace mouse listener
                            child.setOnMousePressed(me -> {
                                // show a popupMenu which lists all columns
                                columnPopupMenu.show(child, Side.BOTTOM, 0, 0);
                                me.consume();
                            });
                        }
                    }
    
                }
            }
        }
    
        /**
         * Create a menu with custom items. The important thing is that the menu remains open while you click on the menu items.
         * @param cm
         * @param treeTableView
         */
        private static ContextMenu createContextMenu( TreeTableView treeTableView) {
    
            ContextMenu cm = new ContextMenu();
    
            // create new context menu
            CustomMenuItem cmi;
    
            // select all item
            Label showAll = new Label("Show all");
            showAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
    
                @Override
                public void handle(MouseEvent event) {
                    for (Object obj : treeTableView.getColumns()) {
                        ((TableColumn<?, ?>) obj).setVisible(true);
                    }
                }
    
            });
    
            cmi = new CustomMenuItem(showAll);
            cmi.setHideOnClick(false);
            cm.getItems().add(cmi);
    
            // deselect all item
            Label hideAll = new Label("Hide all");
            hideAll.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
    
                @Override
                public void handle(MouseEvent event) {
    
                    for (Object obj : treeTableView.getColumns()) {
                        ((TableColumn<?, ?>) obj).setVisible(false);
                    }
                }
    
            });
    
            cmi = new CustomMenuItem(hideAll);
            cmi.setHideOnClick(false);
            cm.getItems().add(cmi);
    
            // separator
            cm.getItems().add(new SeparatorMenuItem());
    
            // menu item for each of the available columns
            for (Object obj : treeTableView.getColumns()) {
    
                TreeTableColumn<?, ?> tableColumn = (TreeTableColumn<?, ?>) obj;
    
                CheckBox cb = new CheckBox(tableColumn.getText());
                cb.selectedProperty().bindBidirectional(tableColumn.visibleProperty());
    
                cmi = new CustomMenuItem(cb);
                cmi.setHideOnClick(false);
    
                cm.getItems().add(cmi);
            }
    
            return cm;
        }
    }