文档



JavaFX:使用JavaFX UI组件

13 表格视图

在本章中,您将学习如何在JavaFX应用程序中执行表格的基本操作,例如添加表格、填充表格数据和编辑表格行。

JavaFX SDK API中的几个类被设计用于以表格形式表示数据。在创建JavaFX应用程序中的表格时,最重要的类是TableViewTableColumnTableCell。您可以通过实现数据模型和应用单元格工厂来填充表格。

表格类提供了内置的功能,可以对列中的数据进行排序,并在必要时调整列的大小。

图13-1显示了一个典型的表格,表示通讯录中的联系人信息。

图13-1 表格示例

通讯录
"图13-1 表格示例"的描述

创建表格

示例13-1中的代码片段创建了一个空表格,包含三列,并将其添加到应用场景中。

示例13-1 添加表格

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
public class TableViewSample extends Application {
 
    private final TableView table = new TableView();
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(300);
        stage.setHeight(500);
 
        final Label label = new Label("通讯录");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn firstNameCol = new TableColumn("名字");
        TableColumn lastNameCol = new TableColumn("姓氏");
        TableColumn emailCol = new TableColumn("电子邮件");
        
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
}

通过实例化TableView类来创建表格控件。在示例13-1中,它被添加到VBox布局容器中,但你也可以直接将其添加到应用场景中。

示例13-1定义了三列,用于在通讯录中存储以下信息:联系人的名字、姓氏和电子邮件地址。这些列是通过使用TableColumn类创建的。

TableView类的getColumns方法将之前创建的列添加到表格中。在你的应用程序中,你可以使用这个方法来动态地添加和删除列。

编译和运行这个应用程序会产生如图13-2所示的输出。

图13-2 无数据的表格

一个空表格
"图13-2 无数据的表格"的描述

您可以通过调用setVisible方法来管理列的可见性。例如,如果您的应用程序的逻辑要求隐藏用户的电子邮件地址,您可以按照以下方式实现此任务:emailCol.setVisible(false)

当您的数据结构需要更复杂的表示时,您可以创建嵌套列。

例如,假设通讯录中的联系人有两个电子邮件帐户。然后您需要两列来显示主要和次要电子邮件地址。创建两个子列,并在emailCol上调用getColumns方法,如示例13-2所示。

示例13-2 创建嵌套列

TableColumn firstEmailCol = new TableColumn("主要");
TableColumn secondEmailCol = new TableColumn("次要");

emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);

在将这些行添加到示例13-1中,并编译和运行应用程序代码后,表格将显示如图13-3所示。

图13-3 带有嵌套列的表格

带有嵌套列的表格
"图13-3 带有嵌套列的表格"的描述

虽然表格已添加到应用程序中,但是因为没有定义数据,所以显示了标准标题"表格中没有内容"。您可以使用setPlaceholder方法来指定在空表格中显示的Node对象,而不是显示此标题。

定义数据模型

在JavaFX应用程序中创建表格时,最佳实践是实现一个定义数据模型并提供方法和字段以进一步处理表格的类。 示例13-3 创建了Person类来定义通讯录中的数据。

示例13-3 创建Person类

public static class Person {
    private final SimpleStringProperty firstName;
    private final SimpleStringProperty lastName;
    private final SimpleStringProperty email;
 
    private Person(String fName, String lName, String email) {
        this.firstName = new SimpleStringProperty(fName);
        this.lastName = new SimpleStringProperty(lName);
        this.email = new SimpleStringProperty(email);
    }
 
    public String getFirstName() {
        return firstName.get();
    }
    public void setFirstName(String fName) {
        firstName.set(fName);
    }
        
    public String getLastName() {
        return lastName.get();
    }
    public void setLastName(String fName) {
        lastName.set(fName);
    }
    
    public String getEmail() {
        return email.get();
    }
    public void setEmail(String fName) {
        email.set(fName);
    }
        
}

创建了firstNamelastNameemail字符串属性,以便引用特定的数据元素。

此外,为每个数据元素提供了getset方法。例如,getFirstName方法返回firstName属性的值,setFirstName方法为该属性指定一个值。

当在Person类中概述数据模型时,您可以创建一个ObservableList数组,并定义您想在表格中显示的任意数量的数据行。 示例13-4中的代码片段实现了这个任务。

示例13-4 在Observable List中定义表格数据

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

下一步是将数据与表格列关联起来。您可以通过为每个数据元素定义的属性来实现这一点,如示例13-5所示。

示例13-5:设置列的数据属性

firstNameCol.setCellValueFactory(
    new PropertyValueFactory<>("firstName")
);
lastNameCol.setCellValueFactory(
    new PropertyValueFactory<>("lastName")
);
emailCol.setCellValueFactory(
    new PropertyValueFactory<>("email")
);

