1

I've created a simple TableView that is fed with data from a database, and what I want is just to be able to easily change the value of a numeric column of that table with JavaFx.

But... since I have some mental issue or something, I can't make it work.

Below it's the "SpinnerCell" component, and the issue I've been having is that even after the commitEdit is fired, when I get the items from the TableView, no values were altered. What am I missing from this update lifecycle?

import javafx.scene.control.Spinner;
import javafx.scene.control.TableCell;

public class SpinnerTableCell<S, T extends Number> extends TableCell<S, T> {

    private final Spinner<T> spinner;

    public SpinnerTableCell() {
        this(1);
    }

    public SpinnerTableCell(int step) {
        this.spinner = new Spinner<>(0, 100, step);
        this.spinner.valueProperty().addListener((observable, oldValue, newValue) -> commitEdit(newValue));
    }

    @Override
    protected void updateItem(T c, boolean empty) {
        super.updateItem(c, empty);

        if (empty || c == null) {
            setText(null);
            setGraphic(null);
            return;
        }

        this.spinner.getValueFactory().setValue(c);

        setGraphic(spinner);
    }

}
BBacon
  • 2,456
  • 5
  • 32
  • 52

1 Answers1

2

Because your table cell is always showing the editing control (the Spinner), you bypass the usual table cell mechanism for beginning an edit. For example, in the TextFieldTableCell, if the cell is not in an editing state, then a label is shown. When the user double-clicks the cell, it enters an editing state: the cell's editingProperty() is set to true, and the enclosing TableView's editingCellProperty() is set to the position of the current cell, etc.

In your case, since this never happens, isEditing() is always false for the cell, and as a consequence, commitEdit() becomes a no-op.

Note that the CheckBoxTableCell is implemented similarly: its documentation highlights this fact. (The check box table cell implements its own direct update of properties via the selectedStateCallback.)

So there are two options here: one would be to enter an editing state when the spinner gains focus. You can do this by adding the following to the cell's constructor:

this.spinner.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
    if (isNowFocused) {
        getTableView().edit(getIndex(), getTableColumn());
    }
});

Another option would be to provide a callback for "direct updates". So you could do something like:

public SpinnerTableCell(BiConsumer<S,T> update, int step) {
    this.spinner = new Spinner<>(0, 100, step);

    this.spinner.valueProperty().addListener((observable, oldValue, newValue) -> 
        update.accept(getTableView().getItems().get(getIndex()), newValue));
}

and then given a model class for the table, say

public class Item {
    private int value ;
    public int getValue() { return value ;}
    public void setValue(int value) { this.value = value ;}

    // ...
}

You could do

TableView<Item> table = ... ;
TableColumn<Item, Integer> valueCol = new TableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> new SimpleIntegerProperty(cellData.getValue().getValue()).asObject());
valueCol.setCellFactory(tc -> new SpinnerTableCell<>(Item::setValue, 1));
James_D
  • 201,275
  • 16
  • 291
  • 322
  • 1
    I wish to use the first approach, but it's causing bugs, now the spinner value does nothing when you click increment/decrement button. – BBacon Jan 17 '17 at 18:02
  • 1
    Well, the second one worked like a charm. Thanks a LOT for your help. Sorry for making you answer such simple questions. – BBacon Jan 17 '17 at 18:10
  • 1
    @MBarni Oh, this was an interesting one :). The first approach worked fine for me; sounds like maybe you got in some infinite loop via a `onEditCommit` handler, or something...? – James_D Jan 17 '17 at 18:11