看过了如何在 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";
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...
eleasecallQml.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章