DOC-03-13 表格视图(TableView)
在本章中,你将学习如何在JavaFX应用程序中使用表格,例如创建表格、用为表格填充数据、编辑表格行。
在JavaFX SDK API中有类被设计用于以表格形式展现数据的类。其中最重要的类是TableView、TableColumn和TableCell。你可以通过实现数据模型(data model)以及提供单元格工厂(cell factory)来来填充表格。
表格相关类内置提供了对表格列数据排序、自动调整列宽功能。
图13-1展现了一个典型的表格,它展示了一个地址簿中的联系人信息。
图13-1 表格样例
创建一个表格
下面的代码创建一个包含三列的空表格,并将其添加到应用程序场景(scene)中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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("Address Book"); label.setFont(new Font("Arial", 20)); table.setEditable(true); TableColumn firstNameCol = new TableColumn("First Name"); TableColumn lastNameCol = new TableColumn("Last Name"); TableColumn emailCol = new TableColumn("Email"); 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类来创建表格控件。在上例中将表格添加到了VBox布局中,然而你也可以直接将其添加到scene中。
在上例中添加了3列用于存储地址簿中的下列信息:联系人的姓、名和电子邮件。通过使用TableColumn类来创建列。
TableView的getColumns方法将前面创建的列添加到表格中。在你的应用程序中,你可以用它来动态添加和移除列。
上面的程序编译并运行后将会看到如下图所示的界面:
图13-2 空表格
你可以通过setVisible方法来管理列的可见性。例如需要隐藏电子邮件信息列,你可以通过emailCol.setVisible(false)来实现。
当你的数据结构需要更复杂的表现形式时,你还可以创建嵌套列。
例如,如果地址簿中需要两个电子邮件,那么你需要两列来分别表示主邮件地址和辅邮件地址。创建两个子列,然后仿照下面的例子来调用emailCol的getColumns方法:
1 2 3 |
TableColumn firstEmailCol = new TableColumn("Primary"); TableColumn secondEmailCol = new TableColumn("Secondary"); emailCol.getColumns().addAll(firstEmailCol, secondEmailCol); |
在前面的样例代码中添加这三句代码后,编译运行程序的结果将如图13-2:
图13-3 带有嵌套列的表格
尽管在应用程序中添加了表格,但是由于尚未定义数据,因此只能看到提示“表中无内容”的标准提示信息。如果需要替换此提示信息,你可以使用setPlaceholder方法指定一个Node对象来用于当表格为空时进行提示。
定义数据模型(Data Model)
当你在JavaFX应用程序中创建表格时,一个最佳实践是:创建一个类用于定义数据模型并且提供方法和属性。下面的样例创建了一个Person类用于定义地址簿中的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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); } } |
字符串属性firstName、lastName和email都分别对应着一个特定的数据元素。
另外,每个数据元素都提供了get和set方法,例如getFirstName方法将返回firstName属性值,而setFirstName方法将指定对应的属性值。
当使用Person类来说明数据模型之后,你可以通过创建ObservableList数组来定义希望在表格中展现的数据行。下面的样例代码将展示如何实现此任务:
1 2 3 4 5 6 7 |
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") ); |
下一步则是将数据关联到表格中的列。你可以通过为每个数据元素定义属性来完成此操作,样例代码如下:
1 2 3 4 5 6 7 8 9 |
firstNameCol.setCellValueFactory( new PropertyValueFactory<>("firstName") ); lastNameCol.setCellValueFactory( new PropertyValueFactory<>("lastName") ); emailCol.setCellValueFactory( new PropertyValueFactory<>("email") ); |
setCellValueFactory方法为每列指定了一个单元格工厂(cell factory),这些cell factory是通过PropertyValueFactory类来实现的,它将Person类中对应的属性映射到对应的表格列中。
当数据模型被定义完毕,并且数据被关联到列之后,你可以通过TableView类的setItems方法来向表格中添加数据:如:table.setItems(data)。
由于ObservableList对象具有自动追踪其包含元素的改变的功能,所以TableView的内容将会在数据改变时自动更新。
尝试下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
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("Table View Sample"); stage.setWidth(450); stage.setHeight(500); 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 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 填充了数据的表格
增加新行
在上面的例子中,表格内有5行数据,目前这些数据无法被修改。
你可以使用文本框来输入新的姓名和邮件地址信息。TextField控件允许你的应用程序接收来自用户的输入。下面的例子中创建了三个文本框,并且为每个文本框定义了提示信息,并且添加了“Add”按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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(); }); |
当用户点击“Add”按钮时,输入到文本框中的值将会通过Person的构造方法设置到数据中,然后增加到变量data对应的ObservableList中。因此,你会看到在表格中出现一行新的信息。
尝试运行下面的样例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
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); } } } |
这个程序没有提供过滤器来对输入信息进行格式校验,例如:输入一个错误格式的电子邮件地址。你可以在开发自己的应用程序时提供此功能。
目前的样例程序也没有对空值进行校验。如果没有输入任何值,当点击“Add”按钮时,将会在表格中添加一个空行。
图13-5展示了用户如何添加新的数据行:
图13-5 向地址簿添加联系人信息
图13-6展现了点击“Add”按钮之后的表格。Emma White的联系信息现在出现在表格之中了。
图13-6 新增表数据项
对列中的数据排序
TableView类内置提供了对列数据排序的功能。用户可以通过单击列头来改变数据顺序。第一次点击将会升序排列,第二次会降序排列,第三次则会取消对该列排序。默认情况下不会对任何一列进行排序。
用户可以对表格中的多列数据同时进行排序,并能指定每一列在排序中的优先级。如果要进行多列排序,只需要在在点击列头的同时按住Shift键即可。
在图13-7中,FirstName被升序排列,Last Name被降序排列。注意其中第一列的优先级比第二列的优先级高。
图13–7对多列进行排序
作为开发者,你可以通过setSortType方法来为每一列设置排序特性。你可以指定升序和降序类型。例如,使用下面的代码行可以将emailCol列设置为降序:
1 |
emailCol.setSortType(TableColumn.SortType.DESCENDING); |
通过向TableView.sortOrder observable list中增加或移除TableColumn对象,你也可以指定哪些列需要排序。在此list中的列顺序表示排序的优先级(例如,排在第0位的列比排在第1位的列具有更高的排序优先级)
如果要禁用数据排序特性,可在列上调用setSortable(false)方法。
编辑表格中的数据
TableView类不仅仅可以展现表格数据,并且提供了编辑功能。使用setEditable方法来启用对表格内容的编辑。
使用setCellFactory方法来重新实现表格的单元格,使用TextFieldTableCell类来使其变成一个文本域。setOnEditCommit方法处理编辑过程,并且将更新后的值分配给对应的表格单元格。下面的样例代码展示如何来使用这些方法对First Name、Last Name和Email列进行编辑。
例13–9 实现单元格编辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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()); }); |
下面是完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
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); } } } |
下图中用户正在编辑Michael Brown的信息。为了编辑单元格,用户在单元格中输入了新值,然后按下回车键。对应的单元格在按下回车之前不会被修改。相关的行为由TextField类的实现方式决定。
图13–8 编辑表格单元格
注意TextField控件默认实现的行为是在用户按下回车键后提交对内容的编辑。你可以重新定义TextField的行为使其在失去焦点时提交对内容的编辑,这是一个较为常见的用户体验需求。下面的样例代码提供了对应的示例。
例13–11 单元格编辑的另一种解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
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数据到表格中
你可以向表格增加Map数据。在下列中使用MapValueFactory类来展示student的ID map。
例13–12添加Map数据到表格中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
import java.util.HashMap; import java.util.Map; import javafx.application.Application; 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.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.MapValueFactory; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.util.Callback; import javafx.util.StringConverter; public class TableViewSample extends Application { public static final String Column1MapKey = "A"; public static final String Column2MapKey = "B"; 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("Student IDs"); label.setFont(new Font("Arial", 20)); TableColumn<Map, String> firstDataColumn = new TableColumn<>("Class A"); TableColumn<Map, String> secondDataColumn = new TableColumn<>("Class B"); firstDataColumn.setCellValueFactory(new MapValueFactory(Column1MapKey)); firstDataColumn.setMinWidth(130); secondDataColumn.setCellValueFactory(new MapValueFactory(Column2MapKey)); secondDataColumn.setMinWidth(130); TableView tableView = new TableView<>(generateDataInMap()); tableView.setEditable(true); tableView.getSelectionModel().setCellSelectionEnabled(true); tableView.getColumns().setAll(firstDataColumn, secondDataColumn); 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); final VBox vbox = new VBox(); vbox.setSpacing(5); vbox.setPadding(new Insets(10, 0, 0, 10)); vbox.getChildren().addAll(label, tableView); ((Group) scene.getRoot()).getChildren().addAll(vbox); stage.setScene(scene); stage.show(); } 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接口,并且进行了特殊设计以便用于表格列单元格工厂。在上面的例子中dataRow hash Map表示了TableView对象中的一行。在Map中有两个String Key :Column1MapKey和Column2MapKey分别将对应的值映射到第一列和第二列。setCellValueFactory方法用与key值对应的数据来填充列,因此第一列会包含与“A”对应的数据,而第二列包含与“B”对应的数据。
样例程序运行后效果如下图:
图13-9 带有Map数据的表格视图
在JavaFX中列表数据展示控件有一个更为强大的扩展控件:TreeTableView。可参考树表视图(Tree Table)章节获取更多相关信息。
相关的API文档
· TableView
· TableColumn
· TableCell
· TextField
· TextFieldTableCell
· TreeTableView
· MapValueFactory
· Button


