Issue
When I read JavaFX book, they use TodoItem class (1)
final class TodoItem {
final String name;
final BooleanProperty isDone = new SimpleBooleanProperty(false);
public TodoItem(String name) {
this.name = name;
}
@Override
public String toString() {
return name + (isDone.get() ? " DONE" : "");
}
}
and ObservableList (2)
ObservableList<TodoItem> items = FXCollections.observableArrayList((TodoItem item)
-> { return new Observable[] { item.isDone }; });
and CellFactory (3)
list.setCellFactory(CheckBoxListCell.forListView( item -> item.isDone ));
to show the program like:
When I check a CheckBox, text next to CheckBox will be added "DONE".
And when I uncheck a CheckBox, "DONE" will disappear.
Main content is below
I want to write another version of (3) like
list.setCellFactory((ListView<TodoItem> param) -> {
return new ListCell<> () {
@Override
public void updateItem(TodoItem item, boolean empty) {
super.updateItem(item, empty);
if (!(empty || item == null)) {
CheckBox checkBox = new CheckBox();
item.isDone.bind(checkBox.selectedProperty());
setGraphic(checkBox);
setText(item.name);
} else {
setGraphic(null);
setText(null);
}
}
};
});
I create a CheckBox, I bind isDone Property to CheckBox.
And I think, isDone is observable, and isDone is bound with CheckBox,
when I check a CheckBox, It should make isDone to true, and update the text with "DONE".
Please help me find my mistake, and help me solve my problem, thank so much.
Solution
If I understand correctly, you don't want to use the CheckBoxListCell and you want to write your own cell factory. The main change you need to do in your logic is to not create a new CheckBox in the updateItem call. You need to set one CheckBox per each cell. So for that you need to declare a CheckBox on class level.
Below is one way to implement the code to get the required functionality.
listView.setCellFactory((ListView<TodoItem> param) -> new ListCell<TodoItem>() {
private CheckBox checkBox = new CheckBox();
private TodoItem itemBkp;
@Override
public void updateItem(TodoItem item, boolean empty) {
super.updateItem(item, empty);
if (itemBkp != null) {
itemBkp.isDoneProperty().unbindBidirectional(checkBox.selectedProperty());
}
if (!(empty || item == null)) {
item.isDoneProperty().bindBidirectional(checkBox.selectedProperty());
setGraphic(checkBox);
setText(item.toString());
itemBkp = item;
} else {
setGraphic(null);
setText(null);
itemBkp = null;
}
}
});
UPDATE:
As mentioned in the comment, there is a little bug in the above provided code. So I reworked on the implementation. The below code should address the issue mentioned in the comment. In this approach, we dont need any item backup as we are not binding any properties.
listView.setCellFactory((ListView<TodoItem> param) -> new ListCell<TodoItem>() {
private CheckBox checkBox;
@Override
public void updateItem(TodoItem item, boolean empty) {
super.updateItem(item, empty);
if (!(empty || item == null)) {
getCheckBox().setSelected(item.isDoneProperty().getValue());
setGraphic(getCheckBox());
setText(item.toString());
} else {
setGraphic(null);
setText(null);
}
}
private CheckBox getCheckBox(){
if(checkBox==null){
checkBox = new CheckBox();
checkBox.selectedProperty().addListener((obs,old,val)->{
if(getItem()!=null){
getItem().isDoneProperty().setValue(val);
}
});
}
return checkBox;
}
});
Below is the complete code of the demo:
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ListViewCheckBoxDemo extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
ListView<TodoItem> listView = new ListView<>();
listView.setCellFactory((ListView<TodoItem> param) -> new ListCell<TodoItem>() {
private CheckBox checkBox;
@Override
public void updateItem(TodoItem item, boolean empty) {
super.updateItem(item, empty);
if (!(empty || item == null)) {
getCheckBox().setSelected(item.isDoneProperty().getValue());
setGraphic(getCheckBox());
setText(item.toString());
} else {
setGraphic(null);
setText(null);
}
}
private CheckBox getCheckBox() {
if (checkBox == null) {
checkBox = new CheckBox();
checkBox.selectedProperty().addListener((obs, old, val) -> {
if (getItem() != null) {
getItem().isDoneProperty().setValue(val);
}
});
}
return checkBox;
}
});
ObservableList<TodoItem> items = FXCollections.observableArrayList(item -> new Observable[]{item.isDone});
items.addAll(new TodoItem("Sign a Contract"), new TodoItem("Fail Deadline"), new TodoItem("Blame Yourself"), new TodoItem("Suffer"));
for (int i = 1; i < 20; i++) {
items.add(new TodoItem("Testing " + i));
}
listView.setItems(items);
CheckBox cb = new CheckBox("Mark 4th item as done (external updating)");
cb.selectedProperty().addListener((obs, old, val) -> {
listView.getItems().get(3).isDoneProperty().setValue(val);
});
VBox root = new VBox(cb, listView);
root.setSpacing(10);
root.setPadding(new Insets(10));
Scene scene = new Scene(root, 350, 250);
stage.setScene(scene);
stage.setTitle("ListView CheckBox Demo");
stage.show();
}
final class TodoItem {
private final String name;
private final BooleanProperty isDone = new SimpleBooleanProperty(false);
public TodoItem(String name) {
this.name = name;
}
@Override
public String toString() {
return name + (isDone.get() ? " DONE" : "");
}
public BooleanProperty isDoneProperty() {
return isDone;
}
}
}
Answered By - Sai Dandem