1. 程式人生 > >QML與C++混合程式設計

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框架簡介中可以瞭解到,QML (Qt Meta-Object Language的縮寫)其實是對 JavaScript 的擴充套件,是Qt Quick最重要的組成部分。
Qt Quick包含一個元件集合,大部分用於圖形介面。同時也包含一個用於管理元件並與元件互動的C++ API——QtDeclarative模組,用於QML與C++之間的橋樑。
C++與QML的互動通過向QML註冊C++物件實現,其中C++實現中,非視覺化的類均為QObject的子類,可是化的型別均為QDeclarativeItem的子類,而QDeclarativeItem等同於QML的Item類。

QML 中使用 C++ 類和物件


Qt 提供了兩種在 QML 環境中使用 C++ 物件的方式:

  1. 在 C++ 中實現一個類,註冊到 QML 環境中, QML 環境中使用該型別建立物件;
  2. 在 C++ 中構造一個物件,將這個物件設定為 QML 的上下文屬性,在 QML 環境中直接使用改屬性.
    不管哪種方式,對要匯出的 C++ 類都有要求,不是一個類的所有方法、變數都可以被 QML 使用,因此我們先來看看怎樣讓一個方法或屬性可以被 QML 使用。

C++類的實現
C++類要想被QML訪問,首先必須滿足兩個條件:

  1. 派生自QObject類或QObject類的子類;
  2. 二是使用Q_OBJECT巨集。
    這兩個條件跟
    QT訊號和槽的實現
    一樣,是為了讓一個類能夠進入 Qt 強大的元物件系統(meta-object system)中,只有使用元物件系統,一個類的某些方法或屬性才可能通過字串形式的名字來呼叫,才具有了在 QML 中訪問的基礎條件。

訊號與槽實現
只要是訊號或者槽,都可以在 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 中就可以用 O b j e c t . {Object}. {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框架簡介裡邊的搜尋方法)。