JavaFX教程 – JavaFX表视图
我们可以使用来自 JavaFX API
的 TableView
,TableColumn
和 TableCell
类以表格形式表示数据。
通过实现数据模型和应用单元工厂来填充表中的数据。
表类可以按列排序数据,并在必要时调整列大小。
创建表
表控件是通过实例化 TableView
类创建的。
TableView table = new TableView();
table.setEditable(true);
然后使用 TableColumn
类创建三个列。TableView
类的 getColumns
方法将创建的列添加到表中。
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
TableColumn emailCol = new TableColumn("Email");
table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
我们可以通过调用 setVisible
方法隐藏列。
aColumn.setVisible(false).
以下代码创建一个表。
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TableView table = new TableView();
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);
emailCol.setVisible(false);
StackPane root = new StackPane();
root.getChildren().add(table);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
}
上面的代码生成以下结果。
嵌套标头
使用 JavaFX
表视图,我们可以轻松创建嵌套列。
假设我们要将两个子列添加到地址列。
TableColumn cityCol = new TableColumn("City");
TableColumn stateCol = new TableColumn("State");
然后我们将新创建的列添加到地址列。
addressCol.getColumns().addAll(cityCol, stateCol);
完整的源代码
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TableView table = new TableView();
table.setEditable(true);
TableColumn firstNameCol = new TableColumn("First Name");
TableColumn lastNameCol = new TableColumn("Last Name");
TableColumn addressCol = new TableColumn("Email");
table.getColumns().addAll(firstNameCol, lastNameCol, addressCol);
TableColumn cityCol = new TableColumn("City");
TableColumn stateCol = new TableColumn("State");
addressCol.getColumns().addAll(cityCol, stateCol);
StackPane root = new StackPane();
root.getChildren().add(table);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
}
}
上面的代码生成以下结果。
添加新行
以下代码显示如何向表视图中添加数据。 创建 JavaFX JavaBean
以保存单个行的值。 表中的每一行代表一个名字为姓氏的人。 JavaFX JavaBean
称为 Person
,它有两个字段,名字和姓氏。 Person
为这两个值提供了可绑定的属性。
在 UI
逻辑中,它使用 ObservableList
来保存表视图的值。 ObservableList
中的每个元素都是一个Person
对象。
在按钮事件处理程序中,它创建一个新的具有硬编码的名字和姓氏的人,然后添加到 ObservableList
。
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.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(new Person("A", "B"));
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.setWidth(450);
stage.setHeight(550);
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"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);
final Button addButton = new Button("Add");
addButton.setOnAction((ActionEvent e) -> {
data.add(new Person("Z","X"));
});
hb.getChildren().addAll(addButton);
hb.setSpacing(3);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(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 Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
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);
}
}
}
上面的代码生成以下结果。
TableView
列排序
TableView
类具有列的内置排序功能。
我们可以通过点击列标题对数据进行排序。第一次单击启用升序排序,第二次点击使得排序顺序降序,第三个点击禁用排序。
默认情况下,不应用排序。
我们可以排序多个列,并在排序操作中指定每个列的优先级。
要对多个列进行排序,请在按住Shift
键的同时单击列标题。
我们可以使用 setSortType
方法设置排序首选项我们可以指定升序或降序类型。
以下代码设置排序的降序类型
aColumn.setSortType(TableColumn.SortType.DESCENDING);
我们可以通过从 TableView.sortOrder observable
列表中添加和删除 TableColumn
实例来指定要排序的列。
TableView.sortOrder observable
列中的列的顺序表示排序优先级。 列表中零索引的项目的优先级高于列表中的第一个项目。
要禁止对列进行排序,请调用该列上的 setSortable(false)
方法。
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.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(new Person("A", "B"),
new Person("D", "E"));
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setWidth(450);
stage.setHeight(550);
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"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);
firstNameCol.setSortType(TableColumn.SortType.DESCENDING);
lastNameCol.setSortable(false);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(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 Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
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);
}
}
}
上面的代码生成以下结果。
编辑表中的数据
我们可以在表视图中编辑数据。通过调用 setEditable
方法,我们可以启用表内容的编辑。
setCellFactory
方法安装在 TextFieldTableCell
类中定义的文本字段为每个表单元格。
setOnEditCommit
方法更新表单元格,并且数据绑定需要考虑设置数据回到 JavaFX JavaBean
,它用作表视图的下划线数据模型。
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.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new Person("A", "B"),
new Person("C", "D"));
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setWidth(450);
stage.setHeight(550);
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());
});
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll( 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 Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
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);
}
}
}
上面的代码生成以下结果。
表编辑提交事件
当运行上面的代码并双击编辑单元格,我们必须按 Enter
键提交编辑。 如果我们只是双击并键入一些值,并单击单元格外,commit
事件不会运行,旧的值将被填充。
我们可以重新定义焦点更改上的 commit
事件。
/* * Copyright (c) 2008, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
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.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class Main extends Application {
private final TableView<Person> table = new TableView<>();
private final ObservableList<Person> data = FXCollections
.observableArrayList(new Person("A", "B"), new Person("C", "D"));
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
Callback<TableColumn<Person, String>, TableCell<Person, String>> cellFactory = (
TableColumn<Person, String> p) -> new EditingCell();
Scene scene = new Scene(new Group());
stage.setWidth(450);
stage.setHeight(550);
table.setEditable(true);
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
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());
});
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setMinWidth(100);
lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));
table.setItems(data);
table.getColumns().addAll(firstNameCol, lastNameCol);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.setPadding(new Insets(10, 0, 0, 10));
vbox.getChildren().addAll(table);
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
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();
}
}
public static class Person {
private final SimpleStringProperty firstName;
private final SimpleStringProperty lastName;
private Person(String fName, String lName) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
}
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);
}
}
}
上面的代码生成以下结果。
将数据映射添加到表中
我们可以使用 MapValueFactory
类将 Map
数据添加到表中。
/* * Copyright (c) 2008, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * * This file is available and licensed under the following license: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the distribution. * - Neither the name of Oracle nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
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 Main 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;
}
}
上面的代码生成以下结果。