如何将模型 属性 与 TreeView 单元格中的复选框状态相关联?

How to associate a model property with a checkbox state in a TreeView cell?

我正在用 JavaFX 编写一个简单的任务管理器应用程序。以下是我到目前为止编写的简单代码

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class App extends Application {

        class TaskClass {
                public String task_Title;
                public String task_Duration;
                public String Remained_Days;
                public boolean task_Is_Completed;
        }

        @Override
        public void start(Stage primaryStage) {
            
                TaskClass TaskClassObject               = new TaskClass();
                TaskClassObject.task_Title              = "Buy a Car";
                TaskClassObject.task_Duration           = "7 days";
                TaskClassObject.Remained_Days           = "2 days";
                TaskClassObject.task_Is_Completed       = false;
            
                TreeView<TaskClass>         treeView    = new TreeView<TaskClass>();
                CheckBoxTreeItem<TaskClass> rootNode    = new CheckBoxTreeItem<TaskClass>(TaskClassObject);
                treeView.setRoot(rootNode);
                treeView.setCellFactory(CheckBoxTreeCell.<TaskClass>forTreeView());

                
                VBox vb = new VBox();
                VBox.setVgrow(treeView, Priority.ALWAYS);
                vb.getChildren().add(treeView);
                Scene Scene = new Scene(vb, 800, 600);
                primaryStage.setTitle("my task manager app");
                primaryStage.setScene(Scene);
                primaryStage.show();

        }

        public static void main(String[] args) {
                launch();
        }
}

我想遍历 treeView 个节点并为每个节点获取 TaskClassObject 并访问其字段(TaskClassObject.task_Title 和其他字段)并根据 [=15 选中复选框=].如果为真,则应选中节点中的复选框。

澄清有关方法的信息

您说:

I want to iterate over treeView nodes and get the TaskClassObject for each node and access its fields (TaskClassObject.task_Title and other fields) and checkbox gets checked based on TaskClassObject.task_Is_Completed. if its true, checkbox in node should be checked.

然而,那不是你真正想做的。您不想尝试遍历事物并担心像 TreeView 这样的虚拟化控件中的节点和东西。

GUI 编程是 event-driven,您要做的是响应事件并根据事件更新关联的模型状态。

你真正需要问(并且想要)的是:

How do I bind the selected value from a CheckBoxTreeItem to my model class, so that when a CheckBox in an associated CheckBoxTreeCell is selected or unselected my associated property in my model class is updated to reflect the new value?

(我将您的问题标题更新为其中的一个子集,以便其他人遇到相同问题时可以轻松找到它)。

如何实现需要的绑定

CheckBoxTreeItemCheckBoxTreeCell class 相当 high-level class 被设置为响应适当的事件并根据更新模型状态在他们之上。您需要利用它们提供的 high-level 接口来使它们真正可用。要为您的示例执行此操作,您需要:

  1. 使您的模型 class(Task)使用并公开 JavaFX 属性,以便 UI 可以根据 属性 自动更新UI 的值和更新可以自动更新 属性 值。

  2. 将任务实例中的 complete 属性 绑定到关联的 CheckBoxTreeItem。这是通过 bi-directional 绑定完成的,因此 Task 中完整 属性 的更新将更新 UI 和 vice-versa.[=24 中的选择状态=]

    task.completeProperty().bindBidirectional(treeItem.selectedProperty());
    
  3. CheckBoxTreeCell 提供选定的 属性 绑定回调,以将其与您的 CheckBoxTreeItem 相关联。这实际上通常默认发生,但是,如果您还想要一个 StringConverter(在这种情况下您这样做),那么据我所知,您需要手动提供一个。

  4. CheckBoxTreeCell 提供一个 StringConverter,以便它能够以清晰的方式显示您的 Task

示例代码

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.CheckBoxTreeItem;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;

import java.time.Duration;
import java.time.temporal.ChronoUnit;

public class App extends Application {

    public static void main(String[] args) {
        launch();
    }

    @Override
    public void start(Stage stage) {
        Task task = createTask();

        CheckBoxTreeItem<Task> treeItem = new CheckBoxTreeItem<>(task);
        task.completeProperty().bindBidirectional(treeItem.selectedProperty());

        TreeView<Task> treeView = new TreeView<>();
        attachTaskCellFactory(treeView);
        treeView.setRoot(treeItem);

        StackPane layout = new StackPane(treeView);
        Scene Scene = new Scene(layout);
        stage.setScene(Scene);
        stage.show();
    }

    private void attachTaskCellFactory(TreeView<Task> treeView) {
        Callback<TreeItem<Task>, ObservableValue<Boolean>> getSelectedProperty =
                item -> {
                    if (item instanceof CheckBoxTreeItem<?>) {
                        return ((CheckBoxTreeItem<?>)item).selectedProperty();
                    }
                    return null;
                };

        StringConverter<TreeItem<Task>> treeItemStringConverter = new StringConverter<>() {
            @Override
            public String toString(TreeItem<Task> treeItem) {
                return treeItem == null || treeItem.getValue() == null || treeItem.getValue().getTitle() == null
                        ? ""
                        : treeItem.getValue().getTitle();
            }

            @Override
            public TreeItem<Task> fromString(String string) {
                // not actually used, only provided to allow a concrete instance to be created.
                return null;
            }
        };

        treeView.setCellFactory(
                CheckBoxTreeCell.forTreeView(
                        getSelectedProperty,
                        treeItemStringConverter
                )
        );
    }

    private Task createTask() {
        Task task = new Task(
                "Buy a Car",
                Duration.of(7, ChronoUnit.DAYS),
                Duration.of(2, ChronoUnit.DAYS),
                false
        );

        task.completeProperty().addListener((observable, oldValue, newValue) -> {
            System.out.println("Task complete: " + newValue);
        });

        return task;
    }

    public class Task {
        final private StringProperty title = new SimpleStringProperty();
        final private ObjectProperty<Duration> duration = new SimpleObjectProperty<>();
        final private ObjectProperty<Duration> remainingDays = new SimpleObjectProperty<>();
        final private BooleanProperty complete = new SimpleBooleanProperty();

        public Task(String title, Duration duration, Duration remainingDays, Boolean complete) {
            setTitle(title);
            setDuration(duration);
            setRemainingDays(remainingDays);
            setComplete(complete);
        }

        public String getTitle() {
            return title.get();
        }

        public StringProperty titleProperty() {
            return title;
        }

        public void setTitle(String title) {
            this.title.set(title);
        }

        public Duration getDuration() {
            return duration.get();
        }

        public ObjectProperty<Duration> durationProperty() {
            return duration;
        }

        public void setDuration(Duration duration) {
            this.duration.set(duration);
        }

        public Duration getRemainingDays() {
            return remainingDays.get();
        }

        public ObjectProperty<Duration> remainingDaysProperty() {
            return remainingDays;
        }

        public void setRemainingDays(Duration remainingDays) {
            this.remainingDays.set(remainingDays);
        }

        public boolean isComplete() {
            return complete.get();
        }

        public BooleanProperty completeProperty() {
            return complete;
        }

        public void setComplete(boolean complete) {
            this.complete.set(complete);
        }
    }

}