C++ 與 QML 之間進行資料互動的幾種方法

2023-10-26 18:01:02

一、屬性繫結

這是最簡單的方式,可以在QML中直接繫結C++ 物件的屬性。通過在C++ 物件中使用Q_PROPERTY宏定義屬性,然後在QML中使用繫結語法將屬性與QML元素關聯起來。

  1. person.h

    #include <QObject>
    
    class Person : public QObject
    {
        Q_OBJECT
        /* 使用 Q_PROPERTY 定義互動的屬性 */
        Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
        Q_PROPERTY(int age READ getAge WRITE setAge NOTIFY ageChanged)
    
    public:
        explicit Person(QObject *parent = nullptr)
            : QObject(parent), m_name(""), m_age(0)
        {
        }
    
        /* 為屬性提供 getter 和 setter 方法 */
        QString getName() const { return m_name; }
        void setName(const QString& name) { m_name = name; emit nameChanged(); }
    
        int getAge() const { return m_age; }
        void setAge(int age) { m_age = age; emit ageChanged(); }
    
    signals:
        /* 訊號與屬性對應,通過訊號通知其他物件屬性的變化 */
        void nameChanged();
        void ageChanged();
    
    private:
        QString m_name;
        int m_age;
    };
    
    
  2. main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "person.h"
    
    int main(int argc, char *argv[])
    {
        /* 啟用Qt應用程式的高DPI縮放功能 */
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        /* 建立一個Qt應用程式的範例 */
        QGuiApplication app(argc, argv);
    
        // 建立Person物件
        Person person;
    
        QQmlApplicationEngine engine;
    
        /* 將Person物件作為QML上下文屬性 */
        engine.rootContext()->setContextProperty("person", &person);
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        /* 將 QQmlApplicationEngine 物件的 objectCreated 訊號連線到一個 lambda 函數上 */
        /* lambda 函數用於在 QML 檔案中的根物件被建立時進行處理,檢查物件是否成功建立,如果建立失敗則退出應用程式 */
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        /* 載入QML檔案並顯示使用者介面 */
        engine.load(url);
    
        return app.exec();
    }
    
    
    
  3. main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.5
    
    Window {
        visible: true
        width: 480
        height: 800
        title: qsTr("Hello World")
    
        Column {
            spacing: 10
    
            TextField {
                placeholderText: "請輸入姓名"
                text: person.name // 與Person物件的name屬性繫結
                onTextChanged: person.name = text // 當文字改變時,更新Person物件的name屬性
            }
    
            Slider {
                from: 0
                to: 100
                value: person.age // 與Person物件的age屬性繫結
                onValueChanged: person.age = value // 當滾軸值改變時,更新Person物件的age屬性
            }
    
            Text {
                text: "姓名:" + person.name
            }
    
            Text {
                text: "年齡:" + person.age
            }
        }
    
    }
    
    

二、訊號與槽

C++ 物件可以發出訊號,而QML中的元素可以連線到這些訊號上。這樣,當C++ 物件的狀態發生變化時,可以通過訊號與槽機制將這些變化傳遞給QML介面。

  1. myobject.h

    #include <QObject>
    #include <QtDebug>
    
    class MyObject : public QObject
    {
        Q_OBJECT
    
    public:
        explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}
    
    signals:
        void mySignal(QString message);
    
    public slots:
        void mySlot(const QString& message) { qDebug() << "Received message from QML:" << message; emit mySignal("Hello from C++");}
    };
    
    
  2. main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "myobject.h"
    
    int main(int argc, char *argv[])
    {
        /* 啟用Qt應用程式的高DPI縮放功能 */
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        /* 建立一個Qt應用程式的範例 */
        QGuiApplication app(argc, argv);
    
        /* 將自定義 C++ 型別註冊到 QML 中的函數, 將自定義 C++ 型別註冊到 QML 中的函數 */
        qmlRegisterType<MyObject>("com.example", 1, 0, "MyObject");
    
        QQmlApplicationEngine engine;
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        /* 將 QQmlApplicationEngine 物件的 objectCreated 訊號連線到一個 lambda 函數上 */
        /* lambda 函數用於在 QML 檔案中的根物件被建立時進行處理,檢查物件是否成功建立,如果建立失敗則退出應用程式 */
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        /* 載入QML檔案並顯示使用者介面 */
        engine.load(url);
    
        return app.exec();
    }
    
    
  3. main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.5
    import com.example 1.0
    
    Window {
        visible: true
        width: 480
        height: 800
        title: qsTr("Hello World")
    
        /* 定義 sendToCpp 訊號 */
        signal sendToCpp(string message)
    
        /* Connections 元件用於連線 myObject 的 onMySignal 訊號 */
        Connections {
            target: myObject
            onMySignal: console.log("Received message from C++:", message)
        }
    
        MyObject {
            id: myObject
            /* 將 onMySignal 訊號傳遞到 sendToCpp訊號上,便於 QML 處理 */
            onMySignal: sendToCpp(message)
        }
    
        Button {
            text: "Send message to C++"
            anchors.centerIn: parent
            /* 單擊按鈕時,會將訊號傳遞到 C++ 的 mySlot 槽上 */
            onClicked: myObject.mySlot("Hello from QML")
        }
    }
    
    

三、模型檢視

模型檢視(Model-View):可以使用C++ 中的資料模型(QStandardItemModel)來提供資料給QML介面。QML中的檢視元素(如ListView或GridView)可以使用這些模型來顯示資料。

  1. mymodel.h

    #ifndef MYMODEL_H
    #define MYMODEL_H
    
    #include <QAbstractListModel>
    #include <QList>
    
    class MyModel : public QAbstractListModel
    {
        Q_OBJECT
    
    public:
        explicit MyModel(QObject *parent = nullptr);
    
        enum {
            NameRole = Qt::UserRole + 1,
            AgeRole,
            EmailRole
        };
    
        // 重寫以下幾個虛擬函式
        int rowCount(const QModelIndex &parent = QModelIndex()) const override;
        QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
        QHash<int, QByteArray> roleNames() const override;
    
    private:
        struct Person {
            QString name;
            int age;
            QString email;
        };
    
        QList<Person> m_persons;
    };
    
    #endif // MYMODEL_H
    
  2. mymodel.cpp

    #include "mymodel.h"
    
    MyModel::MyModel(QObject *parent)
        : QAbstractListModel(parent)
    {
        // 初始化一些資料
        m_persons.append({"Alice", 25, "[email protected]"});
        m_persons.append({"Bob", 30, "[email protected]"});
        m_persons.append({"Charlie", 35, "[email protected]"});
    }
    
    int MyModel::rowCount(const QModelIndex &parent) const
    {
        Q_UNUSED(parent);
        return m_persons.count();
    }
    
    QVariant MyModel::data(const QModelIndex &index, int role) const
    {
        if (!index.isValid())
            return QVariant();
    
        if (index.row() >= m_persons.count() || index.row() < 0)
            return QVariant();
    
        const Person &person = m_persons[index.row()];
        if (role == NameRole)
            return person.name;
        else if (role == AgeRole)
            return person.age;
        else if (role == EmailRole)
            return person.email;
    
        return QVariant();
    }
    
    QHash<int, QByteArray> MyModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[NameRole] = "name";
        roles[AgeRole] = "age";
        roles[EmailRole] = "email";
        return roles;
    }
    
    
  3. main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "mymodel.h"
    
    int main(int argc, char *argv[])
    {
        /* 啟用Qt應用程式的高DPI縮放功能 */
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        /* 建立一個Qt應用程式的範例 */
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        MyModel myModel;
        engine.rootContext()->setContextProperty("myModel", &myModel);
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        /* 將 QQmlApplicationEngine 物件的 objectCreated 訊號連線到一個 lambda 函數上 */
        /* lambda 函數用於在 QML 檔案中的根物件被建立時進行處理,檢查物件是否成功建立,如果建立失敗則退出應用程式 */
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        /* 載入QML檔案並顯示使用者介面 */
        engine.load(url);
    
        return app.exec();
    }
    
    
  4. main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.5
    
    Window {
        visible: true
        width: 480
        height: 800
        title: qsTr("Hello World")
    
        ListView {
            anchors.fill: parent
            model: myModel
            delegate: Item {
                width: parent.width
                height: 60
                Column {
                    Text { text: name }
                    Text { text: age }
                    Text { text: email }
                }
            }
        }
    }
    
    
  5. 執行效果

四、QML型別註冊

QML型別註冊(QML Type Registration):可以將C++ 物件註冊為自定義的QML型別,使得QML可以直接建立和使用這些物件。通過在C++ 中使用 Q_PROPERTY 宏和 Q_INVOKABLE 函數,可以將C++ 類註冊為QML型別。我需要這樣一個案例

  1. myobject.h

    #include <QQmlEngine>
    #include "QDebug"
    
    class MyObject : public QObject
    {
        Q_OBJECT
        Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    public:
        explicit MyObject(QObject *parent = nullptr) : QObject(parent) {}
        QString name() const { return m_name; }
        void setName(const QString &name) { m_name = name; emit nameChanged(); }
        Q_INVOKABLE void printName() { qDebug() << "Name:" << m_name; }
    
        static void registerQmlType()
        {
            qmlRegisterType<MyObject>("com.example", 1, 0, "MyObject");
        }
    
    signals:
        void nameChanged();
    private:
        QString m_name;
    };
    
    
  2. main.cpp

    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include <QQmlContext>
    #include "myobject.h"
    
    int main(int argc, char *argv[])
    {
        /* 啟用Qt應用程式的高DPI縮放功能 */
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    
        /* 建立一個Qt應用程式的範例 */
        QGuiApplication app(argc, argv);
    
        QQmlApplicationEngine engine;
    
        MyObject::registerQmlType();
    
        const QUrl url(QStringLiteral("qrc:/main.qml"));
        /* 將 QQmlApplicationEngine 物件的 objectCreated 訊號連線到一個 lambda 函數上 */
        /* lambda 函數用於在 QML 檔案中的根物件被建立時進行處理,檢查物件是否成功建立,如果建立失敗則退出應用程式 */
        QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                         &app, [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        }, Qt::QueuedConnection);
        /* 載入QML檔案並顯示使用者介面 */
        engine.load(url);
    
        return app.exec();
    }
    
    
  3. main.qml

    import QtQuick 2.12
    import QtQuick.Window 2.12
    import QtQuick.Controls 2.5
    import com.example 1.0
    
    Window {
        visible: true
        width: 480
        height: 800
        title: qsTr("Hello World")
    
        MyObject {
            id: myObject
            name: "John"
        }
    
        /* 垂直佈置元件 */
        Column {
            anchors.fill: parent        // 大小為父元件的大小
            anchors.margins: 40         // 與父元件四周的間隔
            spacing: 10                 // 子元件之間的間隔
    
            Text {
                text: myObject.name
            }
    
            Button {
                text: "Print Name"
                onClicked: myObject.printName()
            }
        }
    }