ktdh
ktdh

Reputation: 169

How do I persist values of javafx treetablecells using custom controls

I implemented a treetable with custom cell controls and am having trouble persisting the updated data from the cell controls. Specifically how might I call commitEdit, updateControl, or use object binding to ensure changes made in each cell are persisted in the underlying domain object.

The initial question Multiple Controls needed in JavaFX Tree Table Cell

In my code below you can see problems keeping the cell data when collapsing and expanding the tree table nodes.

How would I use custom cell controls to trigger updates and persist data to the underlying row object?

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.converter.BooleanStringConverter;

public class SampleApp extends Application {

public static void main(String[] args) {
    Application.launch(args);
}

@SuppressWarnings("unchecked")
@Override
public void start(Stage primaryStage) throws Exception {
    TreeItem<MyField> fooFields = new TreeItem<MyField>(new MyField("Foo", "Foo", null, false, null));
    TreeItem<MyField> fooText = new TreeItem<MyField>(new MyField("fooText", "fooText", "text", true, null));
    TreeItem<MyField> fooCheck = new TreeItem<MyField>(new MyField("fooCheck", "fooCheck", "check", true, null));
    List<String> fooCombos = Arrays.asList("foo Combo 1", "foo Combo 2");
    TreeItem<MyField> fooCombo = new TreeItem<MyField>(
            new MyField("fooCombo", "foo Combo", "combo", true, fooCombos));
    fooFields.getChildren().addAll(fooText, fooCheck, fooCombo);

    TreeItem<MyField> barFields = new TreeItem<MyField>(new MyField("Bar", "Bar", null, false, null));
    TreeItem<MyField> barText = new TreeItem<MyField>(new MyField("barText", "barText", "text", true, null));
    TreeItem<MyField> barCheck = new TreeItem<MyField>(new MyField("barCheck", "barCheck", "check", true, null));
    List<String> barCombos = Arrays.asList("bar Combo 1", "bar Combo 2");
    TreeItem<MyField> barCombo = new TreeItem<MyField>(
            new MyField("barCombo", "bar Combo", "combo", true, barCombos));
    barFields.getChildren().addAll(barText, barCheck, barCombo);

    TreeItem<MyField> hiddenRoot = new TreeItem<MyField>(new MyField("hidden", "hidden", null, false, null));
    hiddenRoot.getChildren().addAll(fooFields, barFields);

    TreeTableView<MyField> treeTable = new TreeTableView<>(hiddenRoot);
    treeTable.setEditable(true);
    treeTable.setPrefWidth(400);
    treeTable.setShowRoot(false);

    TreeTableColumn<MyField, String> nameCol = new TreeTableColumn<MyField, String>("Name");
    nameCol.setPrefWidth(150);
    nameCol.setCellValueFactory(new TreeItemPropertyValueFactory<MyField, String>("name"));

    TreeTableColumn<MyField, String> valueCol = new TreeTableColumn<MyField, String>("Value");
    valueCol.setPrefWidth(250);
    valueCol.setCellValueFactory(new TreeItemPropertyValueFactory<MyField, String>("value"));
    valueCol.setCellFactory(new MyFieldCellFactory());

    treeTable.getColumns().addAll(nameCol, valueCol);

    HBox root = new HBox(treeTable);
    root.setStyle("-fx-padding: 10;" + "-fx-border-style: solid inside;" + "-fx-border-width: 2;"
            + "-fx-border-insets: 5;" + "-fx-border-radius: 5;" + "-fx-border-color: blue;");
    Scene scene = new Scene(root);
    primaryStage.setScene(scene);
    primaryStage.setTitle("Multi Control Tree Table View");
    primaryStage.show();
}

public class MyField {
    private String name;
    private StringProperty value;
    public String fieldType;
    public boolean isEditable;
    public List<String> comboVals;

    public MyField(String name, String value, String fieldType, boolean isEditable, List<String> comboVals) {
        super();
        this.name = name;
        this.value = new SimpleStringProperty(value);
        this.fieldType = fieldType;
        this.isEditable = isEditable;
        this.comboVals = comboVals;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getValue() {
        return value.get();
    }
    public StringProperty valueProperty() {
        return value;
    }

    public void setValue(String value) {
        this.value.set(value);
    }

    public String getFieldType() {
        return fieldType;
    }

    public void setFieldType(String fieldType) {
        this.fieldType = fieldType;
    }

    public List<String> getComboVals() {
        return comboVals;
    }

    public void setComboVals(List<String> comboVals) {
        this.comboVals = comboVals;
    }

    public boolean isEditable() {
        return isEditable;
    }

    public void setEditable(boolean isEditable) {
        this.isEditable = isEditable;
    }

}

public class MyFieldCellFactory
        implements Callback<TreeTableColumn<MyField, String>, TreeTableCell<MyField, String>> {

    @Override
    public TreeTableCell<MyField, String> call(TreeTableColumn<MyField, String> param) {
        return new MyFieldCell();
    }

}

public class MyFieldCell extends TreeTableCell<MyField, String> {
    private MyEditingControlProvider controlProvider = new MyCellEditingControlProvider();

