在 C++ 中使用 QML 對象


看過了如何在 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++ 混合編程詳解



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM