Search code examples
javafxproperty-binding

JavaFx property binding with multiple objects on on screen


I use JavaFx with property binding. I got a object 'Person' with the properties 'name' and age. These objects are stored in a ObservableList.

The properties are bound to labels on the gui. When I change the person in the ListBox the data also change on the right hand side.

GUI with person list: GUI with person list

And now it comes to my problem. I want to disply all persons on one window, like the next picture shows.

GUI with multiple persons on one view: GUI with multiple persons on one view

How can I handle this. I thought about HBox but the binding doesn't work.

FYI: Here you can find the tutorial I used. https://code.makery.ch/library/javafx-tutorial/part1/


Solution

  • This looks like a perfect time to use a ListView with custom ListCell implementations.

    The sample application below shows a very basic application that displays each Person object in a ListView. We will provide our own ListCell so we can control exactly how each Person gets displayed.

    I also added a profile photo just for fun :)


    import javafx.application.Application;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.collections.FXCollections;
    import javafx.collections.ObservableList;
    import javafx.geometry.Insets;
    import javafx.geometry.Orientation;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.ListCell;
    import javafx.scene.control.ListView;
    import javafx.scene.control.Separator;
    import javafx.scene.image.ImageView;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import javafx.util.Callback;
    
    public class ListViewDetailSample extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            // Simple interface
            VBox root = new VBox(5);
            root.setPadding(new Insets(10));
            root.setAlignment(Pos.CENTER);
    
            // First, let's create our list of Persons
            ObservableList<Person> persons = FXCollections.observableArrayList();
            persons.addAll(
                    new Person("John", 34),
                    new Person("Cheyenne", 24),
                    new Person("Micah", 17),
                    new Person("Katelyn", 28)
            );
    
            // Create a ListView
            ListView<Person> listView = new ListView<>();
    
            // Bind our list to the ListView
            listView.setItems(persons);
    
            // Now, for the magic. We'll create our own ListCells for the ListView. This allows us to create a custom
            // layout for each individual cell. For this sample, we'll include a profile picture, the name, and the age.
            listView.setCellFactory(new Callback<ListView<Person>, ListCell<Person>>() {
                @Override
                public ListCell<Person> call(ListView<Person> param) {
                    return new ListCell<Person>() {
                        @Override
                        protected void updateItem(Person person, boolean empty) {
                            super.updateItem(person, empty);
    
                            // Set any empty cells to show nothing
                            if (person == null || empty) {
                                setText(null);
                                setGraphic(null);
                            } else {
                                // Here we can build our layout. We'll use a HBox for our root container
                                HBox cellRoot = new HBox(5);
                                cellRoot.setAlignment(Pos.CENTER_LEFT);
                                cellRoot.setPadding(new Insets(5));
    
                                // Add our profile picture
                                ImageView imgProfilePic = new ImageView("/sample/user.png");
                                imgProfilePic.setFitHeight(24);
                                imgProfilePic.setFitWidth(24);
                                cellRoot.getChildren().add(imgProfilePic);
    
                                // A simple Separator between the photo and the details
                                cellRoot.getChildren().add(new Separator(Orientation.VERTICAL));
    
                                // Now, create a VBox to hold the name and age
                                VBox vBox = new VBox(5);
                                vBox.setAlignment(Pos.CENTER_LEFT);
                                vBox.setPadding(new Insets(5));
    
                                // Add our Person details
                                vBox.getChildren().addAll(
                                        new Label("Name: " + person.getName()),
                                        new Label("Age: " + person.getAge())
                                );
    
                                // Add our VBox to the cellRoot
                                cellRoot.getChildren().add(vBox);
    
                                // Finally, set this cell to display our custom layout
                                setGraphic(cellRoot);
                            }
                        }
                    };
                }
            });
    
            // Now, add our ListView to the root layout
            root.getChildren().add(listView);
    
            // Show the Stage
            primaryStage.setWidth(450);
            primaryStage.setHeight(400);
            primaryStage.setScene(new Scene(root));
            primaryStage.show();
        }
    }
    
    // Simple Person class
    class Person {
    
        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty age = new SimpleIntegerProperty();
    
        public Person(String name, int age) {
            this.name.set(name);
            this.age.set(age);
        }
    
        public String getName() {
            return name.get();
        }
    
        public StringProperty nameProperty() {
            return name;
        }
    
        public void setName(String name) {
            this.name.set(name);
        }
    
        public int getAge() {
            return age.get();
        }
    
        public IntegerProperty ageProperty() {
            return age;
        }
    
        public void setAge(int age) {
            this.age.set(age);
        }
    }
    

    The Result:

    screenshot


    Without ListView:

    If you'd prefer not to use a ListView for this display, you can keep another list of your Person displays and bind that to the children list of whichever container you want:

        // Create a list to hold our individual Person displays
        ObservableList<Node> personDisplays = FXCollections.observableArrayList();
    
        // Now add a new PersonDisplay to the list for each Person in the personsList
        persons.forEach(person -> personDisplays.add(new PersonDisplay(person)));
    
        // Bind our personsDisplay list to the children of our root VBox
        Bindings.bindContent(root.getChildren(), personDisplays);
    

    PersonDisplay class:

    class PersonDisplay extends HBox {
    
        public PersonDisplay(Person person) {
            // First, let's configure our root layout
            setSpacing(5);
            setAlignment(Pos.CENTER_LEFT);
            setPadding(new Insets(5));
    
            // Add our profile picture
            ImageView imgProfilePic = new ImageView("/user.png");
            imgProfilePic.setFitHeight(24);
            imgProfilePic.setFitWidth(24);
            getChildren().add(imgProfilePic);
    
            // A simple Separator between the photo and the details
            getChildren().add(new Separator(Orientation.VERTICAL));
    
            // Now, create a VBox to hold the name and age
            VBox vBox = new VBox(5);
            vBox.setAlignment(Pos.CENTER_LEFT);
            vBox.setPadding(new Insets(5));
    
            // Add our Person details
            vBox.getChildren().addAll(
                    new Label("Name: " + person.getName()),
                    new Label("Age: " + person.getAge())
            );
    
            // Add our VBox to the layout
            getChildren().add(vBox);
        }
    }
    

    The Result:

    screenshot 2