I tried to customize the ListView in Javafx by trying to display a colored rectangle instead of the string itself.
public class Main extends Application {
@Override
public void start(Stage primaryStage)
{
VBox vBox = new VBox();
ListView<String> listView = new ListView<>();
vBox.getChildren().add(listView);
ObservableList<String> list = FXCollections.observableArrayList("black" , "blue" , "brown" , "gold");
listView.setItems(list);
listView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> stringListView) {
return new cell();
}
});
primaryStage.setScene(new Scene(vBox,400 , 400));
primaryStage.show();
}
public class cell extends ListCell<String>
{
Rectangle rect;
cell() {
super();
this.rect = new Rectangle(20,20);
this.rect.setFill(Color.web(getItem())); // ERROR ERROR ERROR
setGraphic(this.rect);
}
@Override
protected void updateItem(String s, boolean empty) {
super.updateItem(s, empty);
if(empty)
setGraphic(null);
else
setGraphic(this.rect);
}
}
public static void main(String[] args) {
launch(args);
}
}
Apparently the error is in the line in which I have indicated as ERROR. I manipulated the cell class a little and it worked. Below is the manipulated cell class:
public class cell extends ListCell<String>
{
Rectangle rect;
cell() {
super();
this.rect = new Rectangle(20,20);
// this.rect.setFill(Color.web(getItem()));
setGraphic(this.rect);
}
@Override
protected void updateItem(String s, boolean empty) {
super.updateItem(s, empty);
if(empty)
setGraphic(null);
else {
rect.setFill(Color.web(getItem()));
setGraphic(this.rect);
}
}
I understand that the updateItem() is going to be called a lot of times. My first method is indeed reducing the work done by updateItem() , but for some reason its throwing error in that line. What could be the reason for the error in the previous approach
The item
property of a ListCell
initially is null
. Color.web
does not accept null
as parameter. Furthermore you need to be able to handle the fact that the item of a ListCell
can be replaced during its lifecycle and that the same item may be assigned to different cells. ListView
creates only cells that are needed to fill the view and if e.g. the viewport of the scrollable area changes, different items need to be visible and the cells are reused to display the changed set of items.
If you worry about the performance of some calculation in updateItem
, you could cache the results in a map (possibly wrapping the values in SoftReference
s, if you're worried about memory consumption).
In this case this is not necessary, since:
Color.web
isn't expensive, Map
anyways; only a single Color
instance per distinct named color is created regardless of how often you pass the same parameter to Color.web
.BTW: I don't recommend initializing the cell in a way that cannot be the result of updateItem
calls. In your case the graphic
property for empty cells is null
except for the initial state. If you're worried about consistent cell sizes, it would be better to always keep the graphic and setting its visibility:
public class cell extends ListCell<String> {
private final Rectangle rect;
cell() {
super();
this.rect = new Rectangle(20,20);
setGraphic(this.rect);
}
@Override
protected void updateItem(String s, boolean empty) {
super.updateItem(s, empty);
if(empty)
rect.setVisible(false);
else {
rect.setFill(Color.web(getItem()));
rect.setVisible(true);
}
}
}