我的 Qt 上位机项目 MVC 重构日记
# 我的 Qt 上位机项目 MVC 重构日记
日期: 2025-07-31
前言
今天我将把一个刚用 Qt Creator 17.0.0 + Qt 6.9.1 创建的 “Qt Widgets Application”——MyMonitorApp, 从 Qt Creator 默认的文件组织方式(头文件/源文件/界面文件三大 filter)改造成清晰的 MVC(Model–View–Controller)分层项目。
一、项目初始状态
-
Qt 版本:Qt 6.9.1
-
Creator 版本:17.0.0
-
项目名称:MyMonitorApp
-
磁盘目录结构(通过“文件系统”视图看到):
MyMonitorApp/ ├── MyMonitorApp.pro ├── headers/ │ └── mainwindow.h ├── sources/ │ ├── main.cpp │ └── mainwindow.cpp └── forms/ └── mainwindow.ui
-
IDE “项目”视图(逻辑分组,不是真实目录):
├── 头文件 ├── 源文件 └── 界面文件
二、为什么要拆成 MVC?
-
职责分离
- Model:负责管理和存储数据。
- View:只关心 UI 展示(
.ui
+ 界面类实现)。 - Controller:处理业务逻辑、和下位机的串口/网络通信。
-
可维护性
- 功能模块清晰,新同事可快速定位代码。
-
可扩展性
- 增加新功能只要在对应层新增文件夹,不影响其他层。
三、重构步骤
1. 在磁盘上创建分层目录
cd MyMonitorApp
mkdir model view controller util
- model/:放
QAbstractItemModel
子类 - view/:放所有
.ui
+ 对应的.h/.cpp
- controller/:放业务逻辑与通讯类
- util/:放串口管理、日志、配置等辅助工具
2. 移动文件
# View 层
mv headers/mainwindow.h view/
mv sources/mainwindow.cpp view/
mv forms/mainwindow.ui view/
# 根目录下 main.cpp 保持不动或按需放 controller/
此时磁盘目录变为:
MyMonitorApp/
├── model/
├── view/
│ ├── mainwindow.h
│ ├── mainwindow.cpp
│ └── mainwindow.ui
├── controller/
├── util/
├── main.cpp
└── MyMonitorApp.pro
3. 修改 MyMonitorApp.pro
打开并编辑,将原来指向 headers/
、sources/
、forms/
的条目全部替换成 MVC 目录下的路径:
QT += core gui widgets
CONFIG += c++17
- INCLUDEPATH += $$PWD/headers
+ INCLUDEPATH += $$PWD/model \
+ $$PWD/view \
+ $$PWD/controller \
+ $$PWD/util
- HEADERS += headers/mainwindow.h
- SOURCES += sources/main.cpp \
- sources/mainwindow.cpp
- FORMS += forms/mainwindow.ui
+ HEADERS += \
+ view/mainwindow.h \
+ model/DeviceDataModel.h \
+ controller/DeviceController.h
+ SOURCES += \
+ main.cpp \
+ view/mainwindow.cpp \
+ model/DeviceDataModel.cpp \
+ controller/DeviceController.cpp
+ FORMS += \
+ view/mainwindow.ui
小提醒:IDE “项目”视图里的 “头文件/源文件/界面文件” 只是 qmake filter,不影响真实目录。
4. 在 Qt Creator 中验证
- 打开项目:
MyMonitorApp.pro
- 切换到 “文件系统” 视图,确认只剩下
model/
、view/
、controller/
、util/
。 - “构建→清理项目 → 运行 QMake → 编译”
四、代码骨架示例
1. Model
// model/DeviceDataModel.h
#pragma once
#include <QAbstractTableModel>
#include "DeviceItem.h"
class DeviceDataModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit DeviceDataModel(QObject* parent = nullptr);
int rowCount(...) const override;
int columnCount(...) const override;
QVariant data(...) const override;
void appendData(const DeviceItem& item);
private:
QVector<DeviceItem> m_items;
};
2. Controller
// controller/DeviceController.h
#pragma once
#include <QObject>
#include "model/DeviceDataModel.h"
#include "util/SerialPortManager.h"
class DeviceController : public QObject {
Q_OBJECT
public:
DeviceController(DeviceDataModel* model, QObject* parent = nullptr);
private slots:
void onSerialDataReceived(const QByteArray& raw);
private:
DeviceDataModel* m_model;
SerialPortManager m_serial;
};
3. View
<!-- view/mainwindow.ui -->
<ui version="4.0">
<class>MainWindow</class>
<!-- … -->
</ui>
// view/mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_model = new DeviceDataModel(this);
ui->tableView->setModel(m_model);
m_ctrl = new DeviceController(m_model, this);
}
五、心得与建议
-
IDE 视图 vs 文件系统:
- “项目”视图便于按文件类型快速定位;
- “文件系统”视图才映射真实目录结构。
-
分层不分味:
- 小项目可以不严格三层都写,按需增删;
- 大项目建议在各层内部再做二级模块划分。
-
版本管理:
- 重构后别忘了更新
.gitignore
(移除旧目录引用)。
- 重构后别忘了更新
重构完毕后,MyMonitorApp 的目录变得干净整齐,逻辑分层清晰,后续新增功能或移植到嵌入式 Qt 时都省心不少。希望这篇日记对你也有帮助!