8

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.

1
  • found what I meant: have a look at ReactFx, it used to support (still does?) path binding Commented Aug 15, 2019 at 10:23

1 Answer 1

10

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks. Would I have to check for nul? . For example, bathroom may be null, so obviously accessing a property from a bathroom would not work. Also, I am using pojos here, and not a class using StringProperty, etc.
@StealthRabbi - I updated my answer to include the method of implementing this with standard POJO beans.
Beware: the cell value for model is not updated automatically when you set the car on the person!
The potential problem noted by @kleopatra should be solvable with a Bindings.select (though I believe that will make inline editing slightly more complicated). Third-party libraries might provide a type-safe version of select.
@StealthRabbi You may want to ask a new question with a minimal reproducible example demonstrating the issue. However, I will say: (1) Bindings use weak listeners, so while the Property being bound holds a strong reference to the target ObservableValue the reverse is not true; (2) The Disposer class uses PhantomReferences and a ReferenceQueue which means if it "thinks" instances are unreachable it's because they are—the functionality is driven by the garbage collector, after all.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.