在这一章,你将学习如:添加一个表格表、数据填充、编辑表格行等格组件 JavaFx的基本操作。
很多JavaFX SDK API种的类为在表格表单中呈现数据。在JavaFX 应用中对创建表格最重要的是TableView
, TableColumn
和TableCell这三个类。
你可以通过实现数据模型(data model) 和 实现 单元格工厂(cell factory) 来填充表格。
表格类提供了表格列嵌入式的排序能力和必要时调整列宽度的功能。
表格12-1展示了一个呈现地址簿信息内容的典型的表格
表格 12-1 表格示例
创建一个表格
例子12-1表格片段 创建了一个3列的空表格并添加到应用场景中
1. Example 12-1 Adding a Table 2. 3. import javafx.application.Application; 4. import javafx.geometry.Insets; 5. import javafx.scene.Group; 6. import javafx.scene.Scene; 7. import javafx.scene.control.Label; 8. import javafx.scene.control.TableColumn; 9. import javafx.scene.control.TableView; 10. import javafx.scene.layout.VBox; 11. import javafx.scene.text.Font; 12. import javafx.stage.Stage; 13. 14. public class TableViewSample extends Application { 15. 16. private TableView table = new TableView(); 17. public static void main(String[] args) { 18. launch(args); 19. } 20. 21. @Override 22. public void start(Stage stage) { 23. Scene scene = new Scene(new Group()); 24. stage.setTitle("Table View Sample"); 25. stage.setWidth(300); 26. stage.setHeight(500); 27. 28. final Label label = new Label("Address Book"); 29. label.setFont(new Font("Arial", 20)); 30. 31. table.setEditable(true); 32. 33. TableColumn firstNameCol = new TableColumn("First Name"); 34. TableColumn lastNameCol = new TableColumn("Last Name"); 35. TableColumn emailCol = new TableColumn("Email"); 36. 37. table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 38. 39. final VBox vbox = new VBox(); 40. vbox.setSpacing(5); 41. vbox.setPadding(new Insets(10, 0, 0, 10)); 42. vbox.getChildren().addAll(label, table); 43. 44. ((Group) scene.getRoot()).getChildren().addAll(vbox); 45. 46. stage.setScene(scene); 47. stage.show(); 48. } 49. }
此表格组件 通过实例化TableView类来创建。
在例子 12-1中,
表格组件被添加到 VBox
的布局容器中,然而你也可以直接将其添加到应用场景中。
例子 12-1定义了3列将用来存放地址簿的信息:一个联系人的 姓和名以及电子邮箱地址。列通过TableColumn这个类创建。
TableView
的getColumns方法可以获取之前创建过的列。在你的应用中,你可以用此方法动态的添加和移除表格列。
编译并运行此程序将获取输入输出,如图12-2所示:
你可以通过setVisible
方法来控制列是否显示。
如:如果你的应用逻辑需要隐藏电子邮件地址,不可以这样做:emailCol.setVisible(false)
.
如果你的数据需要更加复杂的呈现方式,你可以创建嵌套列。
假设地址簿中的联系方式有两个电子邮箱账户。你需要两列来分别呈现第一个和第二个电子邮箱地址。
像例12-2中展示的一样,创建两个子列,然后调用emailCol
的getColumns方法
1. Example 12-2 Creating Nested Columns 2. 3. TableColumn firstEmailCol = new TableColumn("Primary"); 4. TableColumn secondEmailCol = new TableColumn("Secondary"); 5. 6. emailCol.getColumns().addAll(firstEmailCol, secondEmailCol);
在例 12-1的代码里面添加上述代码然后编译并运行, 此表格将呈现图 12-3中的样子.
图12-3 带有嵌套列的表格
尽管表格已经添加到应用中,但是因为表格中没有数据,标准的标题“No content in table”(表格内容为空)将呈现在表格中。
如果不想显示上述标题,你可以使用setPlaceholder 方法类制定 一个 Node(节点)对象呈现在空表格中。
定义数据模型( Data Model)
当你要在JavaFx应用中创建一个表格,最好先创建一个类来定义数据模型和提供将来和表格交互的方法和属性。例12-3中定义了Person类来定义数据和地址簿。
表格12-3 创建 Person 类
1. public static class Person { 2. private final SimpleStringProperty firstName; 3. private final SimpleStringProperty lastName; 4. private final SimpleStringProperty email; 5. 6. private Person(String fName, String lName, String email) { 7. this.firstName = new SimpleStringProperty(fName); 8. this.lastName = new SimpleStringProperty(lName); 9. this.email = new SimpleStringProperty(email); 10. } 11. 12. public String getFirstName() { 13. return firstName.get(); 14. } 15. public void setFirstName(String fName) { 16. firstName.set(fName); 17. } 18. 19. public String getLastName() { 20. return lastName.get(); 21. } 22. public void setLastName(String fName) { 23. lastName.set(fName); 24. } 25. 26. public String getEmail() { 27. return email.get(); 28. } 29. public void setEmail(String fName) { 30. email.set(fName); 31. } 32. 33. }
firstName
, lastName
, 和 email这些字符串属性用来提供特殊数据元素的引用。
另外,每个数据元素都提供了get set方法。 这样如果调用 getFirstName方法经返回firstName属性的值,可以通过调用setFirstName方法来为这个属性赋值
。
在数据模型已经在Person
类中呈现以后。你可以创建ObservableList
数组随心所欲的定义数据行(data rows) 在你的表格中展示
例12-4 的代码片段实现了这个任务:
Example 12-4 Defining Table Data in an Observable List
1. final ObservableList<Person> data = FXCollections.observableArrayList( 2. new Person("Jacob", "Smith", "jacob.smith@example.com"), 3. new Person("Isabella", "Johnson", "isabella.johnson@example.com"), 4. new Person("Ethan", "Williams", "ethan.williams@example.com"), 5. new Person("Emma", "Jones", "emma.jones@example.com"), 6. new Person("Michael", "Brown", "michael.brown@example.com") 7. );
.
下一步就是将这些数据和表格的列之间建立联系。你可以像例12-5中那样通过对每个数据元素的属性定义来实现。
例12-5 为列创建数据属性
1. firstNameCol.setCellValueFactory( 2. new PropertyValueFactory<Person,String>("firstName") 3. ); 4. lastNameCol.setCellValueFactory( 5. new PropertyValueFactory<Person,String>("lastName") 6. ); 7. emailCol.setCellValueFactory( 8. new PropertyValueFactory<Person,String>("email") 9. );
setCellValueFactory
方法为每个列定制单元工厂。单元工程通过使用PropertyValueFactory类来实现, 表格列的firstName
, lastName
和
email
属性来引用Person中相应的属性的 。
当数据模型已经定义完毕,数据已经添加并关联到对应的列,你还可以通过TableView 的setItems
方法来添加表格数据::table.setItems(data)
.
因为ObservableList
对象能够跟踪表格元素的任何变化,当其中的数据变化,TableView
的内容也自动更新。
验证例12-6应用的代码:
1. //例 12-6 创建一个表格并为其添加数据 2. 3. import javafx.application.Application; 4. import javafx.beans.property.SimpleStringProperty; 5. import javafx.collections.FXCollections; 6. import javafx.collections.ObservableList; 7. import javafx.geometry.Insets; 8. import javafx.scene.Group; 9. import javafx.scene.Scene; 10. import javafx.scene.control.Label; 11. import javafx.scene.control.TableColumn; 12. import javafx.scene.control.TableView; 13. import javafx.scene.control.TextField; 14. import javafx.scene.control.cell.PropertyValueFactory; 15. import javafx.scene.layout.VBox; 16. import javafx.scene.text.Font; 17. import javafx.stage.Stage; 18. 19. public class TableViewSample extends Application { 20. 21. private TableView<Person> table = new TableView<Person>(); 22. private final ObservableList<Person> data = 23. FXCollections.observableArrayList( 24. new Person("Jacob", "Smith", "jacob.smith@example.com"), 25. new Person("Isabella", "Johnson", "isabella.johnson@example.com"), 26. new Person("Ethan", "Williams", "ethan.williams@example.com"), 27. new Person("Emma", "Jones", "emma.jones@example.com"), 28. new Person("Michael", "Brown", "michael.brown@example.com") 29. ); 30. 31. public static void main(String[] args) { 32. launch(args); 33. } 34. 35. @Override 36. public void start(Stage stage) { 37. Scene scene = new Scene(new Group()); 38. stage.setTitle("Table View Sample"); 39. stage.setWidth(450); 40. stage.setHeight(500); 41. 42. final Label label = new Label("Address Book"); 43. label.setFont(new Font("Arial", 20)); 44. 45. table.setEditable(true); 46. 47. TableColumn firstNameCol = new TableColumn("First Name"); 48. firstNameCol.setMinWidth(100); 49. firstNameCol.setCellValueFactory( 50. new PropertyValueFactory<Person, String>("firstName")); 51. 52. TableColumn lastNameCol = new TableColumn("Last Name"); 53. lastNameCol.setMinWidth(100); 54. lastNameCol.setCellValueFactory( 55. new PropertyValueFactory<Person, String>("lastName")); 56. 57. TableColumn emailCol = new TableColumn("Email"); 58. emailCol.setMinWidth(200); 59. emailCol.setCellValueFactory( 60. new PropertyValueFactory<Person, String>("email")); 61. 62. table.setItems(data); 63. table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 64. 65. final VBox vbox = new VBox(); 66. vbox.setSpacing(5); 67. vbox.setPadding(new Insets(10, 0, 0, 10)); 68. vbox.getChildren().addAll(label, table); 69. 70. ((Group) scene.getRoot()).getChildren().addAll(vbox); 71. 72. stage.setScene(scene); 73. stage.show(); 74. } 75. 76. public static class Person { 77. 78. private final SimpleStringProperty firstName; 79. private final SimpleStringProperty lastName; 80. private final SimpleStringProperty email; 81. 82. private Person(String fName, String lName, String email) { 83. this.firstName = new SimpleStringProperty(fName); 84. this.lastName = new SimpleStringProperty(lName); 85. this.email = new SimpleStringProperty(email); 86. } 87. 88. public String getFirstName() { 89. return firstName.get(); 90. } 91. 92. public void setFirstName(String fName) { 93. firstName.set(fName); 94. } 95. 96. public String getLastName() { 97. return lastName.get(); 98. } 99. 100. public void setLastName(String fName) { 101. lastName.set(fName); 102. } 103. 104. public String getEmail() { 105. return email.get(); 106. } 107. 108. public void setEmail(String fName) { 109. email.set(fName); 110. } 111. } 112. }
当你编译并运行此代码将呈现图:12-4的样子.
图 12-4 表格数据填充
添加新行
图标12-4中的表格包好5行数据,目前为止还不能编辑。
你可以用 文本域 为Last Name, and Email columns键入新值 Text Field 组件能够使你的应用接收到用户输入的文本。例12-7 创建3个文本域。并为每个文本域定义提示并创建添加按钮。
例 12-7利用文本域为表格创建新元素
1. final TextField addFirstName = new TextField(); 2. addFirstName.setPromptText("First Name"); 3. addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); 4. final TextField addLastName = new TextField(); 5. addLastName.setMaxWidth(lastNameCol.getPrefWidth()); 6. addLastName.setPromptText("Last Name"); 7. final TextField addEmail = new TextField(); 8. addEmail.setMaxWidth(emailCol.getPrefWidth()); 9. addEmail.setPromptText("Email"); 10. 11. final Button addButton = new Button("Add"); 12. addButton.setOnAction(new EventHandler<ActionEvent>() { 13. @Override public void handle(ActionEvent e) { 14. data.add(new Person( 15. addFirstName.getText(), 16. addLastName.getText(), 17. addEmail.getText() 18. )); 19. addFirstName.clear(); 20. addLastName.clear(); 21. addEmail.clear(); 22. } 23. });
当用户点击添加按钮,在文本域输入的文本将被添加到Person 的构造方法中,并添加到 data (observable list)中。因此带有内容信息的实体出现在表格中。
验证例12-8的代码。
例12-8 用文本域添加条目的表格
1. import javafx.application.Application; 2. import javafx.beans.property.SimpleStringProperty; 3. import javafx.collections.FXCollections; 4. import javafx.collections.ObservableList; 5. import javafx.event.ActionEvent; 6. import javafx.event.EventHandler; 7. import javafx.geometry.Insets; 8. import javafx.scene.Group; 9. import javafx.scene.Scene; 10. import javafx.scene.control.Button; 11. import javafx.scene.control.Label; 12. import javafx.scene.control.TableColumn; 13. import javafx.scene.control.TableView; 14. import javafx.scene.control.TextField; 15. import javafx.scene.control.cell.PropertyValueFactory; 16. import javafx.scene.layout.HBox; 17. import javafx.scene.layout.VBox; 18. import javafx.scene.text.Font; 19. import javafx.stage.Stage; 20. 21. public class FileChooserSample extends Application { 22. 23. private TableView<Person> table = new TableView<Person>(); 24. private final ObservableList<Person> data = 25. FXCollections.observableArrayList( 26. new Person("Jacob", "Smith", "jacob.smith@example.com"), 27. new Person("Isabella", "Johnson", "isabella.johnson@example.com"), 28. new Person("Ethan", "Williams", "ethan.williams@example.com"), 29. new Person("Emma", "Jones", "emma.jones@example.com"), 30. new Person("Michael", "Brown", "michael.brown@example.com")); 31. final HBox hb = new HBox(); 32. 33. public static void main(String[] args) { 34. launch(args); 35. } 36. 37. @Override 38. public void start(Stage stage) { 39. Scene scene = new Scene(new Group()); 40. stage.setTitle("Table View Sample"); 41. stage.setWidth(450); 42. stage.setHeight(550); 43. 44. final Label label = new Label("Address Book"); 45. label.setFont(new Font("Arial", 20)); 46. 47. table.setEditable(true); 48. 49. TableColumn firstNameCol = new TableColumn("First Name"); 50. firstNameCol.setMinWidth(100); 51. firstNameCol.setCellValueFactory( 52. new PropertyValueFactory<Person, String>("firstName")); 53. 54. TableColumn lastNameCol = new TableColumn("Last Name"); 55. lastNameCol.setMinWidth(100); 56. lastNameCol.setCellValueFactory( 57. new PropertyValueFactory<Person, String>("lastName")); 58. 59. TableColumn emailCol = new TableColumn("Email"); 60. emailCol.setMinWidth(200); 61. emailCol.setCellValueFactory( 62. new PropertyValueFactory<Person, String>("email")); 63. 64. table.setItems(data); 65. table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 66. 67. final TextField addFirstName = new TextField(); 68. addFirstName.setPromptText("First Name"); 69. addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); 70. final TextField addLastName = new TextField(); 71. addLastName.setMaxWidth(lastNameCol.getPrefWidth()); 72. addLastName.setPromptText("Last Name"); 73. final TextField addEmail = new TextField(); 74. addEmail.setMaxWidth(emailCol.getPrefWidth()); 75. addEmail.setPromptText("Email"); 76. 77. final Button addButton = new Button("Add"); 78. addButton.setOnAction(new EventHandler<ActionEvent>() { 79. @Override 80. public void handle(ActionEvent e) { 81. data.add(new Person( 82. addFirstName.getText(), 83. addLastName.getText(), 84. addEmail.getText())); 85. addFirstName.clear(); 86. addLastName.clear(); 87. addEmail.clear(); 88. } 89. }); 90. 91. hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton); 92. hb.setSpacing(3); 93. 94. final VBox vbox = new VBox(); 95. vbox.setSpacing(5); 96. vbox.setPadding(new Insets(10, 0, 0, 10)); 97. vbox.getChildren().addAll(label, table, hb); 98. 99. ((Group) scene.getRoot()).getChildren().addAll(vbox); 100. 101. stage.setScene(scene); 102. stage.show(); 103. } 104. 105. public static class Person { 106. 107. private final SimpleStringProperty firstName; 108. private final SimpleStringProperty lastName; 109. private final SimpleStringProperty email; 110. 111. private Person(String fName, String lName, String email) { 112. this.firstName = new SimpleStringProperty(fName); 113. this.lastName = new SimpleStringProperty(lName); 114. this.email = new SimpleStringProperty(email); 115. } 116. 117. public String getFirstName() { 118. return firstName.get(); 119. } 120. 121. public void setFirstName(String fName) { 122. firstName.set(fName); 123. } 124. 125. public String getLastName() { 126. return lastName.get(); 127. } 128. 129. public void setLastName(String fName) { 130. lastName.set(fName); 131. } 132. 133. public String getEmail() { 134. return email.get(); 135. } 136. 137. public void setEmail(String fName) { 138. email.set(fName); 139. } 140. } 141. }
此应用没有提供任何校验的过滤器,比如校验电子邮件格式是否正确。当你开发自己的应用时可以添加这些方法。
当前的应用也没有检查是否键入了空值,如果没有提供任何值,点击添加按钮将在表格中键入一个空行。
表格12-5 举例说明用户怎样输入了空行
图 12-5 往地址簿添加内容
图 12-6 显示点击按钮后表格的信息。Emma White 的详细联系方式出现在了表格中。
图 12-6新添加的实体
列数据排序
TableView类提供了列中数据的排序。用户可以通过点击列头来对数据进行排序。第一次点击将进行升序排列,第二次点击将进行降序排列。第三次点击不排列。默认是不排列。
用户可以对表格的多列进行排序,同样也可以指定每列数据在排序操作中的优先级。如果想多行排列,用户按住Shift的同时点击想要排序的每一列的列头。
图12-7中, first names升序排列, last names降序排列.记住第一列比第二列的优先级更高。
图 12-7 多列排序
作为应用的开发人员,你可以通过setSortType方法设置每一列的排序优先级。你可以分别指定升序和降序的排列规则,例如,用下面的代码来设置emailCol 降序的排序。列:emailCol.setSortType(TableColumn.SortType.DESCENDING);
.
你可以指定哪一行排序 通过添加和移除
来制定哪些列排序。TableView.sortOrder
observable list. TableColumn实例
列的顺序代表排序的优先级(例如,0条比第1条更高)。
如果想禁用排序 调用 列的setSortable(false)
方法即可。
表格的数据编辑
TableView类不仅能够渲染表格式的数据,还能提供编辑的能力。使用 setEditable
方法来开启表格编辑模式。
用 setCellFactory
方法,借助TextFieldTableCell的帮助来
重新实现表格单元格作为文本域。
setOnEditCommit
方法具有编辑 指派更新数据到相应表格单元格的能力。
例12-9 显示怎样用这些方法来 编辑 First Name, Last Name, and Email列。
例 12-9单元格编辑的实现
1. firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn()); 2. firstNameCol.setOnEditCommit( 3. new EventHandler<CellEditEvent<Person, String>>() { 4. @Override 5. public void handle(CellEditEvent<Person, String> t) { 6. ((Person) t.getTableView().getItems().get( 7. t.getTablePosition().getRow()) 8. ).setFirstName(t.getNewValue()); 9. } 10. } 11. ); 12. 13. lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn()); 14. lastNameCol.setOnEditCommit( 15. new EventHandler<CellEditEvent<Person, String>>() { 16. @Override 17. public void handle(CellEditEvent<Person, String> t) { 18. ((Person) t.getTableView().getItems().get( 19. t.getTablePosition().getRow()) 20. ).setLastName(t.getNewValue()); 21. } 22. } 23. ); 24. 25. emailCol.setCellFactory(TextFieldTableCell.forTableColumn()); 26. emailCol.setOnEditCommit( 27. new EventHandler<CellEditEvent<Person, String>>() { 28. @Override 29. public void handle(CellEditEvent<Person, String> t) { 30. ((Person) t.getTableView().getItems().get( 31. t.getTablePosition().getRow()) 32. ).setEmail(t.getNewValue()); 33. } 34. } 35. );
例 12-10将展示完整的代码
例 12-10 允许单元格编辑的表格示例
1. <pre name="code" class="java">import javafx.application.Application; 2. import javafx.beans.property.SimpleStringProperty; 3. import javafx.collections.FXCollections; 4. import javafx.collections.ObservableList; 5. import javafx.event.ActionEvent; 6. import javafx.event.EventHandler; 7. import javafx.geometry.Insets; 8. import javafx.scene.Group; 9. import javafx.scene.Scene; 10. import javafx.scene.control.Button; 11. import javafx.scene.control.Label; 12. import javafx.scene.control.TableColumn; 13. import javafx.scene.control.TableColumn.CellEditEvent; 14. import javafx.scene.control.TableView; 15. import javafx.scene.control.TextField; 16. import javafx.scene.control.cell.PropertyValueFactory; 17. import javafx.scene.control.cell.TextFieldTableCell; 18. import javafx.scene.layout.HBox; 19. import javafx.scene.layout.VBox; 20. import javafx.scene.text.Font; 21. import javafx.stage.Stage; 22. 23. public class TableViewSample extends Application { 24. 25. private TableView<Person> table = new TableView<Person>(); 26. private final ObservableList<Person> data = 27. FXCollections.observableArrayList( 28. new Person("Jacob", "Smith", "jacob.smith@example.com"), 29. new Person("Isabella", "Johnson", "isabella.johnson@example.com"), 30. new Person("Ethan", "Williams", "ethan.williams@example.com"), 31. new Person("Emma", "Jones", "emma.jones@example.com"), 32. new Person("Michael", "Brown", "michael.brown@example.com")); 33. final HBox hb = new HBox(); 34. 35. public static void main(String[] args) { 36. launch(args); 37. } 38. 39. @Override 40. public void start(Stage stage) { 41. Scene scene = new Scene(new Group()); 42. stage.setTitle("Table View Sample"); 43. stage.setWidth(450); 44. stage.setHeight(550); 45. 46. final Label label = new Label("Address Book"); 47. label.setFont(new Font("Arial", 20)); 48. 49. table.setEditable(true); 50. 51. TableColumn firstNameCol = new TableColumn("First Name"); 52. firstNameCol.setMinWidth(100); 53. firstNameCol.setCellValueFactory( 54. new PropertyValueFactory<Person, String>("firstName")); 55. firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn()); 56. firstNameCol.setOnEditCommit( 57. new EventHandler<CellEditEvent<Person, String>>() { 58. @Override 59. public void handle(CellEditEvent<Person, String> t) { 60. ((Person) t.getTableView().getItems().get( 61. t.getTablePosition().getRow()) 62. ).setFirstName(t.getNewValue()); 63. } 64. } 65. ); 66. 67. 68. TableColumn lastNameCol = new TableColumn("Last Name"); 69. lastNameCol.setMinWidth(100); 70. lastNameCol.setCellValueFactory( 71. new PropertyValueFactory<Person, String>("lastName")); 72. lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn()); 73. lastNameCol.setOnEditCommit( 74. new EventHandler<CellEditEvent<Person, String>>() { 75. @Override 76. public void handle(CellEditEvent<Person, String> t) { 77. ((Person) t.getTableView().getItems().get( 78. t.getTablePosition().getRow()) 79. ).setLastName(t.getNewValue()); 80. } 81. } 82. ); 83. 84. TableColumn emailCol = new TableColumn("Email"); 85. emailCol.setMinWidth(200); 86. emailCol.setCellValueFactory( 87. new PropertyValueFactory<Person, String>("email")); 88. emailCol.setCellFactory(TextFieldTableCell.forTableColumn()); 89. emailCol.setOnEditCommit( 90. new EventHandler<CellEditEvent<Person, String>>() { 91. @Override 92. public void handle(CellEditEvent<Person, String> t) { 93. ((Person) t.getTableView().getItems().get( 94. t.getTablePosition().getRow()) 95. ).setEmail(t.getNewValue()); 96. } 97. } 98. ); 99. 100. table.setItems(data); 101. table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 102. 103. final TextField addFirstName = new TextField(); 104. addFirstName.setPromptText("First Name"); 105. addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); 106. final TextField addLastName = new TextField(); 107. addLastName.setMaxWidth(lastNameCol.getPrefWidth()); 108. addLastName.setPromptText("Last Name"); 109. final TextField addEmail = new TextField(); 110. addEmail.setMaxWidth(emailCol.getPrefWidth()); 111. addEmail.setPromptText("Email"); 112. 113. final Button addButton = new Button("Add"); 114. addButton.setOnAction(new EventHandler<ActionEvent>() { 115. @Override 116. public void handle(ActionEvent e) { 117. data.add(new Person( 118. addFirstName.getText(), 119. addLastName.getText(), 120. addEmail.getText())); 121. addFirstName.clear(); 122. addLastName.clear(); 123. addEmail.clear(); 124. } 125. }); 126. 127. hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton); 128. hb.setSpacing(3); 129. 130. final VBox vbox = new VBox(); 131. vbox.setSpacing(5); 132. vbox.setPadding(new Insets(10, 0, 0, 10)); 133. vbox.getChildren().addAll(label, table, hb); 134. 135. ((Group) scene.getRoot()).getChildren().addAll(vbox); 136. 137. stage.setScene(scene); 138. stage.show(); 139. } 140. 141. public static class Person { 142. 143. private final SimpleStringProperty firstName; 144. private final SimpleStringProperty lastName; 145. private final SimpleStringProperty email; 146. 147. private Person(String fName, String lName, String email) { 148. this.firstName = new SimpleStringProperty(fName); 149. this.lastName = new SimpleStringProperty(lName); 150. this.email = new SimpleStringProperty(email); 151. } 152. 153. public String getFirstName() { 154. return firstName.get(); 155. } 156. 157. public void setFirstName(String fName) { 158. firstName.set(fName); 159. } 160. 161. public String getLastName() { 162. return lastName.get(); 163. } 164. 165. public void setLastName(String fName) { 166. lastName.set(fName); 167. } 168. 169. public String getEmail() { 170. return email.get(); 171. } 172. 173. public void setEmail(String fName) { 174. email.set(fName); 175. } 176. } 177. }
在图 12-8中, 用户正在编辑 Michael Brown的姓氏。用户键入在单元格中键入了新的值,然后暗下来 Enter键。只有按下了Enter键,单元格编辑才算结束。这一行为取决于TextField的实现。
表 12-8编辑表格的单元格
请记住:默认的TextField
实现,需要用户按下Enter键来提交编辑。你可以重新定义TextField的行为来通过焦点变化提交编辑,这是一个好的用户体验。尝试修改代码来实现这个替代的行为。
例 12-11 单元格编辑的替换方案
1. import javafx.application.Application; 2. import javafx.beans.property.SimpleStringProperty; 3. import javafx.beans.value.ChangeListener; 4. import javafx.beans.value.ObservableValue; 5. import javafx.collections.FXCollections; 6. import javafx.collections.ObservableList; 7. import javafx.event.ActionEvent; 8. import javafx.event.EventHandler; 9. import javafx.geometry.Insets; 10. import javafx.scene.Group; 11. import javafx.scene.Scene; 12. import javafx.scene.control.Button; 13. import javafx.scene.control.Label; 14. import javafx.scene.control.TableCell; 15. import javafx.scene.control.TableColumn; 16. import javafx.scene.control.TableColumn.CellEditEvent; 17. import javafx.scene.control.TableView; 18. import javafx.scene.control.TextField; 19. import javafx.scene.control.cell.PropertyValueFactory; 20. import javafx.scene.layout.HBox; 21. import javafx.scene.layout.VBox; 22. import javafx.scene.text.Font; 23. import javafx.stage.Stage; 24. import javafx.util.Callback; 25. 26. public class TableViewSample extends Application { 27. 28. private TableView<Person> table = new TableView<Person>(); 29. private final ObservableList<Person> data = 30. FXCollections.observableArrayList( 31. new Person("Jacob", "Smith", "jacob.smith@example.com"), 32. new Person("Isabella", "Johnson", "isabella.johnson@example.com"), 33. new Person("Ethan", "Williams", "ethan.williams@example.com"), 34. new Person("Emma", "Jones", "emma.jones@example.com"), 35. new Person("Michael", "Brown", "michael.brown@example.com")); 36. final HBox hb = new HBox(); 37. 38. public static void main(String[] args) { 39. launch(args); 40. } 41. 42. @Override 43. public void start(Stage stage) { 44. Scene scene = new Scene(new Group()); 45. stage.setTitle("Table View Sample"); 46. stage.setWidth(450); 47. stage.setHeight(550); 48. 49. final Label label = new Label("Address Book"); 50. label.setFont(new Font("Arial", 20)); 51. 52. table.setEditable(true); 53. Callback<TableColumn, TableCell> cellFactory = 54. new Callback<TableColumn, TableCell>() { 55. public TableCell call(TableColumn p) { 56. return new EditingCell(); 57. } 58. }; 59. 60. TableColumn firstNameCol = new TableColumn("First Name"); 61. firstNameCol.setMinWidth(100); 62. firstNameCol.setCellValueFactory( 63. new PropertyValueFactory<Person, String>("firstName")); 64. firstNameCol.setCellFactory(cellFactory); 65. firstNameCol.setOnEditCommit( 66. new EventHandler<CellEditEvent<Person, String>>() { 67. @Override 68. public void handle(CellEditEvent<Person, String> t) { 69. ((Person) t.getTableView().getItems().get( 70. t.getTablePosition().getRow()) 71. ).setFirstName(t.getNewValue()); 72. } 73. } 74. ); 75. 76. 77. TableColumn lastNameCol = new TableColumn("Last Name"); 78. lastNameCol.setMinWidth(100); 79. lastNameCol.setCellValueFactory( 80. new PropertyValueFactory<Person, String>("lastName")); 81. lastNameCol.setCellFactory(cellFactory); 82. lastNameCol.setOnEditCommit( 83. new EventHandler<CellEditEvent<Person, String>>() { 84. @Override 85. public void handle(CellEditEvent<Person, String> t) { 86. ((Person) t.getTableView().getItems().get( 87. t.getTablePosition().getRow()) 88. ).setLastName(t.getNewValue()); 89. } 90. } 91. ); 92. 93. TableColumn emailCol = new TableColumn("Email"); 94. emailCol.setMinWidth(200); 95. emailCol.setCellValueFactory( 96. new PropertyValueFactory<Person, String>("email")); 97. emailCol.setCellFactory(cellFactory); 98. emailCol.setOnEditCommit( 99. new EventHandler<CellEditEvent<Person, String>>() { 100. @Override 101. public void handle(CellEditEvent<Person, String> t) { 102. ((Person) t.getTableView().getItems().get( 103. t.getTablePosition().getRow()) 104. ).setEmail(t.getNewValue()); 105. } 106. } 107. ); 108. 109. table.setItems(data); 110. table.getColumns().addAll(firstNameCol, lastNameCol, emailCol); 111. 112. final TextField addFirstName = new TextField(); 113. addFirstName.setPromptText("First Name"); 114. addFirstName.setMaxWidth(firstNameCol.getPrefWidth()); 115. final TextField addLastName = new TextField(); 116. addLastName.setMaxWidth(lastNameCol.getPrefWidth()); 117. addLastName.setPromptText("Last Name"); 118. final TextField addEmail = new TextField(); 119. addEmail.setMaxWidth(emailCol.getPrefWidth()); 120. addEmail.setPromptText("Email"); 121. 122. final Button addButton = new Button("Add"); 123. addButton.setOnAction(new EventHandler<ActionEvent>() { 124. @Override 125. public void handle(ActionEvent e) { 126. data.add(new Person( 127. addFirstName.getText(), 128. addLastName.getText(), 129. addEmail.getText())); 130. addFirstName.clear(); 131. addLastName.clear(); 132. addEmail.clear(); 133. } 134. }); 135. 136. hb.getChildren().addAll(addFirstName, addLastName, addEmail, addButton); 137. hb.setSpacing(3); 138. 139. final VBox vbox = new VBox(); 140. vbox.setSpacing(5); 141. vbox.setPadding(new Insets(10, 0, 0, 10)); 142. vbox.getChildren().addAll(label, table, hb); 143. 144. ((Group) scene.getRoot()).getChildren().addAll(vbox); 145. 146. stage.setScene(scene); 147. stage.show(); 148. } 149. 150. public static class Person { 151. 152. private final SimpleStringProperty firstName; 153. private final SimpleStringProperty lastName; 154. private final SimpleStringProperty email; 155. 156. private Person(String fName, String lName, String email) { 157. this.firstName = new SimpleStringProperty(fName); 158. this.lastName = new SimpleStringProperty(lName); 159. this.email = new SimpleStringProperty(email); 160. } 161. 162. public String getFirstName() { 163. return firstName.get(); 164. } 165. 166. public void setFirstName(String fName) { 167. firstName.set(fName); 168. } 169. 170. public String getLastName() { 171. return lastName.get(); 172. } 173. 174. public void setLastName(String fName) { 175. lastName.set(fName); 176. } 177. 178. public String getEmail() { 179. return email.get(); 180. } 181. 182. public void setEmail(String fName) { 183. email.set(fName); 184. } 185. } 186. 187. class EditingCell extends TableCell<Person, String> { 188. 189. private TextField textField; 190. 191. public EditingCell() { 192. } 193. 194. @Override 195. public void startEdit() { 196. if (!isEmpty()) { 197. super.startEdit(); 198. createTextField(); 199. setText(null); 200. setGraphic(textField); 201. textField.selectAll(); 202. } 203. } 204. 205. @Override 206. public void cancelEdit() { 207. super.cancelEdit(); 208. 209. setText((String) getItem()); 210. setGraphic(null); 211. } 212. 213. @Override 214. public void updateItem(String item, boolean empty) { 215. super.updateItem(item, empty); 216. 217. if (empty) { 218. setText(null); 219. setGraphic(null); 220. } else { 221. if (isEditing()) { 222. if (textField != null) { 223. textField.setText(getString()); 224. } 225. setText(null); 226. setGraphic(textField); 227. } else { 228. setText(getString()); 229. setGraphic(null); 230. } 231. } 232. } 233. 234. private void createTextField() { 235. textField = new TextField(getString()); 236. textField.setMinWidth(this.getWidth() - this.getGraphicTextGap()* 2); 237. textField.focusedProperty().addListener(new ChangeListener<Boolean>(){ 238. @Override 239. public void changed(ObservableValue<? extends Boolean> arg0, 240. Boolean arg1, Boolean arg2) { 241. if (!arg2) { 242. commitEdit(textField.getText()); 243. } 244. } 245. }); 246. } 247. 248. private String getString() { 249. return getItem() == null ? "" : getItem().toString(); 250. } 251. }
记住这种处理方式可能会在未来的升级中变得多余,TextFieldTableCell 的实现对TextField的取代,提供了更好的而用户体验。
将Map数据添加到表格中
从JavaFX SDK 2.2开始,你可以往表格中添加Map类型的数据。用如 例 12-12 展示的 利用MapValueFactory展示 student IDs Map;
例 12-12 往表格中添加Map数据
1. import java.util.HashMap; 2. import java.util.Map; 3. import javafx.application.Application; 4. import javafx.collections.FXCollections; 5. import javafx.collections.ObservableList; 6. import javafx.geometry.Insets; 7. import javafx.scene.Group; 8. import javafx.scene.Scene; 9. import javafx.scene.control.Label; 10. import javafx.scene.control.TableCell; 11. import javafx.scene.control.TableColumn; 12. import javafx.scene.control.TableView; 13. import javafx.scene.control.cell.MapValueFactory; 14. import javafx.scene.control.cell.TextFieldTableCell; 15. import javafx.scene.layout.VBox; 16. import javafx.scene.text.Font; 17. import javafx.stage.Stage; 18. import javafx.util.Callback; 19. import javafx.util.StringConverter; 20. 21. public class TableViewSample extends Application { 22. 23. public static final String Column1MapKey = "A"; 24. public static final String Column2MapKey = "B"; 25. 26. public static void main(String[] args) { 27. launch(args); 28. } 29. 30. @Override 31. public void start(Stage stage) { 32. Scene scene = new Scene(new Group()); 33. stage.setTitle("Table View Sample"); 34. stage.setWidth(300); 35. stage.setHeight(500); 36. 37. final Label label = new Label("Student IDs"); 38. label.setFont(new Font("Arial", 20)); 39. 40. TableColumn<Map, String> firstDataColumn = new TableColumn<>("Class A"); 41. TableColumn<Map, String> secondDataColumn = new TableColumn<>("Class B"); 42. 43. firstDataColumn.setCellValueFactory(new MapValueFactory(Column1MapKey)); 44. firstDataColumn.setMinWidth(130); 45. secondDataColumn.setCellValueFactory(new MapValueFactory(Column2MapKey)); 46. secondDataColumn.setMinWidth(130); 47. 48. TableView table_view = new TableView<>(generateDataInMap()); 49. 50. table_view.setEditable(true); 51. table_view.getSelectionModel().setCellSelectionEnabled(true); 52. table_view.getColumns().setAll(firstDataColumn, secondDataColumn); 53. Callback<TableColumn<Map, String>, TableCell<Map, String>> 54. cellFactoryForMap = new Callback<TableColumn<Map, String>, 55. TableCell<Map, String>>() { 56. @Override 57. public TableCell call(TableColumn p) { 58. return new TextFieldTableCell(new StringConverter() { 59. @Override 60. public String toString(Object t) { 61. return t.toString(); 62. } 63. @Override 64. public Object fromString(String string) { 65. return string; 66. } 67. }); 68. } 69. }; 70. firstDataColumn.setCellFactory(cellFactoryForMap); 71. secondDataColumn.setCellFactory(cellFactoryForMap); 72. 73. final VBox vbox = new VBox(); 74. 75. vbox.setSpacing(5); 76. vbox.setPadding(new Insets(10, 0, 0, 10)); 77. vbox.getChildren().addAll(label, table_view); 78. 79. ((Group) scene.getRoot()).getChildren().addAll(vbox); 80. 81. stage.setScene(scene); 82. 83. stage.show(); 84. } 85. 86. private ObservableList<Map> generateDataInMap() { 87. int max = 10; 88. ObservableList<Map> allData = FXCollections.observableArrayList(); 89. for (int i = 1; i < max; i++) { 90. Map<String, String> dataRow = new HashMap<>(); 91. 92. String value1 = "A" + i; 93. String value2 = "B" + i; 94. 95. dataRow.put(Column1MapKey, value1); 96. dataRow.put(Column2MapKey, value2); 97. 98. allData.add(dataRow); 99. } 100. return allData; 101. } 102. }
MapValueFactory
类实现了Callback 接口,此接口为定义表格列的单元格工厂而设计的。在例12-12中,数据行 hash map(哈希map) 展现了TableView对象的一个单行。这个map有两个String(字符串)类型的键: Column1MapKey 和 Column2MapKey来映射第一和第二列对应的值。
表格列通过调用setCellValueFactory
来填充与指定键(key)相匹配的数据。以便第一列包含与”A“键(key)对应的值,第二列包含于key(键)”B“对应的值。
当你编译并运行此应用,将显示如12-9所显示的结果。
图 12-9 带 Map 数据的表格