How to Enable Commit on Focuslost for Tableview/Treetableview

TableView doesn't commit values on focus lost event

I got curious and did some background research.

You are facing the problem of a well-known bug in the JavaFX.

Background

When you call commitEdit(textField.getText()), the first thing it does is to check the value of isEditing() and returns if the value is false, without committing.

public void commitEdit(T newValue) {
if (! isEditing()) return;

... // Rest of the things
}

Why does it return false?

As you have probably found out, as soon as you press TAB or ENTER to change your selection, cancelEdit() is called which sets the TableCell.isEditing() to false. By the time the commitEdit() inside textField's focus property listener is called, isEditing() is already returning false.

Solutions / Hacks

There have been on going discussion on the Topic in JavaFX community. People in there have posted hacks, which you are most welcome to look at.

  • TableView, TreeView, ListView - Clicking outside of the edited cell, node, or entry should commit the value
  • TableCell - commit on focus lost not possible in every case

There is a hack shown in a SO thread, which seems to get the job done, although I haven't tried it (yet).

JAVAFX 8 TreeTableView: why doesn't this code detect cell losing focus?

The short answer to why don't I see a change in the focusedProperty is that there is no change because the property is always false.

Reason being, that the focusedProperty of a Tree/Table/Cell is (arguably mis-) used to represent the focused cell of the Tree/TableView's FocusModel (vs. the "real" focus as being focusOwner), but only if cellSelectionEnabled.

The relevant code snippet in updateFocus (in TableCell) which is called f.i. by an InvalidationListener to the focusedProperty of the FocusModel:

private void updateFocus() {
final boolean isFocused = isFocused();
if (! isInCellSelectionMode()) {
if (isFocused) {
setFocused(false);
}
return;
}
...
}

JavaFx TableView - saving edits on focus lost doesn't work if focus is given to same column

After digging, I found out how to get the property name of a column. With that I went ahead and wrote some generic reflections to force update. I wrapped everything in a method commit(Object val) for ease of use. These are modifications to EditCell class used here.

Disclaimer: This only works if you use a PropertyValueFactory and follow naming conventions in your row classes. This is also very fickle code, use and modify at your own discretion.

I modified the cell to be a generic cell with public static class EditingCell<S, T> extends TableCell<S, T>. Everything else from the tutorial should still be the same, if not feel free to let me know and I'll update here accordingly.

public void commit(Object val) {

// Get the table
TableView<S> t = this.getTableView();

// Get the selected row/column
S selectedRow = t.getItems().get(this.getTableRow().getIndex());
TableColumn<S, ?> selectedColumn = t.getColumns().get(t.getColumns().indexOf(this.getTableColumn()));

// Get current property name
String propertyName = ((PropertyValueFactory) selectedColumn.getCellValueFactory()).getProperty();

// Create a method name conforming to java standards ( setProperty )
propertyName = ("" + propertyName.charAt(0)).toUpperCase() + propertyName.substring(1);

// Try to run the update
try {

// Type specific checks - could be done inside each setProperty() method
if(val instanceof Double) {
Method method = selectedRow.getClass().getMethod("set" + propertyName, double.class);
method.invoke(selectedRow, (double) val);
}
if(val instanceof String) {
Method method = selectedRow.getClass().getMethod("set" + propertyName, String.class);
method.invoke(selectedRow, (String) val);
}
if(val instanceof Integer) {
Method method = selectedRow.getClass().getMethod("set" + propertyName, int.class);
method.invoke(selectedRow, (int) val);
}

} catch (Exception e) {
e.printStackTrace();
}

// CommitEdit for good luck
commitEdit((T) val);
}

and then since the text field won't update, I forced an update on it in cancelEdit(). This is slightly specific to my case ( I want a default of 0.0 and only accepted values are doubles ) - modify it as needed.

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

// Default value
String val = "0.0";

// Check to see if there's a value
if (!textField.getText().equals(""))
val = textField.getText();

// Set table cell text
setText("" + val);
setGraphic(null);
}


Related Topics



Leave a reply



Submit