首頁 > 軟體

QML與C++互動的實現步驟

2022-03-15 16:00:37

前言

檔案如是說,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派生類範例的以下成員:

  • 屬性(使用Q_PROPERTY註冊的屬性)
  • 方法(需註冊為public slots或是標記為Q_INVOKABLE)
  • 訊號

(此外,如果已使用Q_ENUMS宣告列舉,則可以使用列舉。)

通常,無論是否已向QML型別系統註冊了QObject派生類,都可以從QML存取它們。但是,如果QML引擎要存取其他型別資訊(例如,如果要將類本身用作方法引數或屬性,或者要將其中一個列舉型別用於以這種方式使用),那麼該類可能需要註冊。

程式碼範例有四個檔案,QtQuick Empty工程的兩個加自定義的Cpp類h和cpp檔案,因為我把幾種常用的方法都寫出來了,所以看起來有點亂(完整程式碼連結見文末)。

#ifndef CPPOBJECT_H
#define CPPOBJECT_H
 
#include <QObject>
 
//派生自QObject
//使用qmlRegisterType註冊到QML中
class CppObject : public QObject
{
    Q_OBJECT
    //註冊屬性,使之可以在QML中存取--具體語法百度Q_PROPERTY
    Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
    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 nameChanged(const QString name);
    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中進行存取。

#include "CppObject.h"
 
#include <QDebug>
 
CppObject::CppObject(QObject *parent)
    : QObject(parent),
      myName("none"),
      myYear(0)
{
 
}
 
void CppObject::sendSignal()
{
    //測試用,呼叫該函數後傳送訊號
    qDebug()<<"CppObject::sendSignal";
    emit cppSignalA();
    emit cppSignalB(myName,myYear);
}
 
void CppObject::setName(const QString &name)
{
    qDebug()<<"CppObject::setName"<<name;
    if(myName!=name){
        qDebug()<<"emit nameChanged";
        myName=name;
        emit nameChanged(name);
    }
}
 
QString CppObject::getName() const
{
    qDebug()<<"CppObject::getName";
    return myName;
}
 
void CppObject::setYear(int year)
{
    qDebug()<<"CppObject::setYear"<<year;
    if(year!=myYear){
        qDebug()<<"emit yearChanged";
        myYear=year;
        emit yearChanged(myYear);
    }
}
 
int CppObject::getYear() const
{
    qDebug()<<"CppObject::getYear";
    return myYear;
}
 
void CppObject::cppSlotA()
{
    qDebug()<<"CppObject::cppSlotA";
}
 
void CppObject::cppSlotB(const QString &str, int value)
{
    qDebug()<<"CppObject::cppSlotB"<<str<<value;
}

為了測試方便,我給每個函數都加了一個列印語句,當呼叫sendSignal函數時將會emit兩個訊號,稍後會在QML中呼叫該函數。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#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;
 
    //也可以註冊為qml全域性物件
    //engine.rootContext()->setContextProperty("cppObj",new CppObject(qApp));
 
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
 
    return app.exec();
}

通過使用qmlRegisterType,將剛才定義的QObject派生類註冊到QML中(Qt5.15增加了新的註冊方式)。

import QtQuick 2.9
import QtQuick.Window 2.9
//引入我們註冊的模組
import MyCppObject 1.0
 
