我有一个特定的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为空。但是,没有什么能阻止我编辑最上面的“人”行的“年龄”或“品种”单元格。在其中一个单元格中设置一个值不会更改Person对象,但是该值仍然像提交的那样徘徊。
有什么方法可以防止这种情况的发生?我知道我可以在自定义TreeTableCell子类中检查是否为空,并防止在startEdit()
方法中启动编辑。但是,在某些情况下,空值是有效的,并且通过检查空值来防止编辑并非在所有情况下都是可行的解决方案。同样,为每种数据类型和对应的列创建自定义TreeTableCell子类也是很麻烦的。如果TreeItemPropertyValueFactory可以提供一种在特定单元格不存在任何值的情况下中止编辑的方法,那就太好了。
[好吧,我通过查看TreeItemPropertyValueFactory
类本身来获得一些启发。这给了我所需的功能,尽管我不确定它是否100%正确或使用它的含义。
基本上归结为安装一个新的单元工厂,该单元检查单元值工厂是否为TreeItemPropertyValueFactory
类型。在这种情况下,将安装一个新的单元工厂,该工厂委派给原始单元工厂,但为表行和树项属性添加侦听器。当TreeItem
更改时,我们获取行数据,并查看是否可以访问所需的属性(通过为性能而缓存的PropertyReference
)。如果我们不能(并且得到两个异常),则假定无法访问该属性,并将单元格的editable-property设置为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);
...