I\'d like to create a table with the following features:
I've run into the same issue and I solved it by combining these two code snippets:
Custom TableCell implementation
public class EditCell extends TableCell {
private final TextField textField = new TextField();
// Converter for converting the text in the text field to the user type, and vice-versa:
private final StringConverter converter;
/**
* Creates and initializes an edit cell object.
*
* @param converter
* the converter to convert from and to strings
*/
public EditCell(StringConverter converter) {
this.converter = converter;
itemProperty().addListener((obx, oldItem, newItem) -> {
setText(newItem != null ? this.converter.toString(newItem) : null);
});
setGraphic(this.textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
this.textField.setOnAction(evt -> {
commitEdit(this.converter.fromString(this.textField.getText()));
});
this.textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (!isNowFocused) {
commitEdit(this.converter.fromString(this.textField.getText()));
}
});
this.textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (event.getCode() == KeyCode.ESCAPE) {
this.textField.setText(this.converter.toString(getItem()));
cancelEdit();
event.consume();
} else if (event.getCode() == KeyCode.TAB) {
commitEdit(this.converter.fromString(this.textField.getText()));
TableColumn nextColumn = getNextColumn(!event.isShiftDown());
if (nextColumn != null) {
getTableView().getSelectionModel().clearAndSelect(getTableRow().getIndex(), nextColumn);
getTableView().edit(getTableRow().getIndex(), nextColumn);
}
}
});
}
/**
* Convenience converter that does nothing (converts Strings to themselves and vice-versa...).
*/
public static final StringConverter IDENTITY_CONVERTER = new StringConverter() {
@Override
public String toString(String object) {
return object;
}
@Override
public String fromString(String string) {
return string;
}
};
/**
* Convenience method for creating an EditCell for a String value.
*
* @return the edit cell
*/
public static EditCell createStringEditCell() {
return new EditCell(IDENTITY_CONVERTER);
}
// set the text of the text field and display the graphic
@Override
public void startEdit() {
super.startEdit();
this.textField.setText(this.converter.toString(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
this.textField.requestFocus();
}
// revert to text display
@Override
public void cancelEdit() {
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
// commits the edit. Update property if possible and revert to text display
@Override
public void commitEdit(T item) {
// This block is necessary to support commit on losing focus, because the baked-in mechanism
// sets our editing state to false before we can intercept the loss of focus.
// The default commitEdit(...) method simply bails if we are not editing...
if (!isEditing() && !item.equals(getItem())) {
TableView table = getTableView();
if (table != null) {
TableColumn column = getTableColumn();
CellEditEvent event = new CellEditEvent<>(table,
new TablePosition(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
super.commitEdit(item);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
/**
* Finds and returns the next editable column.
*
* @param forward
* indicates whether to search forward or backward from the current column
* @return the next editable column or {@code null} if there is no next column available
*/
private TableColumn getNextColumn(boolean forward) {
List> columns = new ArrayList<>();
for (TableColumn column : getTableView().getColumns()) {
columns.addAll(getEditableColumns(column));
}
// There is no other column that supports editing.
if (columns.size() < 2) { return null; }
int currentIndex = columns.indexOf(getTableColumn());
int nextIndex = currentIndex;
if (forward) {
nextIndex++;
if (nextIndex > columns.size() - 1) {
nextIndex = 0;
}
} else {
nextIndex--;
if (nextIndex < 0) {
nextIndex = columns.size() - 1;
}
}
return columns.get(nextIndex);
}
/**
* Returns all editable columns of a table column (supports nested columns).
*
* @param root
* the table column to check for editable columns
* @return a list of table columns which are editable
*/
private List> getEditableColumns(TableColumn root) {
List> columns = new ArrayList<>();
if (root.getColumns().isEmpty()) {
// We only want the leaves that are editable.
if (root.isEditable()) {
columns.add(root);
}
return columns;
} else {
for (TableColumn column : root.getColumns()) {
columns.addAll(getEditableColumns(column));
}
return columns;
}
}
}
Controller
@FXML
private void initialize() {
table.getSelectionModel().setCellSelectionEnabled(true);
table.setEditable(true);
table.getColumns().add(createColumn("First Name", Person::firstNameProperty));
table.getColumns().add(createColumn("Last Name", Person::lastNameProperty));
table.getColumns().add(createColumn("Email", Person::emailProperty));
table.getItems().addAll(
new Person("Jacob", "Smith", "jacob.smith@example.com"),
new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
new Person("Ethan", "Williams", "ethan.williams@example.com"),
new Person("Emma", "Jones", "emma.jones@example.com"),
new Person("Michael", "Brown", "michael.brown@example.com")
);
table.setOnKeyPressed(event -> {
TablePosition pos = table.getFocusModel().getFocusedCell() ;
if (pos != null && event.getCode().isLetterKey()) {
table.edit(pos.getRow(), pos.getTableColumn());
}
});
}
private TableColumn createColumn(String title, Function property) {
TableColumn col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
col.setCellFactory(column -> EditCell.createStringEditCell());
return col;
}