I want to create a simple ListView. I have figured out I can use the method setCellFactory() but I don\'t understand how to use them correctly. So far I have:
If you have a ListView
, then each item in the ListView
is a String
, and the CheckBoxListCell.forListView(...) method expects a Callback
.
In the pre-Java 8 way of thinking of things, a Callback
public ObservableValue call(String s) ;
So you need something that implements that interface, and you pass in an object of that type.
The documentation also tells you how that callback is used:
A Callback that, given an object of type T (which is a value taken out of the ListView.items list), will return an ObservableValue that represents whether the given item is selected or not. This ObservableValue will be bound bidirectionally (meaning that the CheckBox in the cell will set/unset this property based on user interactions, and the CheckBox will reflect the state of the ObservableValue, if it changes externally).
(Since you have a ListView
, here T
is String
.) So, for each element in the list view (each element is a String
), the callback is used to determine an ObservableValue
which is bidirectionally bound to the state of the checkbox. I.e. if the checkbox is checked, that property is set to true
, and if unchecked it is set to false
. Conversely, if the property is set to true
(or false
) programmatically, the checkbox is checked (or unchecked).
The typical use case here is that the type of item in the ListView
would have a BooleanProperty
as part of its state. So you would typically use this with some kind of custom class representing your data, as follows with the inner Item
class:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewWithCheckBox extends Application {
@Override
public void start(Stage primaryStage) {
ListView- listView = new ListView<>();
for (int i=1; i<=20; i++) {
Item item = new Item("Item "+i, false);
// observe item's on property and display message if it changes:
item.onProperty().addListener((obs, wasOn, isNowOn) -> {
System.out.println(item.getName() + " changed on state from "+wasOn+" to "+isNowOn);
});
listView.getItems().add(item);
}
listView.setCellFactory(CheckBoxListCell.forListView(new Callback
- >() {
@Override
public ObservableValue
call(Item item) {
return item.onProperty();
}
}));
BorderPane root = new BorderPane(listView);
Scene scene = new Scene(root, 250, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty on = new SimpleBooleanProperty();
public Item(String name, boolean on) {
setName(name);
setOn(on);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final BooleanProperty onProperty() {
return this.on;
}
public final boolean isOn() {
return this.onProperty().get();
}
public final void setOn(final boolean on) {
this.onProperty().set(on);
}
@Override
public String toString() {
return getName();
}
}
public static void main(String[] args) {
launch(args);
}
}
If you genuinely have a ListView
, it's not really clear what the property you are setting by clicking on the check box would be. But there's nothing to stop you creating one in the callback just for the purpose of binding to the check box's selected state:
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewWithStringAndCheckBox extends Application {
@Override
public void start(Stage primaryStage) {
ListView listView = new ListView<>();
for (int i = 1; i <= 20 ; i++) {
String item = "Item "+i ;
listView.getItems().add(item);
}
listView.setCellFactory(CheckBoxListCell.forListView(new Callback>() {
@Override
public ObservableValue call(String item) {
BooleanProperty observable = new SimpleBooleanProperty();
observable.addListener((obs, wasSelected, isNowSelected) ->
System.out.println("Check box for "+item+" changed from "+wasSelected+" to "+isNowSelected)
);
return observable ;
}
}));
BorderPane root = new BorderPane(listView);
Scene scene = new Scene(root, 250, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Notice that in this case, the BooleanProperty
s are potentially being created and discarded frequently. This probably isn't a problem in practice, but it does mean the first version, with the dedicated model class, may perform better.
In Java 8, you can simplify the code. Because the Callback
interface has only one abstract method (making it a Functional Interface), you can think of a Callback
as a function which takes a Item
and generates an ObservableValue
. So the cell factory in the first example could be written with a lambda expression:
listView.setCellFactory(CheckBoxListCell.forListView(item -> item.onProperty()));
or, even more succinctly using method references:
listView.setCellFactory(CheckBoxListCell.forListView(Item::onProperty));