QML與C++混合編程就是使用QML高效便捷地構建UI,而C++則用來實現業務邏輯和複雜算法。算法
Qt集成了QML引擎和Qt元對象系統,使得QML很容易從C++中獲得擴展,在必定的條件下,QML就能夠訪問QObject派生類的成員,例如信號、槽函數、枚舉類型、屬性、成員函數等。編程
QML訪問C++有兩個方法:一是在Qt元對象系統中註冊C++類,在QML中實例化、訪問;二是在C++中實例化並設置爲QML上下文屬性,在QML中直接使用。第一種方法可使C++類在QML中做爲一個數據類型,例如函數參數類型或屬性類型,也可使用其枚舉類型、單例等,功能更強大。app
C++類要想被QML訪問,首先必須知足兩個條件:一是派生自QObject類或QObject類的子類,二是使用Q_OBJECT宏。QObject類是全部Qt對象的基類,做爲Qt對象模型的核心,提供了信號與槽機制等不少重要特性。Q_OBJECT宏必須在private區(C++默認爲private)聲明,用來聲明信號與槽,使用Qt元對象系統提供的內容,位置通常在語句塊首行。Projects選擇Qt Quick Application,工程名爲Hello。ide
A、C++類實現函數
#ifndef HELLO_H #define HELLO_H #include <QObject> #include <QDebug> class Hello: public QObject { Q_OBJECT public slots: void doSomething() { qDebug() << "Hello::dosomething() is called."; } signals: void begin(); }; #endif // HELLO_H
Hello類中的信號begin()和槽doSomething()均可以被QML訪問。槽必須聲明爲public或protected,信號在C++中使用時要用到emit關鍵字,但在QML中就是個普通的函數,用法同函數同樣,信號處理器形式爲on,Signal首字母大寫。信號不支持重載,多個信號的名字相同而參數不一樣時,可以被識別的只是最後一個信號,與信號的參數無關。ui
B、註冊C++類型spa
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml> #include "hello.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); //註冊C++類型Hello qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
將C++類註冊到Qt元對象系統。指針
C、在QML文件中導入C++類並使用component
import QtQuick 2.5 import QtQuick.Window 2.2 //導入註冊的C++類 import Hello.module 1.0 Window { visible: true width: 640 height: 480 title: qsTr("Hello QML") MouseArea { anchors.fill: parent onClicked: { hello.begin();//單擊鼠標調用begin信號函數 } } Hello{ id:hello //Hello類的實例 onBegin:doSomething() } }
在QML文件中導入註冊的C++類(import),關鍵字Hello就能夠在當前QML文件中看成一種QML類型來用。MouseArea鋪滿界面,單擊鼠標時會發送begin()信號,進而調用doSomething()槽函數。對象
A、C++類中枚舉的定義
#ifndef HELLO_H #define HELLO_H #include <QObject> #include <QDebug> class Hello: public QObject { Q_OBJECT Q_ENUMS(Color) public: Hello():m_color(RED) { qDebug() << "Hello() is called."; } //枚舉 enum Color { RED, BLUE, BLACK }; public slots: void doSomething(Color color) { qDebug() << "Hello::dosomething() is called " << color; } signals: void begin(); private: Color m_color; }; #endif // HELLO_H
C++類中添加了public的Color枚舉類型,枚舉類型要想在QML中使用,須要使用Q_ENUMS()宏。
B、QML文件中使用C++枚舉類型
import QtQuick 2.5 import QtQuick.Window 2.2 //導入註冊的C++類 import Hello.module 1.0 Window { visible: true width: 640 height: 480 title: qsTr("Hello QML") MouseArea { anchors.fill: parent onClicked: { hello.begin();//單擊鼠標調用begin信號函數 } } Hello{ id:hello //Hello類的實例 onBegin:doSomething(Hello.RED) } }
QML中使用枚舉類型的方式是經過C++類型名使用「.」操做符直接訪問枚舉成員,如Hello.RED。
A、成員函數定義
#ifndef HELLO_H #define HELLO_H #include <QObject> #include <QDebug> class Hello: public QObject { Q_OBJECT Q_ENUMS(Color) public: Hello():m_color(RED) { qDebug() << "Hello() is called."; } //枚舉 enum Color { RED, BLUE, BLACK }; Q_INVOKABLE void show() { qDebug() << "show() is called."; } public slots: void doSomething(Color color) { qDebug() << "Hello::dosomething() is called " << color; } signals: void begin(); private: Color m_color; }; #endif // HELLO_H
若是QML中訪問C++成員函數,則C++成員函數必須是public或protected成員函數,且使用Q_INVOKABLE宏,位置在函數返回類型的前面。
B、QML中調用C++類成員函數
import QtQuick 2.5 import QtQuick.Window 2.2 //導入註冊的C++類 import Hello.module 1.0 Window { visible: true width: 640 height: 480 title: qsTr("Hello QML") MouseArea { anchors.fill: parent onClicked: { hello.begin();//單擊鼠標調用begin信號函數 hello.show(); } } Hello{ id:hello //Hello類的實例 onBegin:doSomething(Hello.RED) } }
在QML中訪問C++的成員函數的形式是「.」,如hello.show(),支持函數重載。
A、C++類中屬性的定義
#ifndef HELLO_H #define HELLO_H #include <QObject> #include <QDebug> class Hello: public QObject { Q_OBJECT Q_ENUMS(Color) //屬性聲明 Q_PROPERTY(Color color READ color WRITE setColor NOTIFY colorChanged) public: Hello():m_color(RED) { qDebug() << "Hello() is called."; } //枚舉 enum Color { RED, BLUE, BLACK }; Q_INVOKABLE void show() { qDebug() << "show() is called."; } Color color() const { return m_color; } void setColor(const Color& color) { if(color != m_color) { m_color = color; emit colorChanged(); } } public slots: void doSomething(Color color) { qDebug() << "Hello::dosomething() is called " << color; } signals: void begin(); void colorChanged(); private: Color m_color;//屬性 }; #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中修改屬性
import QtQuick 2.5 import QtQuick.Window 2.2 //導入註冊的C++類 import Hello.module 1.0 Window { visible: true width: 640 height: 480 title: qsTr("Hello QML") MouseArea { anchors.fill: parent onClicked: { hello.begin()//單擊鼠標調用begin信號函數 hello.show() hello.color = 2 //修改屬性 } } Hello{ id:hello //Hello類的實例 onBegin:doSomething(Hello.RED) onColorChanged:console.log("color changed.") } }
C++類中的m_color屬性能夠在QML中訪問、修改,訪問時調用了color()函數,修改時調用setColor()函數,同時還發送了一個信號來自動更新color屬性值。
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等一塊兒使用。
在C++應用程序加載QML對象時,能夠直接嵌入一些C++數據來給QML使用,須要用到QQmlContext::setContextProperty()設置QML上下文屬性,上下文屬性能夠是一個簡單的類型,也能夠是任何自定義的類對象。
A、C++設置上下文屬性
#include <QGuiApplication> #include <QQuickView> #include <QQmlContext> #include "hello.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view; Hello hello; view.rootContext()->setContextProperty("hello", &hello); view.setSource(QUrl(QStringLiteral("qrc:///main.qml"))); view.show(); return app.exec(); }
Hello類先實例化爲hello對象,而後註冊爲QML上下文屬性。
B、QML使用
import QtQuick 2.5 Item { width: 640 height: 480 MouseArea { anchors.fill: parent onClicked: { hello.begin()//單擊鼠標調用begin信號函數 hello.show() } } Connections{ target:hello onBegin:console.log("hello") } }
在main.qml中不能使用Hello類型來實例化,也不能調用doSomething()槽函數,由於doSomething()函數中的枚舉類型在QML中是訪問不到的,正確的用法是經過已經設置的QML上下文屬性「hello」來訪問C++,能夠訪問信號begin()和成員函數show(),此時的信號處理器須要用Connections來處理。
在C++中也能夠訪問QML中的屬性、函數和信號。
在C++中加載QML文件能夠用QQmlComponent或QQuickView,而後就能夠在C++中訪問QML對象。QQuickView提供了一個顯示用戶界面的窗口,而QQmlComponent沒有。
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml> #include "hello.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); //註冊C++類型Hello qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); QQmlApplicationEngine engine; QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); component.create(); return app.exec(); }
在C++中加載了QML文件並進行組件實例化後,就能夠在C++中訪問、修改實例的屬性值,能夠是QML內建屬性,也能夠是自定義屬性。
A、QML中定義部分元素的屬性
import QtQuick 2.5 import QtQuick.Window 2.0 import Hello.module 1.0 Window { visible: true width: 640 height: 480 title: "Hello QML" color: "white" MouseArea { anchors.fill: parent onClicked: { hello.begin()//單擊鼠標調用begin信號函數 hello.doSomething(2) } } Rectangle{ objectName: "rect" anchors.fill: parent color:"red" } Hello{ id:hello onBegin:console.log("hello") onColorChanged:hello.show() } }
QML中添加了一個Rectangle,設置objectName屬性值爲「rect」,objectName是爲了在C++中可以找到Rectangle。
B、C++中使用QML元素的屬性
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml> #include "hello.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); //註冊C++類型Hello qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); QQmlApplicationEngine engine; QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); QObject* object = component.create(); qDebug() << "width value is" << object->property("width").toInt(); object->setProperty("width", 500);//設置window的寬 qDebug() << "width value is" << object->property("width").toInt(); qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); QQmlProperty::write(object, "height", 300);//設置window的高 qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); QObject* rect = object->findChild<QObject*>("rect");//查找名稱爲「rect」的元素 if(rect) { rect->setProperty("color", "blue");//設置元素的color屬性值 qDebug() << "color is " << object->property("color").toString(); } return app.exec(); }
QObject::property()/setProperty()來讀取、修改width屬性值。
QQmlProperty::read()/write()來讀取、修改height屬性值。
若是某個對象的類型是QQuickItem,例如QQuickView::rootObject()的返回值,可使用QQuickItem::width/setWidth()來訪問、修改width屬性值。
QML組件是一個複雜的樹型結構,包含兄弟組件和孩子組件,可使用QObject::findchild()/findchildren()來查找。
在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中定義信號與函數
import QtQuick 2.5 import QtQuick.Window 2.0 import Hello.module 1.0 Window { visible: true width: 640 height: 480 title: "Hello QML" color: "white" //定義信號 signal qmlSignal(string message) //定義函數 function qmlFunction(parameter) { console.log("qml function parameter is", parameter) return "function from qml" } MouseArea { anchors.fill: parent onClicked: { hello.begin()//單擊鼠標調用begin信號函數 hello.doSomething(2) qmlSignal("This is an qml signal.")//發送信號 } } Rectangle{ objectName: "rect" anchors.fill: parent color:"red" } Hello{ id:hello onBegin:console.log("hello") onColorChanged:hello.show() } }
QML中添加了qmlSignal()信號和qmlFunction()函數,信號在QML中發送,函數在C++中調用。
B、C++中調用
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QtQml> #include "hello.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); //註冊C++類型Hello qmlRegisterType<Hello>("Hello.module",1,0,"Hello"); QQmlApplicationEngine engine; QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); QObject* object = component.create(); qDebug() << "width value is" << object->property("width").toInt(); object->setProperty("width", 500);//設置window的寬 qDebug() << "width value is" << object->property("width").toInt(); qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); QQmlProperty::write(object, "height", 300);//設置window的高 qDebug() << "height value is" << QQmlProperty::read(object, "height").toInt(); QObject* rect = object->findChild<QObject*>("rect");//查找名稱爲「rect」的元素 if(rect) { rect->setProperty("color", "blue");//設置元素的color屬性值 qDebug() << "color is " << object->property("color").toString(); } //調用QML中函數 QVariant returnedValue; QVariant message = "Hello from C++"; QMetaObject::invokeMethod(object, "qmlFunction", Q_RETURN_ARG(QVariant, returnedValue), Q_ARG(QVariant, message)); qDebug() << "returnedValue is" << returnedValue.toString(); // function from qml Hello hello; //鏈接QML元素中的信號到C++槽函數 QObject::connect(object, SIGNAL(qmlSignal(QString)), &hello, SLOT(qmlSlot(QString))); return app.exec(); }
C++類中的槽函數:
public slots: void doSomething(Color color) { qDebug() << "Hello::dosomething() is called " << color; } void qmlSlot(const QString& message) { qDebug() << "C++ called: " << message; }
QML與混合編程注意事項:
A、自定義C++類必定要派生自QObject類或其子類,並使用Q_OBJECT宏。
B、註冊自定義C++類到Qt元對象系統或設置自定義類對象實例爲QML上下文屬性是必須的。
C、二者交互進行數據傳遞時,要符合QML與C++間數據類型的轉換規則。