1. 程式人生 > >在C++中與QML物件互動

在C++中與QML物件互動

        所有的 QML 物件型別 - 無論由引擎內部實現還是由第三方源定義,都是 QObject 派生的型別。這意味著,QML 引擎可以使用 Qt 元物件系統動態例項化任何 QML 物件型別並檢查建立的物件。

       這對於在 C++ 中建立 QML 物件非常有用,無論是顯示一個視覺化呈現的 QML 物件,還是將非可視 QML 物件資料整合到 C++ 應用程式中。一旦 QML 物件被建立,就可以從 C++ 中檢查它,以便讀取和寫入屬性、呼叫方法、以及接收訊號通知。

在C++載入QML物件

       可以使用 QQuickView 或 QQmlComponent 來載入 QML 文件。QQmlComponent 將 QML 文件載入為一個 C++ 物件,然後可以從 C++ 程式碼中修改該物件。QQuickView 也做到了這一點,但由於 QQuickView 是一個基於 QWindow 的派生類,載入的物件也將被渲染至視覺化顯示,QQuickView 通常用於將一個視覺化的 QML 物件整合到應用程式的使用者介面中。

      例如,有一個 QML 檔案,如下所示:

import QtQuick 2.3

Item {
    width: 100; height: 100
}

    可以使用 QQmlComponent 或 QQuickView 的 C++ 程式碼載入該 QML 文件。當使用 QQmlComponent 時,需要呼叫 QQmlComponent::create() 來建立元件的新例項:

// 使用 QQmlComponent
QQmlEngine engine;
QQmlComponent component(&engine, QUrl("qrc:/main.qml"));
QObject *object = component.create();
//...
delete object;

       而 QQuickView 會自動建立元件的例項,該例項可以通過 QQuickView::rootObject() 來訪問:

// 使用 QQuickView
QQuickView view;
view.setSource(QUrl("qrc:/main.qml"));
view.show();
QObject *object = view.rootObject();

      例項(object)建立後,就可以使用 QObject::setProperty() 或者 QQmlProperty 來修改其屬性:

object->setProperty("width", 300);
QQmlProperty(object, "width").write(300);

     或者,將 object 轉換為其實際型別,並使用編譯時安全性呼叫方法。在這種情況下,main.qml 的基本物件是一個 Item,由 QQuickItem 類定義:

QQuickItem *item = qobject_cast<QQuickItem*>(object);
item->setWidth(300);

根據objectName 訪問載入的QML物件

      QML 元件實質上是具有子物件的物件樹,子物件有兄弟,也有孩子。可以使用 QObject::objectName 屬性和 QObject::findChild() 來定位 QML 元件的子物件。

        例如,如果 QML 中的根 Item 有一個 Rectangle 子項:

// main.qml
import QtQuick 2.3

Item {
    width: 100; height: 100

    Rectangle {
        anchors.fill: parent
        objectName: "rect"
    }
}

     可以通過這樣來定位孩子:

QObject *rect = object->findChild<QObject*>("rect");
if (rect)
    rect->setProperty("color", "red");

     注意: 一個物件可能有多個具有相同 objectName 的子項,這種情況下,QObject::findChildren() 可用於查詢具有匹配 objectName 的所有子項。

     警告: 雖然可以使用 C++ 深入物件樹中訪問和操作 QML 物件,但建議不要在應用程式測試和原型設計之外採用此方法。QML 和 C++ 整合的一個優勢是實現 QML 使用者介面獨立於 C++ 邏輯和資料集後端,如果 C++ 端深入到 QML 元件中直接操作它們,這種策略就會被打破。對於 C++ 實現,最好儘可能少地瞭解 QML 使用者介面實現和 QML 物件樹的組成。

在C++中訪問QML物件型別的成員

屬性

      QML 中宣告的任何屬性都可以從 C++ 中訪問。

       例如,下面的 QML 聲明瞭一個簡單的字串:

// main.qml
import QtQuick 2.3

Item {
    property string hey: "Hello, Qter!"
}

      在 C++ 中,屬性 hey 的值可以使用 QQmlProperty 來設定和讀取,也可使用 QObject::setProperty() 和 QObject::property():

QQmlEngine engine;
QQmlComponent component(&engine, QUrl("qrc:/main.qml"));
QObject *object = component.create();

qDebug() << "Property value:" << QQmlProperty::read(object, "hey").toString();
QQmlProperty::write(object, "hey", "Hello, Qt!");

qDebug() << "Property value:" << object->property("hey").toString();
object->setProperty("hey", "Hello, QML!");
qDebug() << "Property value:" << object->property("hey").toString();

       注意: 應該始終使用 QObject::setProperty()、QQmlProperty 或 QMetaProperty::write() 來改變 QML 的屬性值,以確保 QML 引擎感知屬性的變化。

       例如,有一個自定義型別 PushButton,它有一個 buttonText 屬性。在內部,該屬性以成員變數 m_buttonText 來反映值,可以像下面這樣直接修改成員變數:

