20240101更新:原内容完善后已调整到JavaFX:MVC模式学习01-使用PropertyValueFactory将模型与视图绑定-CSDN博客
类PropertyValueFactory用来绑定模型中的项与视图中的列。示例如下:
TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));
查看文档,可以看到PropertyValueFactory继承接口Callback<TableColumn.CellDataFeatures<S,T>,ObservableValue<T>>,参数2的ObservableValue<T>就是PropertyValueFactory实例的返回值,所以只要继承该接口,就可以实现自定义的数据绑定。
通过api文档,可以看到PropertyValueFactory除了构造方法外只有2个方法。
方法getProperty不影响数据绑定,在这里只重写方法call。示例代码如下:
测试类TableViewDataTest
package javafx8.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 javafx8.ch11.AgeCategory;
import javafx8.ch11.Person;
import javafx8.ch13.PersonTableUtil;
/**
* @copyright 2003-2024
* @author qiao wei
* @date 2024-01-01 11:37
* @version 1.0
* @brief
* @history
*/
public class TableViewDataTest extends Application {
@Override
public void start(Stage primaryStage) {
try {
start02(primaryStage);
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static void main(String[] args) {
Application.launch(TableViewDataTest.class, args);
}
/**
* @author qiao wei
* @brief ModelDataByPropertyValueFactory类继承PropertyValueFactory类,间接继承Callback回调接口,重写call方法。
* @param primaryStage 主窗体。
* @return
* @throws
*/
private void start01(Stage primaryStage) throws Exception {
// 绑定模型、视图。
TableView<Person> table = new TableView<>(PersonTableUtil.getPersonList());
/**
* Create an "Age" computed column.
* TableColumn<S, T>(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<Person, String> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new ModelDataByPropertyValueFactory(new Person(), new String()));
// Create an "Age Category" column.
TableColumn<Person, AgeCategory> ageCategoryColumn = new TableColumn<>("Age Category");
// ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
ageCategoryColumn.setCellValueFactory(new ModelDataByAgeCategory("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:12
* @author qiao wei
* @version 1.0
* @brief ModelDataByCallback类实现Callback接口。
* @param primaryStage Main window.
* @return
* @throws
*/
private void start02(Stage primaryStage) throws Exception {
// Create a TableView and bind model.
TableView<Person> table = new TableView<>(PersonTableUtil.getPersonList());
/**
* Create an "Age" computed column.
* TableColumn<S, T>(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<Person, String> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new ModelDataByCallback("age"));
// AgeCategory和String类型对比。
// TableColumn<Person, AgeCategory> ageCategoryColumn = new TableColumn<>("Age Category");
// ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
TableColumn<Person, String> 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<AgeCategory>,在call方法中将
* AgeCategory枚举类型包装成ReadOnlyObjectWrapper类型返回。
* @param primaryStage Main window.
* @return
* @throws
*/
private void start03(Stage primaryStage) throws Exception {
// Create a TableView and bind model.
TableView<Person> table = new TableView<>(PersonTableUtil.getPersonList());
/**
* Create an "Age" computed column.
* TableColumn<S, T>(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<Person, String> ageColumn = new TableColumn<>("Age");
ageColumn.setCellValueFactory(new ModelDataByCallback("age"));
/**
* Create an "Age Category" column.
* 创建Age列,其中项类型是Person类,列类型是Person.AgeCategory。将“ageCategory”作为属性名称传给PropertyValueFactory
* 类的构造方法。首先,PropertyValueFactory类在Person类中检索名为“ageCategory”的属性,但是Person类中没有该属性。
* PropertyValueFactory类按照POJO原则(简答Java原则)处理该属性。PropertyValueFactory类在Person类中寻找
* getAgeCategory方法和setAgeCategory方法,如果只检索到getAgeCategory方法,则该列设置为只读。
*/
TableColumn<Person, AgeCategory> ageCategoryColumn = new TableColumn<>("Age Category");
// ageCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("ageCategory"));
ageCategoryColumn.setCellValueFactory(new ModelDataByAgeCategory("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();
}
}
ModelDataByAgeCategory
package javafx8.ch13.tableview04;
import java.time.LocalDate;
import java.time.Period;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.util.Callback;
import javafx8.ch11.AgeCategory;
import javafx8.ch11.Person;
/**
* @copyright 2003-2024
* @author qiao wei
* @date 2024-01-01 11:38
* @version 1.0
* @brief
* @history
*/
public class ModelDataByAgeCategory
implements Callback<CellDataFeatures<Person, AgeCategory>, ObservableValue<AgeCategory>> {
public ModelDataByAgeCategory(String content) {
this.property = content;
}
@Override
public ObservableValue<AgeCategory> call(CellDataFeatures<Person, AgeCategory> cellDataFeatures) {
LocalDate localDate = cellDataFeatures.getValue().getBirthDate();
// 计算年龄。
int years = Period.between(localDate, LocalDate.now()).getYears();
if (0 <= years && 2 > years) {
return new ReadOnlyObjectWrapper<>(AgeCategory.BABY);
} else if (2 <= years && 13 > years) {
return new ReadOnlyObjectWrapper<>(AgeCategory.CHILD);
} else if (13 <= years && 19 >= years) {
return new ReadOnlyObjectWrapper<>(AgeCategory.TEEN);
} else if (19 < years && 50 >= years) {
return new ReadOnlyObjectWrapper<>(AgeCategory.ADULT);
} else if (50 < years) {
return new ReadOnlyObjectWrapper<>(AgeCategory.SENIOR);
} else {
return new ReadOnlyObjectWrapper<>(AgeCategory.UNKNOWN);
}
}
private final String property;
}
ModelDataByCallback
package javafx8.ch13.tableview04;
import java.time.LocalDate;
import java.time.Period;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.util.Callback;
import javafx8.ch11.Person;
/**
* @copyright 2003-2023
* @author qiao wei
* @date 2023-12-30 23:46
* @version 1.0
* @brief 实现Callback接口。根据TableColumn.setCellValueFactory方法需求,实现Callback接口。
* CellDataFeatures<Person, String>:The type of the argument provided to the call method。
* ObservableValue<String>:The type of the return type of the call method。
* @history
*/
public class ModelDataByCallback implements Callback<CellDataFeatures<Person, String>, ObservableValue<String>> {
public ModelDataByCallback(String content) {
this.property = content;
}
/**
* @author qiao wei
* @brief 获取Person的年龄。
* @param cellDataFeatures 数据项。
* @return
* @throws
*/
@Override
public ObservableValue<String> call(CellDataFeatures<Person, String> cellDataFeatures) {
LocalDate localDate = cellDataFeatures.getValue().getBirthDate();
String ageInYear = "Unknown";
if (null != localDate) {
int years = Period.between(localDate, LocalDate.now()).getYears();
if (years == 0) {
ageInYear = "< 1 year";
} else if (years == 1) {
ageInYear = years + " year";
} else {
ageInYear = years + " years";
}
}
// return new ReadOnlyStringWrapper(ageInYear);
return new SimpleStringProperty(ageInYear);
}
private final String property;
}
ModelDataByPropertyValueFactory
package javafx8.ch13.tableview04;
import java.time.LocalDate;
import java.time.Period;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx8.ch11.Person;
/**
* @copyright 2003-2024
* @author qiao wei
* @date 2024-01-01 11:19
* @version 1.0
* @brief 继承PropertyValueFactory类,重写call方法,间接实现Callback接口。接口为泛型
* Callback<TableColumn.CellDataFeatures<S, T>, ObservableValue<T>>。
* @history
*/
public class ModelDataByPropertyValueFactory extends PropertyValueFactory<Person, String> {
public ModelDataByPropertyValueFactory(Person person, String content) {
// Call PropertyValueFactory constructor.
super(content);
}
@Override
public ObservableValue<String> call(CellDataFeatures<Person, String> cellDataFeatures) {
LocalDate localDate = cellDataFeatures.getValue().getBirthDate();
String ageInYear = "Unknown";
if (null != localDate) {
int years = Period.between(localDate, LocalDate.now()).getYears();
if (years == 0) {
ageInYear = "< 1 year";
} else if (years == 1) {
ageInYear = years + " year";
} else {
ageInYear = years + " years";
}
}
return new ReadOnlyStringWrapper(ageInYear);
}
}
AgeCategory
package javafx8.ch11;
/**
* @copyright 2003-2023
* @author qiao wei
* @date 2023-12-29 20:23
* @version 1.0
* @brief 年龄段。枚举类型,划分不同年龄段人员。
* @history
*/
public enum AgeCategory {
BABY,
CHILD,
TEEN,
ADULT,
SENIOR,
UNKNOWN
}
PersonTableUtil
package javafx8.ch13;
import java.time.LocalDate;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx8.ch11.Person;
/**
* @copyright 2003-2024
* @author qiao wei
* @date 2024-01-01 11:43
* @version 1.0
* @brief
* @history
*/
public class PersonTableUtil {
/**
* @class PersonTableUtil
* @date 2023-07-15 15:17
* @author qiao wei
* @version 1.0
* @brief Retrieve an observable list for person.
* @param
* @return Observable list.
* @throws
*/
public static ObservableList<Person> getPersonList() {
Person p1 = new Person(
"Ashwin"
, "Sharan"
, LocalDate.of(2012, 10, 11));
Person p2 = new Person(
"Advik"
,"Sharan"
,LocalDate.of(1955, 10, 11));
Person p3 = new Person(
"Layne"
, "Estes"
, LocalDate.of(1994, 12, 16));
Person p4 = new Person(
"Mason"
, "Boyd"
, LocalDate.of(2003, 4, 20));
Person p5 = new Person(
"Babalu"
, "Sharan"
, LocalDate.of(1972, 1, 10));
Person p6 = new Person(
"春玲"
, "张"
, LocalDate.of(1953, 2, 2));
return FXCollections.<Person>observableArrayList(p1, p2, p3, p4, p5, p6);
}
/**
* @class PersonTableUtil
* @date 2023-07-15 15:09
* @author qiao wei
* @version 1.0
* @brief Retrieve Person id table column.
* @param
* @return Id column.
* @throws
*/
public static TableColumn<Person, Integer> getIdColumn() {
TableColumn<Person, Integer> personIdColumn = new TableColumn<>("用户Id");
personIdColumn.setCellValueFactory(new PropertyValueFactory<>("personId"));
return personIdColumn;
}
/**
* @class PersonTableUtil
* @date 2023-07-15 15:09
* @author qiao wei
* @version 1.0
* @brief Retrieve first name table column.
* @param
* @return First name column.
* @throws
*/
public static TableColumn<Person, String> getFirstNameColumn() {
TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
firstNameColumn.setCellValueFactory(new PropertyValueFactory<>("firstName"));
return firstNameColumn;
}
/**
* @class PersonTableUtil
* @date 2023-07-15 15:09
* @author qiao wei
* @version 1.0
* @brief Retrieve last name table column.
* @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;
}
/**
* @class PersonTableUtil
* @date 2023-07-15 15:10
* @author qiao wei
* @version 1.0
* @brief Retrieve birthdate table column.
* @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;
}
}
Person
/**
* @copyright 2003-2023
* @author qiao wei
* @package javafx8.ch11
* @file Person.java
* @date 2023-12-29 13:39
* @brief
* @history
*/
package javafx8.ch11;
import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
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;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
/**
* @copyright 2003-2024
* @author qiao wei
* @date 2024-01-01 11:44
* @version 1.0数据类,在与PersonView和PersonPresenter的示例中,作为模型类。保存数据,字段使用属性,可以通过注册监听器
* 监听数据更新情况,自动更新属性。
* @brief
* @history
*/
public class Person {
/**
* @author qiao wei
* @date 2023-12-29 13:43
* @brief 默认构造函数。
* @param
* @return
* @throws
* @history
*/
public Person() {
this("None", "None", null);
}
/**
* @author qiao wei
* @date 2023-12-29 13:43
* @brief 构造函数。
* @param firstName 名。
* @param lastName 姓。
* @param birthDate 出生日期。
* @return
* @throws
* @history
*/
public Person(String firstName,
String lastName,
LocalDate birthDate) {
this.personId = new ReadOnlyIntegerWrapper(this, "personId", personSequence.incrementAndGet());
this.firstName = new SimpleStringProperty(this, "firstName", firstName);
this.lastName = new SimpleStringProperty(this, "lastName", lastName);
this.birthDate = new SimpleObjectProperty<>(this, "birthDate", birthDate);
}
@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();
}
/**
* @author qiao wei
* @brief 判断传入的日期是否有效。
* @param localDate 日期
* @return true 日期有效;false 日期无效。
* @throws
*/
public boolean isValidBirthDate(LocalDate localDate) {
return isValidBirthDate(localDate, new ArrayList<>());
}
/**
* @author qiao wei
* @brief 验证输入的出生日期是否有效。出生日期无效时,将错误日志记录到errorList中。
* @param date 出生日期。
* @param errorList 错误日志。
* @return 出生日期有效返回true,反之返回false。
* @throws
*/
public boolean isValidBirthDate(LocalDate date, List<String> errorList) {
if (null == date) {
errorList.add("Birth date is null");
return false;
}
// 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
* @author qiao wei
* @brief 重写方法,验证个人信息是否正确。Domain specific business rules。
* @param errorList 错误信息列表。
* @return
* @throws
*/
public boolean isValidPerson(List<String> errorList) {
return isValidPerson(this, errorList);
}
/**
* @author qiao wei
* @brief 重写方法,验证个人信息是否正确,对个人的姓、名、生日进行有效性验证。Domain specific business rules。
* @param person 需要验证的个人信息。
* @param errorList 错误信息列表。记录个人错误信息。
* @return true 个人信息有效;false 个人信息无效。
* @throws
*/
public boolean isValidPerson(Person person, List<String> errorList) {
boolean isValidPerson = true;
String firstName = person.getFirstName();
// 将以下3个判断条件都走一遍,将所有异常信息统计到errorList中
if (null == firstName || 0 == firstName.trim().length()) {
errorList.add("First name must contain minimum one character.");
isValidPerson = false;
}
String lastName = person.getLastName();
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;
}
public boolean save(List<String> errorList) {
boolean isSaved = false;
if (isValidPerson(errorList)) {
System.out.println("Saved " + this.toString());
isSaved = true;
}
return isSaved;
}
/**
* @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;
}
// 计算年龄。
int ages = Period.between(getBirthDate(), LocalDate.now()).getYears();
if (0 <= ages && 2 > ages) {
return AgeCategory.BABY;
} else if (2 <= ages && 13 > ages) {
return AgeCategory.CHILD;
} else if (13 <= ages && 19 >= ages) {
return AgeCategory.TEEN;
} else if (19 < ages && 50 >= ages) {
return AgeCategory.ADULT;
} else if (50 < ages) {
return AgeCategory.SENIOR;
} else {
return AgeCategory.UNKNOWN;
}
}
/**
* @class Person
* @date 2023-07-21 00:29
* @author qiao wei
* @version 1.0
* @brief 方法命名符合***Property格式,PropertyValueFactory类构造方法通过反射调用。返回值继承接口
* ObservableValue<String>
* @param
* @return
* @throws
*/
public ReadOnlyStringWrapper getAgeCategoryProperty() {
if (null == birthDate.get()) {
return new ReadOnlyStringWrapper(AgeCategory.UNKNOWN.toString());
}
// 计算年龄。
int ages = Period.between(getBirthDate(), LocalDate.now()).getYears();
if (0 <= ages && 2 > ages) {
return new ReadOnlyStringWrapper(AgeCategory.BABY.toString());
} else if (2 <= ages && 13 > ages) {
return new ReadOnlyStringWrapper(AgeCategory.CHILD.toString());
} else if (13 <= ages && 19 >= ages) {
return new ReadOnlyStringWrapper(AgeCategory.TEEN.toString());
} else if (19 < ages && 50 >= ages) {
return new ReadOnlyStringWrapper(AgeCategory.ADULT.toString());
} else if (50 < ages) {
return new ReadOnlyStringWrapper(AgeCategory.SENIOR.toString());
} else {
return new ReadOnlyStringWrapper(AgeCategory.UNKNOWN.toString());
}
}
public final int getPersonId() {
return personId.get();
}
public final ReadOnlyIntegerProperty getPersonIdProperty() {
return personId.getReadOnlyProperty();
}
/**
* @author qiao wei
* @brief 返回名。
* @param
* @return 返回用户名。
* @throws
*/
public final String getFirstName() {
return firstName.get();
}
public final void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public final StringProperty getFirstNameProperty() {
return firstName;
}
public final String getLastName() {
return lastName.get();
}
public final void setLastName(String lastName) {
this.lastName.set(lastName);
}
public final StringProperty getLastNameProperty() {
return lastName;
}
/** birthDate Property */
public final LocalDate getBirthDate() {
return birthDate.get();
}
public final void setBirthDate(LocalDate birthDate) {
this.birthDate.set(birthDate);
}
public final ObjectProperty<LocalDate> getBirthDateProperty() {
return birthDate;
}
/**
* @date 2023-07-01 21:33
* @author qiao wei
* @brief Person id。只读类型。
*/
private final ReadOnlyIntegerWrapper personId;
/**
* @date 2023-12-29 11:48
* @author qiao wei
* @brief 用户姓。
*/
private final StringProperty firstName;
/**
* @date 2023-12-29 11:48
* @author qiao wei
* @brief 用户名。
*/
private final StringProperty lastName;
/**
* @date 2023-07-01 21:33
* @author qiao wei
* @brief 出生日期。
*/
private final ObjectProperty<LocalDate> birthDate;
/**
* @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);
}