Search code examples
javajavafxpropertiestablecolumn

Accessing nested properties in JavaFx TableView / TableColumn


I havea TableView, and I access properties of the list's objects as follows. This works just fine.

    <TableColumn fx:id="dateColumn" editable="false" prefWidth="135.0" text="Date">
        <cellValueFactory>
            <PropertyValueFactory property="date" />
        </cellValueFactory>
    </TableColumn>

However, I'd like to access a nested property of the object, for example:

        <TableColumn prefWidth="100.0" text="Course">
            <cellValueFactory>
                <PropertyValueFactory property="house.bathroom"/>
            </cellValueFactory>
        </TableColumn>

where my list object has a getHouse(), and House has a getBathroom(). Unfortunately, this doesn't work. I've tried a few spelling variations, but no luck.


Solution

  • I don't believe you can do this via FXML (though I could be wrong).

    Assuming you're using proper JavaFX properties, you'd just setup your own CellValueFactory in the controller like so:

    bathroomColumn.setCellValueFactory(tf -> tf.getValue().getHouse().bathroomProperty());
    

    Since it's unclear what you want to display in the bathroom column, this would return the BathroomProperty.

    If, however, you wanted to return a property within the Bathroom object, you'd simply call getBathroom().yourProperty() as well:

    bathroomColumn.setCellValueFactory(tf -> tf.getValue().getHouse().getBathroom().myStringProperty());
    

    Perhaps an example might help to demonstrate the concept:

    import javafx.application.Application;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class TableViewValues extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            // Simple Interface
            VBox root = new VBox(10);
            root.setAlignment(Pos.CENTER);
            root.setPadding(new Insets(10));
    
            // Simple TableView
            TableView<Person> personTableView = new TableView<>();
            TableColumn<Person, String> colName = new TableColumn<>("Name");
            TableColumn<Person, String> colCar = new TableColumn<>("Car");
    
            // Setup the CellValueFactories
            colName.setCellValueFactory(tf -> tf.getValue().nameProperty());
            colCar.setCellValueFactory(tf -> tf.getValue().getCar().modelProperty());
    
            personTableView.getColumns().addAll(colName, colCar);
    
            root.getChildren().add(personTableView);
    
            // Sample Data
            personTableView.getItems().addAll(
                    new Person("Jack", new Car("Accord")),
                    new Person("John", new Car("Mustang")),
                    new Person("Sally", new Car("Yugo"))
            );
    
            // Show the stage
            primaryStage.setScene(new Scene(root));
            primaryStage.setTitle("Sample");
            primaryStage.show();
        }
    }
    
    class Person {
    
        private final StringProperty name = new SimpleStringProperty();
        private final ObjectProperty<Car> car = new SimpleObjectProperty<>();
    
        public Person(String name, Car car) {
            this.name.set(name);
            this.car.set(car);
        }
    
        public String getName() {
            return name.get();
        }
    
        public void setName(String name) {
            this.name.set(name);
        }
    
        public StringProperty nameProperty() {
            return name;
        }
    
        public Car getCar() {
            return car.get();
        }
    
        public void setCar(Car car) {
            this.car.set(car);
        }
    
        public ObjectProperty<Car> carProperty() {
            return car;
        }
    }
    
    class Car {
        private final StringProperty model = new SimpleStringProperty();
    
        public Car(String model) {
            this.model.set(model);
        }
    
        public String getModel() {
            return model.get();
        }
    
        public void setModel(String model) {
            this.model.set(model);
        }
    
        public StringProperty modelProperty() {
            return model;
        }
    }
    

    The Result:

    screenshot


    If you're using plain Java beans, the process is similar. But instead of setting the CellValueProperty to a Property within your data model classes, you'd create a new StringProperty inline and pass it the bean value you need.

    So, in the Car example above, you'd do this instead:

    colName.setCellValueFactory(tf -> new SimpleStringProperty(tf.getValue().getName()));
    colCar.setCellValueFactory(tf -> new SimpleStringProperty(tf.getValue().getCar().getModel()));
    

    Side Note: The tf you see referenced above is just a variable I use to refer to the CellDataFeatures object in Java which we can use to get a reference to that row's data model object (using the getValue() method). Perhaps cdf would be a better choice, but habits are hard to break.