Qt QML與C++混合編程


一、QML與C++混合編程簡介

    QML與C++混合編程就是使用QML高效便捷地構建UI,而C++則用來實現業務邏輯和復雜算法。

二、QML訪問C++

    Qt集成了QML引擎和Qt元對象系統,使得QML很容易從C++中得到擴展,在一定的條件下,QML就可以訪問QObject派生類的成員,例如信號、槽函數、枚舉類型、屬性、成員函數等。

    QML訪問C++有兩個方法:一是在Qt元對象系統中注冊C++類,在QML中實例化、訪問;二是在C++中實例化並設置為QML上下文屬性,在QML中直接使用。第一種方法可以使C++類在QML中作為一個數據類型,例如函數參數類型或屬性類型,也可以使用其枚舉類型、單例等,功能更強大。

三、C++類的實現

    C++類要想被QML訪問,首先必須滿足兩個條件:一是派生自QObject類或QObject類的子類,二是使用Q_OBJECT宏。QObject類是所有Qt對象的基類,作為Qt對象模型的核心,提供了信號與槽機制等很多重要特性。Q_OBJECT宏必須在private區(C++默認為private)聲明,用來聲明信號與槽,使用Qt元對象系統提供的內容,位置一般在語句塊首行。Projects選擇Qt Quick Application,工程名為Hello。

1、信號與槽實現

A、C++類實現

 1 #ifndef HELLO_H  2 #define HELLO_H
 3 #include <QObject>
 4 #include <QDebug>
 5  
 6 class Hello: public QObject  7 {  8 Q_OBJECT  9 public slots: 10   void doSomething() 11  { 12     qDebug() << "Hello::dosomething() is called."; 13  } 14 signals: 15   void begin(); 16 }; 17  
18 #endif // HELLO_H

    Hello類中的信號begin()和槽doSomething()都可以被QML訪問。槽必須聲明為public或protected,信號在C++中使用時要用到emit關鍵字,但在QML中就是個普通的函數,用法同函數一樣,信號處理器形式為on,Signal首字母大寫。信號不支持重載,多個信號的名字相同而參數不同時,能夠被識別的只是最后一個信號,與信號的參數無關。

B、注冊C++類型

 1 #include <QGuiApplication>
 2 #include <QQmlApplicationEngine>
 3 #include <QtQml>
 4 #include "hello.h"
 5  
 6 int main(int argc, char *argv[])  7 {  8  QGuiApplication app(argc, argv);  9   //注冊C++類型Hello
10   qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); 11  
12  QQmlApplicationEngine engine; 13   engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 14  
15   return app.exec(); 16 }

將C++類注冊到Qt元對象系統。

C、在QML文件中導入C++類並使用

 1 import QtQuick 2.5
 2 import QtQuick.Window 2.2
 3 //導入注冊的C++類
 4 import Hello.module 1.0
 5  
 6 Window {  7     visible: true
 8     width: 640
 9     height: 480
10     title: qsTr("Hello QML") 11  MouseArea { 12  anchors.fill: parent 13  onClicked: { 14             hello.begin();//單擊鼠標調用begin信號函數
15  } 16  } 17  Hello{ 18         id:hello   //Hello類的實例
19  onBegin:doSomething() 20  } 21 }

    在QML文件中導入注冊的C++類(import),關鍵字Hello就可以在當前QML文件中當作一種QML類型來用。MouseArea鋪滿界面,單擊鼠標時會發送begin()信號,進而調用doSomething()槽函數。

2、枚舉類型實現

A、C++類中枚舉的定義

 1 #ifndef HELLO_H  2 #define HELLO_H
 3 #include <QObject>
 4 #include <QDebug>
 5  
 6 class Hello: public QObject  7 {  8 Q_OBJECT  9 Q_ENUMS(Color) 10 public: 11  Hello():m_color(RED) 12  { 13     qDebug() << "Hello() is called."; 14  } 15   //枚舉
16   enum Color 17  { 18  RED, 19  BLUE, 20  BLACK 21  }; 22 public slots: 23   void doSomething(Color color) 24  { 25     qDebug() << "Hello::dosomething() is called " << color; 26  } 27  
28 signals: 29   void begin(); 30 private: 31  Color m_color; 32 }; 33  
34 #endif // HELLO_H

    C++類中添加了public的Color枚舉類型,枚舉類型要想在QML中使用,需要使用Q_ENUMS()宏。