setCellValueFactory方法为每一列指定了一个单元格工厂。单元格工厂是通过使用PropertyValueFactory类实现的,该类使用表格列的firstNamelastNameemail属性作为对应Person类的方法的引用。

当数据模型被定义并且数据被添加并与列关联时,可以使用TableView类的setItems方法将数据添加到表格中:table.setItems(data)

由于ObservableList对象能够跟踪其元素的任何更改,所以当数据发生变化时,TableView的内容会自动更新。

请查看示例13-6中的应用程序代码。

示例13-6 创建表格并添加数据

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
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) {
        Scene scene = new Scene(new Group());
        stage.setTitle("表格视图示例");
        stage.setWidth(450);
        stage.setHeight(500);
 
        final Label label = new Label("通讯录");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn firstNameCol = new TableColumn("名字");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<>("firstName"));
 
        TableColumn lastNameCol = new TableColumn("姓氏");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<>("lastName"));
 
        TableColumn emailCol = new TableColumn("电子邮件");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<>("email"));
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
} 

当您编译和运行此应用程序代码时,将显示图13-4中所示的表格。

图13-4 填充有数据的表格

填充有联系信息的表格
"图13-4 填充有数据的表格"的描述

添加新行

图13-4中的表格中包含了五行数据,目前还不能修改。

您可以使用文本字段来输入新的值到“名字”、“姓氏”和“电子邮件”列中。文本字段控件Text Field使您的应用程序能够接收用户的文本输入。在示例13-7中,创建了三个文本字段,为每个字段定义了提示文本,并创建了“添加”按钮。

示例13-7 使用文本字段在表格中输入新项目

final TextField addFirstName = new TextField();
addFirstName.setPromptText("名字");
addFirstName.setMaxWidth(firstNameCol.getPrefWidth());

final TextField addLastName = new TextField();
addLastName.setMaxWidth(lastNameCol.getPrefWidth());
addLastName.setPromptText("姓氏");

final TextField addEmail = new TextField();
addEmail.setMaxWidth(emailCol.getPrefWidth());
addEmail.setPromptText("电子邮件");
 
final Button addButton = new Button("添加");
addButton.setOnAction((ActionEvent e) -> {
    data.add(new Person(
        addFirstName.getText(),
        addLastName.getText(),
        addEmail.getText()
    ));
    addFirstName.clear();
    addLastName.clear();
    addEmail.clear();
});

当用户点击“添加”按钮时,文本字段中输入的值将包含在一个Person构造函数中,并添加到data可观察列表中。因此,包含联系信息的新条目将出现在表格中。

请查看示例13-8中显示的应用程序代码。

示例 13-8 带有文本字段的表格以输入新项目

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
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"));
    final HBox hb = new HBox();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn firstNameCol = new TableColumn("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
                new PropertyValueFactory<>("firstName"));
 
        TableColumn lastNameCol = new TableColumn("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
                new PropertyValueFactory<>("lastName"));
 
        TableColumn emailCol = new TableColumn("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
                new PropertyValueFactory<>("email"));
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            data.add(new Person(
                    addFirstName.getText(),
                    addLastName.getText(),
                    addEmail.getText()));
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
        });
 
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
} 

该应用程序不提供任何过滤器来检查是否以错误的格式输入了电子邮件地址等。在开发自己的应用程序时,您可以提供这样的功能。

当前的实现也不检查是否输入了空值。如果没有提供任何值,点击“添加”按钮会在表格中插入一行空行。

图13-5演示了用户如何添加新的数据行。

图13-5 向通讯录添加联系信息

一个带有文本字段的表格以添加新数据
"图13-5 向通讯录添加联系信息"的描述

图13-6显示了点击“添加”按钮后的表格。Emma White的联系详细信息现在显示在表格中。

图13-6 新添加的条目

通讯录中新输入的数据
"图13-6 新添加的条目"的描述

在列中排序数据

TableView类提供了内置的功能来对列中的数据进行排序。用户可以通过点击列标题来改变数据的顺序。第一次点击启用升序排序,第二次点击启用降序排序,第三次点击禁用排序。默认情况下,不应用任何排序。

用户可以在表格中对多个列进行排序,并指定每个列在排序操作中的优先级。要对多个列进行排序,用户在点击要排序的每个列的标题时按住Shift键。

图13-7中,名字按升序排序,姓氏按降序排序。请注意,第一列的优先级高于第二列。

图13-7 多列排序

按列排序的表格。
图13-7 多列排序的描述

作为应用程序开发者,您可以通过应用setSortType方法为应用程序中的每个列设置排序偏好。您可以指定升序和降序类型。例如,使用以下代码行为emailCol列设置降序排序类型:emailCol.setSortType(TableColumn.SortType.DESCENDING);

您还可以通过向TableView.sortOrder可观察列表中添加和删除TableColumn实例来指定要排序的列。此列表中的列的顺序表示排序优先级(例如,第一项的优先级高于第二项)。

