這就是一個簡單的數據雙向綁定的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"}
}