返回值是图片路径字符串,怎么在table上显示ImageView呢
这种情况下,你可以自定义CellFactory,根据路径字符串初始化一个ImageView对象,再作为CellFactory返回给TableView使用就可以了
怎么回去行啊 求告知 我想点击这一行 然后让这行变颜色
之前看到了但是没有想清楚怎么做,但是才意识到,表格选中时本身就有一个选中色啊。
如果你重写了TableCell的画,改选中行的颜色就是把选中的单元格的Cell内容的颜色设置以下。
如果你没有重写TableCell,可以试一下.row-selection或者.cell-selection样式(http://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#tableview),我没有写代码具体尝试,思路供参考。
有疑问可以继续交流。
想控制TableView的滚动条。应该怎么操作呢?
下面是我以前写的一小段代码,你可以参考一下,重点是通过lookupAll()方法使用class找到相关组件
for (Node node : table.lookupAll(“.scroll-bar”)) {
if (node instanceof ScrollBar) {
if (((ScrollBar) node).getOrientation() == orientation) {
tmpScroll = (ScrollBar) node;
}
}
}
我已经解决。鉴于我从这个网站也有收获。贡献代码如下:
TableView tableView = new TableView();
TableViewSkin tableViewSkin = new TableViewSkin(tableView) {
@Override
protected VirtualFlow createVirtualFlow() {
VirtualFlow virtualFlow = new VirtualFlow(){
private VirtualFlow getScrollBar(){
VirtualScrollBar horizontalScrollBar = getHbar();
VirtualScrollBar verticalScrollBar = getVbar();
return this;
}
}.getScrollBar();
return virtualFlow;
}
};
至于,horizontalScrollBar 和 verticalScrollBar 该如何使用。那就是不是我的事情了。
我的TableView在getChildren().addAll()后,最后会多一列空白列,请问这一空白列要怎么去掉?我看例子中设置了minWidth,但把窗口拖宽后,多的那一列还是会显示出来,我想让其中一列自适应,在改变窗口大小时自动变宽或变窄。
更正一下,是getColumns().addAll()
我的做法是:按照tableview父类容器的宽度按比例均分给tableview的每一列,这样就不会有多余的一列出来。 方法是监听tableview父类容器宽度的变化修改,把最新的宽度值传给tableview的每列按比例设置值。
我在VBox中,添加了TableView,但是TableView只能占满VBox的大部分,底部还留有较大空白区域,如何让TableView的高度自适应VBox的高度?谢谢。
可以通过设置setMaxHeight(Double.MAX_VALUE)来使TableView高度尽可能大。如果不行,可以让TableView的preferHeight属性监听VBox的Height属性。
thanks! 我试试。
VBox.setVgrow(tableview, Priority.ALWAYS); 这样就会跟随VBox高度变化。
怎么获取表格中的所以数据呢
你在初始化表格的时候不是传入了一个ObservableList,这就是所有的数据。可以通过table.getItems()获取到,或者你自己应该有记录。如果取某一行你首先得到行的索引后,也可以从这个列表中取出。
怎么获取表格中的所以数据呢
如何在表格里删除一行呢,博主?
把对应行的数据从ObservableList中移除,界面就可以自动刷新
1.创建一个表格
2.表格中每一行 都是一个继承Task的对象
3.删除表格中的一行,但是内存中的对象实例并不会减少
4.在没有继承Task的时候,删除表格中的一行,内存中的实例减少了
5.继承Task,是为了实现进度条
有没有更好的方法实现表格中有进度条。。。。。
数据模型的成员set方法参数名能写得不要这么任性嘛……
public void setLastName(String fName) { lastName.set(fName);}public void setEmail(String fName) { email.set(fName);}
数据模型的成员set方法参数名能写得不要这么任性嘛……
public void setLastName(String fName) {lastName.set(fName);}public void setEmail(String fName) {email.set(fName);}
请问,如何做一个HTML中哪种全选功能呢。一直没研究明白,如何将表头设置为一个checkbox
这个好难,看不懂
在点击列表中某一行之后 在另一个TextFiled上想得到这一行的信息怎么实现呀
列表可以做点击事件或者选中事件
如何删除鼠标所点击的行? 也是用到TabelView自带的事件么?我从哪里可以找到这些事件?谢谢
将ObservableList中某条数据删除即可删除行。至于你要知道具体选中的是哪行,tableview有selection model可以获取。
想问下TableView的单元格怎么监听 能监听到行的选中 当时单元格的选中监听不到
selection model应该可以获取选中的单元格?你开启了单元格选中模式吗?参考:http://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TableSelectionModel.html#setCellSelectionEnabled-boolean-
嗯嗯开启了 后来问题解决了 我是这样添加的监听的tableView.getSelectionModel().getSelectedCells().addListener(tableInvalidation);
各位大神 请教一下 tableview有鼠标悬浮时间吗 我是想让鼠标悬浮在某一个单元格上面时鼠标旁边显示针对该单元格的提示 一直没找到相应的方法 请问有什么方向可以实现这个功能 另,请问tableView可以设置某一个单元格的背景色吗?
没有你说的悬浮事件,但是有鼠标移动事件,可以检测到鼠标当前在哪个单元格上,你再根据你自己的需求设置提示。设置单元格的背景色需要自定义CellFactory
嗯嗯好的 后来设置了单元格的Tooltip属性解决了问题 谢谢
悬浮事件
firstNameCol.setCellValueFactory( new PropertyValueFactory(“firstName”));对象名改成其他的为啥就显示不了数据,比如firstName改成其他的
更正一下,就是问一下他的属性是在哪里定义的,为什么更该不了
修改表格数据时,如果要在回车时弹出一个“是否确认修改的界面”,这个事件需要加在哪里?
createTextField函数里,添加textField控件的KEY事件
博主例13–11的功能应该无法实现,根据我的测试,当编辑框失去焦点时,会先调用cancelEdit(这函数会使属性editing为false),再进入到focusedProperty().addListener的事件里,这时调用commitEdit函数,会因为属性editing为false,而立即退出,而无法产生CellEditEvent事件,从而无法进入setOnEditCommit的事件里。
但是可以利用focusedProperty().addListener事件处理一些事情,比如我就是想把修改后的值提交了cell的属性值里,可以使用getTableView().getItems().get(getIndex())得到ObservableList里对应的值,然后直接往里面赋值。
textField.focusedProperty().addListener(new ChangeListener() { @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { if ( (!newValue) && (!enterkey)) { if ( ToolFunc.isInteger(textField.getText() )) { Integer value = Integer.parseInt(textField.getText()); //commitEdit( value ); HarmonicPara hp = (HarmonicPara) getTableView().getItems().get(getIndex()); hp.setPercent(value); } } } });
是的,我测试下来也是这样,我是把textview失去焦点,提交数据.直接重回写cancelEdit,在super前调用commitEdit(converter.fromString(textField.getText()));来完成提交的
怎么将tableview中的数据输出到文件(或数据库)中,还有提取呢
请问怎么把table自动滚动到数据的最下方?
请问一下如果使用的是FXML写的tableview的话,怎么实现对单元格内容的编辑啊?
怎么把这句firstNameCol.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
转换成FXML语句啊?
用FXML写tebleview实现对单元格内容的编辑,需要这样做吗?如果采用上面提到的第一种方法。
使用FXML写tableview,并使用第二种方法实现tableview的单元格内容编辑功能,怎么实现啊?求大神指导。[/玫瑰]
可以和数据库连接吗
请问各位大佬怎么将tableview中的数据输出到文件(或数据库)中,还有提取呢?
博主,我想问一下你最下面的map,为什么一映射就是一行,能不能就映射一个单元格啊,如果映射一行就没什么作用了,表格中每一列的数剧相同就没意义了
分享图片
分享图片
不管如何,应该调查一下。
请问如何删除表格视图的内容
tableview 可实现Excel那样的筛选功能吗,有偿求一个连接数据库取数的动态筛选案例