So I've been trying to implement a TableView where you can edit a column just by clicking on it, and then save the edit by pressing the enter key. I've taken at lot of the code from the answer in this thread. This is the result:
import com.sun.javafx.collections.ObservableListWrapper;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.stage.Stage;
public class TestEditableTable extends Application {
public void start(Stage stage) {
TableView<ObservableList<String>> tableView = new TableView<ObservableList<String>>();
tableView.setEditable(true);
// Some dummy data.
ObservableList<ObservableList<String>> dummyData = FXCollections.observableArrayList();
ObservableList<String> firstRow = FXCollections.observableArrayList("Jack", "Smith");
dummyData.add(firstRow);
ObservableList<String> secondRow = FXCollections.observableArrayList("Peter", "Smith");
dummyData.add(secondRow);
TableColumn<ObservableList<String>, String> firstCol = new TableColumn<ObservableList<String>, String>(
"First name");
firstCol.setCellValueFactory(
(TableColumn.CellDataFeatures<ObservableList<String>, String> param) -> new SimpleStringProperty(
param.getValue().get(0)));
TableColumn<ObservableList<String>, String> secondCol = new TableColumn<ObservableList<String>, String>(
"Last name");
secondCol.setCellValueFactory(
(TableColumn.CellDataFeatures<ObservableList<String>, String> param) -> new SimpleStringProperty(
param.getValue().get(1)));
secondCol.setCellFactory(cell -> new EditableCell());
tableView.getColumns().addAll(firstCol, secondCol);
tableView.getItems().addAll(dummyData);
Scene scene = new Scene(tableView);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
class EditableCell extends TableCell<ObservableList<String>, String> {
private TextField textfield = new TextField();
// When the user presses the enter button the edit is saved.
public EditableCell() {
setOnKeyPressed(e -> {
if (e.getCode().equals(KeyCode.ENTER)) {
commitEdit(textfield.getText());
}
});
}
@Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (isEmpty()) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
textfield.setText(item);
setGraphic(textfield);
setText(null);
} else {
setText(item);
setGraphic(null);
}
}
}
@Override
public void startEdit() {
super.startEdit();
textfield.setText(getItem());
setGraphic(textfield);
setText(null);
}
@Override
public void cancelEdit() {
super.cancelEdit();
setGraphic(null);
setText(getItem());
}
@Override
public void commitEdit(String value) {
super.commitEdit(value);
// This works. But gives me a "Discouraged access: The type 'ObservableListWrapper<String>' is not API (restriction on required library 'C:\Program Files\Java\jre1.8.0_60\lib\ext\jfxrt.jar')".
ObservableListWrapper<String> ob = ((ObservableListWrapper<String>) this.getTableRow().getItem());
ob.set(1, value);
// I had to put this in a Platform.runLater(), otherwise the textfield remained open.
Platform.runLater(() -> {
setText(value);
setGraphic(null);
});
}
}
}
And this program works OK. The cell is edited when you press the enter button. But there are two main issues with this:
Once I've pressed enter and a cell has been edited. I can't edit it again by clicking on it. I need to press another row to be able to edit it again. Setting to focus on the parent of the row didn't do the trick.
The code inside commitEdit()
works. But it is ugly and using a ObservableListWrapper
gives me the warning "Discouraged access: The type 'ObservableListWrapper' is not API (restriction on required library 'C:\Program Files\Java\jre1.8.0_60\lib\ext\jfxrt.jar')". Also the cell index is hard coded, which wont work if I use this in many different columns.
None of the issues mentioned above is acceptable.
The final implementation must support restriction of the input in the textfield. As far as I understand it this means that I need to have access to the TextField
object displayed in the cell, as in my current implementation.
You are registering the event handler as a key event handler on the cell. It makes more sense to register a handler on the text field, since that is the control on which the event of interest actually occurs. Note also that TextField
s fire action events on pressing enter.
So replace
public EditableCell() {
setOnKeyPressed(e -> {
if (e.getCode().equals(KeyCode.ENTER)) {
commitEdit(textfield.getText());
}
});
}
with
public EditableCell() {
textfield.setOnAction(e -> commitEdit(textfield.getText()));
}
This will make sure the editing state is properly maintained (which is not happening with your event handler, for reasons I can't quite figure out), so it fixes the issue of "re-editing". It also means you don't have to manually reset the text and graphic in commitEdit
.
For the second issue, you can just downcast to ObservableList<String>
instead of ObservableListWrapper<String>
. But note you can also just do
@Override
public void commitEdit(String value) {
super.commitEdit(value);
ObservableList<String> row = getTableView().getItems().get(getIndex());
row.set(1, value);
}
which not only avoids the private API, it avoids the downcast entirely.