JavaFX 2: Save edit in TableCell


Question

In my JavaFX-program I use a TableCell where you can edit a value. Like shown at the examples on the JavaFX-page "Example", I use this function to save the changes (function is set on TextField in edit Cell)

textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
    @Override public void handle(KeyEvent t) {
    if (combo.match(t)) {
        commitEdit(textField.getText());
    } else if (t.getCode() == KeyCode.ESCAPE) {
        cancelEdit();
    }
 }

When using ENTER to leave the cell, the value is changed / saved, but how can I change / save the value, when leaving the cell by clicking in another cell? Actually the value is resetted.

Thanks Basti

1
2
10/23/2011 6:29:28 PM

Accepted Answer

Listening to a change in focus on the TextField is one way.. I added a listener to the focusedProperty of the textField. The example from Oracle didn't include this. [edit - here is a link to another question that has a different approach UITableView - Better Editing through Binding? ]

private void createTextField() {
        textField = new TextField(getItem());
        textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);

        // Detect a change in focus on the text field.. If we lose the focus we take appropriate action
        textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if(!newValue.booleanValue())
                    commitEdit(textField.getText());
            }
        } );
        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();
                }
            }
        });
    }
2
5/23/2017 10:33:50 AM

  import javafx.beans.property.ObjectProperty;
  import javafx.beans.value.ChangeListener;
  import javafx.beans.value.ObservableValue;
  import javafx.collections.ObservableList;
    import javafx.scene.Node;
  import javafx.scene.control.Cell;
  import javafx.scene.control.ComboBox;
  import javafx.scene.control.TextField;
  import javafx.scene.input.KeyCode;
 import javafx.scene.input.KeyEvent;
 import javafx.scene.input.MouseEvent;
 import javafx.scene.layout.HBox;
import javafx.util.StringConverter;

class CellGenerator {

/***************************************************************************
 * * Private fields * *
 **************************************************************************/
private final static StringConverter defaultStringConverter = new StringConverter<Object>() {
    @Override
    public String toString(Object t) {
        return t == null ? null : t.toString();
    }

    @Override
    public Object fromString(String string) {
        return (Object) string;
    }
};

static <T> StringConverter<T> defaultStringConverter() {
    return (StringConverter<T>) defaultStringConverter;
}

private static <T> String getItemText(Cell<T> cell, StringConverter<T> converter) {
    return converter == null ? cell.getItem() == null ? "" : cell.getItem().toString()
            : converter.toString(cell.getItem());
}

/***************************************************************************
 * * TextField convenience * *
 **************************************************************************/
static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final TextField textField) {
    updateItem(cell, converter, null, null, textField);
}

static <T> void updateItem(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox,
        final Node graphic, final TextField textField) {
    if (cell.isEmpty()) {
        cell.setText(null);
        cell.setGraphic(null);
    } else {
        if (cell.isEditing()) {
            if (textField != null) {
                textField.setText(getItemText(cell, converter));
            }
            cell.setText(null);
            if (graphic != null) {
                hbox.getChildren().setAll(graphic, textField);
                cell.setGraphic(hbox);
            } else {
                cell.setGraphic(textField);
            }
        } else {
            cell.setText(getItemText(cell, converter));
            cell.setGraphic(graphic);
        }
    }
}

static <T> void startEdit(final Cell<T> cell, final StringConverter<T> converter, final HBox hbox,
        final Node graphic, final TextField textField) {
    if (textField != null) {
        textField.setText(getItemText(cell, converter));
    }
    cell.setText(null);
    if (graphic != null) {
        hbox.getChildren().setAll(graphic, textField);
        cell.setGraphic(hbox);
    } else {
        cell.setGraphic(textField);
    }
    textField.selectAll();
    // requesting focus so that key input can immediately go into the
    // TextField (see RT-28132)
    textField.requestFocus();
}

static <T> void cancelEdit(Cell<T> cell, final StringConverter<T> converter, Node graphic) {
    cell.setText(getItemText(cell, converter));
    cell.setGraphic(graphic);

}

