C++中如何訪問qml

作者:billy
版權聲明:著作權歸作者所有,商業轉載請聯繫作者獲得授權,非商業轉載請註明出處

前言

上一章節我們介紹了在 Qt中如何註冊一個C++類到qml,也就是在qml中使用C++的類。那麼這一章節我們要倒過來,看看在C++中是如何去訪問qml的,包括修改qml中控件的屬性,以及動態創建qml組件。

Qt Quick 加載QML文檔過程解析

先來看一下 Qt Quick 中常用的一些類的繼承關係

inherit
inherit
inherit
inherit
inherit
inherit
inherit
inherit
QQuickItem
QObject
QQuickPaintedItem
QWindow
QQuickWindow
QQuickView
QCoreApplication
QGuiApplication
QApplication
inherit
inherit
inherit
inherit
inherit
inherit
QJSEngine
QObject
QQmlEngine
QQmlApplicationEngine
QQmlContext
QQmlComponent
QQmlExpression

介紹幾個重要的成員:

  1. QQmlEngine
    每個QML組件都在 QQmlContext 中實例化。QQmlContext 對於向QML組件傳遞數據至關重要。在QML中上下文按層次結構排列,此層次結構由 QQmlEngine 管理。在創建任何QML組件之前,應用程序必須已創建 QQmlEngine 才能訪問QML上下文

  2. QQmlContext
    上下文允許數據暴露給由QML引擎實例化的QML組件。每個 QQmlContext 都包含一組不同於其QObject屬性的屬性,這些屬性允許按名稱將數據顯式綁定到上下文。通過調用 QQmlContext::setContextProperty() 來定義和更新上下文屬性。

  3. QQmlComponent
    組件是可重用的、封裝的QML類型,具有定義良好的接口。可以從QML文件創建QQmlComponent實例。

  4. QQmlExpression
    動態執行表達式 QQmlExpression, 允許客戶端在C++中利用一個特定的QML上下文執行JavaScript表達式,表達式執行的結果以QVariant的形式返回,並且遵守QML引擎確定的轉換規則。

  5. QQuickWindow
    QQuickWindow 提供與 QQuickItems 的場景交互和顯示所需的圖形場景管理。QQuickWindow總是有一個不可見的根項。若要將項添加到此窗口,請將項重新分配到根項或場景中的現有項。

  6. QQuickView
    這是QQuickWindow的一個子類,當給定主源文件的URL時,它將自動加載和顯示QML場景。或者,您可以使用QQmlComponent實例化自己的對象,並將它們放置在手動設置的QQuickWindow中。

Qt Quick 加載一個 QML 對象的條件:

  • 唯一的 一個 QQmlEngine 引擎用於加載QML文件
  • 唯一的 一個 QQmlContext 用於QML對象實例化和表達式執行的上下文環境
  • 一個 QQmlComponent 組件用於實例化第一個QML對象

第一種加載方式:QQmlEngine + QQmlComponent

#include <QApplication>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQuickWindow>
#include <QDebug>

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

    QQmlEngine engine;
    QQmlComponent *component = new QQmlComponent(&engine);

    QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));

    component->loadUrl(QUrl("qrc:/main.qml"));

    if (!component->isReady() ) {
        qWarning("%s", qPrintable(component->errorString()));
        return -1;
    }

    QObject *topLevel = component->create();
    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);

    QSurfaceFormat surfaceFormat = window->requestedFormat();
    window->setFormat(surfaceFormat);
    window->show();

    return app.exec();
}

使用 QQmlComponent 來實例化 QML 對象的注意事項:

  1. 要在 QQmlEngine 實例不可用的代碼中創建組件實例,可以使用qmlContext() 或qmlEngine()
  2. 必須通過 component.create() 來創建實例
  3. 創建完成之後需要把對象放入窗口下才能顯示

第二種加載方式:QQmlApplicationEngine + Window

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}


import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
}

QQmlApplicationEngine 是Qt中創建 Qt Quick 工程默認使用的引擎。
QQmlApplicationEngine 聯合了 QQmlEngine 和 QmlComponent 去加載單獨的QML文件,QQmlApplicationEngine 加載以 Window 爲根對象的QML文檔,QML文檔擁有窗口的完整控制權

第三種加載方式:QQuickView + Item

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    QQuickView *view = new QQuickView;
    view->setSource(QUrl("qrc:/main.qml"));
    view->show();
    return app.exec();
}


import QtQuick 2.12

Item {
    visible: true
    width: 640
    height: 480
}

不同於 QQmlApplicationEngine 的是,使用 QQuickView 顯示QML文檔,對窗口的控制權全部在C++代碼

C++中各種加載方式通過 object name 修改qml中的屬性

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QQuickWindow>
#include <QQuickView>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

    // 1. 使用 QQmlApplicationEngine
//    QQmlApplicationEngine engine;
//    const QUrl url(QStringLiteral("qrc:/main.qml"));
//    engine.load(url);

//    QObject *rect = engine.rootObjects().at(0)->findChild<QObject*>("rect");
//    if (rect)
//        rect->setProperty("color", "red");



    // 2. 使用 QQmlEngine + QQmlComponent
//    QQmlEngine engine;
//    QQmlComponent *component = new QQmlComponent(&engine);
//    component->loadUrl(QUrl("qrc:/main.qml"));
//    QObject *topLevel = component->create();
//    QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel);
//    window->show();

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



    // 3. 使用 QQuickView
    QQuickView *view = new QQuickView;
    view->setSource(QUrl("qrc:/main.qml"));
    view->show();

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

    return app.exec();
}


import QtQuick 2.12
import QtQuick.Window 2.12

Item {
    visible: true
    width: 640
    height: 480

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

Qt中的對象是一種樹形的結構,對Qt對象樹模型不瞭解的同學可以參考 Qt 對象樹。所以我們只要知道了一個節點,就可以通過 findChild 來找到他的子節點。以上三種方式都可以通過 findChild 來找到object然後修改屬性。前面兩種方式,qml文件的根對象是Window,而最後一種是Item

C++ 調用qml中的 function

import QtQuick 2.12

Item {
    function myQmlFunction(msg) {
        console.log("Got message:", msg)
        return "some return value"
    }
}


#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);

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

    QVariant returnedValue;
    QVariant msg = "Hello from C++";
    QMetaObject::invokeMethod(object, "myQmlFunction",
                              Q_RETURN_ARG(QVariant, returnedValue),
                              Q_ARG(QVariant, msg));

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

    return app.exec();
}

運行結果:
qml: Got message: Hello from C++
QML function returned: "some return value"

C++ 連接qml中的信號

myclass.h

#ifndef MYCLASS_H
#define MYCLASS_H

#include <QObject>
#include <QDebug>

class MyClass : public QObject
{
    Q_OBJECT
public:
    explicit MyClass(QObject *parent = nullptr);

signals:

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

#endif // MYCLASS_H
main.qml

import QtQuick 2.0

Item {
    width: 100; height: 100

    signal qmlSignal(string msg)

    MouseArea {
        anchors.fill: parent
        onClicked: qmlSignal("Hello from QML")
    }
}
main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#include <QQuickItem>
#include "myclass.h"

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

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

    MyClass myClass;
    QObject::connect(item, SIGNAL(qmlSignal(QString)),
                     &myClass, SLOT(cppSlot(QString)));

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

運行結果:
Called the C++ slot with message: "Hello from QML"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章