使用C++擴充套件QML功能
分類:Qt QuickQML跨平臺-QT2012-08-30 23:1436人閱讀評論(0)收藏編輯刪除
QML語法宣告性的描述如何在記憶體中構建物件樹.在Qt中QML主要用於描述視覺化場景圖,但是其不僅限於此:QML格式可抽象描述任意物件樹.QT中包含的所有QML元素型別都按本文中描述的機制由C++擴充套件而來的.開發者可以使用這些API函式擴充套件新的型別與Qt已存型別進行互動,或為特殊目更改QML.
新增型別
import People 1.0
Person {
name: "Bob Jones"
shoeSize: 12
}
上面的QML片段定義了一個Person例項及其name和shoeSize
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
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型別物件賦給BirthdayParty的host屬性,將三個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++的繼承層次,並可自由的在型別間進行強制轉換.可向宣告為基類型別的物件或物件列表屬性中賦予子類的例項.在這個片段中,guests和host都是Person型別的(屬性或屬性列表),但可賦值為從Person繼承的Boy和Girl物件的例項.
要給屬性賦值,屬性的型別必須在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
}
}
上面的QML向Boy物件賦予一系列屬性值,其中包括使用分組屬性語法的四個屬性.
分組屬性(Grouped properties)將相似的屬性組織在一個命名塊中.分組屬性可用來向開發者提供完美的API,通過實現重用,也可簡化對跨型別的通用屬性集合的實現.
分組屬性塊作為只讀物件屬性來實現.shoe屬性定義如下:
Q_PROPERTY(ShoeDescription *shoe READ shoe)
類ShoeDescription定義了分組屬性塊中的屬性,本例中為size,color,brand和price屬性.
分組屬性塊可能會遞迴宣告和存取.
附加(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).附加物件中的屬性可在附加屬性塊中訪問.
任何聲明瞭public的qmlAttachedProperties() 函式的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.
如果create為true,則總是返回一個可用的附加物件,如果不存在就建立一個新例項.如果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首先嚐試做正常的賦值