JavaFX: custom ListCell

Posted on July 3, 2013

This post shows the change of a simple list to a multiline list with icons.

I have a list of geocaches in my GeoFroggerFX (replacement for my GeoCachingFrogger based on Netbeans) which should provide more information in a row.

Simple List

The first and simplest version had some textual rows.

simple list

simple list

Custom CellList with more information

But this isn´t too sexy and I also wanted more information in my list. So I decided to use a custom ListCell to support more information in multiple rows and also some icons. The first version just added more information as text to the row.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class CacheListCell extends ListCell<Cache> { 
  @Override 
  public void updateItem(Cache cache, boolean empty) { 
    super.updateItem(cache, empty); 
    if (empty) { 
      setText(null); 
      setGraphic(null); 
    } else { 
      setText(cache.getName() + " (" + cache.getDifficulty() + "/" + cache.getTerrain() + " )"); 
      setGraphic(null); 
    } 
  } 
}
list with more information

list with more information

Custom CellList with multiple columns and icon

The second version splits the information in multiple rows and adds an icon to the cell. I use FontAwesome as icon and not an image.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CacheListCell extends ListCell<Cache> {
    @Override
    public void updateItem(Cache cache, boolean empty) {
        super.updateItem(cache, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            setText(null);
 
            // DO NOT CREATE INSTANCES IN THIS METHOD, THIS IS BAD!
            GridPane grid = new GridPane();
            grid.setHgap(10);
            grid.setVgap(4);
            grid.setPadding(new Insets(0, 10, 0, 10));
 
            // DO NOT CREATE INSTANCES IN THIS METHOD, THIS IS BAD!
            Label icon = new Label(GeocachingIcons.getIcon(cache).toString());
            icon.setFont(Font.font("FontAwesome", FontWeight.BOLD, 24));
            icon.getStyleClass().add("cache-list-icon");
            grid.add(icon, 0, 0, 1, 2);            
 
            // DO NOT CREATE INSTANCES IN THIS METHOD, THIS IS BAD!
            Label name = new Label(cache.getName());
            name.getStyleClass().add("cache-list-name");
            grid.add(name, 1, 0);
 
            // DO NOT CREATE INSTANCES IN THIS METHOD, THIS IS BAD!
            Label dt = new Label(cache.getDifficulty()+" / "+ cache.getTerrain());
            grid.add(dt, 1, 1);            
            dt.getStyleClass().add("cache-list-dt");
 
            if (CacheUtils.hasUserFoundCache(cache, new Long(3906456))) {
                JavaFXUtils.addClasses(this, CACHE_LIST_FOUND_CLASS);
                JavaFXUtils.removeClasses(this, CACHE_LIST_NOT_FOUND_CLASS);
            } else {
                JavaFXUtils.addClasses(this, CACHE_LIST_NOT_FOUND_CLASS);
                JavaFXUtils.removeClasses(this, CACHE_LIST_FOUND_CLASS);
            }
 
            setGraphic(grid);
        }
    }
}
list with icons

list with icons

Styling ListCell with css

I added some classes to the nodes to style the list with those classes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.cache-list-name {
    -fx-font-size: 1.2em;
    -fx-font-weight: bold;
}
 
.cache-list-icon {
    -fx-text-fill: #444444;
    -fx-effect: dropshadow( three-pass-box, rgba(0,0,0,0.4), 3, 0.0, 1, 1);
}
 
.cache-list-found .cache-list-icon {
    -fx-text-fill: #669900;
}

This brings some colour into the list and the name of the cache is bigger than the rest.

listcell with color

listcell with color

Jonathans tip how to do it THE RIGHT WAY

Jonathan Giles wrote me on twitter:

@frosch95 You shouldn’t create new instances of nodes in the updateItem method - instantiate them once in the cell constructor and reuse

So I refactored the class to reuse the objects and for better readability.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class CacheListCell extends ListCell<Cache> {
    private static final String CACHE_LIST_FOUND_CLASS = "cache-list-found";
    private static final String CACHE_LIST_NOT_FOUND_CLASS = "cache-list-not-found";
    private static final String CACHE_LIST_NAME_CLASS = "cache-list-name";
    private static final String CACHE_LIST_DT_CLASS = "cache-list-dt";
    private static final String CACHE_LIST_ICON_CLASS = "cache-list-icon";
    private static final String FONT_AWESOME = "FontAwesome";
 
    private GridPane grid = new GridPane();
    private Label icon = new Label();
    private Label name = new Label();
    private Label dt = new Label();
 
    public CacheListCell() {
        configureGrid();        
        configureIcon();
        configureName();
        configureDifficultyTerrain();
        addControlsToGrid();            
    }
 
    private void configureGrid() {
        grid.setHgap(10);
        grid.setVgap(4);
        grid.setPadding(new Insets(0, 10, 0, 10));
    }
 
    private void configureIcon() {
        icon.setFont(Font.font(FONT_AWESOME, FontWeight.BOLD, 24));
        icon.getStyleClass().add(CACHE_LIST_ICON_CLASS);
    }
 
    private void configureName() {
        name.getStyleClass().add(CACHE_LIST_NAME_CLASS);
    }
 
    private void configureDifficultyTerrain() {
        dt.getStyleClass().add(CACHE_LIST_DT_CLASS);
    }
 
    private void addControlsToGrid() {
        grid.add(icon, 0, 0, 1, 2);                    
        grid.add(name, 1, 0);        
        grid.add(dt, 1, 1);
    }
 
    @Override
    public void updateItem(Cache cache, boolean empty) {
        super.updateItem(cache, empty);
        if (empty) {
            clearContent();
        } else {
            addContent(cache);
        }
    }
 
    private void clearContent() {
        setText(null);
        setGraphic(null);
    }
 
    private void addContent(Cache cache) {
        setText(null);
        icon.setText(GeocachingIcons.getIcon(cache).toString());
        name.setText(cache.getName());
        dt.setText(cache.getDifficulty()+" / "+ cache.getTerrain());
        setStyleClassDependingOnFoundState(cache);        
        setGraphic(grid);
    }
 
    private void setStyleClassDependingOnFoundState(Cache cache) {
        if (CacheUtils.hasUserFoundCache(cache, new Long(3906456))) {
            addClasses(this, CACHE_LIST_FOUND_CLASS);
            removeClasses(this, CACHE_LIST_NOT_FOUND_CLASS);
        } else {
            addClasses(this, CACHE_LIST_NOT_FOUND_CLASS);
            removeClasses(this, CACHE_LIST_FOUND_CLASS);
        }
    }
}