• 1867阅读
  • 2回复

Qml组件化编程5-Qml与C++交互 [复制链接]

上一主题 下一主题
离线dd759378563
 

只看楼主 倒序阅读 楼主  发表于: 2019-05-18

简介

本文是《Qml组件化编程》系列文章的第五篇,涛哥将教大家,Qml与C++的交互。
Qml已经有很多功能,不过终归会有不够用或不适用的地方,需要通过与C++的交互进行功能扩展。
这回涛哥尝试把所有Qml与C++交互相关的知识点都写出来,做一个透彻、全面的总结。
  
顺便说一下,涛哥的TaoQuick项目正式开源了, 系列文章中的所有功能,包括动态换皮肤、切换多语言等等,都集成在了TaoQuick中,
同时涛哥也在TaoQuick中使用了持续集成(CI)技术,目前已经能够自动编译、发布Windows和 Macos平台的软件包,可以在github的Release界面下载体验。
互联网行业很流行的DevOps理念,在TaoQuick项目中得到了最佳的实践。
(linux平台的发布工具linuxdeployqt暂时还有点问题,涛哥后续会搞定的)


地址在这https://github.com/jaredtao/TaoQuick, 赶快去star吧。

注:文章主要发布在涛哥的博客知乎专栏-涛哥的Qt进阶之路


C++访问Qml

c++访问Qml有两种方式: findChild和 QQmlComponent。


findChild

了解Qt的人都知道,Qt的很多对象是QObject的子类,这些QObject只要设置了parent,就是有父子关系的,会产生一棵 “对象树”。
只要有了根节点,树上的任意节点都可以通过findChild的方式获取到。
写个简单的TaoObject,来示意一下:
  1. class TaoObject
  2. {
  3. public:
  4.     //构造函数,传递parent进来
  5.     TaoObject(TaoObject *parent = nullptr) : m_pParent(parent)
  6.     {
  7.         if (m_pParent)
  8.         {
  9.             m_pParent->appendChild(this);
  10.         }
  11.     }
  12.     //析构函数,析构children。即子对象自动回收机制。
  13.     ~TaoObject()
  14.     {
  15.         for (auto *pObj : m_children)
  16.         {
  17.             delete pObj;
  18.         }
  19.         m_chilrren.clear();
  20.     }
  21.     //获取name
  22.     const QString &getName() const { return m_name;}
  23.     //设置name
  24.     void setName(const QString &name) { m_name = name;}
  25.     //查找子Object
  26.     TaoObject * findChild(const QString &name)
  27.     {
  28.         //先检查自己的名字,是否匹配目标名字
  29.         if (m_name == name)
  30.         {
  31.             return this;
  32.         }
  33.         //遍历子Object,查找
  34.         for (auto pObj: m_children)
  35.         {
  36.             //递归调用,深度优先的搜索
  37.             auto resObj = pObj->findChild(name);
  38.             if (resObj)
  39.             {
  40.                 return resObj;
  41.             }
  42.         }
  43.         return nullptr;
  44.     }
  45. protected:
  46.     void appendChild(TaoObject *child)
  47.     {
  48.         m_children.push_back(child);
  49.     }
  50. private:
  51.     //存储名字
  52.     QString m_name;
  53.     //子对象列表
  54.     std::vector<TaoObject *> m_children;
  55.     //父对象指针
  56.     TaoObject *m_pParent = nullptr;
  57. }



Qml的基本元素,大多是继承于QQuickItem,而QQuickItem继承于QObject。
所以Qml大多数对象都是QObject的子类,也是可以通过findChild的方式获取到对象指针。
拿到了QObject,可以通过qobject_cast转换成具体的类型来使用,也可以直接用QObject的invok方法。
例如有如下Qml代码:
  1. Item {
  2.     id: root
  3.     ...
  4.     Rectangle {
  5.         id: centerRect
  6.         objectName: "centerRect"        //必不可少的objectName
  7.         property bool canSee: visible   //自定义属性,可以被C++ invok访问
  8.         signal sayHello()               //自定义信号1,可以被C++ invok调用
  9.         signal sayHelloTo(name)         //自定义信号2,带参数。可以被C++ invok调用。参数的名字要起好,后面通过这个名字来使用参数
  10.         function rotateToAngle(angle)   //自定义js函数,旋转至指定角度。可以被C++ invok调用。
  11.         {
  12.             rotation = angle
  13.             return true;
  14.         }
  15.     }
  16.     ...
  17. }



