Thursday, November 1, 2012

ListView and custom Cells

If you want to display a 'complex object' in a ListView, specify it as a parameter, for example
ListView<Thing> listView = new ListView<>();
However this only displays the result of calling Thing.toString() on the objects. To show more complex information you need to customise the ListCell. Start by providing a factory which creates new, customised cells.
listView.setCellFactory(new Callback<ListView<Thing>, ListCell<Thing>>() {
 @Override
 public ListCell<Thing> call(ListView<Thing> p) {
  return new ThingCell();
 }
});
JavaFX maintains some kind of relationship between ListCells and items in the list; it seems to be roughly one cell per visible row, not one cell per item in the list. You need to override updateItem() in Cell to attach a list item to a cell.
public class ThingCell extends ListCell<Thing> {
 @Override
 protected void updateItem(Thing t, boolean isblank) {
  super.updateItem(t, isblank);
  ...
 }
}
In other words, JavaFX uses the factory to create some cells, then attaches the list's contents to them. As the list is scrolled, different objects will be attached in turn. Bear in mind, t may be null - either because you've added a null object to the list, or because JavaFX wants a cell to suitable for displaying an empty row. In the latter case, isblank will be true. The appearance of the cell may be the same for either, in which case you can ignore isblank.
When an object is attached, remember to bind its properties to the cell, instead of just setting them. If the item then changes 'internally', the list will update automatically. For example, suppose Thing has a title property. If you do this:
protected void updateItem(Thing t, boolean isblank) {
 super.updateItem(t, isblank);
 if (t != null) {
  setText(t.titleProperty().get());
 }
 else {
  setText("");
 }
}
the item will display correctly when it is added to the list (and also if JavaFX re-attaches the objects), but if someone calls thing.titleProperty().set("New text") the list will not change. Instead, try
protected void updateItem(Thing t, boolean isblank) {
 super.updateItem(t, isblank);
 if (t != null) {
  textProperty().bind(t.titleProperty());
 }
 else {
  textProperty().unbind();
  textProperty().set("");
 }
}
Of course, if the list items never change after they've been added to the list, it isn't necessary to create the bindings and the first method can be used.

1 comment: