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);}@Overridepublic 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() {}@Overridepublic 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。