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:
listView.setCellFactory(CheckBoxListCell.forListView(new Callback<String, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> 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 ;
}
}));
Thank you! This helps me to solve my problem.
Thanks for previous answers. I miss the information that setCellValueFactory is not needed, but value assigned should also be done in setCellFactory. Here is my approach (much copied from previous solution).
public TreeTableColumn<RowContainer, Boolean> treetblcolHide;
...
treetblcolHide.setCellFactory(CheckBoxTreeTableCell.<RowContainer, Boolean>forTreeTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> call(final Integer param) {
final RowContainer rowitem = treetblcolHide.getTreeTableView().getTreeItem(param).getValue();
BooleanProperty observable = new SimpleBooleanProperty();
observable.addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
rowitem.setHideMenuItem(newValue.toString());
}
}
);
observable.setValue(Boolean.parseBoolean(rowitem.getHideMenuItem()));
return observable ;
}
}));
If you have a ListView<String>
, then each item in the ListView
is a String
, and the CheckBoxListCell.forListView(...) method expects a Callback<String, ObservableValue<Boolean>>
.
In the pre-Java 8 way of thinking of things, a Callback<String, ObservableValue<Boolean>> is an interface that defines a single method,
public ObservableValue<Boolean> 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<String>
, 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<Boolean>
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<Item> 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<Item, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> 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<String>
, 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<String> listView = new ListView<>();
for (int i = 1; i <= 20 ; i++) {
String item = "Item "+i ;
listView.getItems().add(item);
}
listView.setCellFactory(CheckBoxListCell.forListView(new Callback<String, ObservableValue<Boolean>>() {
@Override
public ObservableValue<Boolean> 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<Item, ObservableValue<Boolean>>
as a function which takes a Item
and generates an ObservableValue<Boolean>
. 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));