Window {
    id: root
    visible: true
    width: 500
    height: 300
    title: qsTr("QML呼叫Cpp物件:by 龔建波1992")
    color:"green"
 
    signal qmlSignalA
    signal qmlSignalB(string str,int value)
 
    //滑鼠點選區域
    MouseArea{
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        //測試時點選左鍵或右鍵
        onClicked: {
            if(mouse.button===Qt.LeftButton){
                console.log('----qml 點選左鍵:Cpp發射訊號')
                cpp_obj.name="gongjianbo"  //修改屬性會觸發set函數,獲取值會觸發get函數
                cpp_obj.year=1992
                cpp_obj.sendSignal() //呼叫Q_INVOKABLE宏標記的函數
            }else{
                console.log('----qml 點選右鍵:QML發射訊號')
                root.qmlSignalA()
                root.qmlSignalB('gongjianbo',1992)
            }
        }
    }
 
    //作為一個QML物件
    CppObject{
        id:cpp_obj
        //也可以像原生QML物件一樣操作,增加屬性之類的
        property int counts: 0
 
        onYearChanged: {
            counts++
            console.log('qml onYearChanged',counts)
        }
        onCountsChanged: {
            console.log('qml onCountsChanged',counts)
        }
    }
 
    //元件載入完成執行
    Component.onCompleted: {
        //關聯訊號與訊號處理常式的方式同QML中的型別
        //Cpp物件的訊號關聯到Qml
        //cpp_obj.onCppSignalA.connect(function(){console.log('qml signalA process')})
        cpp_obj.onCppSignalA.connect(()=>console.log('qml signalA process')) //js的lambda
        cpp_obj.onCppSignalB.connect(processB)
        //Qml物件的訊號關聯到Cpp
        root.onQmlSignalA.connect(cpp_obj.cppSlotA)
        root.onQmlSignalB.connect(cpp_obj.cppSlotB)
    }
 
    //定義的函數可以作為槽函數
    function processB(str,value){
        console.log('qml function processB',str,value)
    }
}

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

這個範例很簡單,點選滑鼠左鍵呼叫CppObj的sendSignal函數來傳送訊號,QML處理;點選滑鼠右鍵QML傳送訊號,CppObj處理,下面是操作結果:

可以看到QML成功的存取了CppObj的屬性和方法,並能進行訊號槽的關聯。

第二個例子:C++中載入QML物件

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

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

可以使用QQmlComponentQQuickView來載入QML檔案。QQmlComponent將QML檔案作為為一個C++物件載入,然後可以從C++ 程式碼進行修改。QQuickView也可以這樣做,但由於QQuickView是一個基於QWindow的派生類,載入的物件也將視覺化顯示,QQuickView通常用於將一個視覺化的QML物件整合到應用程式的使用者介面中。參見檔案Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html

下面通過程式碼來演示(完整程式碼連結見文末)。

import QtQuick 2.9
 
Item{
    id: root
    width: 250
    height: 250
    //自定義屬性  --cpp可以存取
    property string msg: "GongJianBo1992"
    //自定義訊號  --可以觸發cpp槽函數
    signal qmlSendMsg(string arg1,string arg2)
 
    Rectangle {
        anchors.fill: parent
        color: "green"
        objectName: "rect" //用於cpp查詢物件
    }
 
    MouseArea {
        anchors.fill: parent
        onClicked: {
            console.log("qml 點選滑鼠, 傳送訊號 qmlSendMsg")
            root.qmlSendMsg(root.msg,"myarg2")
        }
    }
 
    onHeightChanged: console.log("qml height changed")
    onWidthChanged: console.log("qml width changed")
 
    //QML中的方法可以被cpp呼叫,也可以作為槽函數
    function qml_method(val_arg){
        console.log("qml method runing",val_arg,"return ok")
        return "ok"
    }
    //注意槽函數引數為var型別
    function qmlRecvMsg(arg1,arg2){
        console.log("qml slot runing",arg1,arg2)
    }
}

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

#ifndef CPPOBJECT_H
#define CPPOBJECT_H
 
#include <QObject>
#include <QDebug>
 
class CppObject : public QObject
{
    Q_OBJECT
public:
    explicit CppObject(QObject *parent = Q_NULLPTR)
        :QObject(parent){}
 
signals:
    //訊號 --用來觸發qml的函數
    //注意引數為var型別,對應qml中js函數的引數型別
    void cppSendMsg(const QVariant &arg1,const QVariant &arg2);
 
public slots:
    //槽函數 --用來接收qml的訊號
    void cppRecvMsg(const QString &arg1,const QString &arg2){
        qDebug()<<"CppObject::cppRecvMsg"<<arg1<<arg2;
        qDebug()<<"emit cppSendMsg";
        emit cppSendMsg(arg1,arg2);
    }
};
 
#endif // CPPOBJECT_H

Cpp中定義了一個槽函數,用來接收QML物件的訊號。 

#include <QGuiApplication>
#include <QQmlProperty>
#include <QQuickView>
#include <QQuickItem>
#include <QMetaObject>
#include <QDebug>
 
#include "CppObject.h"
 
