看过了如何在 QML 中使用 C++ 类型或对象,现在来看如何在 C++ 中使用 QML 对象。

我们可以使用 QML 对象的信号、槽,访问它们的属性,都没有问题,因为很多 QML 对象对应的类型,原本就是 C++ 类型,比如 Image 对应 QQuickImage , Text 对应 QQuickText……但是,这些与 QML 类型对应的 C++ 类型都是私有的,你写的 C++ 代码也不能直接访问。肿么办?

Qt 最核心的一个基础特性,就是元对象系统,通过元对象系统,你可以查询 QObject 的某个派生类的类名、有哪些信号、槽、属性、可调用方法等等信息,然后也可以使用 QMetaObject::invokeMethod() 调用 QObject 的某个注册到元对象系统中的方法。而对于使用 Q_PROPERTY 定义的属性,可以使用 QObject 的 property() 方法访问属性,如果该属性定义了 WRITE 方法,还可以使用 setProperty() 修改属性。所以只要我们找到 QML 环境中的某个对象,就可以通过元对象系统来访问它的属性、信号、槽等。

一、查找一个对象的孩子

QObject 类的构造函数有一个 parent 参数,可以指定一个对象的父亲, QML 中的对象其实借助这个组成了以根 item 为父的一棵对象树。

而 QObject 定义了一个属性 objectName ,这个对象名字属性,就可以用于查找对象。现在该说到查找对象的方法了:findChild()findChildren()。它们的函数原型如下:

T QObject::findChild(const QString & name = QString(),\
Qt::FindChildOptions options = \
Qt::FindChildrenRecursively) const; QList<T> QObject::findChildren(const QString & name = \
QString(), Qt::FindChildOptions options = \
Qt::FindChildrenRecursively) const; QList<T> QObject::findChildren(const QRegExp & regExp, \
Qt::FindChildOptions options = \
Qt::FindChildrenRecursively) const; QList<T> QObject::findChildren(const QRegularExpression & re,\
Qt::FindChildOptions options = \
Qt::FindChildrenRecursively) const;

都是模板方法,从命名上也可以看出,一个返回单个对象,一个返回对象列表。闲话少说,现在让我们看看如何查询一个或多个对象,我们先以 Qt Widgets 为例来说明用法哈。

示例 1 :

QPushButton *button = parentWidget->findChild<QPushButton *>("button1");

查找 parentWidget 的名为 "button1" 的类型为 QPushButton 的孩子。

示例 2 :

QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");

返回 parentWidget 所有名为 "widgetname" 的 QWidget 类型的孩子列表。

二、使用元对象调用一个对象的方法

QMetaObject 的 invokeMethod() 方法用来调用一个对象的信号、槽、可调用方法。它是个静态方法,其函数原型如下(省略了部分参数):

bool QMetaObject::invokeMethod(QObject * obj, const char * member, Qt::ConnectionType type, QGenericReturnArgument ret, ...) [static]
  • 第一个参数是被调用对象的指针。
  • 第二个参数是方法名字。
  • 第三个参数是连接类型,看到这里你就知道, invokeMethod 为信号与槽而生,你可以指定连接类型,如果你要调用的对象和发起调用的线程是同一个线程,那么可以使用 Qt::DirectConnection 或 Qt::AutoConnection 或 Qt::QueuedConnection ,如果被调用对象在另一个线程,那么建议你使用 Qt::QueuedConnection 。
  • 第四个参数用来接收返回指。
  • 然后就是多达 10 个可以传递给被调用方法的参数(这里省略了)。嗯,看来信号与槽的参数个数是有限制的,不能超过 10 个。

对于要传递给被调用方法的参数,使用 QGenericArgument 来表示,你可以使用 Q_ARG 宏来构造一个参数,它的定义是:

QGenericArgument Q_ARG( Type, const Type & value)

返回类型是类似的,使用 QGenericReturnArgument 表示,你可以使用 Q_RETURN_ARG 宏来构造一个接收返回指的参数,它的定义是:

QGenericReturnArgument Q_RETURN_ARG( Type, Type & value)

好啦,总算把这个天杀的函数介绍完了,下面我们看看怎么用。

假设一个对象有这么一个槽 compute(QString, int, double) ,返回一个 QString 对象,那么你可以这么调用(同步方式):

QString retVal;
QMetaObject::invokeMethod(obj, "compute", Qt::DirectConnection,
Q_RETURN_ARG(QString, retVal),
Q_ARG(QString, "sqrt"),
Q_ARG(int, 42),
Q_ARG(double, 9.7));

如果你要让一个线程对象退出,可以这么调用(队列连接方式):

QMetaObject::invokeMethod(thread, "quit",
Qt::QueuedConnection);

三、callQml 示例

现在让我们创建一个新的项目,名字是 callQml ,添加 changeColor.h 、 changeColor.cpp 两个文件。 main.qml 内容如下:

import QtQuick 2.2
import QtQuick.Controls 1.2
import QtQuick.Window 2.1 Window {
objectName: "rootObject";
width: 360;
height: 360;
visible: true;
Text {
objectName: "textLabel";
text: "Hello World";
anchors.centerIn: parent;
font.pixelSize: 26;
} Button {
anchors.right: parent.right;
anchors.rightMargin: 4;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 4;
text: "quit";
objectName: "quitButton";
}
}

我们给根元素起了个名字 "rootRect" ,给退出按钮起了个名字 "quitButton" ,给文本起了名字 "textLabel" ,我们会在 C++ 代码中通过这些个名字来查找对应的对象并改变它们。

现在来看看 main.cpp :

