首先,在QT中我还没有看到过“双向绑定”这个概念,那么这个概念是从哪来的呢?没错,就是WPF,熟悉WPF的同学应该清楚,就是在view层和viewModel层建立一种关系,当更新view层时,对应viewModel中的数据会自动更新,当更新viewModel层时,view对应的属性也会更新。那么在QT中,是否也有类似的机制呢?这篇文章我们就来聊一聊QT中qml与C++之间的交互。
二 单属性的双向绑定 1 创建ViewModel#ifndef MYVIEWMODEL_H #define MYVIEWMODEL_H #includeclass MyViewModel : public QObject { Q_OBJECT Q_PROPERTY(bool flag READ getFlag WRITE setFlag NOTIFY flagChanged) public: explicit MyViewModel(QObject *parent = nullptr); MyViewModel(bool b); bool getFlag() const; void setFlag(const bool& flag); signals: bool flagChanged(); private: bool flag; }; #endif // MYVIEWMODEL_H
简单介绍一下上述的头文件;
- 我们需要绑定的属性,封装在一个类中,这个类必须继承自QObject
- 使用Q_PROPERTY宏用于申明一个可以被QML访问的属性,包括它的类型、gettersetter、发生变化时会发出的信号。当这个信号被触发的时候,QML端会重新渲染所有依赖这个属性值得界面组件。(注意,这里只能实现单向绑定)
#include2 将数据传递给QML#include "MyViewModel.h" #include "qdebug.h" MyViewModel::MyViewModel(QObject *parent) : QObject{parent} { } MyViewModel::MyViewModel(bool _flag) :flag(_flag) { } bool MyViewModel::getFlag() const { return flag; } void MyViewModel::setFlag(const bool& _flag) { if(flag!=_flag) { flag=_flag; qDebug()<<"setFlag:"<<_flag; emit flagChanged(); } }
#include#include #include #include "MyViewModel.h" int main(int argc, char *argv[]) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif QGuiApplication app(argc, argv); MyViewModel viewModel(true); QQmlApplicationEngine engine; QQmlContext* context=engine.rootContext(); context->setContextProperty("viewModel",QVariant::fromValue(&viewModel)); const QUrl url(QStringLiteral("qrc:/main.qml")); engine.load(url); return app.exec(); }
我们直接在main函数里将数据传递到QML中,在QML中就可以直接使用viewModle对象访问数据了。
3 QMLimport QtQuick 2.2
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.3
import QtQuick.Controls.Styles 1.4
import QtQml.Models 2.1
ApplicationWindow {
id: root
width: 640
height: 480
visible: true
title: qsTr("Hello World")
Column {
Switch {
id: m_flag
checked: viewModel.flag
}
Button {
text: qsTr("ChangeData")
onClicked: {
viewModel.flag=!viewModel.flag
}
}
}
}
在QML中,viewModel.flag就是调用了在viewModel中声明的属性,注意,这里的属性名称为Q_PROPERTY中声明的名称。我们还定义了一个Buton用于修改数据源的值。但是这样做也只能实现单向绑定(viewModel=>view),当flag值发生变化的时候,会自动更新Switch的checked属性。但是当我们修改Switch的checked属性时,viewModel中的值并未更新。
所以,要在view更新的时候,也更新一下viewModel,于是修改一下QML。
Switch {
id: m_flag
checked: viewModel.flag
onCheckedChanged: viewModel.flag = m_flag.checked
}
果然,添加onCheckedChanged之后,可以更新viewModel了。**但是,这里遇到了一个问题。当我先操作Switch的,viewModel中的值会更新,再点击按钮更新viewModel中的值的时候,Switch的checked属性没有发生变化!!!!,**但是,从日志分析,点击按钮的时候确实更新了viewModel中的值,并且也调用了信号函数,可是就是没有更新Switch的checked属性,这里很不理解。
所以,我们想要的双向绑定还是没能实现,那么就没有其他办法了吗?我们知道viewModel中定义的信号函数,在每一次更新绑定的值的时候都会触发,但是其对应的槽函数我们是没有管的,因为前面介绍它会自动更新界面的属性。但是我们也可以自己实现。
三 Connections在C++代码中触发信号函数后,我们可以在QML中接收并处理,就要使用到Connections。具体可以查看Connections QML Type | Qt QML 6.3.1。于是,我们修改QML代码如下:
Switch {
id: m_flag
checked: viewModel.flag
onCheckedChanged: viewModel.flag = m_flag.checked
Connections {
target: viewModel
function onFlagChanged() {
m_flag.checked = viewModel.flag
console.log("onFlagChanged")
}
}
}
接收信号函数中,我们手动更新了checked属性,这样就避免了上述问题,实现了双向绑定。这是笔者自己理解尝试的方案,也不知道是不是最好了,也不知道上面说的问题是什么情况,看看大伙儿有什么高见。



