I have a use case which I would assume is pretty standard, however I haven't been able to find an example on exactly how to do this, or if it's possible.
Let's assume I have the following TableView
First Name Last Name Street NewRecord
Tom Smith Main St. Yes
Mike Smith First St. No
In this case, the grid should have the first three cells editable since the record is new, however when the record is not new then the Last Name cell should be disabled.
I tried this in the CellFactory and RowFactory - but haven't seen a way to accomplish this.
Thanks for your help.
The easiest way to do this is with a third-party binding library: ReactFX 2.0 has this functionality built-in, as described here. Using that you can do
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
TableCell<Person, String> cell = new TextFieldTableCell<>();
cell.editableProperty().bind(
// horrible cast needed because TableCell.tableRowProperty inexplicably returns a raw type:
Val.flatMap(cell.tableRowProperty(), row -> (ObservableValue<Person>)row.itemProperty())
.flatMap(Person::newRecordProperty)
.orElseConst(false));
return cell ;
});
(assumes a Person
table model object with the obvious JavaFX properties and methods).
Without the library, you need a pretty miserable nested list of listeners:
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
TableCell<Person, String> cell = new TextFieldTableCell<>();
ChangeListener<Boolean> newRecordListener = (obs, wasNewRecord, isNewRecord) -> updateEditability(cell);
ChangeListener<Person> rowItemListener = (obs, oldPerson, newPerson) -> {
if (oldPerson != null) {
oldPerson.newRecordProperty().removeListener(newRecordListener);
}
if (newPerson != null) {
newPerson.newRecordProperty().addListener(newRecordListener);
}
updateEditability(cell);
};
ChangeListener<TableRow> rowListener = (obs, oldRow, newRow) -> {
if (oldRow != null) {
((ObservableValue<Person>)oldRow.itemProperty()).removeListener(rowItemListener);
if (oldRow.getItem() != null) {
((Person)oldRow.getItem()).newRecordProperty().removeListener(newRecordListener);
}
}
if (newRow != null) {
((ObservableValue<Person>)newRow.itemProperty()).addListener(rowItemListener);
if (newRow.getItem() != null) {
((Person)newRow.getItem()).newRecordProperty().addListener(newRecordListener);
}
}
updateEditability(cell);
};
cell.tableRowProperty().addListener(rowListener);
return cell ;
});
and then
private void updateEditability(TableCell<Person, String> cell) {
if (cell.getTableRow() == null) {
cell.setEditable(false);
} else {
TableRow<Person> row = (TableRow<Person>) cell.getTableRow();
if (row.getItem() == null) {
cell.setEditable(false);
} else {
cell.setEditable(row.getItem().isNewRecord());
}
}
}
An alternative using a "legacy style" API is
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setEditable(true);
lastNameColumn.setCellFactory(tc -> {
TableCell<Person, String> cell = new TextFieldTableCell<>();
cell.editableProperty().bind(
Bindings.selectBoolean(cell.tableRowProperty(), "item", "newRecord"));
return cell ;
});
I dislike this option, because it lacks any type safety (or indeed any compiler checks at all), and additionally in some earlier versions of JavaFX would generate almost endless warning messages if any of the properties in the "chain" had null values (which they will, frequently, in this case). I believe the latter issue is fixed, but the ReactFX version of this is far better, imho.