B、QML文件中使用C++枚舉類型

 1 import QtQuick 2.5
 2 import QtQuick.Window 2.2
 3 //導入注冊的C++類
 4 import Hello.module 1.0
 5  
 6 Window {  7     visible: true
 8     width: 640
 9     height: 480
10     title: qsTr("Hello QML") 11  MouseArea { 12  anchors.fill: parent 13  onClicked: { 14             hello.begin();//單擊鼠標調用begin信號函數
15  } 16  } 17  Hello{ 18         id:hello   //Hello類的實例
19  onBegin:doSomething(Hello.RED) 20  } 21 }

    QML中使用枚舉類型的方式是通過C++類型名使用“.”操作符直接訪問枚舉成員,如Hello.RED。

3、成員函數實現

A、成員函數定義

 1 #ifndef HELLO_H  2 #define HELLO_H
 3 #include <QObject>
 4 #include <QDebug>
 5  
 6 class Hello: public QObject  7 {  8 Q_OBJECT  9 Q_ENUMS(Color) 10 public: 11  Hello():m_color(RED) 12  { 13     qDebug() << "Hello() is called."; 14  } 15   //枚舉
16   enum Color 17  { 18  RED, 19  BLUE, 20  BLACK 21  }; 22   Q_INVOKABLE void show() 23  { 24     qDebug() << "show() is called."; 25  } 26 public slots: 27   void doSomething(Color color) 28  { 29     qDebug() << "Hello::dosomething() is called " << color; 30  } 31  
32 signals: 33   void begin(); 34 private: 35  Color m_color; 36 }; 37  
38 #endif // HELLO_H

    如果QML中訪問C++成員函數,則C++成員函數必須是public或protected成員函數,且使用Q_INVOKABLE宏,位置在函數返回類型的前面。

B、QML中調用C++類成員函數

 1 import QtQuick 2.5
 2 import QtQuick.Window 2.2
 3 //導入注冊的C++類
 4 import Hello.module 1.0
 5  
 6 Window {  7     visible: true
 8     width: 640
 9     height: 480
10     title: qsTr("Hello QML") 11  MouseArea { 12  anchors.fill: parent 13  onClicked: { 14             hello.begin();//單擊鼠標調用begin信號函數
15  hello.show(); 16  } 17  } 18  Hello{ 19         id:hello   //Hello類的實例
20  onBegin:doSomething(Hello.RED) 21  } 22 }

    在QML中訪問C++的成員函數的形式是“.”,如hello.show(),支持函數重載。

4、C++類的屬性

A、C++類中屬性的定義

 1 #ifndef HELLO_H  2 #define HELLO_H
 3 #include <QObject>
 4 #include <QDebug>
 5  
 6 class Hello: public QObject  7 {  8 Q_OBJECT  9 Q_ENUMS(Color) 10 //屬性聲明
11 Q_PROPERTY(Color color READ color WRITE setColor NOTIFY colorChanged) 12 public: 13  Hello():m_color(RED) 14  { 15     qDebug() << "Hello() is called."; 16  } 17   //枚舉
18   enum Color 19  { 20  RED, 21  BLUE, 22  BLACK 23  }; 24   Q_INVOKABLE void show() 25  { 26     qDebug() << "show() is called."; 27  } 28   Color color() const
29  { 30     return m_color; 31  } 32   void setColor(const Color& color) 33  { 34     if(color != m_color) 35  { 36         m_color = color; 37  emit colorChanged(); 38  } 39  } 40 public slots: 41   void doSomething(Color color) 42  { 43     qDebug() << "Hello::dosomething() is called " << color; 44  } 45  
46 signals: 47   void begin(); 48   void colorChanged(); 49 private: 50   Color m_color;//屬性
51 }; 52  
53 #endif // HELLO_H

    C++類中添加了Q_PROPERTY()宏,用來在QObject派生類中聲明屬性,屬性同類的數據成員一樣,但又有一些額外的特性可通過Qt元對象系統來訪問。

