rz3r0 rz3r0 - 4 months ago 14
Java Question

Bind ObservableList<TextField> to ListProperty<String> JFX

Given

I have a model class

Model
that contains the following fields:

StringProperty stringProperty; ListProperty<String> listOfStringsProperty


I have a view class
View extends VBox
that has the following:

TextField stringTextField; TextFieldList stringsTextFieldList;


TextFieldList extends VBox
is a custom class that I created that handles multiple
TextFields
with nice + and - buttons to add and remove input text fields.

TextFieldList
class contains the field
ObservableList<Node> inputTextFields
and I can get the data from these InputTextFields by a method call
List<String> getData()




Question

I was able to do the following:

stringTextField.textProperty().bindBidirectional(model.getStringProperty());


in order to bind the result of the
stringTextField
in
View
to the
stringProperty
in
Model


And I need to do something like

stringsTextFieldList.listProperty().bindBidirectional(model.getListOfStringsProperty());


How can I do that?

If this design would not work, then how do you suggest I fix it? Is there a built-in class that does the same as
TextFieldList
but instead
extends Control
?

Answer

If you decide to make your own control you should create the "binding manually", that means that in the input ObservableList you add a ListChangeListener then you process the Change like in the example: check whether a new item is added, removed or updated and maintain your TextFields accordingly. It is possible, but my answer is mainly about proposing an existing control to re-use rather than create your own one.

So if you don't want to re-invent the wheel:

I don't know your exact use-case, but maybe it is reasonable to reuse a control that actually support a data model, like a ListView.

In the example I have modified the Model class to have an ObservableList<StringProperty> rather than a ListProperty<String> (note: it is also possible to simply have String objects in the list, I just modified it to make the binding really clear). I have added a ListView and used setCellFactory to draw TextFields as elements in the list which are bidirectionally bounded to the corresponding StringProperty in the list. I have also added several buttons to add and remove elements and a button to print the current content of the model.

Example:

Model.java

public class Model {

    public ObservableList<StringProperty> listOfStringsProperty;

    public Model(){
        listOfStringsProperty = FXCollections.observableArrayList();    
    }

}

Main.java

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {
        try {
            BorderPane root = new BorderPane();
            Scene scene = new Scene(root,400,400);      

            Model m = new Model();
            m.listOfStringsProperty.addAll(new SimpleStringProperty("First"),
                    new SimpleStringProperty("Second"),
                    new SimpleStringProperty("Third"));

            ListView<StringProperty> lv = new ListView<StringProperty>();
            lv.setCellFactory(new Callback<ListView<StringProperty>, ListCell<StringProperty>>() {

                @Override
                public ListCell<StringProperty> call(ListView<StringProperty> param) {
                    return new ListCell<StringProperty>(){

                        @Override
                        protected void updateItem(StringProperty item, boolean empty) {

                            super.updateItem(item, empty);

                            if(item == null){
                                setText(null);
                                setGraphic(null);
                                return;
                            }

                            TextField tf = new TextField();
                            tf.textProperty().bindBidirectional(item);
                            setGraphic(tf);
                        }
                    };
                }
            });


            lv.setItems(m.listOfStringsProperty);

            root.setCenter(lv);

            // Control buttons
            HBox hbox = new HBox();
            Button buttonAdd = new Button("Add");
            buttonAdd.setOnAction(e -> m.listOfStringsProperty.add(new SimpleStringProperty("")));

            Button buttonRemove = new Button("Remove last");
            buttonRemove.setOnAction(e -> m.listOfStringsProperty.remove(m.listOfStringsProperty.size()-1));

            Button buttonPrintModel = new Button("Print model");
            buttonPrintModel.setOnAction(e -> System.out.println(m.listOfStringsProperty.toString()));

            hbox.getChildren().addAll(buttonAdd, buttonRemove, buttonPrintModel);


            root.setBottom(hbox);

            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

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

This will produce the following window:

enter image description here

Comments