Search code examples
javajavafxrowtreetableview

TreeTableView : setting a row not editable


I want to have control over the styling of some rows of a TreeTableView based on the level in the tree. I used setRowFactory and apply a styling if this row is part of the first level children of the root of the Table. The styling works fine, but I also want to disable clicking on the checkbox for those rows. I am able to setDisable(true) but that also disables the expanding of the TreeItem and SetEditable(false) does not seem to have any effect.

EDIT: What I understand is that the Table must be set editable, then the columns are by default editable. But if I set TreeTableRow.setEditable(true); or TreeTableRow.setEditable(false); I never see any effect. The description seems of setEditable seems exactly what I want but I am unable to use it that way.

void javafx.scene.control.Cell.setEditable(boolean arg0)

setEditable public final void setEditable(boolean value)

Allows for certain cells to not be able to be edited. This is useful incases >where, say, a List has 'header rows' - it does not make sense forthe header rows >to be editable, so they should have editable set tofalse. Parameters:value - A boolean representing whether the cell is editable or not.If >true, the cell is editable, and if it is false, the cell can notbe edited.

Main:

public class TreeTableViewRowStyle extends Application {

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

    @Override
    public void start(Stage stage) throws Exception {

        // create the treeTableView and colums
        TreeTableView<Person> ttv = new TreeTableView<Person>();
        TreeTableColumn<Person, String> colName = new TreeTableColumn<>("Name");
        TreeTableColumn<Person, Boolean> colSelected = new TreeTableColumn<>("Selected");
        colName.setPrefWidth(100);
        ttv.getColumns().add(colName);
        ttv.getColumns().add(colSelected);
        ttv.setShowRoot(false);
        ttv.setEditable(true);

        // set the columns
        colName.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
        colSelected.setCellFactory(CheckBoxTreeTableCell.forTreeTableColumn(colSelected));
        colSelected.setCellValueFactory(new TreeItemPropertyValueFactory<>("selected"));

        ttv.setRowFactory(table-> {
            return new TreeTableRow<Person>(){
                @Override
                public void updateItem(Person pers, boolean empty) {
                    super.updateItem(pers, empty);
                    boolean isTopLevel = table.getRoot().getChildren().contains(treeItemProperty().get());
                    if (!isEmpty()) {
                        if(isTopLevel){
                            setStyle("-fx-background-color:lightgrey;");
                            setEditable(false); //THIS DOES NOT SEEM TO WORK AS I WANT
                            //setDisable(true); //this would disable the checkbox but also the expanding of the tree
                        }else{                              
                            setStyle("-fx-background-color:white;");
                        }
                    }
                }
            };
        });


        // creating treeItems to populate the treetableview
        TreeItem<Person> rootTreeItem = new TreeItem<Person>();
        TreeItem<Person> parent1 = new TreeItem<Person>(new Person("Parent 1"));
        TreeItem<Person> parent2 = new TreeItem<Person>(new Person("Parent 1"));
        parent1.getChildren().add(new TreeItem<Person>(new Person("Child 1")));
        parent2.getChildren().add(new TreeItem<Person>(new Person("Child 2")));
        rootTreeItem.getChildren().addAll(parent1,parent2);


        ttv.setRoot(rootTreeItem);

        // build and show the window
        Group root = new Group();
        root.getChildren().add(ttv);
        stage.setScene(new Scene(root, 300, 300));
        stage.show();
    }
}

Model Person :

public class Person {
    private StringProperty name;
    private BooleanProperty selected;

    public Person(String name) {
        this.name = new SimpleStringProperty(name);
        selected = new SimpleBooleanProperty(false);
    }

    public StringProperty nameProperty() {
        return name;
    }

    public BooleanProperty selectedProperty() {
        return selected;
    }

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

    public void setSelected(boolean selected){
        this.selected.set(selected);
    }
}

Solution

  • The base problem is that none of the editable (nor the pseudo-editable like CheckBoxXX) Tree/Table cells respect the editability of the row they are contained in. Which I consider a bug.

    To overcome, you have to extend the (pseudo) editable cells and make them respect the row's editable. The exact implementation is different for pseudo- vs. real editing cells. Below are in-line examples, for frequent usage you would make them top-level and re-use.

    CheckBoxTreeTableCell: subclass and override updateItem to re-bind its disabled property like

    colSelected.setCellFactory(c -> {
        TreeTableCell cell = new CheckBoxTreeTableCell() {
    
            @Override
            public void updateItem(Object item, boolean empty) {
                super.updateItem(item, empty);
                if (getGraphic() != null) {
                    getGraphic().disableProperty().bind(Bindings
                            .not(
                                  getTreeTableView().editableProperty()
                                 .and(getTableColumn().editableProperty())
                                 .and(editableProperty())
                                 .and(getTreeTableRow().editableProperty())
                        ));
                }
            }
    
        };
        return cell;
    });
    

    For a real editing cell, f.i. TextFieldTreeTableCell: override startEdit and return without calling super if the row isn't editable

    colName.setCellFactory(c -> {
        TreeTableCell cell = new TextFieldTreeTableCell() {
    
            @Override
            public void startEdit() {
                if (getTreeTableRow() != null && !getTreeTableRow().isEditable()) return;
                super.startEdit();
            }
    
        };
        return cell;
    });
    

    Now you can toggle the row's editability as you do, changed the logic a bit to guarantee full cleanup in all cases:

    ttv.setRowFactory(table-> {
        return new TreeTableRow<Person>(){
            @Override
            public void updateItem(Person pers, boolean empty) {
                super.updateItem(pers, empty);
                // tbd: check for nulls!
                boolean isTopLevel = table.getRoot().getChildren().contains(treeItemProperty().get());
                if (!isEmpty() && isTopLevel) {
                    //                        if(isTopLevel){
                    setStyle("-fx-background-color:lightgrey;");
                    setEditable(false); 
                }else{
                    setEditable(true);
                    setStyle("-fx-background-color:white;");
    
                }
            }
        };
    });