JavaFX 使用自定义 Listview 将 CheckBox 与 setCellFactory 结合使用

JavaFX Using Custom Listview to using CheckBox with setCellFactory

当我阅读 JavaFX 书籍时,他们使用 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" : "");
    }
}

和 ObservableList (2)

ObservableList<TodoItem> items = FXCollections.observableArrayList((TodoItem item)
    -> { return new Observable[] { item.isDone }; });

和细胞工厂 (3)

list.setCellFactory(CheckBoxListCell.forListView( item -> item.isDone ));

显示程序如下:

当我选中复选框时,复选框旁边的文本将添加“完成”。 当我取消选中复选框时,“完成”将消失。
主要内容如下
我想写另一个版本的 (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);
            }
        }
    };
});

我创建了一个 CheckBox,我将 isDone 属性 绑定到 CheckBox。 而且我认为,isDone 是可观察的,并且 isDone 与 CheckBox 绑定, 当我检查一个复选框时,它应该使 isDone 为真,并用“DONE”更新文本。
请帮我找出我的错误,并帮我解决我的问题,非常感谢。

如果我没理解错的话,您不想使用 CheckBoxListCell,而是想编写自己的单元工厂。您需要在逻辑中做的主要更改是不要在 updateItem 调用中创建新的 CheckBox。您需要为每个单元格设置一个 CheckBox。因此,为此您需要在 class 级别上声明一个 CheckBox。

下面是实现代码以获得所需功能的一种方法。

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;
        }
    }
});

更新:

如评论中所述,上面提供的代码中存在一个小错误。所以我重新实施了。下面的代码应该解决评论中提到的问题。在这种方法中,我们不需要任何项目备份,因为我们没有绑定任何属性。

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;
    }
});

下面是完整的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;
        }
    }
}