    public MyFieldCell() {
        super();
    }

    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            setText(null);
            System.out.println("updating getItem()" + getItem());
            System.out.println("getTableRow().getItem().getName() " + getTreeTableRow().getItem().getName());
            setGraphic(controlProvider.getControl(getTreeTableRow().getItem()));
        }
    }

    protected void commitEdit() {
        super.commitEdit(getItem());
        System.out.println("committing edit");
        MyField myField = getTreeTableRow().getItem();
        controlProvider.updateFromControl(myField);
    }
}

public interface MyEditingControlProvider {
    public Control getControl(MyField field);
    public void updateFromControl(MyField field);
}

public class MyCellEditingControlProvider implements MyEditingControlProvider {

    private Map<String, MyEditingControlProvider> providers;

    public MyCellEditingControlProvider() {
        providers = new HashMap<>();
        providers.put("check", new CheckProvider());
        providers.put("combo", new ComboProvider());
        providers.put("text", new TextProvider());
    }

    @Override
    public Control getControl(MyField field) {
        if (field == null || field.getFieldType() == null) {
            return null;
        } else {
            return providers.get(field.getFieldType()).getControl(field);
        }
    }

    @Override
    public void updateFromControl(MyField field) {
        providers.get(field.getFieldType()).updateFromControl(field);
    }

}

public class CheckProvider implements MyEditingControlProvider {
    private CheckBox checkBox;


    @Override
    public Control getControl(MyField field) {
        if (checkBox == null) {
            createCheckBox(field);
        }
        return checkBox;
    }

    private void createCheckBox(MyField field) {
        checkBox = new CheckBox("Check");
        checkBox.setSelected(getBoolean(field));
        field.valueProperty().bindBidirectional(checkBox.selectedProperty(), new BooleanStringConverter());
        checkBox.focusedProperty().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    updateFromControl(field);
                }
            }
        });
    }

    private Boolean getBoolean(MyField field) {
        return field.getValue() == null ? false : convertYNToBoolean(field.getValue());
    }

    private Boolean convertYNToBoolean(String val) {
        if (val != null && val.equals("Y")) {
            return true;
        } else {
            return false;
        }
    }

    private String convertBooleanToYN(Boolean val) {
        if (val) {
            return "Y";
        } else {
            return "N";
        }
    }

    @Override
    public void updateFromControl(MyField field) {
        field.setValue(convertBooleanToYN(checkBox.isSelected()));
    }

}

public class ComboProvider implements MyEditingControlProvider {
    private ComboBox<String> comboBox;

    @Override
    public Control getControl(MyField field) {
        if (comboBox == null) {
            createComboBox(field);
        }
        return comboBox;
    }

    private void createComboBox(MyField field) {
        comboBox = new ComboBox<String>();
        comboBox.setEditable(true);
        resetBox(field);
        field.valueProperty().bindBidirectional(comboBox.valueProperty());

    }

    private void resetBox(MyField field) {
        comboBox.getItems().clear();
        comboBox.getItems().addAll(field.getComboVals());
    }

    @Override
    public void updateFromControl(MyField field) {
        field.setValue(comboBox.getValue());
    }

}

public class TextProvider implements MyEditingControlProvider {
    private TextField textField;

    @Override
    public Control getControl(MyField field) {
        if (textField == null) {
            createTextField(field);
        }
        return textField;
    }

    private void createTextField(MyField field) {
        textField = new TextField(field.getValue());
        field.valueProperty().bindBidirectional(textField.textProperty());
    }

    @Override
    public void updateFromControl(MyField field) {
        field.setValue(textField.getText());
    }

}

}

Upvotes: 0

Views: 265

Answers (1)

James_D
James_D

Reputation: 209694

I guess you need to pass the cell through to the control provider:

public interface MyEditingControlProvider {
    public Control getControl(TreeTableCell<MyField, String> cell);
    public void updateFromControl(MyField field);
}

and

public class MyCellEditingControlProvider implements MyEditingControlProvider {

    private Map<String, MyEditingControlProvider> providers;

    public MyCellEditingControlProvider() {
        providers = new HashMap<>();
        providers.put("check", new CheckProvider());
        providers.put("combo", new ComboProvider());
        providers.put("text", new TextProvider());
    }

    @Override
    public Control getControl(TreeTableCell<MyField, String> cell) {
        if (field == null || field.getFieldType() == null) {
            return null;
        } else {
            return providers.get(field.getFieldType()).getControl(cell);
        }
    }

    @Override
    public void updateFromControl(MyField field) {
        providers.get(field.getFieldType()).updateFromControl(field);
    }

}

Then the individual implementations can do, e.g.

public class CheckProvider implements MyEditingControlProvider {
    private CheckBox checkBox;


    @Override
    public Control getControl(TreeTableCell<MyField, String> cell) {
        if (checkBox == null) {
            createCheckBox(cell);
        }
        return checkBox;
    }

    private void createCheckBox(TreeTableCell<MyField, String> cell) {
        checkBox = new CheckBox("Check");
        MyField field = cell.getTreeTableRow().getItem();
        checkBox.setSelected(getBoolean(field));
        field.valueProperty().bindBidirectional(checkBox.selectedProperty(), new BooleanStringConverter());
        checkBox.setOnAction(cell.commitEdit(convertBooleanToYN(checkBox.isSelected())));
    }

    // ...

}

Upvotes: 2

Related Questions