QML旨在通过C ++代码轻松扩展。Qt QML模块中的类使QML对象能够从C ++加载和操作,QML引擎与Qt元对象系统集成的本质使得C ++功能可以直接从QML调用。这允许开发混合应用程序,这些应用程序是通过混合使用QML,JavaScript和C ++代码实现的。除了从QML访问C ++功能的能力之外,Qt QML模块还提供了从C ++代码执行反向和操作QML对象的方法。下面会通过示例来讲解QML与C++的交互是如何实现的。
使用C ++代码中定义的功能可以轻松扩展QML。由于QML引擎与Qt元对象系统的紧密集成,可以从QML代码访问由QObject派生的类适当公开的任何功能。这使得C ++类的属性和方法可以直接从QML访问,通常很少或无需修改。
QML引擎能够通过元对象系统内省QObject实例。这意味着,任何QML代码都可以访问QObject派生类实例的以下成员:
(此外,如果已使用Q_ENUMS声明枚举,则可以使用枚举。)
通常,无论是否已向QML类型系统注册了QObject派生类,都可以从QML访问它们。但是,如果QML引擎要访问其他类型信息(例如,如果要将类本身用作方法参数或属性,或者要将其中一个枚举类型用于以这种方式使用),那么该类可能需要注册。代码示例有四个文件,QtQuick Empty工程的两个加自定义的Cpp类h和cpp文件。
- #ifndef CPPOBJECT_H
- #define CPPOBJECT_H
-
- #include
-
- //派生自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中。
- 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的属性和方法,并能进行信号槽的关联。
所有QML对象类型都是源自QObject类型,无论它们是由引擎内部实现还是第三方定义。这意味着QML引擎可以使用Qt元对象系统动态实例化任何QML对象类型并检查创建的对象。
这对于从C ++代码创建QML对象非常有用,无论是显示可以直观呈现的QML对象,还是将非可视QML对象数据集成到C ++应用程序中。一旦创建了QML对象,就可以从C ++中检查它,以便读取和写入属性,调用方法和接收信号通知。
可以使用QQmlComponent或QQuickView来加载QML文档。QQmlComponent将QML文档作为为一个C++对象加载,然后可以从C++ 代码进行修改。QQuickView也可以这样做,但由于QQuickView是一个基于QWindow的派生类,加载的对象也将可视化显示,QQuickView通常用于将一个可视化的QML对象集成到应用程序的用户界面中。
- 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中才会做这些操作,但是混合两个框架很多坑。下面是测试输出结果: