JavaFX TreeTableView - 防止编辑不可用的单元格

JavaFX TreeTableView - Prevent editing of unavailable cells

我有一个特定的 TreeTableView,它显示混合类型的分层树。这些类型不一定具有重叠的列,因此某些行的列将为空。例如,考虑以下 类:

public class Person {

    private final StringProperty nameProperty;
    private final StringProperty surnameProperty;

    public Person() {
        this.nameProperty = new SimpleStringProperty();
        this.surnameProperty = new SimpleStringProperty();
    }

    public StringProperty nameProperty() {
        return this.nameProperty;
    }

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

    public String getName() {
        return this.nameProperty.get();
    }

    public StringProperty surnameProperty() {
        return this.surnameProperty;
    }

    public void setSurname(String value) {
        this.surnameProperty.set(value);
    }

    public String getSurname() {
        return this.surnameProperty.get();
    }
}

public class Dog {

    private final StringProperty nameProperty;
    private final IntegerProperty ageProperty;
    private final StringProperty breedProperty;

    public Dog() {
        this.nameProperty = new SimpleStringProperty();
        this.ageProperty = new SimpleIntegerProperty();
        this.breedProperty = new SimpleStringProperty();
    }

    public StringProperty nameProperty() {
        return this.nameProperty;
    }

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

    public String getName() {
        return this.nameProperty.get();
    }

    public IntegerProperty ageProperty() {
        return this.ageProperty;
    }

    public void setAge(int value) {
        this.ageProperty.setValue(value);
    }

    public int getAge() {
        return this.ageProperty.get();
    }

    public StringProperty breedProperty() {
        return this.breedProperty;
    }

    public void setBreed(String breed) {
        this.breedProperty.set(breed);
    }

    public String getBreed() {
        return this.breedProperty.get();
    }
}

如果我按如下方式构建 TreeTableView:

TreeTableView<Object> treeTableView = new TreeTableView<>();
treeTableView.setEditable(true);

List<TreeTableColumn<Object, ?>> columns = treeTableView.getColumns();

TreeTableColumn<Object, String> nameColumn = new TreeTableColumn<>("Name");
nameColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
columns.add(nameColumn);

TreeTableColumn<Object, String> surnameColumn = new TreeTableColumn<>("Surname");
surnameColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
surnameColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("surname"));
columns.add(surnameColumn);

TreeTableColumn<Object, Integer> ageColumn = new TreeTableColumn<>("Age");
ageColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn(new IntegerStringConverter()));
ageColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("age"));
columns.add(ageColumn);

TreeTableColumn<Object, String> breedColumn = new TreeTableColumn<>("Breed");
breedColumn.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
breedColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("breed"));
columns.add(breedColumn);

TreeItem<Object> rootItem = new TreeItem<>();
treeTableView.setRoot(rootItem);
treeTableView.setShowRoot(false);

List<TreeItem<Object>> rootChildren = rootItem.getChildren();

Person john = new Person();
john.setName("John");
john.setSurname("Denver");
TreeItem<Object> johnTreeItem = new TreeItem<>(john);

rootChildren.add(johnTreeItem);

List<TreeItem<Object>> johnChildren = johnTreeItem.getChildren();

Dog charlie = new Dog();
charlie.setName("Charlie");
charlie.setAge(4);
charlie.setBreed("Labrador");
TreeItem<Object> charlieTreeItem = new TreeItem<>(charlie);
johnChildren.add(charlieTreeItem);

Dog daisy = new Dog();
daisy.setName("Daisy");
daisy.setAge(7);
daisy.setBreed("Bulldog");
TreeItem<Object> daisyTreeItem = new TreeItem<>(daisy);
johnChildren.add(daisyTreeItem);

我将得到一个如下所示的 TreeTableView:

对于包含 Person 对象的 TreeItems,Age 和 Breed 列为空。但是,没有什么能阻止我为最顶层的 Person 行编辑 Age 或 Breed 单元格。在其中一个单元格中设置一个值不会更改 Person 对象,但该值仍然像已提交一样保留在那里。

有什么办法可以防止这种情况发生吗?我知道我可以检查自定义 TreeTableCell 子类中的空值并防止在 startEdit() 方法中开始编辑。但是,在某些情况下空值是有效的,并且通过检查空值来防止编辑并不是对所有情况都可行的解决方案。此外,为每种数据类型和相应的列创建自定义 TreeTableCell 子类也很痛苦。如果 TreeItemPropertyValueFactory 可以提供一种在特定单元格没有值时中止编辑的方法,那就太好了。

好的,我通过查看 TreeItemPropertyValueFactory class 本身来寻找灵感,从而拼凑了一些东西。这给了我想要的功能,虽然我不确定它是否 100% 正确或者使用它的含义是什么。

基本上归结为安装一个新的单元工厂来检查单元值工厂是否为 TreeItemPropertyValueFactory 类型。如果是这种情况,则会安装一个新的单元工厂,委托给原始单元工厂,但会为 table-row 和 tree-item 属性添加侦听器。当 TreeItem 发生变化时,我们获取行数据并查看是否可以访问所需的 属性(通过为性能而缓存的 PropertyReference)。如果我们不能(并且我们得到两个例外)我们假设无法访问 属性 并且我们将单元格的 editable-属性 设置为 false.

public <S, T> void disableUnavailableCells(TreeTableColumn<S, T> treeTableColumn) {
    Callback<TreeTableColumn<S, T>, TreeTableCell<S, T>> cellFactory = treeTableColumn.getCellFactory();
    Callback<CellDataFeatures<S, T>, ObservableValue<T>> cellValueFactory = treeTableColumn.getCellValueFactory();
    if (cellValueFactory instanceof TreeItemPropertyValueFactory) {
        TreeItemPropertyValueFactory<S, T> valueFactory = (TreeItemPropertyValueFactory<S, T>)cellValueFactory;
        String property = valueFactory.getProperty();
        Map<Class<?>, PropertyReference<T>> propertyRefCache = new HashMap<>();
        treeTableColumn.setCellFactory(column -> {
            TreeTableCell<S, T> cell = cellFactory.call(column);
            cell.tableRowProperty().addListener((o1, oldRow, newRow) -> {
                if (newRow != null) {
                    newRow.treeItemProperty().addListener((o2, oldTreeItem, newTreeItem) -> {
                        if (newTreeItem != null) {
                            S rowData = newTreeItem.getValue();
                            if (rowData != null) {
                                Class<?> rowType = rowData.getClass();
                                PropertyReference<T> reference = propertyRefCache.get(rowType);
                                if (reference == null) {
                                    reference = new PropertyReference<>(rowType, property);
                                    propertyRefCache.put(rowType, reference);
                                }
                                try {
                                    reference.getProperty(rowData);
                                } catch (IllegalStateException e1) {
                                    try {
                                        reference.get(rowData);
                                    } catch (IllegalStateException e2) {
                                        cell.setEditable(false);
                                    }
                                }
                            }
                        }
                    });
                }
            });
            return cell;
        });
    }
}

对于问题中列出的示例,您可以在创建所有列后调用它:

...
columns.forEach(this::disableUnavailableCells);

TreeItem<Object> rootItem = new TreeItem<>();
treeTableView.setRoot(rootItem);
treeTableView.setShowRoot(false);
...

您会看到 Age 和 Breed 列的单元格对于 Person 条目现在是 uneditable,而 Surname 列的单元格对于 Dog 条目现在是 uneditable,这就是我们想。通用名称列的单元格是所有条目的 editable,因为这是 Person 和 Dog 对象中的通用 属性。