玩转Qml(5)-Qml与C++交互
本文于
1054
天之前发表,文中内容可能已经过时。
简介
本文是《玩转Qml》系列文章的第五篇,涛哥将教大家,Qml与C++的交互。
Qml已经有很多功能,不过终归会有不够用或不适用的地方,需要通过与C++的交互进行功能扩展。
这回涛哥尝试把所有Qml与C++交互相关的知识点都写出来,做一个透彻、全面的总结。
源码
《玩转Qml》系列文章,配套了一个优秀的开源项目:TaoQuick
github https://github.com/jaredtao/TaoQuick
访问不了或者速度太慢,可以用国内的镜像网站gitee
https://gitee.com/jaredtao/TaoQuick
C++访问Qml
c++访问Qml有两种方式: findChild和 QQmlComponent。
findChild
了解Qt的人都知道,Qt的很多对象是QObject的子类,这些QObject只要设置了parent,就是有父子关系的,会产生一棵 “对象树”。
只要有了根节点,树上的任意节点都可以通过findChild的方式获取到。
写个简单的TaoObject,来示意一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| class TaoObject { public: TaoObject(TaoObject *parent = nullptr) : m_pParent(parent) { if (m_pParent) { m_pParent->appendChild(this); } } ~TaoObject() { for (auto *pObj : m_children) { delete pObj; } m_children.clear(); } const QString &getName() const { return m_name; } void setName(const QString &name) { m_name = name; } TaoObject *findChild(const QString &name) { if (m_name == name) { return this; } for (auto pObj : m_children) { auto resObj = pObj->findChild(name); if (resObj) { return resObj; } } return nullptr; } protected: void appendChild(TaoObject *child) { m_children.push_back(child); }
private: QString m_name; std::vector<TaoObject *> m_children; TaoObject *m_pParent = nullptr; };
|
Qml的基本元素,大多是继承于QQuickItem,而QQuickItem继承于QObject。
所以Qml大多数对象都是QObject的子类,也是可以通过findChild的方式获取到对象指针。
拿到了QObject,可以通过qobject_cast转换成具体的类型来使用,也可以直接用QObject的invok方法。
例如有如下Qml代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Item { id: root ... Rectangle { id: centerRect objectName: "centerRect" property bool canSee: visible signal sayHello() signal sayHelloTo(name) function rotateToAngle(angle) //自定义js函数,旋转至指定角度。可以被C++ invok调用。 { rotation = angle return true; } } ... }
|
那么在C++ 中访问的方式是:
1 2 3 4
| QQuickView view; ... QObject *centerObj = view.rootObject()->findChild<QObject *>("centerRect"); if (!centerObj) { return;}
|
- 如果用QQmlEngine加载qml,就是
1 2 3 4
| QQmlEngine engine; ... QObject *centerObj = engine.rootObject()->findChild<QObject *>("centerRect"); if (!centerObj) { return;}
|
(QObject类型也可以换成QQuickItem 或者其它)
拿到了对象指针,接下来就好办了
访问其属性
1
| bool canSee = centerObj->property("canSee").toBool();
|
发射其信号(其实就是函数调用)
1 2
| QObject::invokeMethod(centerObj, "sayHello"); QObject::invokeMethod(centerObj, "sayHelloTo", Q_ARG(QString, "Tao"))
|
调用其js函数,可以传参数过去,可以取得返回值
1 2
| bool ok; QObject::invokeMethod(centerObj, "rotateToAngle", Q_RETURN_ARG(bool, ok), Q_ARG(qreal 180));
|
这里再补充一下, Qml中给自定义的信号写槽或连接到别的槽(Qml中的槽就是js函数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| Item { id: root ... Rectangle { id: centerRect objectName: "centerRect" signal sayHello() signal sayHelloTo(name)
function rotateToAngle(angle) //自定义js函数,旋转至指定角度 { rotation = angle }
onSayHello: { console.log("hello") } onSayHelloTo: { console.log("hello", name) } Component.onCompleted: { sayHello.connect(root.rootSayHello) } } ...
function rootSayHello(name) { console.log("root: hello", name) } }
|
QQmlComponent
C++中的QQmlComponent可以用来动态加载Qml文件,并可以创建多个实例,
对应Qml中的Component。Qml中还有一个Loader,也可以动态加载并创建单个实例。
(QQmlComponent这种方式不太多见,不过涛哥之前参与过开发一个框架,使用的就是QQmlComponent动态加载Qml,
完全在c++中控制界面的加载,加载效率、内存占用上都比纯Qml优秀。)
来看一个例子:
1 2 3 4 5 6 7
| Rectangle { width: 300 height: width radius: width / 2 color: "red" }
|
1 2 3 4 5 6
| QQmlEngine engine; QQmlComponent component(&engine, QUrl::fromLocalFile("Circle.qml"));
QObject *circleObject = component.create(); QQuickItem *item = qobject_cast<QQuickItem*>(circleObject); 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class BrotherTao : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) public: ... Q_INVOKABLE void sing(); public slots: int playGame(int count);
signals: void hungry(const QString &foodName); ... };
|
这里要说的是,属性、函数参数、返回值的类型,都需要是Qml能识别的类型。
Qt的常用类型已经在Qt内部注册好了,自定义的需要单独注册。
再说说怎么用:
注册分为两种:注册类型和注册实例。
注册类并使用
1
| qmlRegisterTyle<BrotherTao>("BrotherTao",1, 0, "BrotherTao");
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import BrotherTao 1.0 Item { BrotherTao { id: tao onHungry: { if (foodName === "蛋炒饭") { console.log("涛哥要吃蛋炒饭") } else if (foodName === "水饺") { console.log("涛哥要吃水饺") } } } ... Button { onClicked: { tao.sing(); } } Button { onClicked: { let score = tao.sing(3); console.log("涛哥打游戏次数", 3, "得分为", score) } } }
|
注册实例并使用
1 2 3 4 5 6 7 8 9 10 11
| BrotherTao tao;
QQuickView view; ... view.rootContext()->setContextProperty("tao", &tao);
QQmlEngine engine; ... engine..rootContext()->setContextProperty("tao", &tao);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| Item { Connections { target: tao onHungry: { if (foodName === "蛋炒饭") { console.log("涛哥要吃蛋炒饭") } else if (foodName === "水饺") { console.log("涛哥要吃水饺") } } } ... Button { onClicked: { tao.sing(); } } Button { onClicked: { let score = tao.sing(3); console.log("涛哥打游戏次数", 3, "得分为", score) } } }
|