How to commit when clicking outside an editable TableView cell in JavaFX?

user555 picture user555 · May 13, 2014 · Viewed 11.5k times · Source

I have a table cell factory responsible for creating an editable cell in a JavaFX TableView.

I'm trying to implement some added functionality to the tableview so that when the user clicks outside the editable cell a commit is made (the edited text is saved, and not discarded as per the default tableview behavior.)

I added an textField.focusedProperty() event handler, where I commit the text from the text field. However, when one clicks outside the current cell cancelEdit() gets called and calling commitEdit(textField.getText()); has no effect.

I have come to realize that once cancelEdit() is called the TableCell.isEditing() returns false and so the commit will never happen.

How can I make so that when the user clicks outside the editable cell the text is committed?

After committing an setOnEditCommit() event handler will take care of the validation and database logic. I haven't included it here since it will most likely complicate things even further.

// EditingCell - for editing capability in a TableCell
public static class EditingCell extends TableCell<Person, String> {
private TextField textField;

public EditingCell() {
}

@Override public void startEdit() {
    super.startEdit();

    if (textField == null) {
        createTextField();
    }
    setText(null);
    setGraphic(textField);
    textField.selectAll();
}

@Override public void cancelEdit() {
    super.cancelEdit();
    setText((String) getItem());
    setGraphic(null);
}

@Override public void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);
    if (empty) {
        setText(null);
        setGraphic(null);
    } else {
        if (isEditing()) {
            if (textField != null) {
                textField.setText(getString());
            }
            setText(null);
            setGraphic(textField);
        } else {
            setText(getString());
            setGraphic(null);
        }
    }
}

private void createTextField() {
    textField = new TextField(getString());
    textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
    textField.setOnKeyReleased(new EventHandler<KeyEvent>() {                
        @Override public void handle(KeyEvent t) {
            if (t.getCode() == KeyCode.ENTER) {
                commitEdit(textField.getText());
            } else if (t.getCode() == KeyCode.ESCAPE) {
                cancelEdit();
            }
        }
    });

    textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
         @Override
         public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
             if (!newValue) {
                    commitEdit(textField.getText());
             }
         }
    });
}

private String getString() {
    return getItem() == null ? "" : getItem().toString();
}
} 

Answer

Nicolas Filotto picture Nicolas Filotto · Nov 25, 2015

You could do it by overriding the method commitEdit as next:

@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<S> table = getTableView();
        if (table != null) {
            TableColumn<S, T> column = getTableColumn();
            CellEditEvent<S, T> event = new CellEditEvent<>(
                table, new TablePosition<S,T>(table, getIndex(), column), 
                TableColumn.editCommitEvent(), item
            );
            Event.fireEvent(column, event);
        }
    }

    super.commitEdit(item);
}

This workaround comes from https://gist.github.com/james-d/be5bbd6255a4640a5357#file-editcell-java-L109