Brecht Vercruyce
Brecht Vercruyce

Reputation: 109

JavaFX - TableView - Changes of field and fields of that field

I'm in the following situation. I have a TableView populated with data from the model class Foo:

public class Foo {
    private StringProperty someText;
    private Bar someObject;

    // getters, setters and properties dropped for clarity
}

This class has a field of the type Bar:

public class Bar {
    private StringProperty someText;

    // getters, setters and properties dropped for clarity
}

One of the columns must display the text from Bar's someText. So I bound that StringProperty to the column.

But the table should also be updated when the entire Bar object in Foo is replaced by another one. So I made an ObjectProperty in Foo that contained Bar and bound that property to the column. Of course, now it doesn't listen anymore to change in Bar's someText.

How do I get the "combination" of these solutions, so the table listens to changes both of the object itself and it's fields?

Upvotes: 1

Views: 742

Answers (2)

fabian
fabian

Reputation: 82491

You can select a value through a multi-level hierarchy using Bindings.select. This allows you to create a single cellValueFactory class:

import java.util.Arrays;
import javafx.beans.NamedArg;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;

public class NestedPropertyValueFactory<S, T> implements Callback<TableColumn.CellDataFeatures<S, T>, ObservableValue<T>> {

    private final String[] steps;
    private final PropertyValueFactory firstStepSelector;

    public NestedPropertyValueFactory(String... properties) {
        firstStepSelector = new PropertyValueFactory<>(properties[0]);
        this.steps = Arrays.copyOfRange(properties, 1, properties.length);
    }

    /**
     * @param property a string containing the names of the properties seperated
     * by '.'. (For easy use from fxml file)
     */
    public NestedPropertyValueFactory(@NamedArg("property") String property) {
        this(property.split("\\."));
    }

    @Override
    public ObservableValue<T> call(TableColumn.CellDataFeatures<S, T> param) {
        // use PropertyValueFactory for first step and Bindings.select for
        // additonal steps.
        return Bindings.select(firstStepSelector.call(param), steps);
    }

}

Note that if any intermediate step yields null, you'll get warnings in System.err.

Also with this class you need to use property methods that adhere the naming conventions, i.e. the names of the methods should be someObjectProperty and someTextProperty in this case. This allows you to use the class similar to PropertyValueFactory:

column.setCellValueFactory(new NestedPropertyValueFactory<>("someObject", "someText"));

or

column.setCellValueFactory(new NestedPropertyValueFactory<>("someObject.someText"));

Upvotes: 2

James_D
James_D

Reputation: 209684

Using just the JavaFX API, you can do

TableColumn<Foo, String> barColumn = new TableColumn<>("Bar");

barColumn.setCellValueFactory(cellData -> 
    Bindings.selectString(cellData.getValue().barProperty(), "someText"));

I don't really like that part of the API for a couple of reasons. First, there is no compile-time checking: you just provide the name of the property as a String, so if you have a typo it just shows up at runtime. Second, if foo.getBar() returns null (which it will in a table cell), this generates a bunch of warnings to stderr. These are just annoying (they don't stop anything from running), and really shouldn't be there because this is a supported use case according to the API docs. However, it will work.


For a more robust and less legacy-style implementation, you can use Tomas Mikula's ReactFX library, version 2.0 or later, which includes support for bindings like this. Using that library you would do

barColumns.setCellValueFactory(cellData -> 
    Val.flatMap(cellData.getValue().barProperty(), Bar::someTextProperty));

This has compile-time checking and type checking etc, and no spurious warnings.

Upvotes: 2

Related Questions