1. 程式人生 > >使用C++擴充套件QML功能

使用C++擴充套件QML功能

分類:Qt QuickQML跨平臺-QT2012-08-30 23:1436人閱讀評論(0)收藏編輯刪除

QML語法宣告性的描述如何在記憶體中構建物件樹.QtQML主要用於描述視覺化場景圖,但是其不僅限於此:QML格式可抽象描述任意物件樹.QT中包含的所有QML元素型別都按本文中描述的機制由C++擴充套件而來的.開發者可以使用這些API函式擴充套件新的型別與Qt已存型別進行互動,或為特殊目更改QML.

新增型別

import People 1.0

Person {

name: "Bob Jones"

shoeSize: 12

}

上面的QML片段定義了一個Person例項及其nameshoeSize

屬性.本質上QML中的每條語句都是在定義一個物件例項或設定屬性的值.

QML依賴於Qt的元物件系統,僅可例項化QObject的子類.對於視覺化元素型別,都是QDeclarativeItem的子類;檢視中元素的模型,都是QAbstractItemModel的子類;任意帶有屬性的物件都是QObject的直接子類.

QML引擎不知道任何型別資訊.需要程式設計師使用QML中所用的名稱來註冊C++.

自定義C++型別使用模版函式來註冊:

template<typename T>

int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

呼叫qmlRegisterType()QML系統中註冊型別T,QML中以qmlName引用型別,庫的版本為versionMajor.versionMinor.qmlName可以與C++型別同名.

型別T必須是QObject的子類,必須有預設建構函式.

註冊後,類中所有屬性都在QML中可用.QML原生支援的屬性型別列表在QML Basic Types文件中描述,包括:

·         bool, unsigned int, int, float, double, qreal

QML中元素是基於C++類支援的,當一個屬性加入到C++,會自動生成一個對應的value-changed

訊號.見下面的Signal Support .

QML是型別安全的.試圖向屬性賦非法值會生成一個錯誤.例如,Person元素的name屬性是QString型別的,下面程式碼將引起錯誤:

Person {

// Will NOT work

name: 12

}

QML型別版本

C++中向類新增新的方法或屬性不會影響原來的應用程式.然而在QML,新新增的方法或屬性可能會改變以前的屬性引用方式.

例如考慮到如下兩個QML檔案

// main.qml

import QtQuick 1.0

Item {

id: root

MyComponent {}

}

// MyComponent.qml

import MyModule 1.0

CppItem {

value: root.x

}

CppItem對映到C++QCppItem.

如果QCppItem的作者在新版本模組中添加了一個新的屬性root,將破壞上面程式中的root.x,使其指向了一個不同的值.解決方法是讓QCppItem的作者做出宣告,新的root屬性只對特定版本的QCppItem生效.這樣可以避免在已存在的元素中新增新屬性和特性時破壞已有程式.

QML允許將屬性,方法和訊號與特定版本號繫結,只有特定版本的模組被匯入時他們才能被訪問.本例中,作者在子版本號為1的模型中添加了root屬性,並註冊模板版本為1.1.

REVISION標籤標記root屬性在子版本號為1的類中新增.Q_INVOKABLE方法,訊號,槽也可以通過Q_REVISION(x)巨集與子版本號繫結:

class CppItem : public QObject

{

Q_OBJECT

Q_PROPERTY(int root READ root WRITE setRoot NOTIFY rootChanged REVISION 1)

signals:

Q_REVISION(1) void rootChanged();

};

用下面的函式註冊帶有實際子版本號的新類:

template<typename T, int metaObjectRevision>

int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

註冊MyModule模組1.1版本中的CppItem:

qmlRegisterType<QCppItem,1>("MyModule", 1, 1, "CppItem")

root屬性僅在匯入MyModule的1.1版本時可用.

物件和列表屬性型別

BirthdayParty {

host: Person {

name: "Bob Jones"

shoeSize: 12

}

guests: [

Person { name: "Leo Hodges" },

Person { name: "Jack Smith" },

Person { name: "Anne Brown" }

]

}

