6

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.

Stealth Rabbi
  • 9,370
  • 17
  • 89
  • 161
  • found what I meant: have a [look at ReactFx](https://github.com/TomasMikula/ReactFX/wiki), it used to support (still does?) path binding – kleopatra Aug 15 '19 at 10:23

1 Answers1

7

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.

Zephyr
  • 8,846
  • 3
  • 19
  • 50
  • 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. – Stealth Rabbi Aug 15 '19 at 03:07
  • @StealthRabbi - I updated my answer to include the method of implementing this with standard POJO beans. – Zephyr Aug 15 '19 at 03:22
  • Beware: the cell value for model is not updated automatically when you set the car on the person! – kleopatra Aug 15 '19 at 10:05
  • 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`. – Slaw Aug 15 '19 at 21:09
  • @Slaw, i'm not sure if this is what you're discussing here, but I'm facing an issue when SOMETIMES trying to edit the controls. Usually the controls are bound to the underlying property via an on-the-fly created string / boolean properties. But sometimes, when my application is in a certain state where background tasks are taking place unrelated to the objects I'm binding to, soemtimes the binding doesn't work. The JavaFX always has the current state of the object set in initialize(), but when I go to edit the controls, sometimes it's as if there's no binding. – Stealth Rabbi Aug 23 '19 at 19:52
  • This is in a new view that isn't a table, but has text fields and such serving as an edit interface for other POJOs that don't have Property fields on them. – Stealth Rabbi Aug 23 '19 at 20:04
  • turns out `JavaBeanStringProperty` and similar classes have a Dispoer which tries to clean up what it thinks are unreferenced Property beans in periods of higher memory use. The Properties were jsut being created on the fly and not stored anywhere, so it thought there wasn't any real references to it, despite being set in a bind method to a control. Storing the properties as fields in my View class worked, but is a bit annoying. – Stealth Rabbi Aug 23 '19 at 20:44
  • 1
    @StealthRabbi You may want to ask a new question with a [mre] 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 `PhantomReference`s 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. – Slaw Aug 24 '19 at 00:42