文档



JavaFX:FXML入门
3 使用FXML创建通讯录 (Release 8)

3 使用FXML创建通讯录

在本章中,您将创建一个包含姓名和电子邮件地址表格的通讯录应用程序,如图3-1所示。本教程将展示如何填充表格数据,如何在应用程序启动时对数据进行排序,如何对齐表格单元格中的数据,以及如何向表格中添加行。

本教程假设您具有一定的FXML和应用程序开发知识。在开始之前,您应该已经完成了Getting Started系列中的FXML教程,因为它教授了FXML开发的基础知识。具体来说,对于通讯录教程,您应该了解以下内容:

  • FXML项目的基本结构(.java、.fxml和控制器文件)

  • 如何在NetBeans IDE中创建和运行JavaFX FXML项目

  • 布局和用户界面组件的基础知识

在开始本教程之前,请确保您使用的NetBeans IDE版本支持您的JavaFX版本。

图3-1 通讯录应用程序

图3-1的描述
"图3-1 通讯录应用程序"的描述

设置项目

您的第一个任务是在NetBeans IDE中设置一个JavaFX FXML项目。

  1. 文件菜单中选择新建项目

  2. JavaFX类别中选择JavaFX FXML应用程序。点击下一步

  3. 将项目命名为FXMLTableView,然后点击完成

    NetBeans IDE打开一个包含示例Hello World应用程序代码的FXML项目。该应用程序包括三个文件:FXMLTableView.javaFXMLDocument.fxmlFXMLDocumentController.java

  4. FXMLDocumentController.java重命名为FXMLTableViewController.java,以使名称对于此应用程序更有意义。

    1. 在项目窗口中,右键点击FXMLDocumentController.java,选择重构然后选择重命名

    2. 输入FXMLTableViewController,然后点击重构

  5. FXMLDocument.fxml重命名为fxml_tableview.fxml

    1. 右键点击FXMLDocument.fxml,选择重命名

    2. 输入fxml_tableview,然后点击确定

  6. 打开FXMLTableView.java并编辑FXMLTableView类,使其看起来像示例3-1

    示例3-1 FXMLTableView.java

    public class FXMLTableView extends Application {
        
        @Override
        public void start(Stage primaryStage) throws Exception {
           primaryStage.setTitle("FXML TableView 示例");
           Pane myPane = (Pane)FXMLLoader.load(getClass().getResource
        ("fxml_tableview.fxml"));
           Scene myScene = new Scene(myPane);
           primaryStage.setScene(myScene);
           primaryStage.show();
        }
     
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    请注意,Java文件中不包含场景的代码。在教程的下一部分创建基本用户界面中,您将在FXML文件中添加场景的代码。

  7. 按下Ctrl(或Cmd)+ Shift + I来纠正导入语句。

创建基本用户界面

通过创建一个GridPane布局容器作为场景的根节点,定义用户界面。然后,将一个Label和一个TableView组件作为GridPane布局容器的子节点添加进去。

  1. 编辑fxml_tableview.fxml文件。

  2. 删除NetBeans IDE自动生成的<AnchorPane>标记。

  3. 按照示例3-2中所示,将GridPane布局容器作为场景的根节点添加进去。

    示例3-2 GridPane

    <GridPane alignment="CENTER" hgap="10.0" vgap="10.0"
        xmlns:fx="http://javafx.com/fxml"
        fx:controller="fxmltableview.FXMLTableViewController">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
        </padding> 
    </GridPane>
    

    你可以忽略输出窗口中可能出现的“在指定地址中找不到文件:http://javafx.com/fxml”错误。

  4. LabelTableView组件添加到GridPane布局容器中。代码如示例3-3所示。

    示例3-3 Label和TableView

    <GridPane alignment="CENTER" hgap="10.0" vgap="10.0"
        xmlns:fx="http://javafx.com/fxml"
        fx:controller="fxmltableview.FXMLTableViewController">
        <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
        </padding>
        <Label style="-fx-font: NORMAL 20 Tahoma;" text="通讯录"                
            GridPane.columnIndex="0" GridPane.rowIndex="0">
        </Label>
        <TableView fx:id="tableView" GridPane.columnIndex="0" 
            GridPane.rowIndex="1">
        </TableView>
    </GridPane>
    
  5. 添加Insets类的导入语句。

    <?import javafx.geometry.Insets?>

  6. 运行程序。你将看到标签“通讯录”和一个带有文本“表中没有列”的表格,这是TableView实现定义的默认标题,如图3-2所示。

    图3-2 没有列的表格

    图3-2的描述
    "图3-2 没有列的表格"的描述

向表格中添加列

使用TableColumn类添加三列用于显示数据:名字、姓氏和电子邮件地址。代码如示例3-4所示。

示例3-4 表格列

<TableView fx:id="tableView" GridPane.columnIndex="0" GridPane.rowIndex="1">
     <columns>
          <TableColumn text="名字">
          </TableColumn>
          <TableColumn text="姓氏">
          </TableColumn>
          <TableColumn text="电子邮件地址">
          </TableColumn>
     </columns>    
</TableView>

提示:有关TableColumn类或本教程中讨论的任何其他JavaFX类的更多信息,请参阅API文档

图3-3显示了具有名字、姓氏和电子邮件地址列的表格。

图3-3 带有三列的通讯录

图3-3的描述
"图3-3 带有三列的通讯录"的描述

定义数据模型

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

  1. 在NetBeans IDE中,右键单击Source Packages下的fxmltableview文件夹,选择New,然后选择Java Class

  2. 将类命名为Person,然后点击Finish

  3. 实现一个Person类来定义数据,如Example 3-5所示。

    Example 3-5 Person类

    package fxmltableview;
     
    import javafx.beans.property.SimpleStringProperty;
     
    public class Person {
       private final SimpleStringProperty firstName = new SimpleStringProperty("");
       private final SimpleStringProperty lastName = new SimpleStringProperty("");
       private final SimpleStringProperty email = new SimpleStringProperty("");
    
    public Person() {
            this("", "", "");
        }
     
        public Person(String firstName, String lastName, String email) {
            setFirstName(firstName);
            setLastName(lastName);
            setEmail(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);
        }
    }
    

为表格列关联数据

下一步是为数据定义行,并将数据与表格列关联起来。您需要将以下代码添加到FXML文件中。

  1. fxml_tableview.fxml文件中,创建一个ObservableList数组,并定义与您想在表格中显示的数据行数相同的数据行。示例代码在示例3-6中。将代码添加在</columns></TableView>标记之间。

    示例3-6 ObservableList数组

    </columns>
    <items>
        <FXCollections fx:factory="observableArrayList">
            <Person firstName="Jacob" lastName="Smith"  
                 email="jacob.smith@example.com"/>
            <Person firstName="Isabella" lastName="Johnson" 
                 email="isabella.johnson@example.com"/>
            <Person firstName="Ethan" lastName="Williams" 
                 email="ethan.williams@example.com"/>
            <Person firstName="Emma" lastName="Jones"
                 email="emma.jones@example.com"/>
            <Person firstName="Michael" lastName="Brown" 
                 email="michael.brown@example.com"/>
        </FXCollections>
    </items>
    </TableView>
    
  2. 为每个列指定一个单元格工厂,将数据与列关联起来,如示例3-7所示。

    示例3-7 单元格工厂

    <columns>
         <TableColumn text="名字">
            <cellValueFactory><PropertyValueFactory property="firstName" />
            </cellValueFactory>
         </TableColumn>
         <TableColumn text="姓氏">
            <cellValueFactory><PropertyValueFactory property="lastName" />
            </cellValueFactory>
         </TableColumn>
         <TableColumn text="电子邮件地址">
             <cellValueFactory><PropertyValueFactory property="email" />
             </cellValueFactory>
        </TableColumn>
    </columns> 
    

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

  3. 导入所需的包,如示例3-8所示:

    示例3-8 导入语句

    <?import javafx.scene.control.cell.*?> 
    <?import javafx.collections.*?> 
    <?import fxmltableview.*?> 
    

此时运行应用程序将显示填充有数据的表格,如图3-4所示。

图3-4 带有数据的表格

图3-4的描述
"图3-4 带有数据的表格"的描述

以下是TableView类的一些内置功能供您尝试:

  • 通过在表头中拖动列分隔符来调整列宽。

  • 通过拖动列头来移动列。

  • 通过点击列头来改变数据的排序顺序。第一次点击启用升序排序,第二次点击启用降序排序,第三次点击禁用排序。默认情况下,不应用排序。

在启动时设置排序顺序

在这个任务中,您将设置排序顺序,使得“姓氏”列中的条目在应用程序启动时以升序的字母顺序显示。您可以通过为表格列创建一个ID,然后设置对它的引用来实现这一点。

  1. 为“姓氏”列添加一个ID:

    <TableColumn fx:id="firstNameColumn" text="姓氏">

  2. 通过在</items></TableView>标记之间添加示例3-9中的代码来指定排序顺序。

    示例3-9 排序顺序

    </items>
         <sortOrder>
              <fx:reference source="firstNameColumn"/>
         </sortOrder> 
    </TableView>
    

您可以在图3-5中看到结果。

图3-5 启动时按照姓氏列数据排序的表格

图3-5的描述
"图3-5 启动时按照姓氏列数据排序的表格"的描述

定义列宽

添加prefWidth属性来增加列宽,如示例3-10所示。

示例3-10 列宽

<TableColumn fx:id="firstnameColumn" text="名字" prefWidth="100">
        <cellValueFactory><PropertyValueFactory property="firstName" />
        </cellValueFactory>
     </TableColumn>
     <TableColumn text="姓氏" prefWidth="100">
        <cellValueFactory><PropertyValueFactory property="lastName" />
        </cellValueFactory>
     </TableColumn>
     <TableColumn text="电子邮件地址" prefWidth="200">
        <cellValueFactory><PropertyValueFactory property="email" />
        </cellValueFactory>
     </TableColumn>

结果如图3-6所示。列宽已增加,以便在每个表格行中显示所有数据。

图3-6 设置列宽的表格

图3-6的描述
"图3-6 设置列宽的表格"的描述

设置表格单元格的对齐方式

另一种自定义方式是设置表格单元格中数据的对齐方式。您可以在一个名为FormattedTableCellFactory的新类中实现逻辑,然后在FXML代码中的<TableColumn>标记中设置对齐方式。

  1. 在NetBeans IDE中,右键单击Source Packages下的fxmltableview文件夹,选择新建然后选择Java类

  2. 将类命名为FormattedTableCellFactory,然后点击完成

  3. 通过实现Callback类并创建TextAlignmentFormat类的实例,修改FormattedTableCellFactory类,如示例3-11所示。其中S参数是TableView泛型类型的类型,T参数是该表格列中所有单元格内容的类型。

    示例3-11 Callback类

    public class FormattedTableCellFactory<S, T> 
        implements Callback<TableColumn<S, T>, TableCell<S, T>> {
        private TextAlignment alignment;
        private Format format;
     
        public TextAlignment getAlignment() {
            return alignment;
        }
     
        public void setAlignment(TextAlignment alignment) {
            this.alignment = alignment;
        }
     
        public Format getFormat() {
            return format;
        }
     
        public void setFormat(Format format) {
            this.format = format;
        }
    
  4. 通过追加示例3-12中的代码,实现TableCellTableColumn类。该代码重写了TableCell类的updateItem方法,并在表格单元格上调用setTextAlignment方法。

    示例3-12 TableCell和TableColumn类

    @Override
        @SuppressWarnings("unchecked")
        public TableCell<S, T> call(TableColumn<S, T> p) {
            TableCell<S, T> cell = new TableCell<S, T>() {
                @Override
                public void updateItem(Object item, boolean empty) {
                    if (item == getItem()) {
                        return;
                    }
                    super.updateItem((T) item, empty);
                    if (item == null) {
                        super.setText(null);
                        super.setGraphic(null);
                    } else if (format != null) {
                        super.setText(format.format(item));
                    } else if (item instanceof Node) {
                        super.setText(null);
                        super.setGraphic((Node) item);
                    } else {
                        super.setText(item.toString());
                        super.setGraphic(null);
                    }
                }
            };
            cell.setTextAlignment(alignment);
            switch (alignment) {
                case CENTER:
                    cell.setAlignment(Pos.CENTER);
                    break;
                case RIGHT:
                    cell.setAlignment(Pos.CENTER_RIGHT);
                    break;
                default:
                    cell.setAlignment(Pos.CENTER_LEFT);
                    break;
            }
            return cell;
        }
    }
    
  5. 修正导入语句。

  6. fxml_tableview.fxml文件中,在<cellValueFactory>标记下添加以下代码,为“First Name”列提供居中对齐,如示例3-13所示。

    示例3-13 数据单元格的对齐方式

    <TableColumn fx:id="firstNameColumn" text="First Name" prefWidth="100">
         <cellValueFactory><PropertyValueFactory property="firstName" />
         </cellValueFactory>
         <cellFactory>
              <FormattedTableCellFactory alignment="center">
              </FormattedTableCellFactory>
         </cellFactory>
    </TableColumn>
    

    您可以使用左对齐、右对齐或居中对齐的方式为其他列创建对齐方式。

现在运行应用程序会导致数据在“名字”列中居中对齐,如图3-7所示。

图3-7 数据在“名字”列中居中对齐

图3-7的描述
“图3-7 数据在“名字”列中居中对齐”的描述

向表格中添加行

您可以添加用户向表格中添加数据行的功能。在FXMLTableViewController类中添加应用程序逻辑。然后,修改用户界面以包含三个文本字段和一个用于输入数据的按钮。

  1. 打开FXMLTableViewController.java文件。

  2. 编辑FXMLTableViewController类,使其看起来像示例3-14中的代码。

    示例3-14 FXMLTableViewController.java

    public class FXMLTableViewController {
        @FXML private TableView<Person> tableView;
        @FXML private TextField firstNameField;
        @FXML private TextField lastNameField;
        @FXML private TextField emailField;
        
        @FXML
        protected void addPerson(ActionEvent event) {
            ObservableList<Person> data = tableView.getItems();
            data.add(new Person(firstNameField.getText(),
                lastNameField.getText(),
                emailField.getText()
            ));
            
            firstNameField.setText("");
            lastNameField.setText("");
            emailField.setText("");   
        }
    }
    
  3. 更正导入语句,如示例3-15所示。

    示例3-15 FXMLTableViewController中的导入语句

    import javafx.collections.ObservableList;
    import javafx.event.ActionEvent;
    import javafx.fxml.FXML;
    import javafx.scene.control.TableView;
    import javafx.scene.control.TextField;
    
  4. fxml_tableview.fxml文件中,在</GridPane>标记之前添加以下代码,如示例3-16所示。

    示例3-16 用于添加行的文本字段和按钮

    </TableView>
         <HBox spacing="10" alignment="bottom_right" GridPane.columnIndex="0" 
              GridPane.rowIndex="2">
              <TextField fx:id="firstNameField" promptText="名字"
                   prefWidth="90"/>
              <TextField fx:id="lastNameField" promptText="姓氏"
                   prefWidth="90"/>
              <TextField fx:id="emailField" promptText="电子邮件"
                   prefWidth="150"/>
              <Button text="添加" onAction="#addPerson"/>
         </HBox>
    </GridPane>
    

运行应用程序,你会看到文本字段和按钮出现在表格下方,如图3-8所示。在文本字段中输入数据,然后点击添加按钮,查看应用程序的效果。

下载FXMLTableView.zip文件,查看FXMLTableView应用程序的完整源代码。

图3-8 带有文本字段和添加数据按钮的表格

图3-8的描述
"图3-8 带有文本字段和添加数据按钮的表格"的描述

从这里开始

这就结束了地址簿教程,但是你可以尝试以下一些事情:

关闭窗口

目录

JavaFX: FXML入门

展开 折叠