#include <QtGui/QGuiApplication>
#include <QQmlApplicationEngine>
#include "changeColor.h"
#include <QMetaObject>
#include <QDebug>
#include <QColor>
#include <QVariant> int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv); QQmlApplicationEngine engine;
engine.load(QUrl("qrc:///main.qml")); QObject * root = NULL;//= qobject_cast<QObject*>(viewer.rootObject());
QList<QObject*> rootObjects = engine.rootObjects();
int count = rootObjects.size();
qDebug() << "rootObjects- " << count;
for(int i = 0; i < count; i++)
{
if(rootObjects.at(i)->objectName() == "rootObject")
{
root = rootObjects.at(i);
break;
}
}
new ChangeQmlColor(root);
QObject * quitButton = root->findChild<QObject*>("quitButton");
if(quitButton)
{
QObject::connect(quitButton, SIGNAL(clicked()), &app, SLOT(quit()));
} QObject *textLabel = root->findChild<QObject*>("textLabel");
if(textLabel)
{
//1. failed call
bool bRet = QMetaObject::invokeMethod(textLabel, "setText", Q_ARG(QString, "world hello"));
qDebug() << "call setText return - " << bRet;
textLabel->setProperty("color", QColor::fromRgb(255,0,0));
bRet = QMetaObject::invokeMethod(textLabel, "doLayout");
qDebug() << "call doLayout return - " << bRet;
} return app.exec();
}

在一开始我通过 viewer.rootObject() ,获取到了作为根元素的 Rectangle ,然后把它交给一个 ChangeQmlColor 对象,该对象会内部通过一个定时器,一秒改变一次传入对象的颜色。

紧接着,我使用 QObject 的 findChild() 找到了 quitButton 按钮,把它的 clicked() 信号连接到 QGuiApplication 的 quit() 槽上。所以你点击这个按钮,应用就退出了。

后来,我又通过名字 "textLabel" 找到了 textLabel 对象。首先我企图使用 invodeMethod() 调用 setText() 方法来改变 textLabel 的文本,这个注定是会失败的,因为 QML 中的Text 对象对应 C++ QQuickText 类,而 QQuickText 没有名为 setText 的槽或者可调用方法。我查看了头文件 qquicktext_p.h ,发现它有一个使用 Q_INVOKABLE 宏修饰的 doLayout() 的方法,所以后来我又调用 doLayout() ,这次成功了。

下图是运行效果:

Hello World 这行字变成了红色,是因为我在 main() 函数中使用 setProperty 修改了 textLabel 的 color 属性。

下面是 Qt Creator 应用程序输出窗口的信息,可以验证对 Text 方法的调用是否成功:

Starting D:\projects\...\release\callQml.exe...
QMetaObject::invokeMethod: No such method QQuickText::setText(QString)
call setText return - false
call doLayout return - true

好啦,最后看看界面背景为么变成了浅绿色。这正是下面这行代码的功劳:

new ChangeQmlColor(rootItem);

它以 rootItem 为参数创建了一个 ChangeQmlColor 对象,而 ChangeQmlColor 类会改变传给它的对象的颜色。

ChangeQmlColor 类定义如下:

#ifndef CHANGECOLOR_H
#define CHANGECOLOR_H
#include <QObject>
#include <QTimer> class ChangeQmlColor : public QObject
{
Q_OBJECT
public:
ChangeQmlColor(QObject *target, QObject *parent = 0);
~ChangeQmlColor(); protected slots:
void onTimeout(); private:
QTimer m_timer;
QObject *m_target;
}; #endif

实现文件 changeColor.cpp :

#include "changeColor.h"
#include <QDateTime>
#include <QColor>
#include <QVariant> ChangeQmlColor::ChangeQmlColor(QObject *target, QObject *parent)
: QObject(parent)
, m_timer(this)
, m_target(target)
{
qsrand(QDateTime::currentDateTime().toTime_t());
connect(&m_timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
m_timer.start(1000);
} ChangeQmlColor::~ChangeQmlColor()
{} void ChangeQmlColor::onTimeout()
{
QColor color = QColor::fromRgb(qrand()%256, qrand()%256, qrand()%256);
m_target->setProperty("color", color);
}

参考:

《Qt Quick核心编程》第11章

Qt Quick 之 QML 与 C++ 混合编程详解

最新文章

  1. 通过bitmap对100w数字进行排序去重
  2. Hibernate之对象的三种状态
  3. IO完成端口
  4. 使用easyui时 进入一个新页面 前要经过一个页面混乱的时候 才到正常的页面去
  5. 最新微信小程序(应用号)视频教程,实战教程
  6. PAT 08-2 求矩阵的局部最大值
  7. NOIP2005 过河
  8. jsp验证码 (通过单击验证码或超链接换验证码)
  9. html5中的postMessage解决跨域问题
  10. oracle 数据字典和动态性能视图
  11. UE4中FString转UTF8及UTF8转FString
  12. java入门day04-方法简述
  13. Winform中使用WPF控件并动态读取Xaml
  14. 洛谷P3567 KUR-Couriers [POI2014] 主席树/莫队
  15. $mount方法是用来挂载我们的Vue.extend扩展的
  16. C# 压缩文件 的创建
  17. 【DB2】SQL优化
  18. VMWare 桥接模式
  19. git fetch 和 git pull 的区别
  20. 北京Uber优步司机奖励政策(3月11日)

热门文章

  1. 用Python完成毫秒级抢单,助你秒杀淘宝大单
  2. C#编辑xml文件
  3. Xamarin移动开发备忘
  4. Python对csv排序
  5. Restful API接口规范
  6. selenium和AutoIt工具辅助下载和上传
  7. 原生 JavaScript 代替 jQuery【转】
  8. Python 报错 MySQLdb._exceptions.OperationalError: (2059, )
  9. 打开前端工程 Node Sass does not yet support your current environment: Windows 64-bit
  10. 以太网PHY寄存器分析【转】