QML與C++混合程式設計
文章參考:
https://blog.csdn.net/foruok/article/details/32698603
http://blog.51cto.com/9291927/1975383
QT專案開發過程中,畫面上顯示的佈局以及對應的資料可能會動態變化,如果只用QML來構件簡單的佈局是無法滿足需求的,所以會用通過C++程式碼來實現業務邏輯,QML負責構件UI。這裡其實有MVC模式的思想。
QML與C++混合程式設計簡介
QML與C++混合程式設計就是使用QML高效便捷地構建UI,而C++則用來實現業務邏輯和複雜演算法。
Qt Quick與QML
Qt Quick是Qt User Interface Creation Kit的縮寫。
從
Qt Quick包含一個元件集合,大部分用於圖形介面。同時也包含一個用於管理元件並與元件互動的C++ API——QtDeclarative模組,用於QML與C++之間的橋樑。
C++與QML的互動通過向QML註冊C++物件實現,其中C++實現中,非視覺化的類均為QObject的子類,可是化的型別均為QDeclarativeItem的子類,而QDeclarativeItem等同於QML的Item類。
QML 中使用 C++ 類和物件
Qt 提供了兩種在 QML 環境中使用 C++ 物件的方式:
- 在 C++ 中實現一個類,註冊到 QML 環境中, QML 環境中使用該型別建立物件;
- 在 C++ 中構造一個物件,將這個物件設定為 QML 的上下文屬性,在 QML 環境中直接使用改屬性.
不管哪種方式,對要匯出的 C++ 類都有要求,不是一個類的所有方法、變數都可以被 QML 使用,因此我們先來看看怎樣讓一個方法或屬性可以被 QML 使用。
C++類的實現
C++類要想被QML訪問,首先必須滿足兩個條件:
- 派生自QObject類或QObject類的子類;
- 二是使用Q_OBJECT巨集。
這兩個條件跟
訊號與槽實現
只要是訊號或者槽,都可以在 QML 中訪問。
訊號在C++中使用時要用到emit關鍵字,但在QML中就是個普通的函式,用法同函式一樣。
註冊QML型別
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName) 有兩個模板函式:
template<typename T>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);
template<typename T, int metaObjectRevision>
int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName);
前一個原型一般用來註冊一個新型別,而後一個可以為特定的版本註冊型別。通常使用的是第一個,後面這個有興趣可以自己詳細瞭解。
引數含義:
typename :實現的 C++ 類的類名。
uri :讓你指定一個唯一的包名,類似 Java 中的那種,一是用來避免名字衝突,而是可以把多個相關類聚合到一個包中方便引用。比如我們常寫這個語句 “import QtQuick 1.0” ,其中的 “QtQuick” 就是包名 uri
versionMajor:指上述包名後面1.0中的“1”
versionMinor:指上述包名後面1.0中的“0”
qmlName : QML 中可以使用的類名。
例如:
qmlRegisterType<TestHuman >("qt.test.TestHuman ", 1, 0, "TestHuman "); //只要保證在載入qml之前即可
當將C++類註冊成功之後則可以在 QML 中建立 C++ 匯入型別的物件了,與 QML 內建型別的使用完全一樣。如下是建立一個 TestHuman 例項的程式碼:
import QtQuick 2.0
import qt.test.TestFlowers 1.0
Rectangle {
width: 200;
height: 200;
TestFlowers {
id: testflowers ;
anchors.top: parent.top;
}
Button{
id: testBtn1;
text: qsTr("Hello World");
anchors.top: testflowers .bottom;
onClicked: {
testHuman .onBtnClick();
}
}
}
上述qml檔案可以看出C++物件可以跟其它部件使用完全一樣,qml檔案中通常要使用C++物件的成員屬性、方法,為了能讓C++的相關資料能被訪問,則需要做特殊處理。
Q_PROPERTY
Q_PROPERTY 巨集用來定義可通過元物件系統訪問的屬性,通過它定義的屬性,可以在 QML 中訪問、修改,也可以在屬性變化時發射特定的訊號。要想使用 Q_PROPERTY 巨集,你的類必須是 QObject 的派生類,必須在類首使用 Q_OBJECT 巨集。
下面是 Q_PROPERTY 巨集的原型:
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 :如果你沒有為屬性指定 MEMBER 標記,則 READ 標記必不可少;宣告一個讀取屬性的函式,該函式一般沒有引數,返回定義的屬性。
- WRITE:可選配置。宣告一個設定屬性的函式。它指定的函式,只能有一個與屬性型別匹配的引數,必須返回 void 。
- WRITE:可選配置。宣告一個設定屬性的函式。它指定的函式,只能有一個與屬性型別匹配的引數,必須返回 void 。
其它不常用的就不介紹了,感興趣的可以檢視SDK。
Q_ENUMS
如果你要匯出的類定義了想在 QML 中使用列舉型別,可以使用 Q_ENUMS 巨集將該列舉註冊到元物件系統中。
Q_INVOKABLE
在定義一個類的成員函式時使用 Q_INVOKABLE 巨集來修飾,就可以讓該方法被元物件系統呼叫。這個巨集必須放在返回型別前面。在 QML 中就可以用
{method} 來訪問。
程式碼:
C++類(.h檔案):
class TestFlowers : public QObject
{
Q_OBJECT
Q_ENUMS(Color)
Q_PROPERTY(Color color READ color WRITE setColor NOTIFY colorChange)
Q_INVOKABLE void (onBtnClick)
public:
TestFlowers (QObject *parent = 0);
~TestFlowers ();
enum Color {
RED,
GREEN,
BLUE
};
};
qml:
import QtQuick 2.0
import qt.test.TestFlowers 1.0
Rectangle {
width: 200;
height: 200;
TestFlowers {
id: testflowers ;
anchors.top: parent.top;
}
Button{
id: testBtn1;
text: qsTr("Hello World");
anchors.top: testflowers .bottom;
onClicked: {
testflowers.onBtnClick(); //控制元件的點選事件,可以在類檔案(.cpp)中實現業務邏輯
}
}
Rectangle{
id:background;
width: parent.width;
height:50;
color:testflowers .color; //可以直接使用C++屬性值
anchors.bottom: testBtn1.bottom;
}
}
具體的C++類的.cpp檔案就不詳細寫了,這裡主要表達的是在qml中使用C++類的相關屬性和方法。如果想在qml中使用C++類屬性,則必須用特殊的巨集才能使用。
註冊C++ 物件為 QML 的屬性
在C++應用程式載入QML物件時,可以直接嵌入一些C++資料來給QML使用,需要用到QQmlContext::setContextProperty()設定QML上下文屬性,上下文屬性可以是一個簡單的型別,也可以是任何自定義的類物件。
以下程式碼在main函式中註冊:
QtQuick2ApplicationViewer viewer;
viewer.rootContext()->setContextProperty("testflowers ", new TestFlowers ); //這裡new建立了物件,需要自己手動回收
viewer.setMainQmlFile(QStringLiteral("qml/testflowers /main.qml")); //"qml/testflowers /main.qml"表示qml檔案路徑
通過上述方法註冊之後。qml的使用有部分改動:
import QtQuick 2.0
// import qt.test.TestFlowers 1.0 已經註冊,不需要了
Rectangle {
width: 200;
height: 200;
// 已經註冊成屬性了,不要了
/* TestFlowers {
id: testflowers ;
anchors.top: parent.top;
}*/
Button{
id: testBtn1;
text: qsTr("Hello World");
anchors.top: testflowers .bottom;
onClicked: {
testflowers.onBtnClick(); //控制元件的點選事件,可以在類檔案(.cpp)中實現業務邏輯
}
}
Rectangle{
id:background;
width: parent.width;
height:50;
color:testflowers .color; //可以直接使用C++屬性值
anchors.bottom: testBtn1.bottom;
}
}
在 C++ 中使用 QML 物件
在C++中也可以訪問QML中的屬性、函式和訊號。QObject 類的建構函式有一個 parent 引數,可以指定一個物件的父親, QML 中的物件其實藉助這個組成了以根 item 為父的一棵物件樹。
而 QObject 定義了一個屬性 objectName ,這個物件名字屬性,就可以用於查詢物件。現在該說到查詢物件的方法了: findChild() 和 findChildren() 。它們的函式原型如下:
T QObject::findChild(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
QList<T> QObject::findChildren(const QString &name = QString(), Qt::FindChildOptions options = Qt::FindChildrenRecursively) const
用例:
QPushButton *button = parentWidget->findChild<QPushButton *>("button1");
QList<QWidget *> widgets = parentWidget.findChildren<QWidget *>("widgetname");
在TestFlowers 類中查詢testBtn1物件呼叫方法如下:
Button* button1 = this->findChild<Button*>(“testBtn1”);
PS:上述程式碼的例項沒有寫全,所以不要直接copy在實機上執行,上述相關方法都可以到QT SDK中找到,可以自己下載一個qt 工具,然後在裡邊搜尋相關方法,裡邊都有詳細介紹且非常方便(參考:QT框架簡介裡邊的搜尋方法)。