I'm using a ListView
in a JavaFX application. The items in the list require more than just a string to display them so I made a custom implementation of ListCell<T>
, where T
is the class of the objects I'm displaying. In this custom class, I create a BorderPane
in the constructor and override updateItem(T item, boolean empty)
. The idea was that when updateItem
is called, I set the graphic of the cell to be the BorderPane while change some properties of Label
s inside of it. However, I noticed updateItem
is not called once (e.g. when a cell is recycled through scrolling or a new item is added), but it is called all the time, e.g. when the focus changes from one cell to another (even without scrolling), or when the scene is resized, or when a button inside the borderpane is pressed, ...
I need to have a ListView
with custom ListCell
s. I want to receive one callback when a cell is reused, passing the new item to be rendered as a parameter. Then I want to use that item as a model to construct a view-controller pair of which I take the view and use it as the graphic
of the cell. Buttons and other controls inside this view should work. Is this possible in JavaFX?
Linked problem:
updateItem
)The basic idea is that cells are constructed rarely but the updateItem(...)
method is likely to be called frequently. There is no actual guarantee that the item has really changed between calls to updateItem(...)
. The default implementation of updateItem(...)
takes care of handling selection, focus, etc, so this method probably needs to be invoked if any of those properties change (even if the item has not changed).
You should therefore strive to reduce the overhead of the updateItem(...)
method. It's not clear that multiple, frequent calls would prevent you doing what you want (for example, when you pass the new item as a parameter to your model, check to see if it's really different to the one you already have before doing any updates).
I'd argue that updateItem(...)
is really somewhat misnamed: it's called not only when the item
is updated, but really any time the cell might need to be updated. There's already a mechanism for executing code only when the item
changes though: just register a listener with the cell's itemProperty()
. You can use this technique (which I generally prefer) to create a different style of ListCell
:
ListView<...> listView = ... ;
listView.setCellFactory(lv -> {
BorderPane cellRoot = new BorderPane();
// create nodes, register listeners on them, populate cellRoot, etc...
ListCell<...> cell = new ListCell<>();
cell.itemProperty().addListener((obs, oldItem, newItem) -> {
if (newItem != null) {
// update cellRoot (or its child nodes' properties) accordingly
}
});
cell.emptyProperty().addListener((obs, wasEmpty, isEmpty) -> {
if (isEmpty) {
cell.setGraphic(null);
} else {
cell.setGraphic(cellRoot);
}
});
cell.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
return cell ;
});
This approach may work better in your scenario.
I'm just starting with JavaFX and I won't be able to explain all the little details for why it worked (it calls the method on new and existing items), but the way I solved the problem is by simply calling myContainer.getChildren().removeAll( ... )
In my implementation of ListCell public class MyCell extends ListCell<MyContent>
I'm creating many different items and adding them to a container, which becomes a line in my list view. So, I just had to remove the same items from that container right before I add them back in. Here's simplified version of what I've done. Seems to be working just fine. I did try James_D's suggestion, but it didn't work for me and produced same errors.
@Override
public void updateItem(MyContent item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty == true) {
setGraphic(null);
setText(null);
} else {
// here I user the "item" to decide how to create
// objects a, b, c ... which are Rectangle, Label and Label
// I also update myMainContainer, which is a Pane
myMainContainer.getChildren().removeAll(a, b, c);
myMainContainer.getChildren().addAll(a, b, c);
setGraphic(myMainContainer);
}
}