1

I am using a TableView which is populated by my model.

I want to use a spinner control in one column.

I am able to create the spinner in the cells in the desired column, but I am struggling to bind the spinners value to the models property.

This is the fxml

<ScrollPane> <content> <TableView prefHeight="525.0" prefWidth="814.0"> <columns> <TableColumn prefWidth="75.0"> <cellValueFactory><PropertyValueFactory property="state"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Side"> <cellValueFactory><PropertyValueFactory property="side"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Source"> <cellValueFactory><PropertyValueFactory property="sourceContract"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Reference"> <cellValueFactory><PropertyValueFactory property="referenceContract"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Destination"> <cellValueFactory><PropertyValueFactory property="destinationContract"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Margin" editable="true"> <cellFactory><SpinnerTableCellFactory /></cellFactory> <cellValueFactory><PropertyValueFactory property="margin"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Bot"> <cellValueFactory><PropertyValueFactory property="bot"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Price"> <cellValueFactory><PropertyValueFactory property="price"/></cellValueFactory> </TableColumn> <TableColumn prefWidth="75.0" text="Volume"> <cellValueFactory><PropertyValueFactory property="volume"/></cellValueFactory> </TableColumn> </columns> <items> <FXCollections fx:factory="observableArrayList"> <GridRowModel state="false" side="BID" sourceContract="s01" referenceContract="" destinationContract="d01" margin="0" bot="MinMax" price="15.125" volume="0" /> <GridRowModel state="false" side="ASK" sourceContract="s02" referenceContract="" destinationContract="d02" margin="0" bot="MinMax" price="15.125" volume="0" /> </FXCollections> </items> </TableView> </content> </ScrollPane> 

And this is the SpinnerTableCellFactory

public class SpinnerTableCellFactory<S, T> implements Callback<TableColumn<S, Double>, TableCell<S, Double>> { @Override public TableCell<S, Double> call(TableColumn<S, Double> param) { return new TableCell<S, Double>() { Spinner<Double> spinner = new Spinner<>(0d, 1d, 0d, 0.025d); protected void updateItem(Double item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { setText(null); setGraphic(null); } else { spinner.getValueFactory().setValue(getItem()); setText(null); setGraphic(spinner); } } }; }; } } 

When the fxml is loaded the updateItem method is called with the default values from the fxml for the margin property. I can see and use the spinner in the cells. But how do I pass any new spinner value back to the margin property in the GridRowModel object?

1 Answer 1

3

Just register a listener with the spinner's valueProperty():

public class SpinnerTableCellFactory<S, T> implements Callback<TableColumn<S, Double>, TableCell<S, Double>> { @Override public TableCell<S, Double> call(TableColumn<S, Double> param) { return new TableCell<S, Double>() { Spinner<Double> spinner = new Spinner<>(0d, 1d, 0d, 0.025d); { spinner.valueProperty().addListener((obs, oldValue, newValue) -> { ObservableValue<Double> value = getTableColumn().getCellObservableValue(getIndex()); if (value instanceof WritableValue) { ((WritableValue<Double>)value).setValue(newValue); } }); } protected void updateItem(Double item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { setText(null); setGraphic(null); } else { spinner.getValueFactory().setValue(getItem()); setText(null); setGraphic(spinner); } } }; }; } } 

I'm assuming here that your model class GridRowModel follows the JavaFX properties pattern.

If you don't use JavaFX Properties, or want to avoid the cast, you can give the cell factory a BiConsumer<GridRowModel, Double> to process updates:

public class SpinnerTableCellFactory<S, T> implements Callback<TableColumn<S, Double>, TableCell<S, Double>> { private BiConsumer<S, Double> updater = null ; public void setUpdater(BiConsumer<S, Double> updater) { this.updater = updater ; } @Override public TableCell<S, Double> call(TableColumn<S, Double> param) { return new TableCell<S, Double>() { Spinner<Double> spinner = new Spinner<>(0d, 1d, 0d, 0.025d); { spinner.valueProperty().addListener((obs, oldValue, newValue) -> { if (updater != null) { updater.accept(getTableView().getItems().get(getIndex()), newValue); } }); } protected void updateItem(Double item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { setText(null); setGraphic(null); } else { spinner.getValueFactory().setValue(getItem()); setText(null); setGraphic(spinner); } } }; }; } } 

Then you can add a fx:id to your factory:

<cellFactory><SpinnerTableCellFactory fx:id="marginCellFactory" /></cellFactory> 

and in your controller do:

@FXML private SpinnerTableCellFactory<GridRowModel, Double> marginCellFactory ; public void initialize() { marginCellFactory.setUpdater(GridRowModel::setMargin); } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.