JavaFX learning: PropertyValueFactory in MVC mode

 The PropertyValueFactory class is“TableColumn cell value factory”,绑定创建列表中的项。示例如下:

 TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");
 firstNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("firstName"));

In the above, the Person class is the item (items) of the list bound by the TableView view, and the String is the type of the item data in the TableColumn. The Person class must be public, and "First Name" is the header content displayed in the TableView. The construction method of the PropertyValueFactory class passes in the parameter "firstName" to create an instance, and searches for the corresponding no-parameter property method firstNameProperty in the list item Person class (the firstNameProperty method must correspond to the passed in parameter firstName, and it should correspond to the name through reflection. The firstNameProperty method can correspond to any property field with any name, such as the firstNameABC property field) and returns ObservableValue<String> .

If the parameterless property method firstNameProperty exists , this method will be triggered and return an instance of Property<String> (Property is a subinterface of ObservableValue) . The returned instance is used as a data item to populate the "Table Cell". At the same time, TableView registers an observer (observer) for the returned Property<String> instance, so that any change to the data item will immediately notify TableView by triggering the observer, and the item content will be updated immediately.

If there is no parameterless firstNameProperty method corresponding to "firstName" in the Person class , the PropertyValueFactory class will scan whether there is a parameterless method getFirstName or a parameterless method isFirstName in the Person class whose return value is String type . If there are the above two methods, the method will be called, and the value wrapped by ReadOnlyObjectWrapper will be returned, and the value will fill the "Table Cell". In this case, TableCell cannot register observers for the wrapped properties to observe the data change status. This case is different from calling the firstNameProperty method.

Note: In the example in Chapter 13 of the book "Learn JavaFX 8 Building User Experience and Interfaces with Java8", the ageCategory data is displayed as Person.AgeCategory (enumeration type) data, but it feels like String type data processing is used during processing. In the following example of Person, add public SimpleStringProperty ageCategoryProperty() method,

The public ReadOnlyStringWrapper ageCategoryProperty() method is used according to the documentation description of the PropertyValueFactory class.

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:

The following code is for reference:

TableColumn<Person,String> firstNameCol = new TableColumn<Person,String>("First Name");

firstNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
     public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
         // p.getValue() returns the Person instance for a particular TableView row
         return p.getValue().firstNameProperty();
     }
  });
 }

Here is an example using PropertyValueFactory:

Inherit the class of PropertyValueFactory and implement the method 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<Person, String> {

	public ModelDataByPropertyValueFactory(Person person, String content) {
		super(content);
	}

	@Override
	public ObservableValue<String> call(CellDataFeatures<Person, String> 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);
	}
}

Call the ModelDataByPropertyValueFactory class instead of the PropertyValueFactory class. Just look at the call implementation of the start01 method. In the function of the start0* method, you can modify the ageCategory method provided by the Person class to test the parameters provided to the PropertyValueFactory constructor.

package learn.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 learn.javafx8.ch11.AgeCategory;
import learn.javafx8.ch11.Person;
import learn.javafx8.ch13.PersonTableUtil;

/**
 * @copyright 2023-2022
 * @package   cn.javafx.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 {
			start02(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<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());

		// 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<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());

		
//		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   
	 * @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());

		/**
		 * 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());

		// 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 as model data:

package learn.javafx8.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.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

/**
 * @copyright 2023-2022
 * @package   cn.javafx.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 personId() {
		return personId.get();
	}
	
	public final ReadOnlyIntegerProperty personIdProperty() {
		return personId.getReadOnlyProperty();
	}

	/**
	 * @class   Person
	 * @date    2023-07-01 21:37
	 * @author  qiao wei
	 * @version 1.0
	 * @brief   Retrieve first name property.
	 * @param
	 * @return  Person first name.
	 * @throws
	 */
	public final String firstName() {
		return firstName.get();
	}

	public final void setFirstName(String firstName) {
		this.firstName.set(firstName);
	}

	public final StringProperty firstNameProperty() {
		return firstName;
	}

	public final String lastName() {
		return lastName.get();
	}

	public final void setLastName(String lastName) {
		this.lastName.set(lastName);
	}

	public final StringProperty lastNameProperty() {
		return lastName;
	}

	/** birthDate Property */
	public final LocalDate birthDate() {
		return birthDate.get();
	}
	
	public final void setBirthDate(LocalDate birthDate) {
		this.birthDate.set(birthDate);
	}
	
	public final ObjectProperty<LocalDate> 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<String> 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<String> 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<String> 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;
		}
	}

	/**
	 * @class   Person
	 * @date    2023-07-21 00:29
	 * @author  qiao wei
	 * @version 1.0
	 * @brief   方法命名符合***Property格式,PropertyValueFactory类构造方法通过反射调用。返回值继承接口ObservableValue<String>
	 * @param
	 * @return
	 * @throws
	 */
	public ReadOnlyStringWrapper ageCategoryProperty() {
		if (null == birthDate.get()) {
			return new ReadOnlyStringWrapper(AgeCategory.UNKNOWN.toString());
		}

		// 计算年龄。
		long years = ChronoUnit.YEARS.between(birthDate.get(), LocalDate.now());
		if (0 <= years && 2 > years) {
			return new ReadOnlyStringWrapper(AgeCategory.BABY.toString());
		} else if (2 <= years && 13 > years) {
			return new ReadOnlyStringWrapper(AgeCategory.CHILD.toString());
		} else if (13 <= years && 19 >= years) {
			return new ReadOnlyStringWrapper(AgeCategory.TEEN.toString());
		} else if (19 < years && 50 >= years) {
			return new ReadOnlyStringWrapper(AgeCategory.ADULT.toString());
		} else if (50 < years) {
			return new ReadOnlyStringWrapper(AgeCategory.SENIOR.toString());
		} else {
			return new ReadOnlyStringWrapper(AgeCategory.UNKNOWN.toString());
		}
	}

	/**
	 * @class   Person
	 * @date    2023-07-21 00:29
	 * @author  qiao wei
	 * @version 1.0
	 * @brief   方法命名符合***Property格式,PropertyValueFactory类构造方法通过反射调用。返回值继承接口ObservableValue<String>
	 * @param
	 * @return
	 * @throws
	 */
//	public SimpleStringProperty ageCategoryProperty() {
//		if (null == birthDate.get()) {
//			return new SimpleStringProperty(AgeCategory.UNKNOWN.toString());
//		}
//
//		// 计算年龄。
//		long years = ChronoUnit.YEARS.between(birthDate.get(), LocalDate.now());
//		if (0 <= years && 2 > years) {
//			return new SimpleStringProperty(AgeCategory.BABY.toString());
//		} else if (2 <= years && 13 > years) {
//			return new SimpleStringProperty(AgeCategory.CHILD.toString());
//		} else if (13 <= years && 19 >= years) {
//			return new SimpleStringProperty(AgeCategory.TEEN.toString());
//		} else if (19 < years && 50 >= years) {
//			return new SimpleStringProperty(AgeCategory.ADULT.toString());
//		} else if (50 < years) {
//			return new SimpleStringProperty(AgeCategory.SENIOR.toString());
//		} else {
//			return new SimpleStringProperty(AgeCategory.UNKNOWN.toString());
//		}
//	}

	public boolean save(List<String> 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<LocalDate> 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 is adjusted from Person's internal data.

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
}

operation result:

Guess you like

Origin blog.csdn.net/weiweiqiao/article/details/131618097