将 CheckBox 列添加到现有的 TableView

Posted

技术标签:

【中文标题】将 CheckBox 列添加到现有的 TableView【英文标题】:Adding a CheckBox column to an existing TableView 【发布时间】:2021-10-28 07:50:40 【问题描述】:

我最近想将CheckBox 列添加到现有的TableView。为了孤立地研究这个问题,我从Example 13-6 Creating a Table and Adding Data to It 开始。我添加了BooleanPropertyPerson 模型类的访问器,并添加了一个新的TableColumnCheckBoxTableCell 作为单元工厂。如图所示,我在每一行看到一个CheckBox。虽然所有值都是true,但没有一个被检查;复选框是实时的,但永远不会调用 setActive()。最近关于这个主题的questions 表明我遗漏了一些东西;我欢迎任何见解。

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.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

/**
 * Example 13-6 Creating a Table and Adding Data to It
 * https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/table-view.htm#CJAGAAEE
 */
public class TableViewSample extends Application 

    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data
        = FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com")
        );

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

    @Override
    public void start(Stage stage) 
        stage.setTitle("Table View Sample");
        stage.setWidth(600);
        stage.setHeight(400);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        table.setEditable(true);

        TableColumn<Person, Boolean> active = new TableColumn<>("Active");
        active.setCellValueFactory(new PropertyValueFactory<>("active"));
        active.setCellFactory(CheckBoxTableCell.forTableColumn(active));

        TableColumn<Person, String> firstName = new TableColumn<>("First Name");
        firstName.setCellValueFactory(new PropertyValueFactory<>("firstName"));

        TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
        lastName.setCellValueFactory(new PropertyValueFactory<>("lastName"));

        TableColumn<Person, String> email = new TableColumn<>("Email");
        email.setCellValueFactory(new PropertyValueFactory<>("email"));

        table.setItems(data);
        table.getColumns().addAll(active, firstName, lastName, email);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(8));
        vbox.getChildren().addAll(label, table);

        stage.setScene(new Scene(vbox));
        stage.show();
    

    public static class Person 

        private final BooleanProperty active;
        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty email;

        private Person(String fName, String lName, String email) 
            this.active = new SimpleBooleanProperty(true);
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        

        public boolean getActive() 
            return active.get();
        

        public void setActive(boolean b) 
            active.set(b);
        

        public String getFirstName() 
            return firstName.get();
        

        public void setFirstName(String s) 
            firstName.set(s);
        

        public String getLastName() 
            return lastName.get();
        

        public void setLastName(String s) 
            lastName.set(s);
        

        public String getEmail() 
            return email.get();
        

        public void setEmail(String s) 
            email.set(s);
        
    

【问题讨论】:

这是一个错误:bugs.openjdk.java.net/browse/JDK-8235630 【参考方案1】:

总结:如here 所述,这很可能是bug;避免该陷阱的步骤包括:

验证数据模型是否正确导出属性,如图here。 如here、here 和here 所述,在可能的情况下,严格检查将PropertyValueFactory 替换为显式Callback 的价值。

问题是CheckBoxTableCell无法根据提供的参数找到或绑定ObservableProperty&lt;Boolean&gt;

active.setCellFactory(CheckBoxTableCell.forTableColumn(active));

CheckBoxTableCell 遵循表列以访问目标 Boolean 属性。要查看效果,请将active 参数替换为Callback,该ObservableValue&lt;Boolean&gt; 为行i 显式返回ObservableValue&lt;Boolean&gt;

active.setCellFactory(CheckBoxTableCell.forTableColumn(
    (Integer i) -> data.get(i).active));

虽然这使复选框起作用,但根本问题是Person 类需要active 属性的访问器。 Using JavaFX Properties and Binding 讨论了属性方法命名约定,Ensemble8 tablecellfactoryPerson 类说明了一个工作模型类,其中每个属性都有一个属性获取器,如下所示。

通过此更改PropertyValueFactory 可以找到新添加的BooleanProperty,并且forTableColumn() 的原始形式有效。请注意,PropertyValueFactory 的便利性带有一些limitations。特别是,工厂对先前缺少的属性访问器的支持没有引起注意。幸运的是,同一个访问器允许为每个列的值工厂替换一个简单的Callback。如图here,而不是PropertyValueFactory

active.setCellValueFactory(new PropertyValueFactory<>("active"));

传递一个返回相应属性的lamda expression:

active.setCellValueFactory(cd -> cd.getValue().activeProperty());

另请注意,Person 现在可以是 private。此外,显式类型参数的使用在编译期间提供了更强的类型检查。

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.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

/**
 * https://***.com/a/68969223/230513
 */
public class TableViewSample extends Application 

    private final TableView<Person> table = new TableView<>();
    private final ObservableList<Person> data
        = FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com"),
            new Person("Michael", "Brown", "michael.brown@example.com")
        );

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

    @Override
    public void start(Stage stage) 
        stage.setTitle("Table View Sample");
        stage.setWidth(600);
        stage.setHeight(400);

        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));

        table.setEditable(true);

        TableColumn<Person, Boolean> active = new TableColumn<>("Active");
        active.setCellValueFactory(cd -> cd.getValue().activeProperty());
        active.setCellFactory(CheckBoxTableCell.forTableColumn(active));

        TableColumn<Person, String> firstName = new TableColumn<>("First Name");
        firstName.setCellValueFactory(cd -> cd.getValue().firstNameProperty());

        TableColumn<Person, String> lastName = new TableColumn<>("Last Name");
        lastName.setCellValueFactory(cd -> cd.getValue().lastNameProperty());

        TableColumn<Person, String> email = new TableColumn<>("Email");
        email.setCellValueFactory(cd -> cd.getValue().emailProperty());

        table.setItems(data);
        table.getColumns().addAll(active, firstName, lastName, email);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(8));
        vbox.getChildren().addAll(label, table);

        stage.setScene(new Scene(vbox));
        stage.show();
    

    private static class Person 

        private final BooleanProperty active;
        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty email;

        private Person(String fName, String lName, String email) 
            this.active = new SimpleBooleanProperty(true);
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        

        public BooleanProperty activeProperty() 
            return active;
        

        public StringProperty firstNameProperty() 
            return firstName;
        

        public StringProperty lastNameProperty() 
            return lastName;
        

        public StringProperty emailProperty() 
            return email;
        
    

【讨论】:

如果我们可以完全控制数据项,建议不要使用 PropertyValueFactory :) 另一方面,不处理 ReadOnlyObjectPropertyWrapper(如果bean prop 既可读又可写)正确是 CheckBoxXXCell bugs.openjdk.java.net/browse/JDK-8235630 中的一个错误 感谢您引用这个有用的错误报告;它确实看起来在 API 中很矛盾。请不要犹豫编辑此答案或添加您自己的详细说明。我的目标是在此期间发布一些相当具有决定性的东西。我讨厌指导其他人学习教程,只是为了让他们遇到类似的问题。

以上是关于将 CheckBox 列添加到现有的 TableView的主要内容,如果未能解决你的问题,请参考以下文章

将自动增量 ID 添加到现有的升序列?

如何将多维数组添加到现有的 Spark DataFrame

将 UIView 添加到现有的 UIViewController

查找和替换替代方法?而不是 "REPLACE",只是简单地添加文本到现有的单元格中?

如何将 Mailchimp 添加到现有的注册框

将核心数据添加到现有的 iPhone 项目