JavaFX可以让你使用GUI组件创建桌面应用程序。一个GUI应用程序执行三个任务:接受用户的输入,处理输入,并显示输出。而一个GUI应用程序包含两个
类型的代码:
- 领域代码。处理特定领域的数据和遵循业务规范。
- 交互代码。处理用户输入信息。
MVC模型最大的优点就是同一组数据可以根据需求在不同的界面或表格中显示。例如你可以在web端,桌面端、工控机等不同UI界面上同时查看同一组数据。。MVC模型对应的有三个模型组件:model,view和controller。如下图所示:
MVC模型介绍。
- model:由记录数据的领域对象组成。
- view:显示给用户的界面。
- controller:处理用户输入,及对用户输入的响应。
这里有个简单的例子。模型模块PersonTableUtil保存领域数据Person,因为只是显示数据,所有此示例中没有controller模块,显示模块SimplestableView
领域数据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 futureif (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;}@Overridepublic 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);
}
模型模块PersonTableUtil,将Person数据添加到ObservabList中,作为数据模型返回给View。同时,PersonTableUtil有多个static方法返回TableColumn,进行处理,并根据显示模块的要求进行处理。
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是显示模块,将模型模块中的数据显示出来。
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 {@Overridepublic 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();}
}
启用方法start01的运行结果如下图,02/03方法主要对数据插入不同列,在模型外添加模型数据测试。
start02方法和start03方法自测。