文章目录
-
- [一、什么是 MVVM 架构](#一、什么是 MVVM 架构)
- 二、MVVM的优缺点
- [三、Qt 下 MVVM 示例结构](#三、Qt 下 MVVM 示例结构)
- [四、MVC 与 MVVM 的比较](#四、MVC 与 MVVM 的比较)
- 五、完整示例
一、什么是 MVVM 架构
MVVM 全称:Model -- View -- ViewModel ;属于 UI 架构模式之一。
核心目标:解耦 UI 和 业务逻辑,通过数据绑定 (Binding) 实现 UI 自动更新。
三层职责对比:

CSV:MVVM 运行流程 (强绑定)
cpp
用户输入 → View → 绑定 → ViewModel → 更新 Model → 更新 ViewModel 属性 → 自动刷新界面
特点:ViewModel 中数据变化 → UI 自动更新,无需手动调用 setText()
为什么 MVVM 适用于 Qt?
Qt 的框架天生支持:

因此:
- Qt QML = 完整 MVVM
- Qt Widgets = 稍弱 MVVM(手动绑定)
二、MVVM的优缺点
MVVM 的优势

MVVM 的劣势

适合长生命周期项目或复杂界面
三、Qt 下 MVVM 示例结构
以"加减乘除计算器"为例:
cpp
+---------------------+
| View | ← UI LineEdit/Button/Label
+---------------------+
↑ ↓ (绑定)
+---------------------+
| ViewModel | ← Q_PROPERTY、信号槽、命令按钮
+---------------------+
↑ ↓
+---------------------+
| Model | ← 纯业务逻辑
+---------------------+
一个简单的实际例子(伪代码说明)
Model
cpp
double add(double a, double b);
ViewModel → 暴露 Q_PROPERTY
cpp
Q_PROPERTY(double operandA READ operandA WRITE setOperandA NOTIFY operandAChanged)
Q_PROPERTY(double result READ result NOTIFY resultChanged)
当 operandA 改变
cpp
setOperandA() → Model::calculate() → 更新 result → emit resultChanged → UI自动刷新
View(Widgets)
cpp
connect(lineEditA, textChanged => ViewModel::setOperandA)
connect(ViewModel::resultChanged => label->setText(...))
- UI 不直接操作业务逻辑
- ViewModel 不知道 UI 存在
四、MVC 与 MVVM 的比较
MVC vs MVVM

结构职责对比

本质区别

优点 vs 缺点
MVC
优点:
- 结构简单、易理解
- 小项目开发快速
缺点:
- Controller 容易越来越臃肿
- UI 更新逻辑分散在各处
- 模块复用性弱
MVVM
优点:
- UI 自动更新(减少 setText 等 UI 控制代码)
- 逻辑脱耦彻底,可复用
- ViewModel 可单元测试
- 大型项目可维护性增强
缺点:
- 初期设计成本更高
- 双向绑定不当会出现循环风险
- 对开发者要求更高(特别是 Qt Widgets)
应用场景选型建议

五、完整示例
支持加减乘除,可直接使用的计算器 MVVM 示例
示例分成三层:
- Model(纯业务逻辑)
- ViewModel(UI 逻辑 + 绑定)
- View(界面,使用 ViewModel,不访问内部变量)
所有成员变量都 private,公开接口均通过 Q_PROPERTY / 方法 / 信号,符合 Qt MVVM 最佳实践。
1.Model 层(纯业务:加、减、乘、除)
cpp
// CalculatorModel.h
#pragma once
#include <QObject>
class CalculatorModel : public QObject {
Q_OBJECT
public:
explicit CalculatorModel(QObject* parent = nullptr)
: QObject(parent) {}
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div(double a, double b) { return b != 0 ? a / b : 0; }
};
- Model 层没有 UI,也没有信号槽,只处理 纯数学逻辑。
- 成员变量全封装。
2.ViewModel 层(UI 绑定与行为逻辑)
通过 Q_PROPERTY 绑定 UI,让 UI 自动收到 ViewModel 的状态改变。
cpp
// CalculatorViewModel.h
#pragma once
#include <QObject>
#include "CalculatorModel.h"
class CalculatorViewModel : public QObject {
Q_OBJECT
Q_PROPERTY(double result READ result NOTIFY resultChanged)
public:
explicit CalculatorViewModel(QObject* parent = nullptr)
: QObject(parent), m_model(new CalculatorModel(this)) {}
double result() const { return m_result; }
public slots:
void calculateAdd(double a, double b) {
m_result = m_model->add(a, b);
emit resultChanged();
}
void calculateSub(double a, double b) {
m_result = m_model->sub(a, b);
emit resultChanged();
}
void calculateMul(double a, double b) {
m_result = m_model->mul(a, b);
emit resultChanged();
}
void calculateDiv(double a, double b) {
m_result = m_model->div(a, b);
emit resultChanged();
}
signals:
void resultChanged();
private:
CalculatorModel* m_model; // 完全封装,不暴露
double m_result = 0; // 绑定变量
};
特点:
- m_model 与 m_result 完全私有
- View 层无法直接访问内部变量
- UI 会自动通过 Q_PROPERTY 收到更新
- 所有业务都通过 public slots() 执行
符合 MVVM 的标准模式:
cpp
View → 调用 ViewModel 方法 → ViewModel 调用 Model → 更新结果 → 发射信号 → View 刷新 UI
3.View 层(Qt Widgets 版本示例)
View 只操作 ViewModel 的公开接口,不访问其内部变量。
cpp
// CalculatorWindow.h
#pragma once
#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include "CalculatorViewModel.h"
class CalculatorWindow : public QWidget {
Q_OBJECT
public:
explicit CalculatorWindow(CalculatorViewModel* vm, QWidget* parent = nullptr)
: QWidget(parent), m_vm(vm)
{
m_a = new QLineEdit(this);
m_b = new QLineEdit(this);
m_result = new QLineEdit(this);
m_result->setReadOnly(true);
auto btnAdd = new QPushButton("加", this);
auto btnSub = new QPushButton("减", this);
auto btnMul = new QPushButton("乘", this);
auto btnDiv = new QPushButton("除", this);
connect(btnAdd, &QPushButton::clicked, this, &CalculatorWindow::onAdd);
connect(btnSub, &QPushButton::clicked, this, &CalculatorWindow::onSub);
connect(btnMul, &QPushButton::clicked, this, &CalculatorWindow::onMul);
connect(btnDiv, &QPushButton::clicked, this, &CalculatorWindow::onDiv);
// View绑定 ViewModel 的结果
connect(m_vm, &CalculatorViewModel::resultChanged, this, [this]() {
m_result->setText(QString::number(m_vm->result()));
});
}
private slots:
void onAdd() { m_vm->calculateAdd(a(), b()); }
void onSub() { m_vm->calculateSub(a(), b()); }
void onMul() { m_vm->calculateMul(a(), b()); }
void onDiv() { m_vm->calculateDiv(a(), b()); }
private:
double a() const { return m_a->text().toDouble(); }
double b() const { return m_b->text().toDouble(); }
CalculatorViewModel* m_vm;
QLineEdit* m_a;
QLineEdit* m_b;
QLineEdit* m_result;
};
4.图示:MVVM 调用链
cpp
[View] 按钮点击
↓ 调用
[ViewModel] calculateAdd()
↓ 调用
[Model] add(a, b)
↓
[ViewModel] 设置 m_result → emit resultChanged()
↓
[View] 监听 resultChanged() → 更新界面
MVVM(Model--View--ViewModel)架构 + Q_PROPERTY + 自动绑定
1.设计图(MVVM)
cpp
+----------------------+
| View(UI) |
| QML/Widget,只做绑定 |
+-----------+----------+
|
Binding
|
+-----------v----------+
| ViewModel (逻辑) |
| Q_PROPERTY, signal |
+-----------+----------+
|
调用
|
+-----------v----------+
| Model(数据) |
| 真正的计算逻辑 |
+----------------------+
2.Model(数据 + 计算逻辑,不暴露成员)
cpp
// CalculatorModel.h
#pragma once
#include <QObject>
class CalculatorModel : public QObject
{
Q_OBJECT
public:
explicit CalculatorModel(QObject* parent=nullptr);
double add(double a, double b) const;
double sub(double a, double b) const;
double mul(double a, double b) const;
double div(double a, double b, bool& ok) const;
};
cpp
// CalculatorModel.cpp
#include "CalculatorModel.h"
CalculatorModel::CalculatorModel(QObject* parent)
: QObject(parent)
{
}
double CalculatorModel::add(double a, double b) const { return a + b; }
double CalculatorModel::sub(double a, double b) const { return a - b; }
double CalculatorModel::mul(double a, double b) const { return a * b; }
double CalculatorModel::div(double a, double b, bool& ok) const
{
if (b == 0) { ok = false; return 0; }
ok = true;
return a / b;
}
3. ViewModel(MVVM 核心:对外暴露 Q_PROPERTY)
核心:ViewModel 维护 a、b、result 的状态,View 通过绑定自动更新。
cpp
// CalculatorViewModel.h
#pragma once
#include <QObject>
class CalculatorModel;
class CalculatorViewModel : public QObject
{
Q_OBJECT
Q_PROPERTY(double operandA READ operandA WRITE setOperandA NOTIFY operandAChanged)
Q_PROPERTY(double operandB READ operandB WRITE setOperandB NOTIFY operandBChanged)
Q_PROPERTY(double result READ result NOTIFY resultChanged)
public:
explicit CalculatorViewModel(CalculatorModel* model,
QObject* parent=nullptr);
// 属性读写
double operandA() const;
double operandB() const;
double result() const;
void setOperandA(double v);
void setOperandB(double v);
public slots:
void add();
void sub();
void mul();
void div();
signals:
void operandAChanged();
void operandBChanged();
void resultChanged();
private:
CalculatorModel* m_model;
double m_a = 0;
double m_b = 0;
double m_result = 0;
void updateResult(double r);
};
cpp
// CalculatorViewModel.cpp
#include "CalculatorViewModel.h"
#include "CalculatorModel.h"
CalculatorViewModel::CalculatorViewModel(
CalculatorModel* model,
QObject* parent)
: QObject(parent)
, m_model(model)
{
}
double CalculatorViewModel::operandA() const { return m_a; }
double CalculatorViewModel::operandB() const { return m_b; }
double CalculatorViewModel::result() const { return m_result; }
void CalculatorViewModel::setOperandA(double v)
{
if (m_a == v) return;
m_a = v;
emit operandAChanged();
}
void CalculatorViewModel::setOperandB(double v)
{
if (m_b == v) return;
m_b = v;
emit operandBChanged();
}
void CalculatorViewModel::updateResult(double r)
{
if (m_result == r) return;
m_result = r;
emit resultChanged();
}
void CalculatorViewModel::add()
{
updateResult(m_model->add(m_a, m_b));
}
void CalculatorViewModel::sub()
{
updateResult(m_model->sub(m_a, m_b));
}
void CalculatorViewModel::mul()
{
updateResult(m_model->mul(m_a, m_b));
}
void CalculatorViewModel::div()
{
bool ok;
double r = m_model->div(m_a, m_b, ok);
updateResult(r);
}
4. View(UI 层)
cpp
// CalculatorView.h
#pragma once
#include <QWidget>
class CalculatorViewModel;
class QLineEdit;
class QPushButton;
class CalculatorView : public QWidget
{
Q_OBJECT
public:
explicit CalculatorView(CalculatorViewModel* vm,
QWidget* parent=nullptr);
private:
CalculatorViewModel* m_vm;
};
cpp
// CalculatorView.cpp
#include "CalculatorView.h"
#include "CalculatorViewModel.h"
#include <QGridLayout>
#include <QLineEdit>
#include <QPushButton>
CalculatorView::CalculatorView(CalculatorViewModel* vm,
QWidget* parent)
: QWidget(parent)
, m_vm(vm)
{
auto editA = new QLineEdit(this);
auto editB = new QLineEdit(this);
auto editR = new QLineEdit(this);
editR->setReadOnly(true);
auto btnAdd = new QPushButton("+");
auto btnSub = new QPushButton("-");
auto btnMul = new QPushButton("*");
auto btnDiv = new QPushButton("/");
auto layout = new QGridLayout(this);
layout->addWidget(editA, 0, 0, 1, 2);
layout->addWidget(editB, 1, 0, 1, 2);
layout->addWidget(editR, 2, 0, 1, 2);
layout->addWidget(btnAdd, 3, 0);
layout->addWidget(btnSub, 3, 1);
layout->addWidget(btnMul, 4, 0);
layout->addWidget(btnDiv, 4, 1);
// UI → ViewModel
connect(editA, &QLineEdit::textChanged,
this, [=](const QString& t){ m_vm->setOperandA(t.toDouble()); });
connect(editB, &QLineEdit::textChanged,
this, [=](const QString& t){ m_vm->setOperandB(t.toDouble()); });
// ViewModel → UI
connect(m_vm, &CalculatorViewModel::resultChanged,
this, [=]{ editR->setText(QString::number(m_vm->result())); });
// 操作→VM 触发计算
connect(btnAdd, &QPushButton::clicked, m_vm, &CalculatorViewModel::add);
connect(btnSub, &QPushButton::clicked, m_vm, &CalculatorViewModel::sub);
connect(btnMul, &QPushButton::clicked, m_vm, &CalculatorViewModel::mul);
connect(btnDiv, &QPushButton::clicked, m_vm, &CalculatorViewModel::div);
}
完全基于 Signal + Slot 绑定的 MVVM 版本
总设计(Signal/Slot MVVM)
cpp
View(UI)
▲ (UI 绑定 ViewModel)
| signals/slots
ViewModel(逻辑)
▲ (ViewModel 驱动 Model)
|
Model(业务逻辑)
1.Model(只做计算,不保存 UI 状态)
cpp
// CalculatorModel.h
#pragma once
#include <QObject>
class CalculatorModel : public QObject
{
Q_OBJECT
public:
explicit CalculatorModel(QObject* parent=nullptr);
public slots:
double add(double a, double b) const;
double sub(double a, double b) const;
double mul(double a, double b) const;
double div(double a, double b, bool& ok) const;
};
cpp
// CalculatorModel.cpp
#include "CalculatorModel.h"
CalculatorModel::CalculatorModel(QObject* parent)
: QObject(parent)
{
}
double CalculatorModel::add(double a, double b) const { return a + b; }
double CalculatorModel::sub(double a, double b) const { return a - b; }
double CalculatorModel::mul(double a, double b) const { return a * b; }
double CalculatorModel::div(double a, double b, bool& ok) const
{
if (b == 0) { ok = false; return 0; }
ok = true;
return a / b;
}
2. ViewModel(绑定层,用信号驱动计算)
ViewModel 不直接访问 UI,只维护数据状态,并通过信号广播结果。
cpp
// CalculatorViewModel.h
#pragma once
#include <QObject>
class CalculatorModel;
class CalculatorViewModel : public QObject
{
Q_OBJECT
public:
explicit CalculatorViewModel(CalculatorModel* model,
QObject* parent=nullptr);
public slots:
void setOperandA(double v);
void setOperandB(double v);
void requestAdd();
void requestSub();
void requestMul();
void requestDiv();
signals:
void resultChanged(double v);
private:
CalculatorModel* m_model;
double m_a = 0;
double m_b = 0;
};
cpp
// CalculatorViewModel.cpp
#include "CalculatorViewModel.h"
#include "CalculatorModel.h"
CalculatorViewModel::CalculatorViewModel(
CalculatorModel* model,
QObject* parent)
: QObject(parent)
, m_model(model)
{
}
void CalculatorViewModel::setOperandA(double v)
{
m_a = v;
}
void CalculatorViewModel::setOperandB(double v)
{
m_b = v;
}
void CalculatorViewModel::requestAdd()
{
emit resultChanged(m_model->add(m_a, m_b));
}
void CalculatorViewModel::requestSub()
{
emit resultChanged(m_model->sub(m_a, m_b));
}
void CalculatorViewModel::requestMul()
{
emit resultChanged(m_model->mul(m_a, m_b));
}
void CalculatorViewModel::requestDiv()
{
bool ok;
double r = m_model->div(m_a, m_b, ok);
emit resultChanged(r);
}
3. View(UI 部分,完全使用 signal/slot 绑定 ViewModel)
View 不做任何计算,只负责输入与显示。
cpp
// CalculatorView.h
#pragma once
#include <QWidget>
class CalculatorViewModel;
class QLineEdit;
class QPushButton;
class CalculatorView : public QWidget
{
Q_OBJECT
public:
explicit CalculatorView(CalculatorViewModel* vm,
QWidget* parent=nullptr);
private:
CalculatorViewModel* m_vm;
};
cpp
// CalculatorView.cpp
#include "CalculatorView.h"
#include "CalculatorViewModel.h"
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>
CalculatorView::CalculatorView(CalculatorViewModel* vm,
QWidget* parent)
: QWidget(parent)
, m_vm(vm)
{
auto editA = new QLineEdit();
auto editB = new QLineEdit();
auto editR = new QLineEdit();
editR->setReadOnly(true);
auto btnAdd = new QPushButton("+");
auto btnSub = new QPushButton("-");
auto btnMul = new QPushButton("*");
auto btnDiv = new QPushButton("/");
auto layout = new QGridLayout(this);
layout->addWidget(editA, 0, 0, 1, 2);
layout->addWidget(editB, 1, 0, 1, 2);
layout->addWidget(editR, 2, 0, 1, 2);
layout->addWidget(btnAdd, 3, 0);
layout->addWidget(btnSub, 3, 1);
layout->addWidget(btnMul, 4, 0);
layout->addWidget(btnDiv, 4, 1);
//
// ----------- UI → ViewModel -----------
//
connect(editA, &QLineEdit::textChanged,
this, [=](const QString& txt){
m_vm->setOperandA(txt.toDouble());
});
connect(editB, &QLineEdit::textChanged,
this, [=](const QString& txt){
m_vm->setOperandB(txt.toDouble());
});
//
// ----------- ViewModel → UI -----------
//
connect(m_vm, &CalculatorViewModel::resultChanged,
this, [=](double v){
editR->setText(QString::number(v));
});
//
// ----------- 按钮触发计算 -----------
//
connect(btnAdd, &QPushButton::clicked, m_vm, &CalculatorViewModel::requestAdd);
connect(btnSub, &QPushButton::clicked, m_vm, &CalculatorViewModel::requestSub);
connect(btnMul, &QPushButton::clicked, m_vm, &CalculatorViewModel::requestMul);
connect(btnDiv, &QPushButton::clicked, m_vm, &CalculatorViewModel::requestDiv);
}
MVVM + 双向绑定 Qt Widgets 示例
功能说明(双向绑定):

关键:避免无限循环,例如 LineEdit → ViewModel → LineEdit 更新 → 再触发输入回。我们使用 信号阻断器 + 值比较 防止递归更新。
1.Model(业务逻辑)
cpp
// CalculatorModel.h
#pragma once
class CalculatorModel
{
public:
double calculate(double a, double b, char op)
{
switch(op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return (b != 0) ? a / b : 0;
default: return 0;
}
}
};
2.ViewModel(Q_PROPERTY + 绑定)
cpp
// CalculatorViewModel.h
#pragma once
#include <QObject>
#include "CalculatorModel.h"
class CalculatorViewModel : public QObject
{
Q_OBJECT
Q_PROPERTY(double operandA READ operandA WRITE setOperandA NOTIFY operandAChanged)
Q_PROPERTY(double operandB READ operandB WRITE setOperandB NOTIFY operandBChanged)
Q_PROPERTY(double result READ result NOTIFY resultChanged)
Q_PROPERTY(char op READ op WRITE setOp NOTIFY opChanged)
public:
explicit CalculatorViewModel(QObject* parent=nullptr)
: QObject(parent) {}
double operandA() const { return m_operandA; }
double operandB() const { return m_operandB; }
double result() const { return m_result; }
char op() const { return m_op; }
public slots:
void setOperandA(double v) {
if (qFuzzyCompare(m_operandA, v)) return;
m_operandA = v;
emit operandAChanged(v);
updateResult();
}
void setOperandB(double v) {
if (qFuzzyCompare(m_operandB, v)) return;
m_operandB = v;
emit operandBChanged(v);
updateResult();
}
void setOp(char o) {
if (m_op == o) return;
m_op = o;
emit opChanged(o);
updateResult();
}
signals:
void operandAChanged(double);
void operandBChanged(double);
void resultChanged(double);
void opChanged(char);
private:
void updateResult() {
double newResult = m_model.calculate(m_operandA, m_operandB, m_op);
if (!qFuzzyCompare(m_result, newResult)) {
m_result = newResult;
emit resultChanged(m_result);
}
}
private:
CalculatorModel m_model;
double m_operandA = 0;
double m_operandB = 0;
double m_result = 0;
char m_op = '+';
};
3.View(界面及双向绑定逻辑)
cpp
// CalculatorView.h
#pragma once
#include <QWidget>
#include <QLineEdit>
#include <QLabel>
#include <QComboBox>
#include <QHBoxLayout>
#include "CalculatorViewModel.h"
class CalculatorView : public QWidget
{
Q_OBJECT
public:
explicit CalculatorView(CalculatorViewModel* vm, QWidget* parent=nullptr)
: QWidget(parent), m_vm(vm)
{
auto layout = new QHBoxLayout(this);
m_editA = new QLineEdit("0", this);
m_opBox = new QComboBox(this);
m_editB = new QLineEdit("0", this);
m_labelResult = new QLabel("= 0", this);
m_opBox->addItems({"+", "-", "*", "/"});
layout->addWidget(m_editA);
layout->addWidget(m_opBox);
layout->addWidget(m_editB);
layout->addWidget(m_labelResult);
bindViewToVM();
bindVMToView();
}
private:
void bindViewToVM()
{
connect(m_editA, &QLineEdit::textChanged, this, [this](const QString& text){
m_vm->setOperandA(text.toDouble());
});
connect(m_editB, &QLineEdit::textChanged, this, [this](const QString& text){
m_vm->setOperandB(text.toDouble());
});
connect(m_opBox, &QComboBox::currentTextChanged, this, [this](const QString& op){
m_vm->setOp(op[0].toLatin1());
});
}
void bindVMToView()
{
connect(m_vm, &CalculatorViewModel::operandAChanged, this, [this](double v){
QSignalBlocker blocker(m_editA);
m_editA->setText(QString::number(v));
});
connect(m_vm, &CalculatorViewModel::operandBChanged, this, [this](double v){
QSignalBlocker blocker(m_editB);
m_editB->setText(QString::number(v));
});
connect(m_vm, &CalculatorViewModel::opChanged, this, [this](char o){
QSignalBlocker blocker(m_opBox);
int idx = m_opBox->findText(QString(o));
if (idx >= 0) m_opBox->setCurrentIndex(idx);
});
connect(m_vm, &CalculatorViewModel::resultChanged, this, [this](double r){
m_labelResult->setText("= " + QString::number(r));
});
}
private:
CalculatorViewModel* m_vm;
QLineEdit* m_editA;
QComboBox* m_opBox;
QLineEdit* m_editB;
QLabel* m_labelResult;
};
4.Main(启动程序)
cpp
// main.cpp
#include <QApplication>
#include "CalculatorView.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
CalculatorViewModel vm;
CalculatorView view(&vm);
view.show();
return app.exec();
}