要禁止数据排序,请在列上调用setSortable(false)方法。

编辑表中的数据

TableView类不仅可以渲染表格数据,还可以提供编辑功能。使用setEditable方法启用表格内容的编辑。

使用setCellFactory方法,通过TextFieldTableCell类将表格单元重新实现为文本字段。使用setOnEditCommit方法处理编辑,并将更新后的值分配给相应的表格单元。在示例13-9中展示了如何应用这些方法来处理名字、姓氏和电子邮件列的单元格编辑。

示例13-9 实现单元格编辑

firstNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
firstNameCol.setOnEditCommit(
    (CellEditEvent<Person, String> t) -> {
        ((Person) t.getTableView().getItems().get(
            t.getTablePosition().getRow())
            ).setFirstName(t.getNewValue());
});

lastNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
lastNameCol.setOnEditCommit(
    (CellEditEvent<Person, String> t) -> {
        ((Person) t.getTableView().getItems().get(
            t.getTablePosition().getRow())
            ).setLastName(t.getNewValue());
});

emailCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());       
emailCol.setOnEditCommit(
(CellEditEvent<Person, String> t) -> {
    ((Person) t.getTableView().getItems().get(
        t.getTablePosition().getRow())
        ).setEmail(t.getNewValue());
});

示例13-10中展示了应用的完整代码。

示例13-10 启用单元格编辑的TableViewSample

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
 
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"));
    final HBox hb = new HBox();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
 
        TableColumn<Person, String> firstNameCol = 
            new TableColumn<>("First Name");
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
            new PropertyValueFactory<>("firstName"));
        
        firstNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
        firstNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setFirstName(t.getNewValue());
        });
 
 
        TableColumn<Person, String> lastNameCol = 
            new TableColumn<>("Last Name");
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
            new PropertyValueFactory<>("lastName"));
       lastNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
       lastNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setLastName(t.getNewValue());
        });
 
        TableColumn<Person, String> emailCol = new TableColumn<>("Email");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
            new PropertyValueFactory<>("email"));
        emailCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());       
        emailCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setEmail(t.getNewValue());
        });
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            data.add(new Person(
                    addFirstName.getText(),
                    addLastName.getText(),
                    addEmail.getText()));
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
        });
 
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
}

图13-8中,用户正在编辑Michael Brown的姓氏。要编辑表格单元格,用户在单元格中输入新值,然后按下Enter键。直到按下Enter键之前,单元格不会被修改。这种行为是由TextField类的实现确定的。

图13-8 编辑表格单元格

一个编辑过的表格。
"图13-8 编辑表格单元格"的描述

请注意,TextField控件的默认实现要求用户按下Enter键才能提交编辑。您可以重新定义TextField的行为,使其在焦点更改时提交编辑,这是一种预期的用户体验。尝试修改的代码以实现这种替代行为。

示例13-11 单元格编辑的替代解决方案

