这就是一个简单的数据双向绑定的demo,参考即可(cmakelist我没按他的写,但是大差不差)
目录
1.示例demo
File: CMakeLists.txt
File: main.cpp
File: model/physiologymodel.cpp
File: viewmodel/physiologyviewmodel.h
File: viewmodel/physiologyviewmodel.cpp
File: qml/main.qml
2 双向绑定详解
1. QML 端绑定 heartRate 并触发 setter
2. ViewModel 中定义 heartRate 属性并传值给 Model
3. Model 中设置值并发出信号
4. ViewModel 监听 Model 信号并转发
5. QML 端自动刷新绑定控件
参考
序章:目录结构
MVVMDemo/
│
├── CMakeLists.txt
├── main.cpp
├── model/
│ └── physiologymodel.{h,cpp}
├── viewmodel/
│ └── physiologyviewmodel.{h,cpp}
└── qml/└── main.qml
主要是实现一个简单的两个数据在view上显示并同步后端model数据。
1.demo代码详细
File: CMakeLists.txt
// ============================
// File: CMakeLists.txt
// ============================
cmake_minimum_required(VERSION 3.16)
project(MVVMDemo LANGUAGES CXX)set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)find_package(Qt6 REQUIRED COMPONENTS Core Quick Qml)add_executable(MVVMDemomain.cppmodel/physiologymodel.cppviewmodel/physiologyviewmodel.cpp
)target_include_directories(MVVMDemo PRIVATE${CMAKE_CURRENT_SOURCE_DIR}/model${CMAKE_CURRENT_SOURCE_DIR}/viewmodel
)target_link_libraries(MVVMDemo PRIVATE Qt6::Core Qt6::Quick Qt6::Qml)qt6_add_resources(MVVMDemo "qml"PREFIX "/"FILESqml/main.qml
)
File: main.cpp
// ============================
// File: main.cpp
// ============================
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>#include "physiologyviewmodel.h"int main(int argc, char *argv[]) {QGuiApplication app(argc, argv);PhysiologyModel model;PhysiologyViewModel viewModel(&model);QQmlApplicationEngine engine;engine.rootContext()->setContextProperty("physiologyVM", &viewModel);const QUrl url(QStringLiteral("qrc:/qml/main.qml"));engine.load(url);if (engine.rootObjects().isEmpty())return -1;return app.exec();
}
File: model/physiologymodel.h
// ============================
// File: model/physiologymodel.h
// ============================
#ifndef PHYSIOLOGYMODEL_H
#define PHYSIOLOGYMODEL_H#include <QObject>class PhysiologyModel : public QObject {Q_OBJECT
public:explicit PhysiologyModel(QObject *parent = nullptr);float heartRate() const;float respirationRate() const;void setHeartRate(float rate);void setRespirationRate(float rate);signals:void heartRateChanged();void respirationRateChanged();private:float m_heartRate;float m_respirationRate;
};#endif // PHYSIOLOGYMODEL_H
File: model/physiologymodel.cpp
// ============================
// File: model/physiologymodel.cpp
// ============================
#include "physiologymodel.h"PhysiologyModel::PhysiologyModel(QObject *parent): QObject(parent), m_heartRate(70.0f), m_respirationRate(16.0f) {}float PhysiologyModel::heartRate() const {return m_heartRate;
}float PhysiologyModel::respirationRate() const {return m_respirationRate;
}void PhysiologyModel::setHeartRate(float rate) {if (!qFuzzyCompare(rate, m_heartRate)) {m_heartRate = rate;emit heartRateChanged();}
}void PhysiologyModel::setRespirationRate(float rate) {if (!qFuzzyCompare(rate, m_respirationRate)) {m_respirationRate = rate;emit respirationRateChanged();}
}
File: viewmodel/physiologyviewmodel.h
// ============================
// File: viewmodel/physiologyviewmodel.h
// ============================
#ifndef PHYSIOLOGYVIEWMODEL_H
#define PHYSIOLOGYVIEWMODEL_H#include <QObject>
#include "physiologymodel.h"class PhysiologyViewModel : public QObject {Q_OBJECTQ_PROPERTY(float heartRate READ heartRate WRITE setHeartRate NOTIFY heartRateChanged)Q_PROPERTY(float respirationRate READ respirationRate WRITE setRespirationRate NOTIFY respirationRateChanged)public:explicit PhysiologyViewModel(PhysiologyModel *model, QObject *parent = nullptr);float heartRate() const;float respirationRate() const;void setHeartRate(float rate);void setRespirationRate(float rate);Q_INVOKABLE void sendTrainingCommand();signals:void heartRateChanged();void respirationRateChanged();private:PhysiologyModel *m_model;
};#endif // PHYSIOLOGYVIEWMODEL_H
File: viewmodel/physiologyviewmodel.cpp
// ============================
// File: viewmodel/physiologyviewmodel.cpp
// ============================
#include "physiologyviewmodel.h"
#include <QDebug>PhysiologyViewModel::PhysiologyViewModel(PhysiologyModel *model, QObject *parent): QObject(parent), m_model(model) {connect(m_model, &PhysiologyModel::heartRateChanged, this, &PhysiologyViewModel::heartRateChanged);connect(m_model, &PhysiologyModel::respirationRateChanged, this, &PhysiologyViewModel::respirationRateChanged);
}float PhysiologyViewModel::heartRate() const {return m_model->heartRate();
}float PhysiologyViewModel::respirationRate() const {return m_model->respirationRate();
}void PhysiologyViewModel::setHeartRate(float rate) {m_model->setHeartRate(rate);
}void PhysiologyViewModel::setRespirationRate(float rate) {m_model->setRespirationRate(rate);
}void PhysiologyViewModel::sendTrainingCommand() {qDebug() << "Sending training command with heart rate:" << m_model->heartRate()<< "and respiration rate:" << m_model->respirationRate();// 在此加入 TCP 或串口发送指令的逻辑
}
File: qml/main.qml
// ============================
// File: qml/main.qml
// ============================
import QtQuick 2.15
import QtQuick.Controls 2.15ApplicationWindow {width: 400height: 300visible: truetitle: "Physiology Monitor"Column {anchors.centerIn: parentspacing: 20TextField {id: hrFieldwidth: 200placeholderText: "Heart Rate"text: physiologyVM.heartRate.toString()onTextChanged: physiologyVM.heartRate = parseFloat(text)}TextField {id: rrFieldwidth: 200placeholderText: "Respiration Rate"text: physiologyVM.respirationRate.toString()onTextChanged: physiologyVM.respirationRate = parseFloat(text)}Button {text: "Send Training Command"onClicked: physiologyVM.sendTrainingCommand()}Text {text: "HR: " + physiologyVM.heartRate + " bpm\nRR: " + physiologyVM.respirationRate + " rpm"font.pointSize: 14}}
}
2.双向绑定详解
参考gpt给的答案:Qt/QML MVVM 示例:heartRate 数据绑定流程
以心率为例
[ QML TextField ]
↓
用户输入 → ViewModel.setHeartRate()
↓
Model.setHeartRate()
↓
emit heartRateChanged()
↓
ViewModel emit heartRateChanged()
↓
所有绑定 QML Text 被自动刷新
1️⃣ Step 1:在 ViewModel 中使用 Q_PROPERTY
暴露属性
// physiologyviewmodel.h Q_PROPERTY(float heartRate READ heartRate WRITE setHeartRate NOTIFY heartRateChanged)
这行的作用:
-
READ heartRate
: 提供 getter(用于 QML 读取值) -
WRITE setHeartRate
: 提供 setter(用于 QML 设置值) -
NOTIFY heartRateChanged
: 数据改变时触发,QML 自动响应更新
👉 要点:信号名必须和 NOTIFY 后缀一致。
2️⃣ Step 2:实现 getter、setter 和信号
float PhysiologyViewModel::heartRate() const { return m_model->heartRate(); } void PhysiologyViewModel::setHeartRate(float rate) { m_model->setHeartRate(rate); // Model 处理并发出信号 }
在构造函数中连接 Model 信号到 ViewModel 信号:
connect(m_model, &PhysiologyModel::heartRateChanged, this, &PhysiologyViewModel::heartRateChanged);
这样 Model 更新 → ViewModel 也通知 → QML 自动刷新。
3️⃣ Step 3:把 ViewModel 注册到 QML 上下文
// main.cpp engine.rootContext()->setContextProperty("physiologyVM", &viewModel);
这句代码的作用:
-
将 C++ 对象
viewModel
暴露为名为"physiologyVM"
的上下文属性 -
QML 中就可以通过
physiologyVM.heartRate
来访问
4️⃣ Step 4:QML 使用这个 ViewModel 进行数据绑定
TextField { text: physiologyVM.heartRate.toString() onTextChanged: physiologyVM.heartRate = parseFloat(text) }
含义:
-
初始显示:
text
属性绑定了heartRate
的值 -
用户修改输入框:
onTextChanged
触发 setter -
setter 调用 Model → 如果值改变,Model 发出信号 → ViewModel 传递信号 → QML 自动更新
⚠️ 注意:
-
这是手动实现的双向绑定,一般不这样操作
-
如果你使用
Bindings { ... }
或PropertyBinding
,可以实现更自动的绑定机制(Qt 6 新增)
使用 Bindings {}
TextField {id: hrFieldwidth: 200placeholderText: "Heart Rate"// QML 自动和 C++ 的 heartRate 双向同步Bindings {target: physiologyVMproperty: "heartRate"value: parseFloat(hrField.text)}// UI 自动反映 C++ 数值变更onEditingFinished: text = physiologyVM.heartRate.toString()
}
👆这个例子做了两件事:
-
当
hrField.text
改变 →physiologyVM.heartRate
会自动同步 -
当
physiologyVM.heartRate
从 C++ 改变时 → 我们通过onEditingFinished
显示最新值
使用 PropertyBinding
(推荐用于绑定纯 UI 属性)
Rectangle {width: 200height: 50color: "lightblue"property int heartRate: 0// PropertyBinding 更适合绑定 UI 属性PropertyBinding {target: heartRatevalue: physiologyVM.heartRate}Text {anchors.centerIn: parenttext: heartRate + " bpm"}
}