user2303720
user2303720

Reputation: 25

JavaFX bind choicebox to a property in a collection

With JavaFX, what is the best way to bind ChoiceBox to properties of a collection? In example below I try to bind ChoiceBox elements to name of an ObservableList beans. This works fine when items are added/removed but not when the property value name change.

I was hoping there is a clean and simple solution to this but haven't yet found any example of it...

The class ExampleBean2 in deliberately not implemented with properties since that object may correspond to a external model class out of my control.

package com.playground;

import org.controlsfx.control.PropertySheet;
import org.controlsfx.property.BeanPropertyUtils;

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;

public class BindingPlayGround extends Application{

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        primaryStage.setTitle("FXPlayGround");
        Parent content = createContentPane();
        Scene scene = new Scene(content, 800, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    protected Parent createContentPane() {
        ObservableList<BeanExample2> beans = FXCollections.observableArrayList();       
        ObservableList<PropertySheet> sheets = FXCollections.observableArrayList();
        ListView<PropertySheet> listView = new ListView<PropertySheet>(sheets);
        Button addBeanButton = new Button("Add Bean");
        addBeanButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                BeanExample2 e = new BeanExample2();
                e.setName("Name-not-set");
                PropertySheet propertySheet = new PropertySheet(BeanPropertyUtils.getProperties(e));
                sheets.add(propertySheet);
                beans.add(e);
            }
        });

        VBox vBar = new VBox();
        vBar.getChildren().add(listView);
        vBar.getChildren().add(addBeanButton);
                ObservableList<BeanExample2> names = FXCollections.observableArrayList(new Callback<BeanExample2, Observable[]>() {
            @Override
            public Observable[] call(BeanExample2 param) {
                return new Observable[]{new SimpleStringProperty(param, "name")};
            }
        });
        Bindings.bindContent(names, beans);

        Button addChoiceBoxButton = new Button("Add ChoiceBox");
        addChoiceBoxButton.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                ChoiceBox<BeanExample2> choiceBox = new ChoiceBox<BeanExample2>(names);
                vBar.getChildren().add(choiceBox);
            }
        });
        vBar.getChildren().add(addChoiceBoxButton);
        return vBar;
    }

    static class BeanExample2 {       
        private String name;

        public String getName() {
            return name;
        }        

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

        @Override
        public String toString() {
            return "BeanExample2{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

Upvotes: 1

Views: 2205

Answers (1)

fabian
fabian

Reputation: 82531

Here

ObservableList<BeanExample2> names = FXCollections.observableArrayList(new Callback<BeanExample2, Observable[]>() {
    @Override
    public Observable[] call(BeanExample2 param) {
        return new Observable[]{new SimpleStringProperty(param, "name")};
    }
});

you're creating a new property to listen to for updates that cannot be referenced except from the value returned by the call method. The only relationship between the BeanExample2 instance and the SimpleStringProperty is that the BeanExample2 instance is used as bean for the property, which has no effect besides being available via the getBean() method of the property. The value of the property is never assigned let alone modified on a change of the BeanExample2 instance.

To properly trigger updates in the ObservableList, you need to make sure the element in the array returned by the above method is actually notified of updates. Usually you add the property to the class itself:

public static class BeanExample2 {

    public final String getName() {
        return this.name.get();
    }

    private final StringProperty name = new SimpleStringProperty();

    public final void setName(String value) {
        this.name.set(value);
    }

    @Override
    public String toString() {
        return "BeanExample2{"
                + "name='" + name.get() + '\''
                + '}';
    }

    public final StringProperty nameProperty() {
        return this.name;
    }
}

And return an array containing the property from the Callback

ObservableList<BeanExample2> names = FXCollections.observableArrayList(new Callback<BeanExample2, Observable[]>() {
    @Override
    public Observable[] call(BeanExample2 param) {
        return new Observable[]{param.nameProperty()};
    }
});

Note that currently there seems to be a bug in ChoiceBox that adds entries for every intermediate value to the ChoiceBox.

ComboBox does not have this issue and could be used instead of a ChoiceBox.

Upvotes: 2

Related Questions