import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
 
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"));
    final HBox hb = new HBox();
 
    public static void main(String[] args) {
        launch(args);
    }
 
    @Override
    public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(450);
        stage.setHeight(550);
 
        final Label label = new Label("Address Book");
        label.setFont(new Font("Arial", 20));
 
        table.setEditable(true);
        
        Callback<TableColumn<Person, String>, 
            TableCell<Person, String>> cellFactory
                = (TableColumn<Person, String> p) -> new EditingCell();
 
        TableColumn<Person, String> firstNameCol = 
            new TableColumn<>("First Name");
        TableColumn<Person, String> lastNameCol = 
            new TableColumn<>("Last Name");
        TableColumn<Person, String> emailCol = 
            new TableColumn<>("Email");
 
        firstNameCol.setMinWidth(100);
        firstNameCol.setCellValueFactory(
            new PropertyValueFactory<>("firstName"));
        firstNameCol.setCellFactory(cellFactory);        
        firstNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setFirstName(t.getNewValue());
        });
 
 
        lastNameCol.setMinWidth(100);
        lastNameCol.setCellValueFactory(
            new PropertyValueFactory<>("lastName"));
        lastNameCol.setCellFactory(cellFactory);
        lastNameCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setLastName(t.getNewValue());
        });
 
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(
            new PropertyValueFactory<>("email"));
        emailCol.setCellFactory(cellFactory);
        emailCol.setOnEditCommit(
            (CellEditEvent<Person, String> t) -> {
                ((Person) t.getTableView().getItems().get(
                        t.getTablePosition().getRow())
                        ).setEmail(t.getNewValue());
        });
 
        table.setItems(data);
        table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 
        final TextField addFirstName = new TextField();
        addFirstName.setPromptText("First Name");
        addFirstName.setMaxWidth(firstNameCol.getPrefWidth());
        final TextField addLastName = new TextField();
        addLastName.setMaxWidth(lastNameCol.getPrefWidth());
        addLastName.setPromptText("Last Name");
        final TextField addEmail = new TextField();
        addEmail.setMaxWidth(emailCol.getPrefWidth());
        addEmail.setPromptText("Email");
 
        final Button addButton = new Button("Add");
        addButton.setOnAction((ActionEvent e) -> {
            data.add(new Person(
                    addFirstName.getText(),
                    addLastName.getText(),
                    addEmail.getText()));
            addFirstName.clear();
            addLastName.clear();
            addEmail.clear();
        });
 
        hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton);
        hb.setSpacing(3);
 
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.setPadding(new Insets(10, 0, 0, 10));
        vbox.getChildren().addAll(label, table, hb);
 
        ((Group) scene.getRoot()).getChildren().addAll(vbox);
 
        stage.setScene(scene);
        stage.show();
    }
 
    public static class Person {
 
        private final SimpleStringProperty firstName;
        private final SimpleStringProperty lastName;
        private final SimpleStringProperty email;
 
        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }
 
        public String getFirstName() {
            return firstName.get();
        }
 
        public void setFirstName(String fName) {
            firstName.set(fName);
        }
 
        public String getLastName() {
            return lastName.get();
        }
 
        public void setLastName(String fName) {
            lastName.set(fName);
        }
 
        public String getEmail() {
            return email.get();
        }
 
        public void setEmail(String fName) {
            email.set(fName);
        }
    }
 
    class EditingCell extends TableCell<Person, String> {
 
        private TextField textField;
 
        public EditingCell() {
        }
 
        @Override
        public void startEdit() {
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                textField.selectAll();
            }
        }
 
        @Override
        public void cancelEdit() {
            super.cancelEdit();
 
            setText((String) getItem());
            setGraphic(null);
        }
 
        @Override
        public void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
 
            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());
                    }
                    setText(null);
                    setGraphic(textField);
                } else {
                    setText(getString());
                    setGraphic(null);
                }
            }
        }
 
        private void createTextField() {
            textField = new TextField(getString());
            textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2);
            textField.focusedProperty().addListener(
                (ObservableValue<? extends Boolean> arg0, 
                Boolean arg1, Boolean arg2) -> {
                    if (!arg2) {
                        commitEdit(textField.getText());
                    }
            });
        }
 
        private String getString() {
            return getItem() == null ? "" : getItem().toString();
        }
    }
}

请注意,随着TextFieldTableCell实现的不断改进,这种方法可能在未来的版本中变得多余,以提供更好的用户体验。

向表格中添加数据映射

您可以将Map数据添加到表格中。使用MapValueFactory类,如示例13-12所示,将学生ID的映射显示在表格中。

示例13-12 向表格添加地图数据

public static final String Column1MapKey = "A";
public static final String Column2MapKey = "B";
firstDataColumn.setCellValueFactory(new MapValueFactory(Column1MapKey));
secondDataColumn.setCellValueFactory(new MapValueFactory(Column2MapKey));
TableView tableView = new TableView<>(generateDataInMap());
Callback<TableColumn<Map, String>, TableCell<Map, String>>
cellFactoryForMap = (TableColumn<Map, String> p) ->
new TextFieldTableCell(new StringConverter() {
@Override
public String toString(Object t) {
return t.toString();
}
@Override
public Object fromString(String string) {
return string;
}
});
firstDataColumn.setCellFactory(cellFactoryForMap);
secondDataColumn.setCellFactory(cellFactoryForMap);
private ObservableList<Map> generateDataInMap() {
int max = 10;
ObservableList<Map> allData = FXCollections.observableArrayList();
for (int i = 1; i < max; i++) {
Map<String, String> dataRow = new HashMap<>();
String value1 = "A" + i;
String value2 = "B" + i;
dataRow.put(Column1MapKey, value1);
dataRow.put(Column2MapKey, value2);
allData.add(dataRow);
return allData;
}

MapValueFactory类实现了Callback接口,它专门设计用于在表格列的单元格工厂中使用。在示例13-12中,dataRow哈希映射表示TableView对象中的一行数据。该映射有两个String键:Column1MapKey和Column2MapKey,用于映射第一列和第二列中的相应值。对于表格列调用的setCellValueFactory方法会使用特定的键填充数据,使得第一列包含与"A"键对应的值,第二列包含与"B"键对应的值。

当您编译并运行此应用程序时,会产生如图13-9所示的输出。

图13-9 带有映射数据的TableView

图13-9的描述
"图13-9 带有映射数据的TableView"的描述

TreeTableView是JavaFX中表格数据控件的进一步扩展。有关此UI控件的更多信息,请参阅Tree Table View章节。

相关API文档 

关闭窗口

目录

JavaFX:使用JavaFX UI组件

展开 折叠