上面的QML片段將一個Person型別物件賦給BirthdayPartyhost屬性,將三個Person物件賦給guests屬性.

QML中可以設定比原始屬性如整型和字串的型別更復雜的屬性.屬性也可以是一個物件的指標,Qt介面指標,物件指標列表,Qt介面指標列表.QML是型別安全的可以確保只有合法的值才能賦給這些屬性,和那些原生支援的型別一樣.

指向物件或Qt介面的屬性使用Q_PROPERTY()巨集宣告,與其他屬性相同.host屬性宣告如下:

Q_PROPERTY(Person *host READ host WRITE setHost)

只要屬性的型別,本例中為Person,QML註冊後就可以進行賦值.

QML也支援Qt的介面賦值.要給Qt介面指標型別的屬性賦值,介面也需要在QML中註冊.由於其不能直接例項化,註冊介面與註冊QML中新型別是不同的.要使用下面的函式:

template<typename T>

int qmlRegisterInterface(const char *typeName)

註冊C++的介面T,QML中命名為typeName.

註冊後,QML強迫實現介面的物件為相應型別的屬性賦值.

guests屬性是一個Person物件的列表.物件或介面列表屬性也是用Q_PROPERTY()巨集宣告(與其他屬性相同).列表屬性必須是QDeclarativeListProperty<T>型別.而且型別T必須在QML中註冊.

guests屬性宣告如下:

Q_PROPERTY(QDeclarativeListProperty<Person> guests READ guests)

繼承和強制轉換(Coercion)

BirthdayParty {

host: Boy {

name: "Bob Jones"

shoeSize: 12

}

guests: [

Boy { name: "Leo Hodges" },

Boy { name: "Jack Smith" },

Girl { name: "Anne Brown" }

]

}

上面的QML片段向BirthdayParty物件的host屬性賦予了一個Boy物件值,並向guests屬性賦予了三個其他物件.

QML支援C++的繼承層次,並可自由的在型別間進行強制轉換.可向宣告為基類型別的物件或物件列表屬性中賦予子類的例項.在這個片段中,guestshost都是Person型別的(屬性或屬性列表),但可賦值為從Person繼承的BoyGirl物件的例項.

要給屬性賦值,屬性的型別必須在QML中註冊.上面說是的qmlRegisterType()qmlRegisterInterface()都可向QML中註冊型別.而對於作為基類的純虛型別,是不能在QML中例項化的,也需要使用如下函式進行註冊:

template<typename T>

int qmlRegisterType()

QML中註冊C++中的純虛型別T時qmlRegisterType函式沒有任何引數,是要告訴qmlRegisterType()不為C++類和QML元素名稱做對映,因此這個型別不能在QML中例項化,但可用於型別轉換.

型別T必須繼承於QObject,但這裡不限制其是否擁有實際建構函式或建構函式簽名.

當向物件屬性或列表屬性賦值時QML會自動轉換C++型別.如果型別轉換失敗則丟擲錯誤.

預設屬性

BirthdayParty {

host: Boy {

name: "Bob Jones"

shoeSize: 12

}

Boy { name: "Leo Hodges" }

Boy { name: "Jack Smith" }

Girl { name: "Anne Brown" }

}

上面的QML片段將一個物件列表賦值給BirthdayParty物件的預設屬性.

預設屬性是一個方便的語法,讓類設計者可以指定一個屬性作為類的預設屬性.無論是否指定屬性名稱都可對預設屬性賦值.可像指定屬性名稱一樣對預設屬性賦值,非常方便.

C++中類設計者使用Q_CLASSINFO()標記預設屬性:

Q_CLASSINFO("DefaultProperty", "property")

property標記為類的預設屬性property必須是物件屬性或列表屬性.

預設屬性是可選的.子類繼承父類的預設屬性,但可重新宣告預設屬性覆蓋基類的設定property可以為本類中定義的屬性,也可是基類中定義的屬性.

分組屬性

Boy {

name: "Jack Smith"

shoe {

size: 8

      color: "blue"

brand: "Puma"

price: 19.95

}

}