// 糟糕的程式碼
QQmlComponent component(engine, "MyButton.qml");
PushButton *button = qobject_cast<PushButton*>(component.create());
button->m_buttonText = "Click me";

      但這並不是一個好主意。由於值被直接改變,繞過了 Qt 元物件系統,QML 引擎並沒有意識到屬性的變化。這意味著,繫結到 buttonText 的屬性不會被更新,並且 onButtonTextChanged 處理程式也不會被呼叫。

呼叫QML方法

       所有的 QML 方法都被暴露給了 Qt 元物件系統,可以使用 QMetaObject::invokeMethod() 從 C++ 中呼叫。從 QML 傳遞的方法引數和返回值在 C++ 中被轉換為 QVariant 值。

      寫一個簡單的 QML,併為其新增一個方法:

// main.qml
import QtQuick 2.3

Item {
    function myQmlFunction(msg) {
        console.log("Got message:", msg)
        return "Hello, Qter!"
    }
}

     然後,在 C++ 中使用 QMetaObject::invokeMethod() 進行呼叫:

// main.cpp
QQmlEngine engine;
QQmlComponent component(&engine, QUrl("qrc:/main.qml"));
QObject *object = component.create();

QVariant returnedValue;  // 返回值
QVariant msg = "Hello, QML!";  // 方法引數
// 呼叫 QML 方法
QMetaObject::invokeMethod(object, "qmlFunction",
                          Q_RETURN_ARG(QVariant, returnedValue),
                          Q_ARG(QVariant, msg));

qDebug() << "QML function returned:" << returnedValue.toString();
delete object;

      注意: QMetaObject::invokeMethod() 的 Q_RETURN_ARG() 和 Q_ARG() 引數必須被指定為 QVariant 型別,因為這是用於 QML 方法引數和返回值的通用資料型別。

連線到QML 訊號

      所有的 QML 訊號在 C++ 中都是可用的,和普通的 Qt C++ 訊號一樣,可以使用 QObject::connect() 進行連線。反過來,任何 C++ 訊號可以由 QML 物件使用訊號處理器來接收。

基本型別的訊號引數

     這裡有一個 QML 元件,包含一個名為 qmlSignal 的訊號,該訊號包含一個 string 型別引數:

// main.qml
import QtQuick 2.3

Item {
    id: item
    width: 100; height: 100

    // QML 訊號
    signal qmlSignal(string msg)

    MouseArea {
        anchors.fill: parent
        // 點選滑鼠,發射訊號
        onClicked: item.qmlSignal("Hello, Qter!")
    }
}

        寫一個 C++ 類,並實現一個槽函式,用於接收 QML 發射的訊號:

// qter.h
#ifndef QTER_H
#define QTER_H

#include <QObject>
#include <qDebug>

class Qter : public QObject
{
    Q_OBJECT

public slots:
    // 槽函式
    void cppSlot(const QString &msg) {
        qDebug() << "Called the C++ slot with message:" << msg;
    }
};

#endif // QTER_H

      將訊號連線至 C++ 物件的槽函式,當發出 qmlSignal 訊號時,就會呼叫:

// qter.h
#include <QGuiApplication>
#include <QQuickView>
#include <QQuickItem>
#include "qter.h"

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQuickView view(QUrl("qrc:/main.qml"));
    QObject *item = view.rootObject();

    Qter qter;
    // 連線訊號槽
    QObject::connect(item, SIGNAL(qmlSignal(QString)), &qter, SLOT(cppSlot(QString)));

    view.show();
    return app.exec();
}

物件型別的訊號引數

      當訊號的引數為 QML 物件型別時,應使用 var 作為引數型別,並且在 C++ 中應使用 QVariant 型別接收該值。

      例如,將上述示例中的引數改為 QML 物件型別:

// main.qml
import QtQuick 2.3

Item {
    id: item
    width: 100; height: 100

    // QML 訊號
    signal qmlSignal(var object)

    MouseArea {
        anchors.fill: parent
        // 點選滑鼠,發射訊號
        onClicked: item.qmlSignal(item)
    }
}

        要接收該訊號,C++ 類中的槽函式的引數應該改為 QVariant 型別:

// qter.h
#ifndef QTER_H
#define QTER_H

#include <QObject>
#include <QQuickItem>
#include <qDebug>

class Qter : public QObject
{
    Q_OBJECT

public slots:
    // 槽函式
    void cppSlot(const QVariant &v) {
        qDebug() << "Called the C++ slot with value:" << v;

        QQuickItem *item = qobject_cast<QQuickItem*>(v.value<QObject*>());
        qDebug() << "Item Size:" << item->width() << item->height();
    }
};

#endif // QTER_H

        當然,連線訊號槽的的引數型別也需要修改:

QObject::connect(item, SIGNAL(qmlSignal(QVariant)), &qter, SLOT(cppSlot(QVariant)));