JavaFX custom list cell, updateItem being called a lot


Question

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 Labels 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 ListCells. 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:

1
6
5/23/2017 12:00:02 PM

Accepted Answer

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.

9
8/23/2014 5:06:27 PM

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);
        }
    }

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon