QML 與 C++ 交互


前言

文檔如是說,QML旨在通過C ++代碼輕松擴展。Qt QML模塊中的類使QML對象能夠從C ++加載和操作,QML引擎與Qt元對象系統集成的本質使得C ++功能可以直接從QML調用。這允許開發混合應用程序,這些應用程序是通過混合使用QML,JavaScript和C ++代碼實現的。

QML is designed to be easily extensible through C++ code. The classes in the Qt QML module enable QML objects to be loaded and manipulated from C++, and the nature of QML engine's integration with Qt's meta object system enables C++ functionality to be invoked directly from QML. This allows the development of hybrid applications which are implemented with a mixture of QML, JavaScript and C++ code.

除了從QML訪問C ++功能的能力之外,Qt QML模塊還提供了從C ++代碼執行反向和操作QML對象的方法。

下面會通過示例來講解QML與C++的交互是如何實現的(感覺會有點長)。
第一個例子:QML中創建C++對象

文檔如是說,使用C ++代碼中定義的功能可以輕松擴展QML。由於QML引擎與Qt元對象系統的緊密集成,可以從QML代碼訪問由QObject派生的類適當公開的任何功能。這使得C ++類的屬性和方法可以直接從QML訪問,通常很少或無需修改。

QML引擎能夠通過元對象系統內省QObject實例。這意味着,任何QML代碼都可以訪問QObject派生類實例的以下成員:

    屬性
    方法(需注冊為public slots或是標記為Q_INVOKABLE)
    信號

(此外,如果已使用Q_ENUMS聲明枚舉,則可以使用枚舉。)

通常,無論是否已向QML類型系統注冊了QObject派生類,都可以從QML訪問它們。但是,如果QML引擎要訪問其他類型信息(例如,如果要將類本身用作方法參數或屬性,或者要將其中一個枚舉類型用於以這種方式使用),那么該類可能需要注冊。

代碼示例有四個文件,QtQuick Empty工程的兩個加自定義的Cpp類h和cpp文件,因為我把幾種常用的方法都寫出來了,所以看起來有點亂。

    //file CppObject.h
    #ifndef CPPOBJECT_H
    #define CPPOBJECT_H
     
    #include <QObject>
     
    //派生自QObject
    class CppObject : public QObject
    {
        Q_OBJECT
        //注冊屬性,使之可以在QML中訪問--具體語法請參考其他資料
        Q_PROPERTY(QString name READ getName WRITE setName)
        Q_PROPERTY(int year READ getYear WRITE setYear NOTIFY yearChanged)
     
    public:
        explicit CppObject(QObject *parent = nullptr);
        //通過Q_INVOKABLE宏標記的public函數可以在QML中訪問
        Q_INVOKABLE void sendSignal();//功能為發送信號
     
        //給類屬性添加訪問方法--myName
        void setName(const QString &name);
        QString getName() const;
        //給類屬性添加訪問方法--myYear
        void setYear(int year);
        int getYear() const;
     
    signals:
        //信號可以在QML中訪問
        void cppSignalA();//一個無參信號
        void cppSignalB(const QString &str,int value);//一個帶參數信號
        void yearChanged(int year);
     
    public slots:
        //public槽函數可以在QML中訪問
        void cppSlotA();//一個無參槽函數
        void cppSlotB(const QString &str,int value);//一個帶參數槽函數
     
    private:
        //類的屬性
        QString myName;
        int myYear;
    };
     
    #endif // CPPOBJECT_H

在頭文件中,我定義了信號和public槽函數,以及Q_INVOKABLE宏標記的public函數,還通過Q_PROPERTY注冊了兩個屬性,這些方法和屬性之后都可以在QML中進行訪問。

    //file CppObject.cpp
    #include "CppObject.h"
    #include <QDebug>
     
    CppObject::CppObject(QObject *parent)
        : QObject(parent),myName("none"),myYear(0)
    {
     
    }
     
    void CppObject::sendSignal()
    {
        //測試用,調用該函數后發送信號
        qDebug()<<"cpp sendSignal method";
        emit cppSignalA();
        emit cppSignalB(myName,myYear);
    }
     
    void CppObject::setName(const QString &name)
    {
        qDebug()<<"cpp setName"<<name;
        myName=name;
    }
     
    QString CppObject::getName() const
    {
        qDebug()<<"cpp getName";
        return myName;
    }
     
    void CppObject::setYear(int year)
    {
        qDebug()<<"cpp setYear"<<year;
        if(year!=myYear){
            qDebug()<<"cpp emit yearChanged";
            myYear=year;
            emit yearChanged(myYear);
        }
    }
     
    int CppObject::getYear() const
    {
        qDebug()<<"cpp getYear";
        return myYear;
    }
     
    void CppObject::cppSlotA()
    {
        qDebug()<<"cpp slot a";
    }
     
    void CppObject::cppSlotB(const QString &str, int value)
    {
        qDebug()<<"cpp slot b"<<str<<value;
    }

為了測試方便,我給每個函數都加了一個打印語句,當調用sendSignal函數時將會emit兩個信號,稍后會在QML中調用該函數。

    //file main.cpp
    #include <QGuiApplication>
    #include <QQmlApplicationEngine>
    #include "CppObject.h"
     
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
     
        QGuiApplication app(argc, argv);
     
        //qmlRegisterType注冊C++類型至QML
        //arg1:import時模塊名
        //arg2:主版本號
        //arg3:次版本號
        //arg4:QML類型名
        qmlRegisterType<CppObject>("MyCppObject",1,0,"CppObject");
     
        QQmlApplicationEngine engine;
        engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
        if (engine.rootObjects().isEmpty())
            return -1;
     
        return app.exec();
    }

通過使用qmlRegisterType,將剛才定義的QObject派生類注冊到QML中。

    //file main.qml
    import QtQuick 2.9
    import QtQuick.Window 2.2
    //引入我們注冊的模塊
    import MyCppObject 1.0
     
    Window {
        id: root
        visible: true
        width: 640
        height: 480
        title: qsTr("Hello World")
     
        color:"green"
        signal qmlSignalA
        signal qmlSignalB(string str,int value)
     
        MouseArea{
            anchors.fill: parent
            acceptedButtons: Qt.LeftButton | Qt.RightButton
            //測試從點擊開始
            //左鍵--Cpp發射信號
            //右鍵--Qml發射信號
            onClicked: {
                if(mouse.button===Qt.LeftButton){
                    console.log('----clicked left button')
                    cpp_obj.name="gongjianbo"
                    cpp_obj.year=1992
                    cpp_obj.sendSignal() //調用Q_INVOKABLE宏標記的函數
                }else{
                    console.log('----clicked right button')
                    root.qmlSignalA()
                    root.qmlSignalB('gongjianbo',1992)
                }
            }
        }
     
        //作為一個QML對象
        CppObject{
            id:cpp_obj
            //也可以像原生QML對象一樣操作
            property int counts: 0
     
            onYearChanged: {
                counts++
                console.log('qml name changed process')
            }
            onCountsChanged: {
                console.log('qml counts changed process')
            }
        }
     
        Component.onCompleted: {
            //關聯信號與信號處理函數的方式同QML中的類型
            //cpp object connect qml object
            cpp_obj.onCppSignalA.connect(function(){console.log('qml signal a process')})
            cpp_obj.onCppSignalB.connect(processB)
            //qml object connect cpp object
            root.onQmlSignalA.connect(cpp_obj.cppSlotA)
            root.onQmlSignalB.connect(cpp_obj.cppSlotB)
        }
     
        function processB(str,value){
            console.log('qml signal b process',str,value)
        }
    }

注冊之后就能直接在QML中使用剛才定義的C++類型了,並且可以像QML定義的類型一樣進行操作,如信號槽關聯、屬性綁定等。

這個示例很簡單,點擊鼠標左鍵調用CppObj的sendSignal函數來發送信號,QML處理;點擊鼠標右鍵QML發送信號,CppObj處理,下面是操作結果:

    QML debugging is enabled. Only use this in a safe environment.
    qml: ----clicked left button
    cpp setName "gongjianbo"
    cpp setYear 1992
    cpp emit yearChanged
    qml: qml counts changed process
    qml: qml name changed process
    cpp sendSignal methodA
    qml: qml signal a process
    qml: qml signal b process gongjianbo 1992
    qml: ----clicked right button
    cpp slot a
    cpp slot b "gongjianbo" 1992

可以看到QML成功的訪問了CppObj的屬性和方法,並能進行信號槽的關聯。
第二個例子:C++中加載QML對象

文檔如是說,所有QML對象類型都是源自QObject類型,無論它們是由引擎內部實現還是第三方定義。這意味着QML引擎可以使用Qt元對象系統動態實例化任何QML對象類型並檢查創建的對象。

這對於從C ++代碼創建QML對象非常有用,無論是顯示可以直觀呈現的QML對象,還是將非可視QML對象數據集成到C ++應用程序中。一旦創建了QML對象,就可以從C ++中檢查它,以便讀取和寫入屬性,調用方法和接收信號通知。

可以使用QQmlComponent或QQuickView來加載QML文檔。QQmlComponent將QML文檔作為為一個C++對象加載,然后可以從C++ 代碼進行修改。QQuickView也可以這樣做,但由於QQuickView是一個基於QWindow的派生類,加載的對象也將可視化顯示,QQuickView通常用於將一個可視化的QML對象集成到應用程序的用戶界面中。參見文檔Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html

下面通過代碼來演示。

    //file main.qml
    import QtQuick 2.9
     
    Item{
        id: root
        width: 100
        height: 100
        //自定義屬性  --cpp可以訪問
        property string msg: "gongjianbo1992"
        //自定義信號  --可以觸發cpp槽函數
        signal qmlSendMsg(string msg)
     
        Rectangle {
            anchors.fill: parent
            color: "green"
            objectName: "rect"
        }
     
        MouseArea {
            anchors.fill: parent
            onClicked: {
                console.log("qml clicked, send qmlSendMsg signal")
                root.qmlSendMsg(root.msg)
            }
        }
     
        onHeightChanged: console.log("qml height changed")
        onWidthChanged: console.log("qml width changed")
     
        //QML中的方法可以被cpp調用
        function qml_method(val_arg){
            console.log("qml method",val_arg)
            return "ok"
        }
    }

在QML中我定義了一些屬性和方法等,用於測試。
 

    //file CppObj.h
    #ifndef CPPOBJ_H
    #define CPPOBJ_H
     
    #include <QObject>
    #include <QDebug>
     
    class CppObj : public QObject
    {
        Q_OBJECT
    public:
        explicit CppObj(QObject *parent = Q_NULLPTR)
            :QObject(parent){}
     
    public slots:
        //槽函數 --用來接收qml的信號
        void cppRecvMsg(const QString &msg){
            qDebug()<<"cpp recv msg"<<msg;
        }
    };
     
    #endif // CPPOBJ_H

Cpp中定義了一個槽函數,用來接收QML對象的信號。

    //file main.cpp
    #include <QGuiApplication>
    #include <QQmlProperty>
    #include <QQuickView>
    #include <QQuickItem>
    #include <QMetaObject>
    #include <QDebug>
     
    #include "CppObj.h"
     
    int main(int argc, char *argv[])
    {
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
     
        QGuiApplication app(argc, argv);
     
        //可以使用QQmlComponent或QQuickView的C++代碼加載QML文檔
        //QQuickView不能用Window做根元素
        QQuickView view(QUrl("qrc:/main.qml"));
        view.show();
     
        QObject *qmlObj=view.rootObject();
        /* 應該始終使用QObject::setProperty()、QQmlProperty
         * 或QMetaProperty::write()來改變QML的屬性值,
         * 以確保QML引擎感知屬性的變化。
         */
        //通過QObject設置屬性值
        //qmlObj->setProperty("height",200);
        QQmlProperty(qmlObj,"height").write(200);
        //通過QObject獲取屬性值
        qDebug()<<"qml root height"<<qmlObj->property("height");
        //任何屬性都可以通過C++訪問
        qDebug()<<"qml property msg"<<qmlObj->property("msg");
     
        QQuickItem *item=qobject_cast<QQuickItem*>(qmlObj);
        //通過QQuickItem設置屬性值
        item->setWidth(200);
        //通過QQuickItem獲取屬性值
        qDebug()<<"qml root width"<<item->width();
     
        //通過objectName訪問加載的QML對象
        //QObject::findChildren()可用於查找具有匹配objectName屬性的子項
        QObject *qmlRect=qmlObj->findChild<QObject*>("rect");
        if(qmlRect){
            qDebug()<<"qml rect color"<<qmlRect->property("color");
        }
     
        //調用QML方法
        QVariant val_return;       //返回值
        QVariant val_arg="=.=!";   //參數值
        //Q_RETURN_ARG()和Q_Arg()參數必須制定為QVariant類型
        QMetaObject::invokeMethod(qmlObj,
                                  "qml_method",
                                  Q_RETURN_ARG(QVariant,val_return),
                                  Q_ARG(QVariant,val_arg));
        qDebug()<<"qml method return value"<<val_return; //函數中返回“ok”
     
        //關聯qml信號與cpp槽
        //如果信號參數為QML對象類型,信號用var參數類型,槽用QVariant類型接收
        CppObj cppObj;
        QObject::connect(qmlObj,SIGNAL(qmlSendMsg(QString)),
                         &cppObj,SLOT(cppRecvMsg(QString)));
     
        return app.exec();
    }

然后就把文檔中的東西測試了下,操作起來很簡單。不想相對於QML中使用C++對象來說,感覺作用沒那么大,畢竟QML訪問C++也可以改變C++對象的狀態,可能時我還沒想到合適的應用場景。下面是我的測試輸出結果:

    QML debugging is enabled. Only use this in a safe environment.
    qml: qml height changed
    qml root height QVariant(double, 200)
    qml property msg QVariant(QString, "gongjianbo1992")
    qml: qml width changed
    qml root width 200
    qml rect color QVariant(QColor, QColor(ARGB 1, 0, 0.501961, 0))
    qml: qml method =.=!
    qml method return value QVariant(QString, "ok")
    qml: qml clicked, send qmlSendMsg signal
    cpp recv msg "gongjianbo1992"

 以上兩種方式應該就是最簡單的QML與C++交互應用了,對照文檔或是博客敲一遍代碼可以很容易地理解。

(完結)
---------------------  
作者:龔建波  
來源:CSDN  
原文:https://blog.csdn.net/gongjianbo1992/article/details/87965925  
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

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



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