本文目录导读:

我来为您提供一个完整的Java MVVM案例,使用JavaFX框架实现一个简单的学生管理系统。
项目结构
src/
├── model/
│ └── Student.java
├── view/
│ ├── StudentView.java
│ └── StudentView.fxml
├── viewmodel/
│ └── StudentViewModel.java
└── Main.java
Model层
Student.java
package model;
import javafx.beans.property.*;
public class Student {
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty name = new SimpleStringProperty();
private final StringProperty major = new SimpleStringProperty();
private final DoubleProperty score = new SimpleDoubleProperty();
public Student() {
this(0, "", "", 0.0);
}
public Student(int id, String name, String major, double score) {
this.id.set(id);
this.name.set(name);
this.major.set(major);
this.score.set(score);
}
// Getter and Setter methods
public int getId() { return id.get(); }
public void setId(int id) { this.id.set(id); }
public String getName() { return name.get(); }
public void setName(String name) { this.name.set(name); }
public String getMajor() { return major.get(); }
public void setMajor(String major) { this.major.set(major); }
public double getScore() { return score.get(); }
public void setScore(double score) { this.score.set(score); }
// Property getters
public IntegerProperty idProperty() { return id; }
public StringProperty nameProperty() { return name; }
public StringProperty majorProperty() { return major; }
public DoubleProperty scoreProperty() { return score; }
@Override
public String toString() {
return String.format("Student{id=%d, name='%s', major='%s', score=%.1f}",
getId(), getName(), getMajor(), getScore());
}
}
ViewModel层
StudentViewModel.java
package viewmodel;
import javafx.beans.property.*;
import javafx.collections.*;
import model.Student;
public class StudentViewModel {
private final ObservableList<Student> students = FXCollections.observableArrayList();
// 表单绑定属性
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty name = new SimpleStringProperty();
private final StringProperty major = new SimpleStringProperty();
private final DoubleProperty score = new SimpleDoubleProperty();
// 状态属性
private final BooleanProperty addEnabled = new SimpleBooleanProperty(true);
private final BooleanProperty deleteEnabled = new SimpleBooleanProperty(false);
private final BooleanProperty updateEnabled = new SimpleBooleanProperty(false);
private Student selectedStudent;
public StudentViewModel() {
initializeData();
setupBindings();
}
private void initializeData() {
students.add(new Student(1, "张三", "计算机科学", 85.5));
students.add(new Student(2, "李四", "软件工程", 92.0));
students.add(new Student(3, "王五", "人工智能", 78.5));
students.add(new Student(4, "赵六", "大数据", 88.0));
}
private void setupBindings() {
// 根据是否有选中的学生来更新按钮状态
// 可以在selectStudent方法中更新
}
// 选择学生
public void selectStudent(Student student) {
if (student != null) {
selectedStudent = student;
id.set(student.getId());
name.set(student.getName());
major.set(student.getMajor());
score.set(student.getScore());
addEnabled.set(true);
deleteEnabled.set(true);
updateEnabled.set(true);
}
}
// 添加学生
public void addStudent() {
int newId = getNextId();
Student student = new Student(newId, name.get(), major.get(), score.get());
students.add(student);
clearForm();
}
// 更新学生
public void updateStudent() {
if (selectedStudent != null) {
selectedStudent.setName(name.get());
selectedStudent.setMajor(major.get());
selectedStudent.setScore(score.get());
// 刷新表格显示
int index = students.indexOf(selectedStudent);
students.set(index, selectedStudent);
clearForm();
}
}
// 删除学生
public void deleteStudent() {
if (selectedStudent != null) {
students.remove(selectedStudent);
clearForm();
}
}
// 清空表单
public void clearForm() {
selectedStudent = null;
id.set(0);
name.set("");
major.set("");
score.set(0.0);
addEnabled.set(true);
deleteEnabled.set(false);
updateEnabled.set(false);
}
// 获取下一个ID
private int getNextId() {
if (students.isEmpty()) return 1;
return students.stream()
.mapToInt(Student::getId)
.max()
.orElse(0) + 1;
}
// Getters
public ObservableList<Student> getStudents() { return students; }
public IntegerProperty idProperty() { return id; }
public StringProperty nameProperty() { return name; }
public StringProperty majorProperty() { return major; }
public DoubleProperty scoreProperty() { return score; }
public BooleanProperty addEnabledProperty() { return addEnabled; }
public BooleanProperty deleteEnabledProperty() { return deleteEnabled; }
public BooleanProperty updateEnabledProperty() { return updateEnabled; }
}
View层
StudentView.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.geometry.Insets?>
<VBox spacing="10" xmlns:fx="http://javafx.com/fxml"
fx:controller="view.StudentView">
<padding>
<Insets top="10" right="10" bottom="10" left="10"/>
</padding>
<Label text="学生信息管理" style="-fx-font-size: 18px; -fx-font-weight: bold;"/>
<!-- 表格 -->
<TableView fx:id="tableView" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="idColumn" text="ID" prefWidth="60"/>
<TableColumn fx:id="nameColumn" text="姓名" prefWidth="100"/>
<TableColumn fx:id="majorColumn" text="专业" prefWidth="150"/>
<TableColumn fx:id="scoreColumn" text="成绩" prefWidth="80"/>
</columns>
</TableView>
<!-- 表单区域 -->
<GridPane hgap="10" vgap="5">
<rowConstraints>
<RowConstraints minHeight="10" prefHeight="30"/>
</rowConstraints>
<columnConstraints>
<ColumnConstraints minWidth="60"/>
<ColumnConstraints minWidth="100"/>
<ColumnConstraints minWidth="60"/>
<ColumnConstraints minWidth="100"/>
<ColumnConstraints minWidth="60"/>
<ColumnConstraints minWidth="100"/>
<ColumnConstraints minWidth="10"/>
<ColumnConstraints minWidth="50"/>
</columnConstraints>
<Label text="姓名:" GridPane.row="0" GridPane.column="0"/>
<TextField fx:id="nameField" GridPane.row="0" GridPane.column="1"/>
<Label text="专业:" GridPane.row="0" GridPane.column="2"/>
<TextField fx:id="majorField" GridPane.row="0" GridPane.column="3"/>
<Label text="成绩:" GridPane.row="0" GridPane.column="4"/>
<TextField fx:id="scoreField" GridPane.row="0" GridPane.column="5"/>
<Button text="添加" fx:id="addButton"
GridPane.row="0" GridPane.column="7"/>
</GridPane>
<!-- 按钮区域 -->
<HBox spacing="10" alignment="CENTER_RIGHT">
<Button text="更新" fx:id="updateButton" styleClass="update-btn"/>
<Button text="删除" fx:id="deleteButton" styleClass="delete-btn"/>
<Button text="清空" fx:id="clearButton"/>
<Button text="退出" fx:id="exitButton" onAction="#handleExit"/>
</HBox>
<!-- 状态栏 -->
<Label fx:id="statusLabel" text="就绪" styleClass="status-label"/>
</VBox>
StudentView.java
package view;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import model.Student;
import viewmodel.StudentViewModel;
import javafx.beans.binding.Bindings;
import javafx.util.converter.DoubleStringConverter;
import javafx.util.converter.IntegerStringConverter;
public class StudentView {
@FXML private TableView<Student> tableView;
@FXML private TableColumn<Student, Integer> idColumn;
@FXML private TableColumn<Student, String> nameColumn;
@FXML private TableColumn<Student, String> majorColumn;
@FXML private TableColumn<Student, Double> scoreColumn;
@FXML private TextField nameField;
@FXML private TextField majorField;
@FXML private TextField scoreField;
@FXML private Button addButton;
@FXML private Button updateButton;
@FXML private Button deleteButton;
@FXML private Button clearButton;
@FXML private Label statusLabel;
@FXML private VBox root;
private StudentViewModel viewModel;
@FXML
private void initialize() {
viewModel = new StudentViewModel();
setupTableView();
setupBindings();
setupButtons();
attachEventHandlers();
}
private void setupTableView() {
idColumn.setCellValueFactory(data -> data.getValue().idProperty().asObject());
nameColumn.setCellValueFactory(data -> data.getValue().nameProperty());
majorColumn.setCellValueFactory(data -> data.getValue().majorProperty());
scoreColumn.setCellValueFactory(data -> data.getValue().scoreProperty().asObject());
tableView.setItems(viewModel.getStudents());
// 成绩列格式化,显示一位小数
scoreColumn.setCellFactory(column -> new TableCell<Student, Double>() {
@Override
protected void updateItem(Double item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(String.format("%.1f", item));
}
}
});
}
private void setupBindings() {
// 绑定表单字段到ViewModel属性
nameField.textProperty().bindBidirectional(viewModel.nameProperty());
majorField.textProperty().bindBidirectional(viewModel.majorProperty());
// 使用转换器处理数值字段
Bindings.bindBidirectional(scoreField.textProperty(),
viewModel.scoreProperty(), new DoubleStringConverter());
// 绑定按钮启用状态
addButton.disableProperty().bind(viewModel.addEnabledProperty().not());
updateButton.disableProperty().bind(viewModel.updateEnabledProperty().not());
deleteButton.disableProperty().bind(viewModel.deleteEnabledProperty().not());
}
private void setupButtons() {
addButton.setOnAction(e -> handleAdd());
updateButton.setOnAction(e -> handleUpdate());
deleteButton.setOnAction(e -> handleDelete());
clearButton.setOnAction(e -> handleClear());
}
private void attachEventHandlers() {
// 表格选择监听
tableView.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
if (newValue != null) {
viewModel.selectStudent(newValue);
updateStatus("选择了学生: " + newValue.getName());
}
}
);
// 表单输入监听,更新添加按钮状态
nameField.textProperty().addListener((obs, oldVal, newVal) -> {
addButton.setDisable(newVal == null || newVal.trim().isEmpty());
});
}
private void handleAdd() {
if (validateInput()) {
viewModel.addStudent();
updateStatus("添加学生成功");
showAlert(Alert.AlertType.INFORMATION, "成功", "学生添加成功!");
}
}
private void handleUpdate() {
if (validateInput()) {
viewModel.updateStudent();
updateStatus("更新学生成功");
showAlert(Alert.AlertType.INFORMATION, "成功", "学生信息更新成功!");
}
}
private void handleDelete() {
Student selected = tableView.getSelectionModel().getSelectedItem();
if (selected != null) {
Alert confirmDialog = new Alert(Alert.AlertType.CONFIRMATION);
confirmDialog.setTitle("确认删除");
confirmDialog.setHeaderText("删除学生");
confirmDialog.setContentText("确定要删除学生 " + selected.getName() + " 吗?");
confirmDialog.showAndWait().ifPresent(response -> {
if (response == ButtonType.OK) {
viewModel.deleteStudent();
updateStatus("删除学生成功");
}
});
}
}
private void handleClear() {
viewModel.clearForm();
tableView.getSelectionModel().clearSelection();
updateStatus("表单已清空");
}
@FXML
private void handleExit() {
Alert confirmDialog = new Alert(Alert.AlertType.CONFIRMATION);
confirmDialog.setTitle("退出确认");
confirmDialog.setHeaderText("退出程序");
confirmDialog.setContentText("确定要退出程序吗?");
confirmDialog.showAndWait().ifPresent(response -> {
if (response == ButtonType.OK) {
root.getScene().getWindow().hide();
}
});
}
private boolean validateInput() {
String name = nameField.getText();
String major = majorField.getText();
String scoreStr = scoreField.getText();
if (name == null || name.trim().isEmpty()) {
showAlert(Alert.AlertType.WARNING, "警告", "请输入学生姓名!");
return false;
}
if (major == null || major.trim().isEmpty()) {
showAlert(Alert.AlertType.WARNING, "警告", "请输入学生专业!");
return false;
}
try {
double score = Double.parseDouble(scoreStr);
if (score < 0 || score > 100) {
showAlert(Alert.AlertType.WARNING, "警告", "成绩必须在0-100之间!");
return false;
}
} catch (NumberFormatException e) {
showAlert(Alert.AlertType.WARNING, "警告", "请输入有效的成绩!");
return false;
}
return true;
}
private void updateStatus(String message) {
statusLabel.setText(message);
}
private void showAlert(Alert.AlertType type, String title, String content) {
Alert alert = new Alert(type);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(content);
alert.showAndWait();
}
}
主程序
Main.java
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/StudentView.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 800, 500);
primaryStage.setTitle("学生管理系统 - MVVM 示例");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
样式文件 (style.css)
.root {
-fx-font-family: "Microsoft YaHei";
-fx-background-color: #f4f4f4;
}
.label {
-fx-text-fill: #333333;
}
.table-view {
-fx-background-color: white;
-fx-border-color: #ddd;
-fx-border-radius: 5px;
}
.table-view .column-header {
-fx-background-color: #4CAF50;
-fx-text-fill: white;
-fx-font-weight: bold;
}
.table-row-cell:selected {
-fx-background-color: #4CAF50;
-fx-text-fill: white;
}
.button {
-fx-background-radius: 5px;
-fx-padding: 8px 16px;
-fx-cursor: hand;
-fx-text-fill: white;
-fx-font-weight: bold;
}
#addButton {
-fx-background-color: #4CAF50;
}
#addButton:hover {
-fx-background-color: #45a049;
}
.update-btn {
-fx-background-color: #2196F3;
}
.update-btn:hover {
-fx-background-color: #1976D2;
}
.delete-btn {
-fx-background-color: #f44336;
}
.delete-btn:hover {
-fx-background-color: #da190b;
}
#clearButton {
-fx-background-color: #FF9800;
}
#clearButton:hover {
-fx-background-color: #E68A00;
}
#exitButton {
-fx-background-color: #9E9E9E;
}
#exitButton:hover {
-fx-background-color: #757575;
}
.text-field {
-fx-pref-width: 150px;
-fx-padding: 5px;
-fx-border-radius: 3px;
-fx-border-color: #ccc;
-fx-focus-color: #4CAF50;
}
.status-label {
-fx-padding: 10px;
-fx-background-color: #e7e7e7;
-fx-border-radius: 3px;
-fx-text-fill: #333;
}
/* 禁用状态样式 */
.button:disabled {
-fx-opacity: 0.5;
-fx-cursor: default;
}
/* 表格行交替颜色 */
.table-row-cell:odd {
-fx-background-color: #f9f9f9;
}
.table-row-cell:even {
-fx-background-color: #ffffff;
}
使用说明
编译运行:
# 编译所有Java文件
javac -cp .:javafx-sdk/lib/javafx.base.jar:javafx-sdk/lib/javafx.controls.jar:javafx-sdk/lib/javafx.fxml.jar \
-d classes src/**/*.java
# 运行程序
java -cp .:classes:javafx-sdk/lib/* Main
# 或者使用Maven
mvn javafx:run
核心特性:
- 数据绑定:使用JavaFX Property机制实现双向绑定
- 命令模式:通过ViewModel暴露操作方法
- 可观察集合:使用ObservableList自动更新UI
- 分离关注点:Model、View、ViewModel完全独立
MVVM优势:
- 可测试性:ViewModel可独立测试
- 可维护性:UI和业务逻辑分离
- 可扩展性:容易添加新功能
- 数据绑定:减少样板代码
这个案例展示了MVVM模式在JavaFX中的应用,包含了完整的CRUD操作、数据验证、状态管理等典型功能。