QML与C++交互的实现步骤
作者:龚建波
前言
文档如是说,QML旨在通过C ++代码轻松扩展。Qt QML模块中的类使QML对象能够从C ++加载和操作,QML引擎与Qt元对象系统集成的本质使得C ++功能可以直接从QML调用。这允许开发混合应用程序,这些应用程序是通过混合使用QML,JavaScript和C ++代码实现的。
QML is designed to be easily extensible through C++ code. The classes in the Qt QML module enable QML objects to be loaded and manipulated from C++, and the nature of QML engine's integration with Qt's meta object system enables C++ functionality to be invoked directly from QML. This allows the development of hybrid applications which are implemented with a mixture of QML, JavaScript and C++ code.
除了从QML访问C ++功能的能力之外,Qt QML模块还提供了从C ++代码执行反向和操作QML对象的方法。
下面会通过示例来讲解QML与C++的交互是如何实现的(内容有点长)。
第一个例子:QML中创建C++对象
文档如是说,使用C ++代码中定义的功能可以轻松扩展QML。由于QML引擎与Qt元对象系统的紧密集成,可以从QML代码访问由QObject派生的类适当公开的任何功能。这使得C ++类的属性和方法可以直接从QML访问,通常很少或无需修改。
QML引擎能够通过元对象系统内省QObject实例。这意味着,任何QML代码都可以访问QObject派生类实例的以下成员:
- 属性(使用Q_PROPERTY注册的属性)
- 方法(需注册为public slots或是标记为Q_INVOKABLE)
- 信号
(此外,如果已使用Q_ENUMS声明枚举,则可以使用枚举。)
通常,无论是否已向QML类型系统注册了QObject派生类,都可以从QML访问它们。但是,如果QML引擎要访问其他类型信息(例如,如果要将类本身用作方法参数或属性,或者要将其中一个枚举类型用于以这种方式使用),那么该类可能需要注册。
代码示例有四个文件,QtQuick Empty工程的两个加自定义的Cpp类h和cpp文件,因为我把几种常用的方法都写出来了,所以看起来有点乱(完整代码链接见文末)。
#ifndef CPPOBJECT_H #define CPPOBJECT_H #include <QObject> //派生自QObject //使用qmlRegisterType注册到QML中 class CppObject : public QObject { Q_OBJECT //注册属性,使之可以在QML中访问--具体语法百度Q_PROPERTY Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) Q_PROPERTY(int year READ getYear WRITE setYear NOTIFY yearChanged) public: explicit CppObject(QObject *parent = nullptr); //通过Q_INVOKABLE宏标记的public函数可以在QML中访问 Q_INVOKABLE void sendSignal();//功能为发送信号 //给类属性添加访问方法--myName void setName(const QString &name); QString getName() const; //给类属性添加访问方法--myYear void setYear(int year); int getYear() const; signals: //信号可以在QML中访问 void cppSignalA();//一个无参信号 void cppSignalB(const QString &str,int value);//一个带参数信号 void nameChanged(const QString name); void yearChanged(int year); public slots: //public槽函数可以在QML中访问 void cppSlotA();//一个无参槽函数 void cppSlotB(const QString &str,int value);//一个带参数槽函数 private: //类的属性 QString myName; int myYear; }; #endif // CPPOBJECT_H
在头文件中,我定义了信号和public槽函数,以及Q_INVOKABLE宏标记的public函数,还通过Q_PROPERTY注册了两个属性,这些方法和属性之后都可以在QML中进行访问。
#include "CppObject.h" #include <QDebug> CppObject::CppObject(QObject *parent) : QObject(parent), myName("none"), myYear(0) { } void CppObject::sendSignal() { //测试用,调用该函数后发送信号 qDebug()<<"CppObject::sendSignal"; emit cppSignalA(); emit cppSignalB(myName,myYear); } void CppObject::setName(const QString &name) { qDebug()<<"CppObject::setName"<<name; if(myName!=name){ qDebug()<<"emit nameChanged"; myName=name; emit nameChanged(name); } } QString CppObject::getName() const { qDebug()<<"CppObject::getName"; return myName; } void CppObject::setYear(int year) { qDebug()<<"CppObject::setYear"<<year; if(year!=myYear){ qDebug()<<"emit yearChanged"; myYear=year; emit yearChanged(myYear); } } int CppObject::getYear() const { qDebug()<<"CppObject::getYear"; return myYear; } void CppObject::cppSlotA() { qDebug()<<"CppObject::cppSlotA"; } void CppObject::cppSlotB(const QString &str, int value) { qDebug()<<"CppObject::cppSlotB"<<str<<value; }
为了测试方便,我给每个函数都加了一个打印语句,当调用sendSignal函数时将会emit两个信号,稍后会在QML中调用该函数。
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "CppObject.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); //qmlRegisterType注册C++类型至QML //arg1:import时模块名 //arg2:主版本号 //arg3:次版本号 //arg4:QML类型名 qmlRegisterType<CppObject>("MyCppObject",1,0,"CppObject"); QQmlApplicationEngine engine; //也可以注册为qml全局对象 //engine.rootContext()->setContextProperty("cppObj",new CppObject(qApp)); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
通过使用qmlRegisterType,将刚才定义的QObject派生类注册到QML中(Qt5.15增加了新的注册方式)。
import QtQuick 2.9 import QtQuick.Window 2.9 //引入我们注册的模块 import MyCppObject 1.0 Window { id: root visible: true width: 500 height: 300 title: qsTr("QML调用Cpp对象:by 龚建波1992") color:"green" signal qmlSignalA signal qmlSignalB(string str,int value) //鼠标点击区域 MouseArea{ anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton //测试时点击左键或右键 onClicked: { if(mouse.button===Qt.LeftButton){ console.log('----qml 点击左键:Cpp发射信号') cpp_obj.name="gongjianbo" //修改属性会触发set函数,获取值会触发get函数 cpp_obj.year=1992 cpp_obj.sendSignal() //调用Q_INVOKABLE宏标记的函数 }else{ console.log('----qml 点击右键:QML发射信号') root.qmlSignalA() root.qmlSignalB('gongjianbo',1992) } } } //作为一个QML对象 CppObject{ id:cpp_obj //也可以像原生QML对象一样操作,增加属性之类的 property int counts: 0 onYearChanged: { counts++ console.log('qml onYearChanged',counts) } onCountsChanged: { console.log('qml onCountsChanged',counts) } } //组件加载完成执行 Component.onCompleted: { //关联信号与信号处理函数的方式同QML中的类型 //Cpp对象的信号关联到Qml //cpp_obj.onCppSignalA.connect(function(){console.log('qml signalA process')}) cpp_obj.onCppSignalA.connect(()=>console.log('qml signalA process')) //js的lambda cpp_obj.onCppSignalB.connect(processB) //Qml对象的信号关联到Cpp root.onQmlSignalA.connect(cpp_obj.cppSlotA) root.onQmlSignalB.connect(cpp_obj.cppSlotB) } //定义的函数可以作为槽函数 function processB(str,value){ console.log('qml function processB',str,value) } }
注册之后就能直接在QML中使用刚才定义的C++类型了,并且可以像QML定义的类型一样进行操作,如信号槽关联、属性绑定等。
这个示例很简单,点击鼠标左键调用CppObj的sendSignal函数来发送信号,QML处理;点击鼠标右键QML发送信号,CppObj处理,下面是操作结果:
可以看到QML成功的访问了CppObj的属性和方法,并能进行信号槽的关联。
第二个例子:C++中加载QML对象
文档如是说,所有QML对象类型都是源自QObject类型,无论它们是由引擎内部实现还是第三方定义。这意味着QML引擎可以使用Qt元对象系统动态实例化任何QML对象类型并检查创建的对象。
这对于从C ++代码创建QML对象非常有用,无论是显示可以直观呈现的QML对象,还是将非可视QML对象数据集成到C ++应用程序中。一旦创建了QML对象,就可以从C ++中检查它,以便读取和写入属性,调用方法和接收信号通知。
可以使用QQmlComponent或QQuickView来加载QML文档。QQmlComponent将QML文档作为为一个C++对象加载,然后可以从C++ 代码进行修改。QQuickView也可以这样做,但由于QQuickView是一个基于QWindow的派生类,加载的对象也将可视化显示,QQuickView通常用于将一个可视化的QML对象集成到应用程序的用户界面中。参见文档Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html
下面通过代码来演示(完整代码链接见文末)。
import QtQuick 2.9 Item{ id: root width: 250 height: 250 //自定义属性 --cpp可以访问 property string msg: "GongJianBo1992" //自定义信号 --可以触发cpp槽函数 signal qmlSendMsg(string arg1,string arg2) Rectangle { anchors.fill: parent color: "green" objectName: "rect" //用于cpp查找对象 } MouseArea { anchors.fill: parent onClicked: { console.log("qml 点击鼠标, 发送信号 qmlSendMsg") root.qmlSendMsg(root.msg,"myarg2") } } onHeightChanged: console.log("qml height changed") onWidthChanged: console.log("qml width changed") //QML中的方法可以被cpp调用,也可以作为槽函数 function qml_method(val_arg){ console.log("qml method runing",val_arg,"return ok") return "ok" } //注意槽函数参数为var类型 function qmlRecvMsg(arg1,arg2){ console.log("qml slot runing",arg1,arg2) } }
在QML中我定义了一些属性和方法等,用于测试。
#ifndef CPPOBJECT_H #define CPPOBJECT_H #include <QObject> #include <QDebug> class CppObject : public QObject { Q_OBJECT public: explicit CppObject(QObject *parent = Q_NULLPTR) :QObject(parent){} signals: //信号 --用来触发qml的函数 //注意参数为var类型,对应qml中js函数的参数类型 void cppSendMsg(const QVariant &arg1,const QVariant &arg2); public slots: //槽函数 --用来接收qml的信号 void cppRecvMsg(const QString &arg1,const QString &arg2){ qDebug()<<"CppObject::cppRecvMsg"<<arg1<<arg2; qDebug()<<"emit cppSendMsg"; emit cppSendMsg(arg1,arg2); } }; #endif // CPPOBJECT_H
Cpp中定义了一个槽函数,用来接收QML对象的信号。
#include <QGuiApplication> #include <QQmlProperty> #include <QQuickView> #include <QQuickItem> #include <QMetaObject> #include <QDebug> #include "CppObject.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); //可以用QQmlComponent\QQuickView\QQuickWidget的C++代码加载QML文档 //QQuickView不能用Window做根元素 QQuickView view(QUrl("qrc:/main.qml")); view.show(); //获取到qml根对象的指针 QObject *qmlObj=view.rootObject(); /*文档如是说: 应该始终使用QObject::setProperty()、QQmlProperty 或QMetaProperty::write()来改变QML的属性值,以确保QML引擎感知属性的变化。*/ //【1】 //通过QObject设置属性值 qDebug()<<"Cpp set qml property height"; //qmlObj->setProperty("height",300); QQmlProperty(qmlObj,"height").write(300); //通过QObject获取属性值 qDebug()<<"Cpp get qml property height"<<qmlObj->property("height"); //任何属性都可以通过C++访问 qDebug()<<"Cpp get qml property msg"<<qmlObj->property("msg"); //【2】 QQuickItem *item=qobject_cast<QQuickItem*>(qmlObj); //通过QQuickItem设置属性值 qDebug()<<"Cpp set qml property width"; item->setWidth(300); //通过QQuickItem获取属性值 qDebug()<<"Cpp get qml property width"<<item->width(); //【3】 //通过objectName访问加载的QML对象 //QObject::findChildren()可用于查找具有匹配objectName属性的子项 QObject *qmlRect=qmlObj->findChild<QObject*>("rect"); if(qmlRect){ qDebug()<<"Cpp get rect color"<<qmlRect->property("color"); } //【4】 //调用QML方法 QVariant val_return; //返回值 QVariant val_arg="GongJianBo"; //参数值 //Q_RETURN_ARG()和Q_Arg()参数必须制定为QVariant类型 QMetaObject::invokeMethod(qmlObj, "qml_method", Q_RETURN_ARG(QVariant,val_return), Q_ARG(QVariant,val_arg)); qDebug()<<"QMetaObject::invokeMethod result"<<val_return; //qml函数中返回“ok” //【5】 //关联信号槽 CppObject cppObj; //关联qml信号与cpp槽 //如果信号参数为QML对象类型,信号用var参数类型,槽用QVariant类型接收 QObject::connect(qmlObj,SIGNAL(qmlSendMsg(QString,QString)), &cppObj,SLOT(cppRecvMsg(QString,QString))); //关联cpp信号与qml槽 //qml中js函数参数为var类型,信号也用QVariant类型 QObject::connect(&cppObj,SIGNAL(cppSendMsg(QVariant,QVariant)), qmlObj,SLOT(qmlRecvMsg(QVariant,QVariant))); //此外,cpp信号也可以关联qml信号 return app.exec(); }
然后就把文档中的东西测试了下,操作起来很简单。不过相对于QML中使用C++对象来说,感觉作用没那么大,因为一般把QML嵌入到Widgets中才会做这些操作,但是混合两个框架很多坑。下面是我的测试输出结果:
以上两种方式应该就是最简单的QML与C++交互应用了,对照文档或是博客敲一遍代码可以很容易地理解。
(完结)
代码完整链接(GitHub)如下:
Qml中创建Cpp对象(Cpp注册给Qml):https://github.com/gongjianbo/MyTestCode/tree/master/Qml/QmlCallCpp2020
Cpp中加载Qml对象(Cpp操作Qml):https://github.com/gongjianbo/MyTestCode/tree/master/Qml/CppCallQml2020
代码的下载链接:QML-C_jb51.rar
参考
文档:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-overview.html
文档:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html
文档:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-topic.html
(注:文档中有很多相关链接,此处忽略)
博客:https://blog.csdn.net/u011012932/column/info/14318
博客:https://blog.csdn.net/baidu_33850454/article/details/81907857
博客:https://blog.csdn.net/baidu_33850454/article/details/81914821
到此这篇关于QML与C++交互的实现步骤的文章就介绍到这了,更多相关QML与C++交互内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!