那么在C++ 中访问的方式是:
  • 如果用QQuickView加载qml,就是
  1. QQuickView view;
  2. ...
  3. QObject *centerObj = view.rootObject()->findChild<QObject *>("centerRect");
  4. if (!centerObj) { return;}


  • 如果用QQmlEngine加载qml,就是

  1. QQmlEngine engine;
  2. ...
  3. QObject *centerObj = engine.rootObject()->findChild<QObject *>("centerRect");
  4. if (!centerObj) { return;}


(QObject类型也可以换成QQuickItem 或者其它)
拿到了对象指针,接下来就好办了
访问其属性
  1. bool canSee = centerObj->property("canSee").toBool();



发射其信号(其实就是函数调用)
  1. QObject::invokeMethod(centerObj, "sayHello");
  2. QObject::invokeMethod(centerObj, "sayHelloTo", Q_ARG(QString, "Tao"))


调用其js函数,可以传参数过去,可以取得返回值
  1. bool ok;
  2. QObject::invokeMethod(centerObj, "rotateToAngle", Q_RETURN_ARG(bool, ok), Q_ARG(qreal 180));






这里再补充一下, Qml中给自定义的信号写槽或连接到别的槽(Qml中的槽就是js函数):
  1. Item {
  2.     id: root
  3.     ...
  4.     Rectangle {
  5.         id: centerRect
  6.         objectName: "centerRect" //必不可少的objectName
  7.         signal sayHello()       //自定义信号1
  8.         signal sayHelloTo(name) //自定义信号2,带参数。参数的名字要起好,后面通过这个名字来使用参数
  9.         function rotateToAngle(angle) //自定义js函数,旋转至指定角度
  10.         {
  11.             rotation = angle
  12.         }
  13.         onSayHello: {               //信号1的槽
  14.             console.log("hello")
  15.         }
  16.         onSayHelloTo: {             //信号2的槽,直接用信号定义时的参数名字name作为关键字访问参数
  17.             console.log("hello", name)
  18.         }
  19.         Component.onCompleted: {
  20.             //信号2 连接到 root的函数。参数会自动匹配。
  21.             sayHello.connect(root.rootSayHello)
  22.         }
  23.     }
  24.     ...
  25.     function rootSayHello(name) {
  26.         console.log("root: hello", name)
  27.     }
  28. }




QQmlComponent

C++中的QQmlComponent可以用来动态加载Qml文件,并可以创建多个实例,
对应Qml中的Component。Qml中还有一个Loader,也可以动态加载并创建单个实例。
(QQmlComponent这种方式不太多见,不过涛哥之前参与过开发一个框架,使用的就是QQmlComponent动态加载Qml,
完全在c++中控制界面的加载,加载效率、内存占用上都比纯Qml优秀。)
来看一个例子:
  1. // Circle.qml
  2. Rectangle {
  3.     width: 300
  4.     height: width
  5.     radius: width / 2
  6.     color: "red"
  7. }
  1. QQmlEngine engine;
  2. QQmlComponent component(&engine, QUrl::fromLocalFile("Circle.qml"));
  3. QObject *circleObject = component.create();
  4. QQuickItem *item = qobject_cast<QQuickItem*>(circleObject);
  5. int width = item->width();

拿到对象指针,就和前面的一样了,这里不再赘述了。


Qml访问C++

Qml要访问C++的内容,需要先从C++把要访问的内容注册进Qml。
先说说能用哪些:
注册过后,Qml中可以访问的内容,包括 Q_INVOKABLE 修饰的函数、枚举、 QObject的属性 信号 槽
Q_INVOKABLE 函数可以用在普通的结构体或者类中,但是这种用法不常见/不方便。常见的是在QObject的子类中,给非槽函数设置为Q_INVOKABLE
枚举的注册Qt帮助文档很详细,而且5.10以后可以在qml中定义枚举了,这里涛哥就不展开了。
QObject的属性 信号 槽,都是可以通过注册后,在qml中使用的。信号、槽都可以带参数,槽可以有返回值。
  1. class BrotherTao : public QObject
  2. {
  3.     Q_OBJECT //这个宏一定要写上。不写可能的后果是,moc生成失败,信号 槽实现不了,编译过不了。
  4.     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) //自定义属性,操作包括: 读、写和通知。Qml可以读写、获取通知
  5. public:
  6.     ...
  7.     //唱歌
  8.     Q_INVOKABLE void sing();        //invok函数,可以被Qml调用。可以带参数和返回值
  9. public slots:
  10.     //打游戏,参数为次数,返回值为得分。
  11.     int playGame(int count);        //槽函数,可以被Qml调用。可以带参数和返回值
  12. signals:
  13.     //肚子饿了。参数为想吃的东西。
  14.     void hungry(const QString &foodName);                  //信号,可以被Qml接收。
  15.     ...
  16. };



