看過了如何在 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章