user3237736
user3237736

Reputation: 857

JavaFX Custom Cell Renderer for TreeTableView

I am trying to implement a TreeTableView in JavaFX, containing 'MyData' objects, and having two columns. First column should contain a string; this was easy:

column1.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, String> entry) 
-> new ReadOnlyStringWrapper(entry.getValue().getValue().toString()));

For the second column, I need to use some more complex data within the MyData object, and I want to render basically a sequence of icons that depict that data. So, I tried to create a custom cell renderer:

MyCellRenderer extends TreeTableCell<MyData, MyData> {
    @Override
    protected void updateItem(MyData item, boolean empty) {
        super.updateItem(item, empty);
        if (item == null || empty) {
            setGraphic(null);
            setText(null);
        } else {
            // building some ContentPane with an HBox of Images here..
            setGraphic(contentPane);
        }
    }
}

and then set the column CellFactory and CellValueFactory as follows:

column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry) 
-> new ReadOnlyObjectWrapper(entry));

column2.setCellFactory(param -> new MyCellRenderer());

But I get this exception at runtime:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: javafx.scene.control.TreeTableColumn$CellDataFeatures cannot be cast to MyData

I am afraid I don't really understand the meaning of the different generic types for all these classes, and also I am not sure about the "ReadOnlyObjectWrapper". I just tried to copy/paste and tweak it from the setup of the first column.

I would be very thankful if someone could shine some light on me. Unfortunately the oracle docs about TreeTableView don't go into that much detail, they just show simple examples.

Thank you

Upvotes: 0

Views: 2225

Answers (2)

Slaw
Slaw

Reputation: 45746

You're passing entry, which is of the type TreeTableColumn.CellDataFeatures<MyData, MyData>, as the initial value to a new ReadOnlyObjectWraper - a raw type - which is expecting a type of MyData at runtime and not TreeTableColumn.CellDataFeatures<MyData, MyData>. As you can see, there is a mismatch of generic types.

Try changing

new ReadOnlyObjectWrapper(entry) 

to

new ReadOnlyObjectWrapper<>(entry.getValue().getValue())

The reason for two getValue() calls is because the first entry.getValue() returns a TreeItem<MyData> and the second getValue() returns the actual MyData instance.

This is all assuming that your table is declared TreeTableView<MyData> and your column is declared TreeTableColumn<MyData, MyData>.


Edit: Since you said you don't really understand all the generic signatures here's a brief explanation.

TreeTableView<S>

Here the S is the type of object the TreeTableView displays. AKA, the model class. An example would be a Person class which would make S a Person.

TreeTableColumn<S, T>

The S here is the same as the S in the TreeTableView that the column is destined to be a part of. The T is the type of object that a TreeTableCell in the column will be displaying. This is normally a value contained within a property of the type S. Such as a StringProperty for a name of a Person which would make T a String.

TreeTableCell<S, T>

The S and T will be the same as the TreeTableColumn which the cell will be a part of.

Now, for the value callback:

Callback<TreeTableColumn.CellDataFeatures<S, T>, ObservableValue<T>>

Again, the S and T represent the same types of the TreeTableColumn for which the Callback will belong to. This Callback returns an ObservableValue that contains the type T so that the TreeTableCell can observe the value for changes and update the UI accordingly. In your case, since the type you want to display is not held in a property you return a new ReadOnlyObjectWrapper to satisfy the API requirements. If I continue the name StringProperty example I gave above you could end up with something like:

TreeTableView<Person> table = ..;
TreeTableColumn<Person, String> column = ...;
column.setCellValueFactory(dataFeatures -> {
    // This could all be done in one line but I figured I'd
    // make it explicit to show all the types used.
    TreeItem<Person> item = dataFeatures.getValue();
    Person person = item.getValue();
    return person.nameProperty(); // returns StringProperty which is an
                                  // ObservableStringValue which in turn
                                  // is an ObservableValue<String>
});

Upvotes: 1

James_D
James_D

Reputation: 209319

You need

column2.setCellValueFactory((TreeTableColumn.CellDataFeatures<MyData, MyData> entry) 
    -> new ReadOnlyObjectWrapper<>(entry.getValue().getValue()));

Note that if you don't use raw types (i.e. use ReadOnlyObjectWrapper<> or ReadOnlyObjectWrapper<MyData>, instead of just ReadOnlyObjectWrapper), the compiler will inform you of the error, which is much better than trying to decipher what went wrong at runtime.

As you can see, the parameter type for the cell value factory is a TreeTableColumn.CellDataFeatures (see docs). This is simply a wrapper for the row value from which you're going to extract the data that are shown in the cell; this wrapper just contains the tree item for the row itself (which you get with getValue()), as well as the column to which the cell value factory is attached (getTreeTableColumn()) and the table to which that column belongs (getTreeTableView()).

The latter two, I believe, are designed to enable you to write general, reusable, cell value factories, which you might want to customize on the basis of the column or table to which they're attached. (Use cases for this are hard for me to envisage, but nevertheless I suspect there is some occasion for them...)

The TreeItem containing the row (which you get with entry.getValue()), of course contains the row value itself (you get this with getValue(), which is why you end up with entry.getValue().getValue()), as well as other TreeItem-specific information (is it expanded, selected, etc etc).

Upvotes: 1

Related Questions