static <T> TextField createTextField(final Cell<T> cell, final StringConverter<T> converter) {

    final TextField textField = new TextField(getItemText(cell, converter));
    EdittingCell cellEdit=(EdittingCell)cell;
    textField.setOnMouseExited(event -> {

        if (converter == null) {
            throw new IllegalStateException("Attempting to convert text input into Object, but provided "
                    + "StringConverter is null. Be sure to set a StringConverter "
                    + "in your cell factory.");
        }

        cell.commitEdit(converter.fromString(textField.getText()));

    });

    textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {

        if (event.getCode() == KeyCode.ESCAPE) {
            cell.cancelEdit();
            event.consume();
        } else if (event.getCode() == KeyCode.RIGHT) {
            cellEdit.getTableView().getSelectionModel().selectRightCell();
            event.consume();
        } else if (event.getCode() == KeyCode.LEFT) {
            cellEdit.getTableView().getSelectionModel().selectLeftCell();
            event.consume();
        } else if (event.getCode() == KeyCode.UP) {
            cellEdit.getTableView().getSelectionModel().selectAboveCell();
            event.consume();
        } else if (event.getCode() == KeyCode.DOWN) {
            cellEdit.getTableView().getSelectionModel().selectBelowCell();
            event.consume();
        } else if (event.getCode() == KeyCode.ENTER) {
            if (converter == null) {
                throw new IllegalStateException("Attempting to convert text input into Object, but provided "
                        + "StringConverter is null. Be sure to set a StringConverter "
                        + "in your cell factory.");
            }

            cell.commitEdit(converter.fromString(textField.getText()));

            event.consume();
        }
        else if (event.getCode() == KeyCode.TAB) {

            cell.commitEdit(converter.fromString(textField.getText()));

            cellEdit.setNextColumn(event);

            event.consume();
        }
    });

    return textField;
}}

//the table cell

  import java.util.ArrayList;
  import java.util.List;

  import javafx.beans.property.ObjectProperty;
  import javafx.beans.property.SimpleObjectProperty;
  import javafx.collections.FXCollections;
  import javafx.collections.ObservableList;
  import javafx.event.EventHandler;
  import javafx.scene.control.*;
   import javafx.scene.input.KeyCode;
   import javafx.scene.input.KeyEvent;
   import javafx.util.Callback;
   import javafx.util.StringConverter;
   import javafx.util.converter.DefaultStringConverter;

 public class EdittingCell<S, T> extends TableCell<S, T> {

public static <S> Callback<TableColumn<S, String>, TableCell<S, String>> 
forTableColumn() {
    return forTableColumn(new DefaultStringConverter());
}

public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> 
forTableColumn(
        final StringConverter<T> converter) {
    return new Callback<TableColumn<S, T>, TableCell<S, T>>() {
        @Override
        public TableCell<S, T> call(TableColumn<S, T> list) {
            return new EdittingCell<S, T>(converter);
        }
    };
}

public static <S, T> Callback<TableColumn<S, T>, TableCell<S, T>> 
forTableColumn(final StringConverter<T> converter,
        final boolean isFieldEditable) {
    return new Callback<TableColumn<S, T>, TableCell<S, T>>() {
        @Override
        public TableCell<S, T> call(TableColumn<S, T> list) {
            return new EdittingCell<S, T>(converter, isFieldEditable);
        }
    };
}

/***************************************************************************
 * * Fields * *
 **************************************************************************/
public TextField textField;
private static int currentRow = -1;
private static int control = 0;

public EdittingCell() {
    this(null);
    textField = CellGenerator.createTextField(this, getConverter());
}

public EdittingCell(StringConverter<T> converter) {

    this.getStyleClass().add("text-field-table-cell");

    setConverter(converter);
    textField = CellGenerator.createTextField(this, getConverter());
    // textField.setEditable(false);

}

public EdittingCell(StringConverter<T> converter, boolean isFieldEditable) {

    this.getStyleClass().add("text-field-table-cell");

    setConverter(converter);
    textField = CellGenerator.createTextField(this, getConverter());
    textField.setEditable(isFieldEditable);

}

/***************************************************************************
 * * Properties * *
 **************************************************************************/
// --- converter
private ObjectProperty<StringConverter<T>> converter = new SimpleObjectProperty<StringConverter<T>>(this,
        "converter");

public final ObjectProperty<StringConverter<T>> converterProperty() {
    return converter;
}

public TextField getTextFiedCell() {
    return textField;
}

public final void setConverter(StringConverter<T> value) {
    converterProperty().set(value);
}

public final StringConverter<T> getConverter() {
    return converterProperty().get();
}

@Override
public void startEdit() {
    if (!isEditable() || !getTableView().isEditable() || !getTableColumn().isEditable()) {
        return;
    }
    super.startEdit();
    if (isEditing()) {

        CellGenerator.startEdit(this, getConverter(), null, null, textField);
    }
}

@Override
public void cancelEdit() {
    super.cancelEdit();
    CellGenerator.cancelEdit(this, getConverter(), null);
}

/** {@inheritDoc} */
@Override
public void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);
    CellGenerator.updateItem(this, getConverter(), null, null, textField);
    // System.out.println("Silas");
}

