1

I'm trying to create a tableview with JvafaFX that will display a list of camera parameters. Some of these parameters are editable and some are not, some are constrained by a list of values others are free text entry. I have setup the table to display the parameters in a key, value type display where one column is used for the name of the parameter and the other the value.

The values for the table are set using an observable list which is generated from the background data model:

propertyNamesColumn.setCellValueFactory(cellData -> cellData.getValue().getName());
propertyValuesColumn.setCellValueFactory(cellData -> cellData.getValue().getValue());

The model also contains whether the property should be editable and what possible values it may contain, these are currently stored in two different fields (I'm not sure if this is the best way) so there are 4 fields in total.

When setting up the rows of the table I want to make those that should be editable (according to the values in the model), editable by either a choicebox containing the values from the possible values list or a text field.

However I'm not sure what I need to implement to make this happen, I have tried extending the ChoiceBoxTableCell class to add in this logic but the table cells never even become editable.

I'm fairly sure that either an extension of the choicebox cell type or a cell factory should be able to do this, but I don't know how.

Thanks for any help.

1
  • You will need a cell factory and a custom TableCell implementation. Depending on the exact requirements (e.g. if and when the "editable" property and the list of allowed values for any given item can change), it might be fairly straightforward, or quite complex. Commented Jan 4, 2016 at 17:07

1 Answer 1

4

I think to do this, you need to create a generic model Parameter class, and use that as the type for your table. You can make it abstract and define an abstract getEditor() method (or perhaps delegate the editor factory to another class, but I will try to keep this as simple as possible). Then define subclasses which create different editors as required.

This might look something like this:

public abstract class Parameter<T> {

    private final BooleanProperty editable = new SimpleBooleanProperty();

    private final ObjectProperty<T> value = new SimpleObjectProperty<>();

    private final String name ;

    public Parameter(String name, T value, boolean editable) {
        this.name = name ;
        setValue(value);
        setEditable(editable);
    }

    public Parameter(String name, T value) {
        this(name, value, true);
    }

    public String getName() {
        return name ;
    }

    public ObjectProperty<T> valueProperty() {
        return value ;
    }

    public T getValue() {
        return valueProperty().get();
    }

    public void setValue(T value) {
        valueProperty().set(value);
    }

    public BooleanProperty editableProperty() {
        return editable ;
    }

    public boolean isEditable() {
        return editableProperty().get() ;
    }

    public void setEditable(boolean editable) {
        editableProperty().set(editable);
    }

    public abstract Node getEditor() ;

}

Then you might have a simple implementation like this for "free" strings:

public class StringParameter extends Parameter<String> {

    private final TextField editor ;

    public StringParameter(String name, String value) {
        super(name, value);
        editor = new TextField();
        editor.textProperty().bindBidirectional(valueProperty());
    }

    @Override
    public Node getEditor() { 
        return editor ;
    }

}

and maybe something like this for a spinner:

public class BoundIntegerParameter extends Parameter<Integer> {

    private final Spinner<Integer> editor ;

    public BoundIntegerParameter(int min, int max, String name, int value) {
        super(name, value);
        editor = new Spinner<>(min, max, value);
        editor.setEditable(true);
        editor.getValueFactory().valueProperty().bindBidirectional(valueProperty());
    }

    @Override
    public Node getEditor() {
        return editor ;
    }

}

For the "fixed list" you could similarly implement a FixedStringParameter which took a list of strings, and whose getEditor method returned a ComboBox. Another approach for fixed choices might be to use Enum types: this might look like

public class EnumParameter<E extends Enum<E>> extends Parameter<E> {

    private final ComboBox<E> editor ;

    public EnumParameter(String name, E value) {
        super(name, value);
        editor = new ComboBox<>();
        @SuppressWarnings("unchecked")
        Class<E> type = (Class<E>) value.getClass();
        E[] values = type.getEnumConstants() ;
        editor.getItems().setAll(values);
        editor.valueProperty().bindBidirectional(valueProperty());
    }

    @Override
    public Node getEditor() {
        return editor ;
    }

}

Now for the cell implementation for the value column, you need a bit of trickery. This seems to work:

public class ParameterValueEditingCell extends TableCell<Parameter<?>, Object> {


    @Override
    public void updateItem(Object item, boolean empty) {
        super.updateItem(item, empty);
        if (empty || item == null) {
            setText(null);
            setGraphic(null);
        } else {
            if (isEditing()) {
                setText(null);
                Parameter<?> param = getTableView().getItems().get(getIndex());
                setGraphic(param.getEditor());
            } else {
                setText(item.toString());
                setGraphic(null);
            }
        }
    }

    @Override
    public void startEdit() {
        // check if current parameter is editable and bail if not:

        int index = getIndex();
        if (index < 0 || index >= getTableView().getItems().size()) {
            return ;
        }
        if (! getTableView().getItems().get(index).isEditable()) {
            return ;
        }

        super.startEdit();
        setText(null);
        setGraphic(getTableView().getItems().get(getIndex()).getEditor());
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();
        Object item = getItem();
        setText(item == null ? null : item.toString());
        setGraphic(null);
    }

}

Finally, you can set things up as

TableView<Parameter<?>> table = new TableView<>();
table.setEditable(true);
TableColumn<Parameter<?>, Object> valueColumn = new TableColumn<>("Value");

// I can't see any way to set this up without the ugly (unchecked) cast
// Any ideas?
valueColumn.setCellValueFactory(cellData -> (ObservableValue<Object>)cellData.getValue().valueProperty());

valueColumn.setCellFactory(tc -> new ParameterValueEditingCell());

I made a complete example here

Sign up to request clarification or add additional context in comments.

3 Comments

Note this probably isn't an ideal design, because the model (Parameter) holds a reference to the view (editor). As well as violating basic UI design principles, this would not perform well for large number of rows (because it creates a node for each item, not just for each visible row). Improving the design seems tricky (to me, at least); it should give you at least a start.
Thanks James! I didn't fully use your solution as I'm happy with dealing with everything as a string on the front end. I did however use your cell implementation as a base to build on which worked great. I also used a similar method to your getEditor() method to read the parameter type and return the appropriate node. I also had to use the setOnAction() methods of the nodes to call the commitEdit(newValue) of the tableview becuase otherwise they were never called.
In my example getting commitEdit(...) invoked is unnecessary, because of the binding. I still don't like the getEditor() method in the model class, but I'm glad it helped.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.