JavaFX lets you create desktop applications using GUI components. A GUI application performs three tasks: accepts user input, processes the input, and displays output. Whereas a GUI application contains two
types of code:
- field code. Handle domain-specific data and follow business norms.
- Interactive code. Handle user input.
The biggest advantage of the MVC model is that the same set of data can be displayed in different interfaces or tables according to requirements. For example, you can view the same set of data at the same time on different UI interfaces such as web, desktop, and industrial computer. . The MVC model corresponds to three model components: model, view and controller. As shown below:
Introduction to the MVC model.
- model: Consists of domain objects that record data.
- view: The interface displayed to the user.
- controller: handles user input and responds to user input.
Here is a simple example. The model module PersonTableUtil saves the domain data Person, because it only displays the data, so there is no controller module in this example, and the display module SimplestableView
Domain data 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 learnjavafx8.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<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;
}
}
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);
}
The model module PersonTableUtil adds Person data to ObservabList and returns it to View as a data model. At the same time, PersonTableUtil has multiple static methods to return TableColumn for processing and processing according to the requirements of the display module.
package cn.learnjavafx.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;
import cn.learnjavafx.ch11.Person;
/**
* @copyright 2023-2022
* @package cn.learnjavafx.ch13.tableview01
* @file PersonTableUtil.java
* @date 2023-07-05 20:30
* @author qiao wei
* @version 1.0
* @brief 模型类。方法getPersonList返回与视图绑定的列表。方法getIdColumn,getFirstNameColumn,
* getLastNameColumn以列的数据格式返回列表中各项的对应值。
* @history
*/
public class PersonTableUtil {
/**
* @class PersonTableUtil
* @date 2023-07-06 16:41
* @author qiao wei
* @version 1.0
* @brief Default constructor.
* @param
* @return
* @throws
*/
public PersonTableUtil() {}
/**
* @class PersonTableUtil
* @date 2023-07-05 20:32
* @author qiao wei
* @version 1.0
* @brief Retrieve an observable list of person.
* @param
* @return Person列表。要显示的模型。
* @throws
*/
public static ObservableList<Person> getPersonList() {
Person p1 = new Person("Ashwin"
, "Sharan"
, LocalDate.of(2012, 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(2003, 4, 20));
Person p5 = new Person("Babalu"
, "Sha"
, LocalDate.of(1980, 1, 10));
return FXCollections.<Person>observableArrayList(p1, p2, p3, p4, p5);
}
/**
* @class PersonTableUtil
* @date 2023-07-05 20:40
* @author qiao wei
* @version 1.0
* @brief Retrieve person Id TableColumn.
* @param
* @return Id column.
* @throws
*/
public static TableColumn<Person, Integer> getIdColumn() {
/**
* 创建显示的列实例。参数Person:列绑定的数据模型。参数Integer:数据模型中数据的类型,类型必须是引用类型。
* “Id”是列表头显示的内容。
*/
TableColumn<Person, Integer> personIdCol = new TableColumn<>("Id");
// 列实例绑定模型的对应属性。
personIdCol.setCellValueFactory(new PropertyValueFactory<>("personId"));
return personIdCol;
}
/**
* @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<>("firstName"));
return firstNameColumn;
}
/**
* @class PersonTableUtil
* @date 2023-07-05 20:59
* @author qiao wei
* @version 1.0
* @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;
}
/**
* @class PersonTableUtil
* @date 2023-07-05 21:00
* @author qiao wei
* @version 1.0
* @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;
}
}
SimplestTableView is a display module that displays the data in the model module.
package cn.learnjavafx.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;
import cn.learnjavafx.ch11.Person;
/**
* @copyright 2023-2022
* @package cn.learnjavafx.ch13.tableview01
* @file SimplestTableView.java
* @date 2023-07-05 22:52
* @author qiao wei
* @version 1.0
* @brief
* @history
*/
public class SimplestTableView extends Application {
@Override
public void start(Stage primaryStage) {
try {
start03(primaryStage);
} catch (Exception exception) {
exception.printStackTrace();
}
}
/**
* @class SimplestTableView
* @date 2023-07-05 22:52
* @author qiao wei
* @version 1.0
* @brief
* @param primaryStage Main window.
* @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()
, PersonTableUtil.getLastNameColumn());
// Add a table column in index position.
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: pink;");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Simplest TableView");
primaryStage.show();
}
/**
* @class SimplestTableView
* @date 2023-07-05 22:53
* @author qiao wei
* @version 1.0
* @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;");
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("Simplest TableView02");
primaryStage.show();
}
private void start03(Stage primaryStage) {
// 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。
TableColumn<Person, Integer> idColumn = PersonTableUtil.getIdColumn();
/**
* 创建TableColumn实例,参数Person表示列中显示数据来自于那里,参数String表示显示数据的类型,参数
* First Name是该列显示的列表头内容。
*/
TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
/**
* PropertyValueFactory的参数是Person对象的字段,绑定Person的字段显示。
* 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 getLastName() of the Person class.
*/
firstNameColumn.setCellValueFactory(new PropertyValueFactory<>("firstName"));
TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
lastNameColumn.setCellValueFactory(new PropertyValueFactory<>("lastName"));
tableView.getColumns().addAll(lastNameColumn, firstNameColumn);
tableView.getColumns().add(0, idColumn);
tableView.getItems().add(new Person("John"
, "Doe"
, LocalDate.of(2000, 8, 12)));
VBox root = new VBox(tableView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
}
The running result of the start01 method is shown in the figure below. The 02/03 method mainly inserts different columns of data and adds model data tests outside the model.
The start02 method and the start03 method self-test.