PropertyValueFactory类是“TableColumn cell value factory”,绑定创建列表中的项。示例如下:
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
在以上中,Person类是TableView视图绑定的列表的项(items),String是TableColumn中项数据的类型。Person类必须是public,“First Name”是在TableView中显示的表头内容。PropertyValueFactory类的构造方法传入参数“firstName”创建实例,在列表项Person类中寻找与对应的无参属性方法firstNameProperty(firstNameProperty方法必须与传入的参数firstName对应,应该是通过反射方式进行名称对应。firstNameProperty方法可以对应任何名称的属性字段,例如firstNameABC属性字段都可以)返回ObservableValue
如果无参属性方法firstNameProperty存在,则该方法会被触发并返回一个Property
如果Person类中没有与“firstName”对应的无参firstNameProperty方法,PropertyValueFactory类则会扫描Person类中是否有返回值是String类型的无参方法getFirstName或无参方法isFirstName。如果有上述两个方法,则方法会被调用,返回被ReadOnlyObjectWrapper包装的值,值填充“Table Cell”。这种情况下,TableCell无法给包装的属性注册观察者观察数据变化状态。这种情况与调用firstNameProperty方法不同。
For reference (and as noted in the TableColumn TableColumn.cellValueFactoryProperty()
cell value factory} documentation), the long form of the code above would be the following:
以下代码作为参考:
TableColumn firstNameCol = new TableColumn("First Name");
firstNameCol.setCellValueFactory(new Callback, ObservableValue>() {
public ObservableValue call(CellDataFeatures p) {
// p.getValue() returns the Person instance for a particular TableView row
return p.getValue().firstNameProperty();
}
});
}
以下是一个使用PropertyValueFactory的例子:
继承PropertyValueFactory的类,实现方法call:
package cn.learnjavafx.ch13.tableview04;
import java.time.LocalDate;
import static java.time.temporal.ChronoUnit.YEARS;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import cn.learnjavafx.ch11.Person;
/**
* @copyright 2023-2022
* @package cn.learnjavafx.ch13.tableview04
* @file ModelDataByPropertyValueFactory.java
* @date 2023-07-08 22:59
* @author qiao wei
* @version 1.0
* @brief 继承PropertyValueFactory类,重写call方法,间接实现Callback接口。
* @history
*/
public class ModelDataByPropertyValueFactory extends PropertyValueFactory {
public ModelDataByPropertyValueFactory(Person person, String content) {
super(content);
}
@Override
public ObservableValue call(CellDataFeatures cellDataFeatures) {
Person person = cellDataFeatures.getValue();
LocalDate localDate = person.birthDate();
String ageInYear = "Unknown";
if (null != localDate) {
long years = YEARS.between(localDate, LocalDate.now());
if (years == 0) {
ageInYear = "< 1 year";
} else if (years == 1) {
ageInYear = years + " year";
} else {
ageInYear = years + " years";
}
}
return new ReadOnlyStringWrapper(ageInYear);
}
}
调用ModelDataByPropertyValueFactory类替代PropertyValueFactory类。只看start01方法的调用实现。
package cn.learnjavafx.ch13.tableview04;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import cn.learnjavafx.ch11.AgeCategory;
import cn.learnjavafx.ch11.Person;
import cn.learnjavafx.ch13.PersonTableUtil;
/**
* @copyright 2023-2022
* @package cn.learnjavafx.ch13.tableview04
* @file TableViewDataTest.java
* @date 2023-07-15 01:56
* @author qiao wei
* @version 1.0
* @brief
* @history
*/
public class TableViewDataTest extends Application {
public static void main(String[] args) {
Application.launch(TableViewDataTest.class, args);
}
@Override
@SuppressWarnings("unchecked")
public void start(Stage primaryStage) {
try {
start03(primaryStage);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* @class TableViewDataTest
* @date 2023-07-15 09:01
* @author qiao wei
* @version 1.0
* @brief ModelDataByPropertyValueFactory类继承PropertyValueFactory类,间接继承Callback回调接口,重写call方法。
* @param
* @return
* @throws
*/
private void start01(Stage primaryStage) throws Exception {
// Create a TableView and bind model.
TableView table = new TableView<>(PersonTableUtil.getPersonList());
/**
* Create an "Age" computed column.
* TableColumn(text)
* S: The type of the TableView generic type.
* T: The type of the content in all cells in this TableColumn.
* "Age": The string to show when the TableColumn is placed within the TableView.
*/
TableColumn ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new ModelDataByPropertyValueFactory(new Person(), new String()));
// Create an "Age Category" column.
TableColumn ageCategoryColumn = new TableColumn<>("Age Category");
ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
// ageCategoryColumn.setCellValueFactory(new ModelDataByAgeCategory());
// Add columns to the TableView.
table.getColumns().addAll(PersonTableUtil.getIdColumn()
, PersonTableUtil.getFirstNameColumn()
, PersonTableUtil.getLastNameColumn()
, PersonTableUtil.getBirthDateColumn()
, ageColumn
, ageCategoryColumn);
HBox root = new HBox(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: blue;");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Populating TableViews");
primaryStage.show();
}
/**
* @class TableViewDataTest
* @date 2023-07-15 09:12
* @author qiao wei
* @version 1.0
* @brief ModelDataByCallback类实现Callback接口。
* @param
* @return
* @throws
*/
private void start02(Stage primaryStage) throws Exception {
// Create a TableView and bind model.
TableView table = new TableView<>(PersonTableUtil.getPersonList());
/**
* Create an "Age" computed column.
* TableColumn(text)
* S: The type of the TableView generic type.
* T: The type of the content in all cells in this TableColumn.
* "Age": The string to show when the TableColumn is placed within the TableView.
*/
TableColumn ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new ModelDataByCallback());
TableColumn ageCategoryColumn = new TableColumn<>("Age Category");
ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
// Add columns to the TableView.
table.getColumns().addAll(PersonTableUtil.getIdColumn()
, PersonTableUtil.getFirstNameColumn()
, PersonTableUtil.getLastNameColumn()
, PersonTableUtil.getBirthDateColumn()
, ageColumn
, ageCategoryColumn);
HBox root = new HBox(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: blue;");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Populating TableViews");
primaryStage.show();
}
/**
* @class TableViewDataTest
* @date 2023-07-15 09:21
* @author qiao wei
* @version 1.0
* @brief ModelDataByAgeCategory类实现Callback接口。call方法的返回值为ObservableValue,在call方法中将
* AgeCategory枚举类型包装成ReadOnlyObjectWrapper类型返回。
* @param
* @return
* @throws
*/
private void start03(Stage primaryStage) throws Exception {
// Create a TableView and bind model.
TableView table = new TableView<>(PersonTableUtil.getPersonList());
/**
* Create an "Age" computed column.
* TableColumn(text)
* S: The type of the TableView generic type.
* T: The type of the content in all cells in this TableColumn.
* "Age": The string to show when the TableColumn is placed within the TableView.
*/
TableColumn ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new ModelDataByCallback());
/**
* Create an "Age Category" column.
* 创建Age列,其中项类型是Person类,列类型是Person.AgeCategory。将“ageCategory”作为属性名称传给PropertyValueFactory
* 类的构造方法。首先,PropertyValueFactory类在Person类中检索名为“ageCategory”的属性,但是Person类中没有该属性。
* PropertyValueFactory类按照POJO原则(简答Java原则)处理该属性。PropertyValueFactory类在Person类中寻找
* getAgeCategory方法和setAgeCategory方法,如果只检索到getAgeCategory方法,则该列设置为只读。
*/
TableColumn ageCategoryColumn = new TableColumn<>("Age Category");
ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
// ageCategoryColumn.setCellValueFactory(new ModelDataByAgeCategory());
// Add columns to the TableView.
table.getColumns().addAll(PersonTableUtil.getIdColumn()
, PersonTableUtil.getFirstNameColumn()
, PersonTableUtil.getLastNameColumn()
, PersonTableUtil.getBirthDateColumn()
, ageColumn
, ageCategoryColumn);
HBox root = new HBox(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: blue;");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Populating TableViews");
primaryStage.show();
}
}
作为模型数据的Person:
package cn.learnjavafx.ch11;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
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.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* @copyright 2023-2022
* @package cn.learnjavafx.ch11
* @file Person.java
* @date 2023-07-01 21:31
* @author qiao wei
* @version 1.0
* @brief 模型类。保存数据,字段使用属性,可以通过注册监听器监听数据更新情况,自动更新属性。
* @history
*/
public class Person {
/**
* @class Person
* @date 2023-07-01 21:31
* @author qiao wei
* @version 1.0
* @brief Default constructor.
* @param
* @return
* @throws
*/
public Person() {
this("None", "None", null);
}
/**
* @class Person
* @date 2023-07-01 21:32
* @author qiao wei
* @version 1.0
* @brief Constructor.
* @param firstName 名。
* @param lastName 姓。
* @param birthDate 出生日期。
* @return
* @throws
*/
public Person(String firstName, String lastName, LocalDate birthDate) {
this.firstName.set(firstName);
this.lastName.set(lastName);
this.birthDate.set(birthDate);
}
public final int getPersonId() {
return personId.get();
}
public final ReadOnlyIntegerProperty getPersonIdProperty() {
return personId.getReadOnlyProperty();
}
/**
* @class Person
* @date 2023-07-01 21:37
* @author qiao wei
* @version 1.0
* @brief Get first name property.
* @param
* @return Person first name.
* @throws
*/
public final String firstName() {
return firstName.get();
}
public final void setFirstName(String firstName) {
firstNameProperty().set(firstName);
}
public final StringProperty firstNameProperty() {
return firstName;
}
public final String lastName() {
return lastName.get();
}
public final void setLastName(String lastName) {
lastNameProperty().set(lastName);
}
public final StringProperty lastNameProperty() {
return lastName;
}
/** birthDate Property */
public final LocalDate birthDate() {
return birthDate.get();
}
public final void setBirthDate(LocalDate birthDate) {
birthDateProperty().set(birthDate);
}
public final ObjectProperty birthDateProperty() {
return birthDate;
}
/**
* @class Person
* @date 2023-07-02 10:30
* @author qiao wei
* @version 1.0
* @brief Domain specific business rules.
* @param localDate 当地时区日期
* @return
* @throws
*/
public boolean isValidBirthDate(LocalDate localDate) {
return isValidBirthDate(localDate, new ArrayList<>());
}
/**
* @class Person
* @date 2023-07-08 19:21
* @author qiao wei
* @version 1.0
* @brief 验证输入的出生日期是否有效。出生日期无效时,将错误日志记录到errorList中。
* @param date 出生日期。
* @param errorList 错误日志。
* @return 出生日期有效返回true,反之返回false。
* @throws
*/
public boolean isValidBirthDate(LocalDate date, List errorList) {
if (null == date) {
return true;
}
// Birthdate cannot be in the future
if (date.isAfter(LocalDate.now())) {
errorList.add(LocalDate.now().toString() + " : Birth date must not be in future.");
return false;
}
return true;
}
/**
* @class Person
* @date 2023-07-02 11:51
* @author qiao wei
* @version 1.0
* @brief 重写方法,验证个人信息是否正确。Domain specific business rules。
* @param errorList 错误信息列表。
* @return
* @throws
*/
public boolean isValidPerson(List errorList) {
return isValidPerson(this, errorList);
}
/**
* @class Person
* @date 2023-07-02 11:53
* @author qiao wei
* @version 1.0
* @brief 重写方法,验证个人信息是否正确,对个人的姓、名、生日进行有效性验证。Domain specific business
* rules。
* @param person 需要验证的个人信息。
* @param errorList 错误信息列表。记录错误信息。
* @return true 个人信息有效;false 个人信息无效。
* @throws
*/
public boolean isValidPerson(Person person, List errorList) {
boolean isValidPerson = true;
String firstName = person.firstName();
// 将以下3个判断条件都走一遍,将所有异常信息统计到errorList中
if (firstName == null || firstName.trim().length() == 0) {
errorList.add("First name must contain minimum one character.");
isValidPerson = false;
}
String lastName = person.lastName();
if (null == lastName || 0 == lastName.trim().length()) {
errorList.add("Last name must contain minimum one character.");
isValidPerson = false;
}
if ( !isValidBirthDate(this.birthDate.get(), errorList)) {
isValidPerson = false;
}
return isValidPerson;
}
/**
* @class Person
* @date 2023-07-02 12:15
* @author qiao wei
* @version 1.0
* @brief 根据年龄,返回不同的年龄层。
* @param
* @return 年龄层,枚举类型。根据不同年龄返回不同年龄层。
* @throws
*/
public AgeCategory getAgeCategory() {
if (null == birthDate.get()) {
return AgeCategory.UNKNOWN;
}
// 计算年龄。
long years = ChronoUnit.YEARS.between(birthDate.get(), LocalDate.now());
if (0 <= years && 2 > years) {
return AgeCategory.BABY;
} else if (2 <= years && 13 > years) {
return AgeCategory.CHILD;
} else if (13 <= years && 19 >= years) {
return AgeCategory.TEEN;
} else if (19 < years && 50 >= years) {
return AgeCategory.ADULT;
} else if (50 < years) {
return AgeCategory.SENIOR;
} else {
return AgeCategory.UNKNOWN;
}
}
public boolean save(List errorList) {
boolean isSaved = false;
if (isValidPerson(errorList)) {
System.out.println("Saved " + this.toString());
isSaved = true;
}
return isSaved;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("[personId=");
stringBuilder.append(personId.get()).
append(", firstName = ").append(firstName.get()).
append(", lastName = ").append(lastName.get()).
append(", birthDate = ").append(birthDate.get()).append("]");
return stringBuilder.toString();
}
/**
* @date 2023-07-01 21:33
* @author qiao wei
* @brief Person id
*/
private final ReadOnlyIntegerWrapper personId =
new ReadOnlyIntegerWrapper(this, "personId", personSequence.incrementAndGet());
private final StringProperty firstName =
new SimpleStringProperty(this, "firstName", null);
private final StringProperty lastName =
new SimpleStringProperty(this, "lastName", null);
/**
* @date 2023-07-01 21:33
* @author qiao wei
* @brief 出生日期。
*/
private final ObjectProperty birthDate =
new SimpleObjectProperty<>(this, "birthDate", null);
/**
* @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);
}
AgeCategory:
package cn.learnjavafx.ch11;
/**
* @copyright 2023-2022
* @package cn.learnjavafx.ch11
* @file AgeCategory.java
* @date 2023-07-07 16:21
* @author qiao wei
* @version 1.0
* @brief 年龄段。枚举类型,划分不同年龄段人员。
* @history
*/
public enum AgeCategory {
BABY,
CHILD,
TEEN,
ADULT,
SENIOR,
UNKNOWN
}
运行结果: