Java MVVM案例

wen java案例 2

本文目录导读:

Java MVVM案例

  1. 项目结构
  2. Model层
  3. ViewModel层
  4. View层
  5. 主程序
  6. 样式文件 (style.css)
  7. 使用说明

我来为您提供一个完整的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

核心特性:

  1. 数据绑定:使用JavaFX Property机制实现双向绑定
  2. 命令模式:通过ViewModel暴露操作方法
  3. 可观察集合:使用ObservableList自动更新UI
  4. 分离关注点:Model、View、ViewModel完全独立

MVVM优势:

  • 可测试性:ViewModel可独立测试
  • 可维护性:UI和业务逻辑分离
  • 可扩展性:容易添加新功能
  • 数据绑定:减少样板代码

这个案例展示了MVVM模式在JavaFX中的应用,包含了完整的CRUD操作、数据验证、状态管理等典型功能。

抱歉,评论功能暂时关闭!