12

I am trying to have a custom ListView made of custom Cell based on a list of custom objects.

The custom object is class name called Message which contains a few fields for the message content, recipient, timestamp and status (read, sent etc.).

After looking at this question : Customize ListView in JavaFX with FXML I have successfully :

  1. created a ListView with custom cells where the cell design is defined in a FXML file ;
  2. associated a controller so that each cell data can be filled with the current item of the collection ;

However, I failed to link both : I cannot seem to find a way so that the current item of the ListView is sent the Cell Controller.

Here is my code for the cell factory and the ListView filling of items:

final ObservableList observableList = FXCollections.observableArrayList();
observableList.setAll(myMessages); //assume myMessage is a ArrayList<Message>
conversation.setItems(observableList); //the listview
conversation.setCellFactory(new Callback<ListView<Message>, ListCell<Message>>() {
    @Override
    public ConversationCell<Message> call(ListView<Message> listView) {
        return new ConversationCell();
    }
});

And now, the ConversationCell class :

public final class ConversationCell<Message> extends ListCell<Message> { 

    @Override
    protected void updateItem(Message item, boolean empty) {
        super.updateItem(item, empty);
        ConversationCellController ccc = new ConversationCellController(null);
        setGraphic(ccc.getView());
    }
}

I cannot show the ConversationCellController but all I can say, this is where (in its constructor) I load the FXML file that designs the cell and then I can fill the values with the given Message item.

The getView() method returns the root pane that contains the now-filled-and-designed cell.

As I previously say, the designing work, but I cannot seem to link the ListView items with the CellFactory because in method

protected void updateItem(Message item, boolean empty)

empty is set to true and item is indeed null.

What can I do to make this work ?

Community
  • 1
  • 1
Mackovich
  • 3,319
  • 6
  • 35
  • 73

1 Answers1

14

All custom cell implementations that override updateItem(...) need to deal with the case where the cell is empty in that method. So you could do a naïve fix of this with

public final class ConversationCell<Message> extends ListCell<Message> { 

    @Override
    protected void updateItem(Message item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setGraphic(null);
        } else {
            // did you mean to pass null here, or item??
            ConversationCellController ccc = new ConversationCellController(null);
            setGraphic(ccc.getView());
        }
    }
}

However, this is not a good solution from the point of view of performance. You are loading the FXML every time updateItem(...) is called with a non-empty cell, and that's a pretty expensive operation (potentially involving file i/o, unzipping the FXML file from a jar file, parsing the file, lots of reflection, creating new UI elements, etc). You don't want to be asking the FX Application Thread to be doing all that work every time the user scrolls the list view by a few pixels. Instead, your cell should cache the node and should update it in the updateItem method:

public final class ConversationCell<Message> extends ListCell<Message> { 

    private final ConversationCellController ccc = new ConversationCellController(null);
    private final Node view = ccc.getView();

    @Override
    protected void updateItem(Message item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setGraphic(null);
        } else {
            ccc.setItem(item);
            setGraphic(view);
        }
    }
}

You should define a setItem(...) method in the ConversationCellController that updates the view (sets text on labels, etc etc) accordingly.

James_D
  • 201,275
  • 16
  • 291
  • 322
  • Hey, this is actually a very good point ! Just like Android ListView adapter with the ViewHolder pattern to keep track of the already generated views to reduce CPU usage and lags. I will try your idea asap. Could you also tell me what I can do to trigger events, with my current setup/code, so that a specific cell is updated ? I have a progress bar for a message being sent and I would like to update its progress, for example. Thank you ! – Mackovich Jan 17 '16 at 15:16
  • For the updates, which is something of a different question, you can arrange that the list view updates its cells by using JavaFX properties in your `Message` class and creating the items list with an extractor. See http://stackoverflow.com/questions/23822550/ – James_D Jan 17 '16 at 15:25
  • thank you. I will have a look ! By the way I implemented your solutions and it works just fine. I did not have many items (just a few) in my ObservableList but I looks snappier ! Anyway, I also noticed that `ccc.setItem` is called multiple times so I had to add a boolean in `ConversationCellController` to avoid setting the views to many time. Was I correct too do that ? For now it works but I wonder... thanks! – Mackovich Jan 17 '16 at 15:36