int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 
    QGuiApplication app(argc, argv);
 
    //可以用QQmlComponentQQuickViewQQuickWidget的C++程式碼載入QML檔案
    //QQuickView不能用Window做根元素
    QQuickView view(QUrl("qrc:/main.qml"));
    view.show();
 
    //獲取到qml根物件的指標
    QObject *qmlObj=view.rootObject();
 
    /*檔案如是說:
    應該始終使用QObject::setProperty()、QQmlProperty
    或QMetaProperty::write()來改變QML的屬性值,以確保QML引擎感知屬性的變化。*/
 
    //【1】
    //通過QObject設定屬性值
    qDebug()<<"Cpp set qml property height";
    //qmlObj->setProperty("height",300);
    QQmlProperty(qmlObj,"height").write(300);
    //通過QObject獲取屬性值
    qDebug()<<"Cpp get qml property height"<<qmlObj->property("height");
    //任何屬性都可以通過C++存取
    qDebug()<<"Cpp get qml property msg"<<qmlObj->property("msg");
 
    //【2】
    QQuickItem *item=qobject_cast<QQuickItem*>(qmlObj);
    //通過QQuickItem設定屬性值
    qDebug()<<"Cpp set qml property width";
    item->setWidth(300);
    //通過QQuickItem獲取屬性值
    qDebug()<<"Cpp get qml property width"<<item->width();
 
    //【3】
    //通過objectName存取載入的QML物件
    //QObject::findChildren()可用於查詢具有匹配objectName屬性的子項
    QObject *qmlRect=qmlObj->findChild<QObject*>("rect");
    if(qmlRect){
        qDebug()<<"Cpp get rect color"<<qmlRect->property("color");
    }
 
    //【4】
    //呼叫QML方法
    QVariant val_return;  //返回值
    QVariant val_arg="GongJianBo";  //引數值
    //Q_RETURN_ARG()和Q_Arg()引數必須制定為QVariant型別
    QMetaObject::invokeMethod(qmlObj,
                              "qml_method",
                              Q_RETURN_ARG(QVariant,val_return),
                              Q_ARG(QVariant,val_arg));
    qDebug()<<"QMetaObject::invokeMethod result"<<val_return; //qml函數中返回「ok」
 
    //【5】
    //關聯訊號槽
    CppObject cppObj;
    //關聯qml訊號與cpp槽
    //如果訊號引數為QML物件型別,訊號用var引數型別,槽用QVariant型別接收
    QObject::connect(qmlObj,SIGNAL(qmlSendMsg(QString,QString)),
                     &cppObj,SLOT(cppRecvMsg(QString,QString)));
    //關聯cpp訊號與qml槽
    //qml中js函數引數為var型別,訊號也用QVariant型別
    QObject::connect(&cppObj,SIGNAL(cppSendMsg(QVariant,QVariant)),
                     qmlObj,SLOT(qmlRecvMsg(QVariant,QVariant)));
    //此外,cpp訊號也可以關聯qml訊號
 
    return app.exec();
}

然後就把檔案中的東西測試了下,操作起來很簡單。不過相對於QML中使用C++物件來說,感覺作用沒那麼大,因為一般把QML嵌入到Widgets中才會做這些操作,但是混合兩個框架很多坑。下面是我的測試輸出結果:

 以上兩種方式應該就是最簡單的QML與C++互動應用了,對照檔案或是部落格敲一遍程式碼可以很容易地理解。

(完結)

程式碼完整連結(GitHub)如下:

Qml中建立Cpp物件(Cpp註冊給Qml):https://github.com/gongjianbo/MyTestCode/tree/master/Qml/QmlCallCpp2020

Cpp中載入Qml物件(Cpp操作Qml):https://github.com/gongjianbo/MyTestCode/tree/master/Qml/CppCallQml2020

程式碼的下載連結:QML-C_jb51.rar

參考

檔案:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-overview.html

檔案:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-interactqmlfromcpp.html

檔案:Qt/Qt5.9.7/Docs/Qt-5.9.7/qtqml/qtqml-cppintegration-topic.html

(注:檔案中有很多相關連結,此處忽略)

部落格:https://blog.csdn.net/u011012932/column/info/14318

部落格:https://blog.csdn.net/baidu_33850454/article/details/81907857

部落格:https://blog.csdn.net/baidu_33850454/article/details/81914821

到此這篇關於QML與C++互動的實現步驟的文章就介紹到這了,更多相關QML與C++互動內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com