Reputation: 857
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
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
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