首页 > 解决方案 > 如何将模型属性与 TreeView 单元格中的复选框状态相关联?

问题描述

我正在用 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和其他字段)并根据TaskClassObject.task_Is_Completed. 如果为真,则应选中节点中的复选框。

标签: javajavafxtreeview

解决方案


澄清方法信息

你说:

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

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

GUI 编程是事件驱动的,您要做的是响应事件并基于此更新关联的模型状态。

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

如何将 CheckBoxTreeItem 中的选定值绑定到我的模型类,以便当关联 CheckBoxTreeCell 中的 CheckBox 被选中或取消选中时,我的模型类中的关联属性会更新以反映新值?

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

如何实现所需的绑定

CheckBoxTreeItem和类是相当高级的CheckBoxTreeCell类,它们被设置为响应适当的事件并根据它们更新模型状态。您需要利用它们提供的高级接口来使它们真正可用。要为您的示例执行此操作,您需要:

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

  2. complete将任务实例中的属性绑定到关联的CheckBoxTreeItem. 这是通过双向绑定完成的,因此对完整属性的Task更新将更新 UI 中的选择状态,反之亦然。

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

  4. 提供一个 StringConverterCheckBoxTreeCell以便它可以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);
        }
    }

}

推荐阅读