这里要说的是,属性、函数参数、返回值的类型,都需要是Qml能识别的类型。
Qt的常用类型已经在Qt内部注册好了,自定义的需要单独注册。
再说说怎么用:
注册分为两种:注册类型和注册实例。


注册类并使用

  1. qmlRegisterTyle<BrotherTao>("BrotherTao",1, 0, "BrotherTao");

  1. import BrotherTao 1.0
  2. Item {
  3.     BrotherTao {         //实例化一个对象
  4.         id: tao
  5.         onHungry: {     //给信号写个槽函数
  6.             if (foodName === "蛋炒饭") {      //示意一下,不要在意吃啥。
  7.                 console.log("涛哥要吃蛋炒饭")
  8.             } else if (foodName === "水饺") {
  9.                 console.log("涛哥要吃水饺")
  10.             }
  11.         }
  12.     }
  13.     ...
  14.     Button {
  15.         onClicked: {
  16.             //这个按钮按下的时候,涛哥开始唱歌
  17.             tao.sing();
  18.         }
  19.     }
  20.     Button {
  21.         onClicked: {
  22.             //这个按钮按下的时候,涛哥开始打游戏
  23.             let score = tao.sing(3);
  24.             console.log("涛哥打游戏次数", 3, "得分为", score)
  25.         }
  26.     }
  27. }




注册实例并使用
  1. BrotherTao tao;     //C++中创建的实例
  2. //如果用QQ'u'ic'kView加载Qml
  3. QQuickView view;
  4. ...
  5. view.rootContext()->setContextProperty("tao", &tao);    //注意这个名字不要用大写字母开头,规则和Qml中的id不能用大写字母开头一样。
  6. //如果用QQmlEngine加载Qml
  7. QQmlEngine engine;
  8. ...
  9. engine..rootContext()->setContextProperty("tao", &tao); //注意这个名字不要用大写字母开头,规则和Qml中的id不能用大写字母开头一样。

  1. //这种不用再import了
  2. Item {
  3.     Connections {        //通过connectins连接信号
  4.         target: tao     //指定target
  5.         onHungry: {     //给信号写个槽函数
  6.             if (foodName === "蛋炒饭") {      //示意一下,不要在意吃啥。
  7.                 console.log("涛哥要吃蛋炒饭")
  8.             } else if (foodName === "水饺") {
  9.                 console.log("涛哥要吃水饺")
  10.             }
  11.         }
  12.     }
  13.     ...
  14.     Button {
  15.         onClicked: {
  16.             //这个按钮按下的时候,涛哥开始唱歌
  17.             tao.sing();
  18.         }
  19.     }
  20.     Button {
  21.         onClicked: {
  22.             //这个按钮按下的时候,涛哥开始打游戏
  23.             let score = tao.sing(3);
  24.             console.log("涛哥打游戏次数", 3, "得分为", score)
  25.         }
  26.     }
  27. }



转载声明

文章出自涛哥的博客
文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可, 转载请注明出处, 谢谢合作 © 涛哥


联系方式

  
作者涛哥
开发理念弘扬鲁班文化,传承工匠精神
博客https://jaredtao.github.io
githubhttps://github.com/jaredtao
知乎https://www.zhihu.com/people/wentao-jia
邮箱jared2020@163.com
微信xsd2410421
QQ759378563
  
请放心联系我,乐于提供咨询服务,也可洽谈商务合作相关事宜。


涛哥是个Qml高手,著有《Qml组件化编程》《Qml特效》系列教程,见知乎专栏-Qt进阶之路:https://zhuanlan.zhihu.com/TaoQt
或微信公众号:Qt进阶之路
离线dd759378563

只看该作者 1楼 发表于: 2019-05-18
代码格式已经修改
涛哥是个Qml高手,著有《Qml组件化编程》《Qml特效》系列教程,见知乎专栏-Qt进阶之路:https://zhuanlan.zhihu.com/TaoQt
或微信公众号:Qt进阶之路
离线big_mouse

只看该作者 2楼 发表于: 2020-04-15
快速回复
限100 字节
 
上一个 下一个