Philayyy Philayyy - 1 month ago 4
CSS Question

How to set the style of list view cells based on a condition in JavaFX

I am attempting to set the style of individual cells in a list view based on a condition. The list view is of type Label, and the condition is determined based on another class when labels are inserted into the listview.

Currently, I have tried to set the background colour of each label in the list view but it doesn't cover the entire area (see picture).

enter image description here

I've also researched this article but it doesn't suit my case as the identifier for highlighting the list view cell is not carried in the string.

How do I dynamically change the background of an item in a listview in JavaFX

Current code so far:

for (Consultation c : notifications){
Label l = new Label(": consult reminder - " + c.getReminderTime());

if(c.getReminderRead() == 1){ //if the consultation reminder hasn't been read
l.setStyle("-fx-background-color: #33CEFF");
counter = counter + 1;
}


notificationList.getItems().add(l);
}


any ideas?

Answer

It is basically always an error to use a node subclass (such as Label) as the data type for controls such as ListView, TableView, etc. (The only exception I can think of is if you were writing a GUI builder, such as Scene Builder, where the data really were the actual GUI nodes. Even then you'd probably find it wouldn't work so well.) GUI node classes are very expensive to create; they typically have hundreds of properties and lots of overhead associated with styling and CSS parsing, event listeners, etc etc. Compare that to your actual data class, Consultation, which probably has a dozen properties or fewer and none of the other overhead. If you use a Label or other node class as the data type for your ListView, you create one node for every item in the list. The whole point of a ListView is that it only creates nodes for each visible cell, and reuses those cells. So if you have a large list, your approach will likely lead to performance issues. (The other point is that you are violating the separation of the view (the presentation) from the model (the data).)

So you should really have a ListView<Consultation> here, and use a cell factory to configure the text and style of the cells:

ListView<Consultation> notificationList = ... ;

notificationList.setCellFactory(lv -> new ListCell<Consultation>() {
    @Override
    protected void updateItem(Consultation c, boolean empty) {
        super.updateItem(c, empty);
        if (empty) {
            setText(null);
            setStyle("");
        } else {
            setText(": consult reminder - "  + c.getReminderTime());

            // Are you REALLY using an int for the reminderRead property??!?
            // Surely this should be a boolean...

            if (c.getReminderRead() == 1) { // I feel dirty just writing that
                setStyle("-fx-background-color: #33CEFF");
            } else {
                setStyle("");
            }
        }
    }
});

notificationList.getItems().setAll(notifications);

It's a little better to factor the style out into an external CSS file. You can use a CSS PseudoClass to turn the different styles on and off:

ListView<Consultation> notificationList = ... ;

PseudoClass reminderRead = PseudoClass.getPseudoClass("reminder-read");

notificationList.setCellFactory(lv -> new ListCell<Consultation>() {
    @Override
    protected void updateItem(Consultation c, boolean empty) {
        super.updateItem(c, empty);
        if (empty) {
            setText(null);
        } else {
            setText(": consult reminder - "  + c.getReminderTime());
        }

        // assumes Consultation.getReminderRead() returns a boolean...
        pseudoClassStateChanged(reminderRead, c != null && c.getReminderRead())
    }
});

notificationList.getItems().setAll(notifications);

Now in your external CSS file you can do:

.list-cell:reminder-read {
    -fx-background-color: #33CEFF ;
}
Comments