前言
在開始講之前首先講一個使用屬性(setContextProperty)和注冊類型(qmlRegisterType)的區別,在這主要講一些我個人工作中的情況,其實二者都是將c++類暴露給QML的方法,只不過在使用時存在一些區別,根據使用方式不同我個人分為C++的形式和QML的定義形式。
1、C++定義方式(主要使用setContextProperty()函數)
- a)、比如我們有一個功能單一的Configure類,我們需要把它暴露給QML,在使用之前必須要先創建類對象m_configuration,就是說類實例化一次,QML中可以直接使用這個類,注意功能單一的類只適合該方式;
- b)、比如我們的業務比較復雜,我們有很多類,若要供QML調用,我們就要寫一個總的被調用類Complex(包含所有的業務類),然后實例化一次這個Complex,然后QML中直接使用實例化后的對象;
兩種業務方式的使用方式如下:
QQuickView viewer; viewer.rootContext()->setContextProperty("_configuration",&m_configuration);
_configuration便可直接在qml中使用,_configuration自然也是一個全局變量。
2、QML的方式,(主要使用qmlRegisterType()函數)
該方式都是使用在業務復雜情況下,還是上面的例子,我們有一堆業務類,這個時候我們使用注冊的方式,用在QML中定義的方式去定義各個實例,也就不用再需要一個總類:
qmlRegisterType<Foo>("App", 1, 0, "Foo"); qmlRegisterType<Bar>("Bar", 1, 0, "Bar");//注冊不可實例化的類型
我們可以再QML中直接使用Foo去定義實例:
import App 1.0 Foo { bar.baz: "abc" Component.onCompleted: print(bar.baz) }
3、二者比較
與C++方式相比,QML具有如下優勢:
-
變量名前面可以加$(
全局變量可用),
從而方便區分全局變量和局部變量,這個在C++定義屬性的時候是不允許的; -
如果某個全局變量(一般是QML對象)構造很慢,可以通過QML中的
Loader
來很方便異步構造,從而加速程序啟動:
一、在QML中使用C++屬性
QObject子類的所有屬性都能夠被QML訪問,QObject子類使用Q_PROPERTY宏定義一個屬性,該宏的作用是向Qt元對象系統注冊類的屬性,一個類的屬性就是類的數據成員,通常會有一個用於讀取的READ函數和一個可選的用於修改的WRITE函數。
該宏定義如下:
一些常見的申明示例:
使用我們一般使用setContextProperty()函數,注意和注冊的區別,使用一版如下,m_configuration就是一個全局變量,便可在qml中直接使用
QQuickView viewer; viewer.rootContext()->setContextProperty("_configuration",&m_configuration);
1.1 使用函數和槽
QML可以有條件地訪問QObject子類的函數:
- 使用Q_INVOKABLE宏標記的public函數
Q_INVOKABLE bool postMessage(const QString &msg){ qDebug()<<"postMessage"; }
- public槽函數
public slot: void refresh() { qDebug()<"refresh"; }
1.2 使用信號
QML代碼可以使用QObject子類的任意public信號,QML引擎會為每一個來自QObject子類的信號自動創建一個信號處理器,其命名規則如為:on<Signal>,其中<Signal>為信號的名字,首字母要大寫,信號傳遞的參數通過其名字在信號處理器中使用。
注意:QML中的信號與前面提到的函數重載不同,如果C++類中具有參數列表不同的多個同名信號,但只有最后一個信號才能被QML訪問到。
二、注冊QML類型
QObject子類可以注冊到QML類型系統中,以便在QML程序中作為一個數據類型使用,前面提到的所有操作數據的基礎就是先注冊Qml類型。可被注冊的類分為可實例化和不可實例化兩種,注冊可實例化的類意味着這個類定義為一個QML對象類型,QML對象類型通過這種注冊能夠獲取這種類型的元數據,以及相關屬性信號等操作,注冊不可實例化的C++類,意味着這種類型不可實例化,比如我們要把枚舉類型暴露給QML,但這個類型本身不需要被實例化。
2.1 注冊可實例化對象類型
QObject子類都可以注冊為QML對象類型,注冊成功后這個類就可以在QML代碼中像其他類型一樣聲明和初始化,創建成功后就可以在QML中使用其屬性值、函數和信號等特征,注冊QObject子類,需要使用qmlRegisterType()函數,下面有兩個類Bar,Foo:
class Bar : public QObject { Q_OBJECT Q_PROPERTY(QString baz READ baz WRITE setBaz NOTIFY bazChanged) public: Bar() {} QString baz() const { return mBaz; } void setBaz(const QString &baz) { if (baz == mBaz) return; mBaz = baz; emit bazChanged(); } signals: void bazChanged(); private: QString mBaz; }; class Foo : public QObject { Q_OBJECT Q_PROPERTY(Bar *bar READ bar CONSTANT FINAL) public: Foo() {} Bar *bar() { return &mBar; } private: Bar mBar; };
我們使用qmlRegisterType將其注冊到QML系統類型,該函數需要一個命名空間和版本號:
qmlRegisterType<Foo>("App", 1, 0, "Foo"); qmlRegisterType<Bar>();//注冊不可實例化的類型
在QML中使用:
import App 1.0
Foo { bar.baz: "abc" Component.onCompleted: print(bar.baz) }
2.2 注冊不可實例化對象類型
有時需要注冊一個不可實例化的對象類型,比如一個C++類:
- 是接口類型;
- 一個基類,不需要通過QML代碼訪問;
- 僅僅提供一些有用的枚舉;
- 是一個單例,只能使用其唯一的實例,不應該從QML進行實例化。
注冊方法:
- 使用無參的qmlRegisterType()函數;
- 使用qmlRegisterInterface()注冊指定QML類型名稱的Qt接口類型;
- 使用qmlRegisterUncreatableType()函數
- 使用qmlRegisterSingletonType()函數