PropertyValueFactory类是“TableColumn cell value factory”,绑定创建列表中的项。示例如下:
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
JavaFX在使用MVC模式绑定数据时,要注意模型中属性与视图中列的绑定。在前面的例子中,Person类是TableView视图绑定的列表的项(items),String和LocalDate是TableColumn中项数据的类型(firstName、lastName是StringProperty,birthDate是ObjectProperty)。
Person类必须是public,“First Name”是在TableView中显示的表头内容。PropertyValueFactory类的构造方法传入参数“firstName”创建实例,在列表项Person类中寻找与对应的无参属性方法firstNameProperty(方法firstNameProperty必须与传入的参数firstName对应,应该是通过反射方式进行名称对应。firstNameProperty方法可以对应任何名称的属性字段,例如firstNameABC属性字段都可以,对应的无参数属性方法为firstNameABCProperty())返回ObservableValue<String>。
如果Person类中没有与“firstName”对应的无参firstNameProperty方法,PropertyValueFactory类则会扫描Person类中是否有返回值是String类型的无参方法getFirstName()或无参方法isFirstName()(注意返回属性方法和返回String方法的命名区别,String方法已get开头)。如果有上述方法(无参方法getFirstName()或无参方法isFirstName()),则方法会被调用,返回被ReadOnlyObjectWrapper包装的值,值填充“Table Cell”。这种情况下,TableCell无法给包装的属性注册观察者观察数据变化状态。这种情况与调用firstNameProperty方法不同。
另:
TableView<S>是JavaFX的视图类,通过绑定模型显示。
// 类TableView构造函数。
TableView(ObservableList<S> items)
TableView()
TableView绑定模型。
// 模型。
ObservableList<Person> teamMembers = FXCollections.observableArrayList(members);
// 方法一:构造函数绑定模型。
TableView<Person> table = new TableView<>(teamMembers);
// 方法二:方法setItems绑定模型。
TableView<Person> table = new TableView<>();
table.setItems(teamMembers);
Person类及创建模型:
public class Person {
private StringProperty firstName;
public void setFirstName(String value) { firstNameProperty().set(value); }
public String getFirstName() { return firstNameProperty().get(); }
public StringProperty firstNameProperty() {
if (firstName == null) firstName = new SimpleStringProperty(this, "firstName");
return firstName;
}
private StringProperty lastName;
public void setLastName(String value) { lastNameProperty().set(value); }
public String getLastName() { return lastNameProperty().get(); }
public StringProperty lastNameProperty() {
if (lastName == null) lastName = new SimpleStringProperty(this, "lastName");
return lastName;
}
public Person(String firstName, String lastName) {
setFirstName(firstName);
setLastName(lastName);
}
}
// 创建模型。
List<Person> members = List.of(
new Person("William", "Reed"),
new Person("James", "Michaelson"),
new Person("Julius", "Dean"));
将数据列与视图绑定:
TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<>(members.get(0).firstNameProperty().getName())));
TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
lastNameCol.setCellValueFactory(new PropertyValueFactory<>(members.get(0).lastNameProperty().getName())));
table.getColumns().setAll(firstNameCol, lastNameCol);
运行结果:
以上是JavaFX官方api示例。以下是我自己写的测试代码。
类AgeCategory,年龄段枚举:
package javafx8.ch13.tableview01;
/**
* @copyright 2003-2023
* @author qiao wei
* @date 2023-12-30 18:19
* @version 1.0
* @brief 年龄段枚举。
* @history
*/
public enum AgeCategoryEnum {
BABY,
CHILD,
TEEN,
ADULT,
SENIOR,
UNKNOWN
}
Person类。
package javafx8.ch13.tableview01;
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* @copyright 2003-2023
* @author qiao wei
* @date 2023-12-30 18:20
* @version 1.0
* @brief
* @history
*/
public class Person {
/**
* @author qiao wei
* @brief 默认构造函数。
* @param
* @return
* @throws
*/
public Person() {
this("None", "None", null);
}
/**
* @author qiao wei
* @brief 构造函数。
* @param firstName 名。
* @param lastName 姓。
* @param birthDate 出生日期。
* @return
* @throws
*/
public Person(String firstName,
String lastName,
LocalDate birthDate) {
// 用户Id由系统自动生成。
this.personIdProperty = new ReadOnlyIntegerWrapper(this,
"personId",
personSequence.incrementAndGet()
);
this.firstNameProperty = new SimpleStringProperty(this,
"firstName",
firstName
);
this.lastNameProperty = new SimpleStringProperty(this,
"lastName",
lastName
);
this.birthDateProperty = new SimpleObjectProperty<>(this,
"birthDate",
birthDate
);
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("[personIdProperty=");
stringBuilder.append(personIdProperty.get()).
append(", firstNameProperty = ").append(firstNameProperty.get()).
append(", lastNameProperty = ").append(lastNameProperty.get()).
append(", birthDateProperty = ").append(birthDateProperty.get()).append("]");
return stringBuilder.toString();
}
public boolean save(List<String> errorList) {
boolean isSaved = false;
if (isValidPerson(errorList)) {
System.out.println("Saved " + this.toString());
isSaved = true;
}
return isSaved;
}
public boolean isValidPerson(Person person, List<String> errorList) {
boolean isValidPerson = true;
String firstName = person.firstName();
if (Objects.equals(firstName, null) || 0 == firstName.trim().length()) {
errorList.add("First name must contain minimum one character.");
isValidPerson = false;
}
String lastName = person.lastName();
if (Objects.equals(null, lastName) || 0 == lastName.trim().length()) {
errorList.add("Last name must contain minimum one character.");
isValidPerson = false;
}
return isValidPerson;
}
public boolean isValidPerson(List<String> errorList) {
return isValidPerson(this, errorList);
}
/**
* @author qiao wei
* @brief 判断录入日期是否有效。
* @param
* @return
* @throws
*/
public boolean isValidBirthDate(LocalDate date, List<String> errorList) {
if (Objects.equals(null, date)) {
errorList.add("Birth date is null");
return false;
}
if (date.isAfter(LocalDate.now())) {
errorList.add(LocalDate.now().toString() + " : Birth date must not be in future.");
return false;
}
return true;
}
/**
* @author qiao wei
* @brief 根据年龄,返回年龄层枚举值。
* @param
* @return 年龄层枚举值。根据不同年龄返回不同年龄层。
* @throws
*/
public AgeCategoryEnum ageCategory() {
if (null == birthDateProperty.get()) {
return AgeCategoryEnum.UNKNOWN;
}
int ages = Period.between(birthDateProperty.get(), LocalDate.now()).getYears();
if (0 <= ages && 2 > ages) {
return AgeCategoryEnum.BABY;
} else if (2 <= ages && 13 > ages) {
return AgeCategoryEnum.CHILD;
} else if (13 <= ages && 19 >= ages) {
return AgeCategoryEnum.TEEN;
} else if (19 < ages && 50 >= ages) {
return AgeCategoryEnum.ADULT;
} else if (50 < ages) {
return AgeCategoryEnum.SENIOR;
} else {
return AgeCategoryEnum.UNKNOWN;
}
}
/**
* @author qiao wei
* @brief 方法命名符合***Property格式,PropertyValueFactory类构造方法通过反射调用。返回值继承接
* 口ObservableValue<String>。
* @param
* @return
* @throws
*/
public ReadOnlyStringWrapper ageCategoryProperty() {
if (null == birthDateProperty.get()) {
return new ReadOnlyStringWrapper(AgeCategoryEnum.UNKNOWN.toString());
}
int ages = Period.between(birthDateProperty.get(), LocalDate.now()).getYears();
if (0 <= ages && 2 > ages) {
return new ReadOnlyStringWrapper(AgeCategoryEnum.BABY.toString());
} else if (2 <= ages && 13 > ages) {
return new ReadOnlyStringWrapper(AgeCategoryEnum.CHILD.toString());
} else if (13 <= ages && 19 >= ages) {
return new ReadOnlyStringWrapper(AgeCategoryEnum.TEEN.toString());
} else if (19 < ages && 50 >= ages) {
return new ReadOnlyStringWrapper(AgeCategoryEnum.ADULT.toString());
} else if (50 < ages) {
return new ReadOnlyStringWrapper(AgeCategoryEnum.SENIOR.toString());
} else {
return new ReadOnlyStringWrapper(AgeCategoryEnum.UNKNOWN.toString());
}
}
public ReadOnlyIntegerProperty personIdProperty() {
return personIdProperty.getReadOnlyProperty();
}
public int personId() {
return personIdProperty.get();
}
public StringProperty firstNameProperty() {
return firstNameProperty;
}
public String firstName() {
return firstNameProperty.get();
}
public void setFirstName(String firstNameProperty) {
this.firstNameProperty.set(firstNameProperty);
}
public StringProperty lastNameProperty() {
return lastNameProperty;
}
public String lastName() {
return lastNameProperty.get();
}
public void setLastName(String lastName) {
this.lastNameProperty.set(lastName);
}
public ObjectProperty<LocalDate> birthDateProperty() {
return birthDateProperty;
}
public LocalDate getBirthDate() {
return birthDateProperty.get();
}
public void setBirthDate(LocalDate birthDate) {
this.birthDateProperty.set(birthDate);
}
/**
* @date 2023-07-01 21:33
* @brief Person id。只读类型。
*/
private final ReadOnlyIntegerWrapper personIdProperty;
/**
* @date 2023-12-29 11:48
* @brief 用户姓。
*/
private final StringProperty firstNameProperty;
/**
* @date 2023-12-29 11:48
* @author qiao wei
* @brief 用户名。
*/
private final StringProperty lastNameProperty;
/**
* @date 2023-07-01 21:33
* @author qiao wei
* @brief 出生日期。
*/
private final ObjectProperty<LocalDate> birthDateProperty;
/**
* @date 2023-07-01 21:34
* @author qiao wei
* @brief Class field. Keeps track of last generated person id.
*/
private static AtomicInteger personSequence = new AtomicInteger(0);
}
Person工厂类PersonTableUtil:
package javafx8.ch13.tableview01;
import java.time.LocalDate;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.PropertyValueFactory;
/**
* @copyright 2003-2023
* @author qiao wei
* @date 2023-12-30 16:59
* @version 1.0
* @brief 模型类。方法getPersonList返回与视图绑定的数据项列表。方法getIdColumn,getFirstNameColumn
* getLastNameColumn以列的数据格式返回列表中各项的对应值。
* @history
*/
public class PersonTableUtil {
/**
* @author qiao wei
* @brief 默认构造方法。
* @param
* @return
* @throws
*/
public PersonTableUtil() {}
/**
* @author qiao wei
* @brief 返回保存类Person实例的观察者列表ObservableList。
* @param
* @return 类Person实例的观察者列表。
* @throws
*/
public static ObservableList<Person> getPersonList() {
Person p1 = new Person("Ashwin",
"Sharan",
LocalDate.of(1972, 10, 11)
);
Person p2 = new Person("Advik",
"Tim",
LocalDate.of(2012, 10, 11)
);
Person p3 = new Person("Layne",
"Estes",
LocalDate.of(2011, 12, 16)
);
Person p4 = new Person("Mason",
"Boyd",
LocalDate.of(1995, 4, 20)
);
Person p5 = new Person("Babalu",
"Sha",
LocalDate.of(1980, 1, 10)
);
// 返回ObservableList。
return FXCollections.<Person>observableArrayList(p1, p2, p3, p4, p5);
}
/**
* @author qiao wei
* @brief Retrieve person Id TableColumn.
* @param
* @return Id column.
* @throws
*/
public static TableColumn<Person, Integer> getIdColumn() {
/**
* 创建显示的列实例。参数Person:列绑定的数据模型。参数Integer:数据模型中数据的类型,类型必须是引用类型。
* “Id”是列表头显示的内容。
*/
TableColumn<Person, Integer> idColumn = new TableColumn<>("Id");
// 列实例通过参数“personId”绑定模型的对应属性。
idColumn.setCellValueFactory(new PropertyValueFactory<>("personId"));
return idColumn;
}
/**
* @class PersonTableUtil
* @date 2023-07-05 20:51
* @author qiao wei
* @version 1.0
* @brief Retrieve first name TableColumn.
* @param
* @return First name column.
* @throws
*/
public static TableColumn<Person, String> getFirstNameColumn() {
TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(new PropertyValueFactory<>("firstName1"));
return firstNameColumn;
}
/**
* @author qiao wei
* @brief Retrieve last name TableColumn.
* @param
* @return Last name column.
* @throws
*/
public static TableColumn<Person, String> getLastNameColumn() {
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setCellValueFactory(new PropertyValueFactory<>("lastName"));
return lastNameColumn;
}
/**
* @author qiao wei
* @brief Retrieve birthdate TableColumn.
* @param
* @return Birthdate column.
* @throws
*/
public static TableColumn<Person, LocalDate> getBirthDateColumn() {
TableColumn<Person, LocalDate> birthDateColumn = new TableColumn<>("Birth Date");
birthDateColumn.setCellValueFactory(new PropertyValueFactory<>("birthDate"));
return birthDateColumn;
}
}
运行类:
package javafx8.ch13.tableview01;
import java.time.LocalDate;
import javafx.application.Application;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
/**
* @copyright 2003-2023
* @author qiao wei
* @date 2023-12-31 11:36
* @version 1.0
* @brief
* @history
*/
public class SimplestTableView extends Application {
public SimplestTableView() {}
@Override
public void start(Stage primaryStage) throws Exception {
start03(primaryStage);
}
public static void main(String[] args) {
try {
Application.launch(SimplestTableView.class, args);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* @author qiao wei
* @brief
* @param primaryStage 主窗体。
* @return
* @throws
*/
private void start01(Stage primaryStage) throws Exception {
// Create a TableView and bind model.
TableView<Person> table = new TableView<>(PersonTableUtil.getPersonList());
// Add columns to the TableView in order.
table.getColumns().addAll(PersonTableUtil.getIdColumn(),
PersonTableUtil.getFirstNameColumn()
);
TableColumn<Person, String> lastNameColumn = new TableColumn<>("姓");
lastNameColumn.setCellValueFactory(
new PropertyValueFactory<>(
PersonTableUtil.getPersonList().get(0).lastNameProperty().getName()
)
);
// Add a table column in index position.
table.getColumns().add(1, PersonTableUtil.getBirthDateColumn());
table.getColumns().add(2, lastNameColumn);
VBox root = new VBox(table);
root.setStyle("-fx-padding: 10;" +
"-fx-border-style: solid inside;" +
"-fx-border-width: 2;" +
"-fx-border-insets: 5;" +
"-fx-border-radius: 5;" +
"-fx-border-color: pink;"
);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Simplest TableView");
primaryStage.show();
}
/**
* @author qiao wei
* @brief 设置复合表头,占位符测试。设置表头Name中包含FirstName和LastName。当表格没有内容时,显示占位符内容。
* @param primaryStage 主窗体。
* @return
* @throws
*/
private void start02(Stage primaryStage) throws Exception {
// Create a TableView with a list of persons.
TableView<Person> table = new TableView<>(PersonTableUtil.getPersonList());
// Placeholder。当table没有内容显示时,显示Label内容。
table.setPlaceholder(new Label("No visible columns and/or data exist."));
// Setup nest table header.
TableColumn<Person, String> nameColumn = new TableColumn<>("Name");
nameColumn.getColumns().addAll(PersonTableUtil.getFirstNameColumn(),
PersonTableUtil.getLastNameColumn()
);
// Inserts columns to the TableView.
table.getColumns().addAll(PersonTableUtil.getIdColumn(), nameColumn);
/**
* 在指定列添加列表信息,列从0开始计数。列FirstName和列LastName设置在复合表头,只算一列。所以插入
* “出生日期”列只能在0~2列。
*/
table.getColumns().add(2, PersonTableUtil.getBirthDateColumn());
VBox root = new VBox(table);
root.setStyle("-fx-padding: 10;"
+ "-fx-border-style: solid inside;"
+ "-fx-border-width: 2;"
+ "-fx-border-insets: 5;"
+ "-fx-border-radius: 5;"
+ "-fx-border-color: gray;"
);
primaryStage.setScene(new Scene(root));
primaryStage.setTitle("Simplest TableView02");
primaryStage.show();
}
/**
* @author qiao wei
* @brief 将Person实例通过getItems方法添加到模型ObservableList中。
* @param primaryStage 主窗体。
* @return
* @throws
*/
private void start03(Stage primaryStage) throws Exception {
// Create a TableView instance and set Placeholder.
TableView<Person> tableView = new TableView<>(PersonTableUtil.getPersonList());
tableView.setPlaceholder(new Label("No rows to display"));
// 调用PersonTableUtil.getIdColumn方法,返回TableColumn<Person, Integer>。
TableColumn<Person, Integer> idColumn = PersonTableUtil.getIdColumn();
/**
* 创建TableColumn实例,参数Person表示列中显示数据来自于那里,参数String表示显示数据的类型,参数
* First Name是该列显示的列表头内容。
*/
TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
// TableColumn<Person, String> firstNameColumn = PersonTableUtil.getFirstNameColumn();
/**
* PropertyValueFactory的参数是Person对象的无参lastNameProperty方法(应该是通过反射方式),如果没
* 有找到对应方法,则会按规则继续寻找对应方法绑定,具体资料见JavaFX文档。
* In the example shown earlier, a second PropertyValueFactory is set on the second TableColumn
* instance. The property name passed to the second PropertyValueFactory is lastName, which will
* match the getter method getLastNameProperty() of the Person class.
*/
firstNameColumn.setCellValueFactory(new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
// lastNameColumn.setCellValueFactory(new PropertyValueFactory<>("lastName"));
lastNameColumn.setCellValueFactory(
new PropertyValueFactory<>(
PersonTableUtil.getPersonList().get(0).lastNameProperty().getName()
)
);
TableColumn<Person, AgeCategoryEnum> ageCategoryColumn = new TableColumn<>("Age");
ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
TableColumn<Person, LocalDate> birthDateColumn = new TableColumn<>("Birth Date");
birthDateColumn.setCellValueFactory(new PropertyValueFactory<>("birthDate"));
// 两种方式将数据列加入到实例tableView。依次加入和按列插入。
tableView.getColumns().addAll(lastNameColumn, firstNameColumn, ageCategoryColumn, birthDateColumn);
tableView.getColumns().add(0, idColumn);
VBox root = new VBox(tableView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
// 添加2个用户信息。
tableView.getItems().add(new Person("John",
"Doe",
LocalDate.of(2000, 8, 12)));
tableView.getItems().add(new Person("123",
"ABC",
LocalDate.of(1980, 10, 4)));
}
}
在执行类的方法start03中,lastName的数据绑定没有直接使用字符串,而是使用属性lastNameProperty的名称字符串,随后字符串绑定方法lastNameProperty。