public TableView<S> getContextTableView() {

    return getTableView();
}

public void setNextColumn(KeyEvent event) {
    TableColumn nextColumn = getNextColumn(!event.isShiftDown());

    if (nextColumn != null) {

        // Get Selected index to reset current editable row
        int selectedRow = getTableRow().getIndex();
        // Set row that serves as a control for tapping through
        if (currentRow == -1) {
            currentRow = getTableRow().getIndex();
        }
        // Reset editing upon selection change row
        if (currentRow != selectedRow) {
            currentRow = selectedRow;
        }

        int colSize = getTableView().getColumns().size();
        int colindex = getTableView().getColumns().indexOf(nextColumn);

        if (colindex == colSize - 1) {
            control++;
        }
        if (control > 0 && colindex == 0) {
            currentRow++;
        }

        if (getTableView().getItems().size() > currentRow) {
            getTableView().edit(currentRow, nextColumn);
            // getTableView().getSelectionModel().select(currentRow,
            // nextColumn);
        } else {
            currentRow = 0;
            // getTableView().getSelectionModel().select(currentRow,
            // nextColumn);
            getTableView().edit(currentRow, nextColumn);
        }
    }
}

private TableColumn<S, ?> getNextColumn(boolean forward) {

    List<TableColumn<S, ?>> columns = new ArrayList<>();

    for (TableColumn<S, ?> column : getTableView().getColumns()) {

        columns.addAll(getLeaves(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);
}

private ObservableList<TableColumn<S, ?>> getLeaves(TableColumn<S, ?> column2) {
    ObservableList<TableColumn<S, ?>> columns = FXCollections.observableArrayList();
    if (column2.getColumns().isEmpty()) {
        // We only want the leaves that are editable.
        if (column2.isEditable()) {
            columns.addAll(column2);
        }
        return columns;
    } else {
        for (TableColumn<S, ?> column : column2.getColumns()) {
            columns.addAll(getLeaves(column));
        }
        return columns;
    }

}

}

//How to use this

    TableColumn<NewInvoice, BigDecimal> quantityCol = new 
   TableColumn<NewInvoice, BigDecimal>("Quantity");
    quantityCol.setCellValueFactory(cellData -> 
   cellData.getValue().quantityProperty());
    quantityCol.setCellFactory(EdittingCell.forTableColumn( new 
   BigDecimalStringConverter()));
    quantityCol.setStyle("-fx-alignment:CENTER-RIGHT;");
    quantityCol.setOnEditCommit(new EventHandler<CellEditEvent<NewInvoice, 
   BigDecimal>>() {
        @Override
        public void handle(CellEditEvent<NewInvoice, BigDecimal> t) {
     t.getTableView().getItems().get(t.getTablePosition().getRow()
   ).setQuantity(t.getNewValue());
        }
    });

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon