本文目录导读:

我来为您提供一个完整的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操作、数据验证、状态管理等典型功能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播