sancho
sancho

Reputation: 608

TreeTableView disable any cell in parent row

How can I disable any cell editable in parent row in treetableview? Please look the pictures and check the sample code. Shortly I want to disable row editable if row is expandable (root row or sub root row)

this picture is correct enter image description here

but this is not correct enter image description here

**Example code **

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class TreeTableExample extends Application {

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

    @Override
    @SuppressWarnings("unchecked")
    public void start(Stage stage) {

        HBox root = new HBox(createTable());
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.setTitle("Using a TreeTableView");
        stage.show();
    }

    public TreeTableView createTable() {

        TreeTableView<Person> treeTable = new TreeTableView<>();
        treeTable.setEditable(true);

        Callback<TreeTableColumn<Person, String>, 
            TreeTableCell<Person, String>> cellFactory
                = (TreeTableColumn<Person, String> p) -> new EditingCell();

        TreeTableColumn<Person, String> firstName = new TreeTableColumn<>("First Name");
        firstName.setCellValueFactory(new TreeItemPropertyValueFactory<>("firstName"));
        firstName.setCellFactory(cellFactory);
        firstName.setOnEditCommit((TreeTableColumn.CellEditEvent<Person, String> event) -> {
            if(event.getNewValue()!=null)
                event.getRowValue().getValue().setFirstName(event.getNewValue());
        });

        TreeTableColumn<Person, String> lastName = new TreeTableColumn<>("Last Name");
        lastName.setCellValueFactory(new TreeItemPropertyValueFactory<>("lastName"));
        lastName.setCellFactory(cellFactory);
        lastName.setOnEditCommit((TreeTableColumn.CellEditEvent<Person, String> event) -> {
            if(event.getNewValue()!=null)
                event.getRowValue().getValue().setLastName(event.getNewValue());
        });

        treeTable.getColumns().addAll(firstName, lastName);
        TreeItem<Person> root = new TreeItem<>();
        for (int i = 0; i < 5; i++) {
            root.getChildren().add(new TreeItem<>(new Person()));
        }
        treeTable.setRoot(root);
        return treeTable;
    }

    public class Person {

        private SimpleStringProperty firstName;
        private SimpleStringProperty lastName;

        public Person(){
            firstName = new SimpleStringProperty(this, "firstName");
            lastName = new SimpleStringProperty(this, "lastName");
        };

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String fName) {
            lastName.set(fName);
        }

    }

    class EditingCell extends TreeTableCell<Person, String> {

        private TextField textField;

        public EditingCell() {
        }

        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                textField.selectAll();
            }
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();

            setText((String) getItem());
            setGraphic(null);
        }

        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);

            if (empty) {
                setText(null);
                setGraphic(null);
            } else if (isEditing()) {
                if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
                    setEditable(false);
                if (textField != null) {
                    textField.setText(getString());
                }
                setText(null);
                setGraphic(textField);
            } else {
                setText(getString());
                setGraphic(null);
            }
        }

        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
            textField.focusedProperty().addListener(
                    (ObservableValue<? extends Boolean> arg0,
                            Boolean arg1, Boolean arg2) -> {
                        if (!arg2) {
                            commitEdit(textField.getText());
                        }
                    });
        }

        private String getString() {
            return getItem() == null ? "" : getItem();
        }
    }
}

just run it and double click on the root item

make-individual-cell-editable-in-javafx-tableview I checked the solution works for tableview but for treetaleview does not work.

Upvotes: 2

Views: 1454

Answers (2)

kleopatra
kleopatra

Reputation: 51524

Actually I disagree with the reasoning in the other answer: there is nothing wrong with core TreeTableCell (it does check for its editability before actually starting an edit) - instead the logic in the custom cell implementation is broken. Particularly, the part of updateItem that sets the editable property:

} else if (isEditing()) {
    if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
        setEditable(false);

Besides being incomplete in not resetting the editable back to true anywhere (remember: cells are re-used), we allow super to first start editing and only after it started, it's disabled.

This logic error is fixed (in the other answer, copied here for convenience) by unconditionally setting the editability in updateItem:

super.updateItem(item, empty);

TreeItem<Person> treeItem = getTreeTableRow().getTreeItem();
setEditable(treeItem != null &&  treeItem.isLeaf());

The other usage error (as already noted) was not fully checking cell state before actually configuring the editor. The suggested fix - check cell's editable - isn't quite complete because table/column editability might be disabled as well. To take that into account, I would tend to let super do its job and only configure the editor if editability actually changed, like

super.startEdit();
// super changed state into editing 
if (isEditing()) {
   // create and install the textField
}  

Upvotes: 1

James_D
James_D

Reputation: 209319

It seems that TreeTableCell does not properly check its editable property before deciding whether or not to call startEdit(). I think that's a bug. You can work around it by checking that yourself in your startEdit() method:

@Override
public void startEdit() {
    if (isEditable() && !isEmpty()) {
        super.startEdit();
        createTextField();
        setText(null);
        setGraphic(textField);
        textField.selectAll();
    }
}

and now in your updateItem() method, you can check the current tree item from the row, and update editable as required:

@Override
public void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);

    TreeItem<Person> treeItem = getTreeTableRow().getTreeItem();
    setEditable(treeItem != null &&  treeItem.isLeaf());

    if (empty) {
        setText(null);
        setGraphic(null);
    } else if (isEditing()) {
        if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
            setEditable(false);
        if (textField != null) {
            textField.setText(getString());
        }
        setText(null);
        setGraphic(textField);
    } else {
        setText(getString());
        setGraphic(null);
    }
}

Upvotes: 2

Related Questions