Q_PROPERTY()(type name

     (READ getFunction [WRITE setFunction] |

             MEMBER memberName [(READ getFunction | WRITE setFunction)])

            [RESET resetFunction]

            [NOTIFY notifySignal]

            [REVISION int]

            [DESIGNABLE bool]

            [SCRIPTABLE bool]

            [STORED bool]

            [USER bool]

            [CONSTANT]

            [FINAL])

    屬性的type、name是必需的,其它是可選項,常用的有READ、WRITE、NOTIFY。屬性的type可以是QVariant支持的任何類型,也可以是自定義類型,包括自定義類、列表類型、組屬性等。另外,屬性的READ、WRITE、RESET是可以被繼承的,也可以是虛函數,不常用。

    READ:讀取屬性值,如果沒有設置MEMBER的話,是必需的。一般情況下,函數是個const函數,返回值類型必須是屬性本身的類型或這個類型的const引用,沒有參數。

    WRITE:設置屬性值,可選項。函數必須返回void,有且僅有一個參數,參數類型必須是屬性本身的類型或類型的指針或引用。

    NOTIFY:與屬性關聯的可選信號,信號必須在類中聲明過,當屬性值改變時,就可觸發信號,可以沒有參數,有參數的話只能是一個類型同屬性本身類型的參數,用來記錄屬性改變后的值。

B、QML中修改屬性

 1 import QtQuick 2.5
 2 import QtQuick.Window 2.2
 3 //導入注冊的C++類
 4 import Hello.module 1.0
 5  
 6 Window {  7     visible: true
 8     width: 640
 9     height: 480
10     title: qsTr("Hello QML") 11  MouseArea { 12  anchors.fill: parent 13  onClicked: { 14             hello.begin()//單擊鼠標調用begin信號函數
15  hello.show() 16             hello.color = 2  //修改屬性
17  } 18  } 19  Hello{ 20         id:hello   //Hello類的實例
21  onBegin:doSomething(Hello.RED) 22         onColorChanged:console.log("color changed.") 23  } 24 }

    C++類中的m_color屬性可以在QML中訪問、修改,訪問時調用了color()函數,修改時調用setColor()函數,同時還發送了一個信號來自動更新color屬性值。

四、注冊C++類為QML類型

    QObject派生類可以注冊到Qt元對象系統,使得類在QML中同其它內建類型一樣,可以作為一個數據類型來使用。QML引擎允許注冊可實例化的類型,也可以是不可實例化的類型,常見的注冊函數有:

qmlRegisterInterface()

qmlRegisterRevision()

qmlRegisterSingletonType()

qmlRegisterType()

qmlRegisterTypeNotAvailable()

qmlRegisterUncreatableType()

template<typename T>

int qmlRegisterType(const char *uri,int versionMajor,

       int versionMinor, const char *qmlName);

    模板函數注冊C++類到Qt元對象系統中,uri是需要導入到QML中的庫名,versionMajor和versionMinor是其版本數字,qmlName是在QML中可以使用的類型名。

    qmlRegisterType<Hello>("Hello.module",1,0,"Hello");

    main.cpp中將Hello類注冊為在QML中可以使用的GHello類型,主版本為1,次版本為0,庫的名字是Hello.module。main.qml中導入了C++庫,使用Hello構造了一個對象,id為hello,可以借助id來訪問C++。

    注冊動作必須在QML上下文創建前,否則無效。

    QQuickView為QtQuickUI提供了一個窗口,可以方便地加載QML文件並顯示其界面。QApplication派生自QGuiApplication,而QGuiApplication又派生自QCoreApplication,這三個類是常見的管理Qt應用程序的類。QQmlApplicationEngine可以方便地從一個單一的QML文件中加載應用程序,派生自QQmlEngine,QQmlEngine則提供了加載QML組件的環境,可以與QQmlComponent、QQmlContext等一起使用。

五、QML上下文屬性設置

    在C++應用程序加載QML對象時,可以直接嵌入一些C++數據來給QML使用,需要用到QQmlContext::setContextProperty()設置QML上下文屬性,上下文屬性可以是一個簡單的類型,也可以是任何自定義的類對象。

A、C++設置上下文屬性

 1 #include <QGuiApplication>
 2 #include <QQuickView>
 3 #include <QQmlContext>
 4 #include "hello.h"
 5  
 6 int main(int argc, char *argv[])  7 {  8  QGuiApplication app(argc, argv);  9   
10  QQuickView view; 11  Hello hello; 12   view.rootContext()->setContextProperty("hello", &hello); 13   view.setSource(QUrl(QStringLiteral("qrc:///main.qml"))); 14  view.show(); 15  
16   return app.exec(); 17 }

    Hello類先實例化為hello對象,然后注冊為QML上下文屬性。

B、QML使用

 1 import QtQuick 2.5
 2  
 3 Item {  4     width: 640
 5     height: 480
 6  MouseArea {  7  anchors.fill: parent  8  onClicked: {  9             hello.begin()//單擊鼠標調用begin信號函數
10  hello.show() 11  } 12  } 13  Connections{ 14  target:hello 15        onBegin:console.log("hello") 16     }

    在main.qml中不能使用Hello類型來實例化,也不能調用doSomething()槽函數,因為doSomething()函數中的枚舉類型在QML中是訪問不到的,正確的用法是通過已經設置的QML上下文屬性“hello”來訪問C++,可以訪問信號begin()和成員函數show(),此時的信號處理器需要用Connections來處理。

六、C++訪問QML

    在C++中也可以訪問QML中的屬性、函數和信號。

    在C++中加載QML文件可以用QQmlComponent或QQuickView,然后就可以在C++中訪問QML對象。QQuickView提供了一個顯示用戶界面的窗口,而QQmlComponent沒有。

1、C++使用QQmlComponent

 1 #include <QGuiApplication>
 2 #include <QQmlApplicationEngine>
 3 #include <QtQml>
 4 #include "hello.h"
 5  
 6 int main(int argc, char *argv[])  7 {  8  QGuiApplication app(argc, argv);  9   //注冊C++類型Hello
10   qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); 11  
12  QQmlApplicationEngine engine; 13   QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); 14  component.create(); 15  
16   return app.exec(); 17 }

2、C++使用QML的屬性

    在C++中加載了QML文件並進行組件實例化后,就可以在C++中訪問、修改實例的屬性值,可以是QML內建屬性,也可以是自定義屬性。

A、QML中定義部分元素的屬性

 1 import QtQuick 2.5
 2 import QtQuick.Window 2.0
 3 import Hello.module 1.0
 4  
 5 Window {  6     visible: true
 7     width: 640
 8     height: 480
 9     title: "Hello QML"
10     color: "white"
11  MouseArea { 12  anchors.fill: parent 13  onClicked: { 14             hello.begin()//單擊鼠標調用begin信號函數
15             hello.doSomething(2) 16  } 17  } 18  Rectangle{ 19         objectName: "rect"
20  anchors.fill: parent 21         color:"red"
22  } 23  Hello{ 24  id:hello 25        onBegin:console.log("hello") 26  onColorChanged:hello.show() 27  } 28 }

    QML中添加了一個Rectangle,設置objectName屬性值為“rect”,objectName是為了在C++中能夠找到Rectangle。

B、C++中使用QML元素的屬性

 1 #include <QGuiApplication>
 2 #include <QQmlApplicationEngine>
 3 #include <QtQml>
 4 #include "hello.h"
 5  
 6 int main(int argc, char *argv[])  7 {  8  QGuiApplication app(argc, argv);  9   //注冊C++類型Hello
10   qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); 11  
12  QQmlApplicationEngine engine; 13   QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); 14   QObject* object = component.create(); 15   qDebug() << "width value is" << object->property("width").toInt(); 16   object->setProperty("width", 500);//設置window的寬
17   qDebug() << "width value is" << object->property("width").toInt(); 18   qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); 19   QQmlProperty::write(object, "height", 300);//設置window的高
20   qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); 21   QObject* rect = object->findChild<QObject*>("rect");//查找名稱為“rect”的元素
22   if(rect) 23  { 24       rect->setProperty("color", "blue");//設置元素的color屬性值
25       qDebug() << "color is " << object->property("color").toString(); 26  } 27  
28   return app.exec(); 29 }

    QObject::property()/setProperty()來讀取、修改width屬性值。

    QQmlProperty::read()/write()來讀取、修改height屬性值。

    如果某個對象的類型是QQuickItem,例如QQuickView::rootObject()的返回值,可以使用QQuickItem::width/setWidth()來訪問、修改width屬性值。

    QML組件是一個復雜的樹型結構,包含兄弟組件和孩子組件,可以使用QObject::findchild()/findchildren()來查找。