上面的QMLBoy物件賦予一系列屬性值,其中包括使用分組屬性語法的四個屬性.

分組屬性(Grouped properties)將相似的屬性組織在一個命名塊中.分組屬性可用來向開發者提供完美的API,通過實現重用,也可簡化對跨型別的通用屬性集合的實現.

分組屬性塊作為只讀物件屬性來實現.shoe屬性定義如下:

Q_PROPERTY(ShoeDescription *shoe READ shoe)

ShoeDescription定義了分組屬性塊中的屬性,本例中為size,color,brandprice屬性.

分組屬性塊可能會遞迴宣告和存取.

附加(Attached)屬性

Boy {

name: "Leo Hodges"

shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }

BirthdayParty.rsvp: "2009-07-06"

}

上面的QML片段使用附加屬性語法向rsvp屬性賦予一個日期值.

附加屬性是型別無關的,可為其他型別定義額外的說明性屬性,便於內部使用.附加屬性使用具體型別做標識,本例中使用BirthdayParty作為屬性名稱的字首.

本例中,BirthdayParty叫做附加(attaching)型別,Boy叫做被附加(attachee)例項.

在附加型別中,附加的屬性塊被實現為一個新的QObject子型別,叫做附加物件(attachment object).附加物件中的屬性可在附加屬性塊中訪問.

任何聲明瞭publicqmlAttachedProperties() 函式的QML型別,並具有QML_HAS_ATTACHED_PROPERTIES(附加屬性),都可作為附加型別:

class MyType : public QObject {

Q_OBJECT

public:

...

static AttachedPropertiesType *qmlAttachedProperties(QObject *object);

};

QML_DECLARE_TYPEINFO(MyType, QML_HAS_ATTACHED_PROPERTIES)

為被繫結物件例項返回一個AttachedPropertiesType型別的附加物件.習慣上給附加物件賦予一個父物件可以防止記憶體洩露,但不是必須的.

AttachedPropertiesType必須是QObject的子類.本型別上的屬性需要通過附加屬性語法進行訪問.

這個方法在每個被附加的物件例項上呼叫一次.QML引擎將快取返回的例項指標給後面的附加屬性訪問.因此附加物件可能會保留到物件刪除的時候.

概念上,附加的屬性就是在一個型別中匯出一系列的額外屬性,可設定在其他物件例項上.附加屬性並沒有限制僅附加到物件例項上,但其產生的影響是這樣限制的.

例如,一個通用場景是為型別增加可用屬性,其下級項(children)可收集到更多例項資料.這裡向所有出現生日派對的客人增加rsvp:

BirthdayParty {

Boy { BirthdayParty.rsvp: "2009-06-01" }

}

然而,不能限制僅在需要附加資訊的例項上附加屬性,下面也是合法的,但這個上下文中增加birthday party rsvp是沒有用處的.

GraduationParty {

Boy { BirthdayParty.rsvp: "2009-06-01" }

}

C++,包含附加類的實現單元后,在例項中訪問附加物件的方法為:

template<typename T>

QObject *qmlAttachedPropertiesObject<T>(QObject *attachee, bool create = true);

返回型別為T的附加到attachee (被附加物件)上的附加物件.如果T不是合法的附加型別,返回0.

如果createtrue,則總是返回一個可用的附加物件,如果不存在就建立一個新例項.如果create false,附加物件僅在被建立時才返回.

記憶體管理和QVariant物件

這個單元負責確保不會返回或訪問到指向非法物件的指標.QML做了如下保證:

·         賦值給QObject指標型別的屬性的值必須在賦值的時候合法.

賦值後,這個類有責任使用類的特定方法或QPointer類來維護這個指標.

·         物件賦值給QVariant必須確保賦值時物件合法.

將一個物件賦值給QVariant屬性,QML會使用QMetaType::QObjectStar型別化QVariant.這個類負責儲存指標.定義類中QVariant型別屬性時通常需要在賦值時檢查QVariant中值的具體型別,如果是不支援的型別,則重置為無效變數值.

·         物件賦值給一個QObject列表屬性時必須確保賦值時物件合法.

