一、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++間數據類型的轉換規則。
