I\'d like to create a table with the following features:
My proposal to solve this atrocity is the following (sorry for missing JavaDoc).
This is a cancel-to-commit redirection solution. I tested it under LINUX with Java 1.8.0-121. Here, the only way how to discard a cell editor is to press ESCAPE.
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public abstract class AutoCommitTableCell extends TableCell
{
private Node field;
private boolean startEditing;
private T defaultValue;
/** @return a newly created input field. */
protected abstract Node newInputField();
/** @return the current value of the input field. */
protected abstract T getInputValue();
/** Sets given value to the input field. */
protected abstract void setInputValue(T value);
/** @return the default in case item is null, must be never null, else cell will not be editable. */
protected abstract T getDefaultValue();
/** @return converts the given value to a string, being the cell-renderer representation. */
protected abstract String inputValueToText(T value);
@Override
public void startEdit() {
try {
startEditing = true;
super.startEdit(); // updateItem() will be called
setInputValue(getItem());
}
finally {
startEditing = false;
}
}
/** Redirects to commitEdit(). Leaving the cell should commit, just ESCAPE should cancel. */
@Override
public void cancelEdit() {
// avoid JavaFX NullPointerException when calling commitEdit()
getTableView().edit(getIndex(), getTableColumn());
commitEdit(getInputValue());
}
private void cancelOnEscape() {
if (defaultValue != null) { // canceling default means writing null
setItem(defaultValue = null);
setText(null);
setInputValue(null);
}
super.cancelEdit();
}
@Override
protected void updateItem(T newValue, boolean empty) {
if (startEditing && newValue == null)
newValue = (defaultValue = getDefaultValue());
super.updateItem(newValue, empty);
if (empty || newValue == null) {
setText(null);
setGraphic(null);
}
else {
setText(inputValueToText(newValue));
setGraphic(startEditing || isEditing() ? getInputField() : null);
}
}
protected final Node getInputField() {
if (field == null) {
field = newInputField();
// a cell-editor won't be committed or canceled automatically by JFX
field.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ENTER || event.getCode() == KeyCode.TAB)
commitEdit(getInputValue());
else if (event.getCode() == KeyCode.ESCAPE)
cancelOnEscape();
});
contentDisplayProperty().bind(
Bindings.when(editingProperty())
.then(ContentDisplay.GRAPHIC_ONLY)
.otherwise(ContentDisplay.TEXT_ONLY)
);
}
return field;
}
}
You can extend this class to support any data type.
Example for a String field is (Person is an example bean):
import javafx.scene.Node;
import javafx.scene.control.TextField;
import jfx.examples.tablebinding.PersonsModel.Person;
public class StringTableCell extends AutoCommitTableCell
{
@Override
protected String getInputValue() {
return ((TextField) getInputField()).getText();
}
@Override
protected void setInputValue(String value) {
((TextField) getInputField()).setText(value);
}
@Override
protected String getDefaultValue() {
return "";
}
@Override
protected Node newInputField() {
return new TextField();
}
@Override
protected String inputValueToText(String newValue) {
return newValue;
}
}
To be applied in this way:
final TableColumn nameColumn = new TableColumn("Name");
nameColumn.setCellValueFactory(
cellDataFeatures -> cellDataFeatures.getValue().nameProperty());
nameColumn.setCellFactory(
cellDataFeatures -> new StringTableCell());