賦值後,這個類有責任使用類的特定方法或QPointer類來維護這個指標.

必須假設任何被QML賦值的物件都有可能隨時刪除,並做相應處理.在場景中明確標明不需要繼續執行的元素,就不會對程式造成影響.

訊號支援

BirthdayParty {

onPartyStarted: console.log("This party started rockin' at " + time);

}

上面的QML片段將一個JavaScript表示式的值與Qt觸發的訊號相關聯.

所有類中宣告的Qt訊號都可在QML中作為特殊的訊號屬性,並可用一段JavaScript表示式進行賦值.訊號屬性名稱為Qt訊號名稱的變形:Qt訊號名稱首字母大寫,並加on字首.例如,本例中的訊號在C++中定義為:

signals:

void partyStarted(const QTime &time);

對於類中具有的多個同名訊號,只有最後一個訊號才作為訊號屬性.注意訊號同名但引數不同是無法區分的.

在指令碼中需要使用訊號引數名稱對其進行賦值.無名引數是不能訪問的,因此在定義C++類的時候要給訊號的所有引數帶上名稱.系統原生支援的訊號引數型別請見Adding Types,同時註冊的物件型別也可作為訊號引數型別.使用其他型別也沒有錯誤,只是不能在指令碼中進行訪問.

如果要在不是由QML建立的物件上使用訊號,可使用Connections來訪問其訊號.

另外,C++類中新增一個屬性後,與這個C++類相對應的QML元素都會自動生成一個此屬性的value-changed訊號.訊號的名稱為on<Property-name>Changed,屬性名稱的首字母大寫.

注意:這種QML訊號總是命名為on<Property-name>Changed,即使是C++中的NOTIFY訊號名稱也一樣.對於C++中的NOTIFY訊號我們推薦使用<property-name>Changed()命名.

方法

使用Q_INVOKABLE標記的槽和函式可在QML中作為函式呼叫.

BirthdayParty {

host: Person {

name: "Bob Jones"

shoeSize: 12

}

guests: [

Person { name: "Leo Hodges" },

Person { name: "Jack Smith" },

Person { name: "Anne Brown" }

]

Component.onCompleted: invite("William Green")

}

本例中使用可呼叫方法在BirthdayParty元素中新增一個呼叫約定(訊號槽繫結).BirthdayParty類中使用Q_INVOKABLE將方法invite()標記為可在QML中呼叫:

Q_INVOKABLE void invite(const QString &name);

invite()方法如果宣告為槽函式也同樣可用.

屬性值源(Property Value Sources)

BirthdayParty {

HappyBirthdaySong on announcement { name: "Bob Jones" }

}

上面的QML程式碼片段對announcement屬性應用了屬性值源HappyBirthdaySong.屬性值源在屬性發生改變時為屬性生成新的值.

屬性值源(Property value sources)通常用於在動畫中.與其構造一個動畫物件並手動設定動畫的目標屬性,使用屬性值源(property value source)可以直接設定任意型別的屬性並自動設定關聯.

本例不是很自然:BirthdayParty物件的announcement屬性是字串,並一直列印輸出其值, HappyBirthdaySong的屬性源生成"Happy Birthday"歌曲的歌詞.

Q_PROPERTY(QString announcement READ announcement WRITE setAnnouncement)

通常,將字串賦值給物件是非法的.在屬性值源(property value source)情形下,不必給其賦值物件例項,QML引擎設定值源和屬性的關聯.

class HappyBirthdaySong : public QObject, public QDeclarativePropertyValueSource

{

Q_OBJECT

Q_INTERFACES(QDeclarativePropertyValueSource)

public:

HappyBirthdaySong(QObject *parent = 0);

virtual void setTarget(const QDeclarativeProperty &);

};

從其他方面看,屬性值源(property value source)是規範的QML型別.必須像其他型別一樣使用同樣的巨集向QML引擎進行註冊,也可以包含屬性,訊號和方法.

當屬性值源(property value source)物件賦值給屬性,QML首先嚐試做正常的賦值