3、C++使用QML中信號與函數

    在C++中,使用QMetaObject::invokeMethod()可以調用QML中的函數,從QML傳遞過來的函數參數和返回值會被轉換為C++中的QVariant類型,成功返回true,參數不正確或被調用函數名錯誤返回false,invokeMethod()共有四個重載函數。必須使用Q_ARG()宏來聲明函數參數,用Q_RETURN_ARG()宏來聲明函數返回值,其原型如下:

QGenericArgument           Q_ARG(Type, const Type & value)

QGenericReturnArgument     Q_RETURN_ARG(Type, Type & value)

    使用QObject::connect()可以連接QML中的信號,connect()共有四個重載函數,都是靜態函數。必須使用SIGNAL()宏來聲明信號,SLOT()宏聲明槽函數。

    使用QObject::disconnect()可以解除信號與槽函數的連接。

A、QML中定義信號與函數

 1 import QtQuick 2.5
 2 import QtQuick.Window 2.0
 3 import Hello.module 1.0
 4  
 5 Window {  6     visible: true
 7     width: 640
 8     height: 480
 9     title: "Hello QML"
10     color: "white"
11     //定義信號
12     signal qmlSignal(string message) 13     //定義函數
14  function qmlFunction(parameter) { 15         console.log("qml function parameter is", parameter) 16         return "function from qml"
17  } 18  MouseArea { 19  anchors.fill: parent 20  onClicked: { 21             hello.begin()//單擊鼠標調用begin信號函數
22             hello.doSomething(2) 23             qmlSignal("This is an qml signal.")//發送信號
24  } 25  } 26  Rectangle{ 27         objectName: "rect"
28  anchors.fill: parent 29         color:"red"
30  } 31  Hello{ 32  id:hello 33        onBegin:console.log("hello") 34  onColorChanged:hello.show() 35  } 36 }

    QML中添加了qmlSignal()信號和qmlFunction()函數,信號在QML中發送,函數在C++中調用。

B、C++中調用

 1 #include <QGuiApplication>
 2 #include <QQmlApplicationEngine>
 3 #include <QtQml>
 4 #include "hello.h"
 5  
 6 int main(int argc, char *argv[])  7 {  8  QGuiApplication app(argc, argv);  9   //注冊C++類型Hello
10   qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); 11  
12  QQmlApplicationEngine engine; 13   QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); 14   QObject* object = component.create(); 15   qDebug() << "width value is" << object->property("width").toInt(); 16   object->setProperty("width", 500);//設置window的寬
17   qDebug() << "width value is" << object->property("width").toInt(); 18   qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); 19   QQmlProperty::write(object, "height", 300);//設置window的高
20   qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); 21   QObject* rect = object->findChild<QObject*>("rect");//查找名稱為“rect”的元素
22   if(rect) 23  { 24       rect->setProperty("color", "blue");//設置元素的color屬性值
25       qDebug() << "color is " << object->property("color").toString(); 26  } 27   //調用QML中函數
28  QVariant returnedValue; 29   QVariant message = "Hello from C++"; 30   QMetaObject::invokeMethod(object, "qmlFunction", 31  Q_RETURN_ARG(QVariant, returnedValue), 32  Q_ARG(QVariant, message)); 33   qDebug() << "returnedValue is" << returnedValue.toString(); // function from qml
34  Hello hello; 35   //連接QML元素中的信號到C++槽函數
36   QObject::connect(object, SIGNAL(qmlSignal(QString)), 37                    &hello, SLOT(qmlSlot(QString))); 38  
39   return app.exec(); 40 }

C++類中的槽函數:

1 public slots: 2   void doSomething(Color color) 3  { 4     qDebug() << "Hello::dosomething() is called " << color; 5  } 6   void qmlSlot(const QString& message) 7  { 8     qDebug() << "C++ called: " << message; 9   }

七、QML與C++混合編程注意事項

    QML與混合編程注意事項:

    A、自定義C++類一定要派生自QObject類或其子類,並使用Q_OBJECT宏。

    B、注冊自定義C++類到Qt元對象系統或設置自定義類對象實例為QML上下文屬性是必須的。

    C、兩者交互進行數據傳遞時,要符合QML與C++間數據類型的轉換規則。


免責聲明!

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



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