(本篇文章為Qt官網英文文章,此為翻譯整理所作)
The Property System
Qt提供了一套和其他通用編譯器提供商所提供的屬性系統類似的屬性系統 ,然而,作為一個獨立於編譯器和平台的庫,Qt不能依賴像__property或者[property]那樣的非標准編譯器特征。Qt的解決方案是在支持任意標准平台上的C++編譯器的基礎上進行工作。它基於元對象系統,元對象系統也通過信號和槽提供對象通訊。
一、聲明屬性
1、要求
1)Q_PROPERTY() 宏進行聲明;
2)必須集成QObject()。
2、屬性類別
Q_PROPERTY(type name
READ getFunction
[WRITE setFunction]
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
1)READ getFunction
必須有的,用於讀取屬性,使用const限定,返回屬性的類型或者類型的指針或引用。
2)WRITE setFunction
可選的,用於設置屬性,參數是一個屬性的類型,或者屬性的const指針或引用,返回值是void。
3)RESET resetFunction
可選的,用於把屬性根據上下文設置回它的特定缺省值,沒有參數,返回void。
4)NOTIFY notifySignal
可選的,當屬性改變時發射的信號。
5)REVISION int
可選的,定義屬性和它的信號暴露給QML的修訂版本。
6)DESIGNABLE bool
該屬性是否在Qt的QtDesigner設計器中顯示,默認true即為顯示。
7)SCRIPTABLE bool
該屬性是否可以通過腳本系統進行訪問,默認true即為可訪問。
8)STORED bool
絕大多數屬性是被存儲的,但是有一小部分的虛擬屬性卻不用。舉個例子,QWidget::minimumWidth()是不用存儲的,因為它只是QWidget::minimumSize()的一種查看,沒有自己的數據。
9)USER bool
USER變量表明屬性是否被設計為面向用戶的或用戶可修改的類屬性。通常,每個類只有一個USER屬性。例如,QAbstractButton::checked是按鈕類的用戶可修改屬性。
10)CONSTANT
CONSTANT的出現表明屬性的值是不變的。對於一個object實例,常量屬性的READ方法在每次被調用時必須返回相同的值。此常量值可能在不同的object實例中不相同。一個常量屬性不能具有WRITE方法或NOYIFY信號。
11)FINAL
FINAL變量的出現表明屬性不能被派生類所重寫。有些情況下,這可以用於效率優化,但不是被moc強制的。程序員必須永遠注意不能重寫一個FINAL屬性。
READ,WRITE和RESET函數都可以被繼承。它們也可以是虛函數。當它們在被多重繼承中被繼承時,它們必須出現在第一個被繼承的類中。
3、類型的種類
屬性的類型可以是被QVariant支持的所有類型,也可以是用戶定義的類型。自定義類型需要使用Q_DECLARE_METATYPE()宏注冊,以使它們的值能被保存在QVariant對象中。
對於QMap,QList和QValueList屬性,屬性的值是一個QVariant,它包含整個list或map。注意Q_PROPERTY字符串不能包含逗號,因為逗號會划分宏的參數。因此,你必須使用QMap作為屬性的類型而不是QMap<QString,QVariant>。為了保持一致性,也需要用QList和QValueList而不是QList<QVariant>和QValueList<QVariant>。
4、屬性讀寫方式
一個屬性可以使用常規函數QObject::property()和QObject::setProperty()進行讀寫,不用知道屬性所在類的任何細節,除了屬性的名字。在下面的小代碼片段中,調用QAbstractButton::setDown()和QObject::setProperty()都把屬性設置為“down”。
QPushButton *button = new QPushButton;
QObject *object = button;
button->setDown(true);
object->setProperty("down", true);
通過WRITE操作器來操作一個屬性是上面兩者中更好的,因為它快並且在編譯時給於更好的診斷幫助,但是以這種方式設置屬性要求你必須在編譯時了解其類。通過名字來操作屬性使你可以操作在編譯器你不了解的類。你可以在運行時發現一個類的屬性們,通過查詢它的QObject,QMetaObject和QMetaProerties。
QObject *object = ...
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i = 0; i < count; ++i) {
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = object->property(name);
...
}
在上面的代碼片段中,QMetaObject::property()被用於獲取未知類中的屬性的metadata。從metadata中獲取屬性名然后傳給QObject::property()來獲取。
5、示例
假設我們有一個類MyClass,它從QObject派生並且在它的private區使用 了Q_OBJECT宏。我們想在MyClass類中聲明一個屬性來持續追蹤一個Priorty值。屬性的值叫做priority,並且它的類型是一個在類MyClass中定義的叫做Priority的枚舉。
我們在類的private區使用Q_PROPERTY()來聲明屬性。READ函數叫做priority,並且我們包含一個WRITE函數叫做setPriority。枚舉類型必須使用Q_ENUMS()注冊到元數據對象系統中。注冊一個枚舉類型使得枚舉的名字可以在調用QObject::setProperty()時使用。我們還必須為READ和WRITE函數提供我們自己的聲明。MyClass的聲明看起來應該是這樣的:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority)
Q_ENUMS(Priority)
public:
MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
void setPriority(Priority priority);
Priority priority() const;
};
READ函數是const的並且返回屬性的類型。WRITE函數返回void並且具有一個屬性類型的參數。元數據對象編譯器強制做這些事情。
在有了一個指向MyClass實例的指針時,我們有兩種方法來設置priority屬性:
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
在此例子中,枚舉類型在MyClass中聲明並被使用Q_ENUMS()注冊到元數據對象系統中。這使得枚舉值可以在調用setProperty()時做為字符串使用。如果枚舉類型是在其它類中聲明的,那么我們就需要用枚舉的全名(如OtherClass::Priority),並且這個其它類也必須從QObject中派生並且也要注冊枚舉類型。
另一個簡單的Q_FLAGS()也是可用的。就像Q_ENUMS(),它注冊一個枚舉類型,但是它把枚舉類型作為一個flag的集合,也就是,值可以用OR操作來合並。一個I/O類可能具有枚舉值Read和Write並且QObject::setProperty()可以接受 Read|Write。此時應使用Q_FLAGS()來注冊枚舉值。
6、動態屬性
Qobject::setProperty()也可以用來在運行時向一個類的實例添加新的屬性。當使用一個名字和值調用它時,如果一個對應的屬性已經存在,並且如果值的類型與屬性的類型兼容,那么值就被存儲到屬性中,然后返回true。如果值類型不兼容,屬性的值就不會發生改變,就會返回false。但是如果對應名字的屬性不存在,那么一個新的屬性就誕生了,以傳入的名字為名,以傳入的值為值,但是依然會返回false。這表示返回值不能用於確定一個屬性是否被設置值,除非你已經知道這個屬性已經存在於QObject中了。
注意動態屬性被添加到單個實現的基礎中,也就是,被添加到QObject,而不是QMetaObject。一個屬性可以從一個實例中刪除,通過傳入屬性的名字和非法的QVariant值給QObject::setProperty()。默認的QVariant構造器構造一個非法的QVariant。
動態屬性可用QObject::property()來查詢,就行使用Q_PROPERTY()聲明的屬性一樣。
通過自定義類型來構造Q_PROPERTY()的靜態類型,這樣可以被用以在動態類型中使用。