Qt屬性系統(Qt Property System)
Qt提供了巧妙的屬性系統,它與某些編譯器支持的屬性系統相似。然而,作為平台和編譯器無關的庫,Qt不能夠依賴於那些非標准的編譯器特性,比如__property 或者 [property]。Qt的解決方案能夠被任何Qt支持的平台下的標准C++編譯器支持。它依賴於元對象系統(Meta_Object Sytstem),元對象系統通過信號和槽提供了對象間通訊的機制。
怎樣聲明屬性
QObject的子類的私有域中使用Q_PROPERTY宏來聲明一個屬性
Q_PROPERTY(type name
(
READ getFunction [
WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[
RESET resetFunction]
[
NOTIFY notifySignal]
[
REVISION int]
[
DESIGNABLE bool]
[
SCRITABLE bool]
[
STORED bool]
[
USER bool]
[
CONSTANT]
[
FINAL]
)
以下是來自QWidget類的一些屬性聲明
Q_PROPERTY(bool focus READ hasFocus)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
以下例子展示了如何使用MEMBER關鍵字將類數據成員導出為Qt屬性。注,NOTIFY signal必須被指定,這樣才能被QML使用
Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
Q_PROPERTY(qreal spaing MEMBER m_spacing NOTIFY spaingChanged)
Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
...
signals:
void colorChanged();
void spacingChanged();
void textChanged(const QString &netText);
private:
QColor m_color;
qreal m_spacing;
QString m_text;
一個屬性的表現就像一個普通的數據成員一樣,但是它有額外供元對象系統訪問的特性。
如果MEMBER關鍵字沒有被指定,則一個READ訪問函數是必須的。它被用來訪問數據成員的值。理想地,它是一個常成員函數,它的返回類型須是屬性類型或者是屬性類型的常引用。比如,QWidget::focus是一個只讀的屬性,通過讀函數,QWidget::hadFocus訪問。
一個WRITE函數是可選的。它被用來設置數據成員的值。它的返回類型必須是void,而且僅能有一個參數,其類型必須是屬性類型或者是屬性類型的指針類型或者是屬性類型引用。例如,QWidget::enabled有一個WRITE函數,QWidget::setEnabled(bool)。只讀屬性不需要WRITE函數。比如QWidget::focus就沒有WRITE函數。
如果屬性沒有READ訪問函數,則需要用MEMBER指定成員變量。這使得給定的成員變量在沒有創建READ和WRITE的函數下可讀可寫。如果你需要控制變量的訪問權限,也可以使用READ和WRITE函數而不僅僅是MEMBER(但是別同時使用)。
一個RESET函數也是可選的。它被用來將屬性設置為上下文指定的默認值。例如,QWidget::cursor有READ和WRITE函數,QWidget::cursor() QWidget::setCursor(),同時它也有一個RESET函數QWidget::unsetCursor(),因為沒有可用的setCursor調用可以確定的將cursor屬性重置為上下文默認的值。RESET函數必須返回void類型,而且不帶參數。
NOTIFY也是可選的。如果定義了NOTIFY則需要指定一個已經存在的信號,該信號在屬性值發生改變是發射。與屬性相關的信號必須有一個或者零個參數,而且必須與屬性的類型相同。參數為數據成員的新值。NOTIFY信號應該僅僅當屬性值真正的發生改變時發出,以避免被QML重新評估。
REVISION也是可選的,如果包含了該關鍵字,它定義了該屬性和信號被特定版本的API使用通常是QML。如果沒有包含該關鍵字其默認為0。
DESIGNABLE指定了該屬性在GUI編輯器中是否可見(比如QtDesigner)。大多數的屬性是可設計的(DESIGNABLE默認為真)。除了true和false,你還可以指定boolean成員函數。
SCRITABLE屬性指定了該屬性是否可以被script engine訪問,其默認為真。除了true和false你還可以指定boolean函數。
STORED屬性指定了該屬性是否是獨立的或者是否依賴於別的屬性。它也指定了當保存對象屬性時是否會保存該屬性。大多數的屬性的STORED為真。但是,QWidget::minmunWidth()的STROED為false,因為它的值是從QQWidget::minimumSize()中取得的,它的類型是QSize。
USER指定了屬性是否被設計為用戶可見和可編輯的。正常情況下,每一個對象只用一個USER屬性(默認為false)。例如,QAbstractButton::clicked對Buttons是可編輯的(checkable)。注,QItemDelegate使用設置和訪問函數色設置widget的USER屬性。
CONSTANT的出現表明屬性是一個常量值。對於給點的對象實例,每一次READ函數的調用都應該返回相同的值。對於不同的實例該屬性可能會不相同。同時不能有WRITE函數和NOTIFY信號。
FINAL表明該屬性不會再子類中被覆蓋。在某些情況下它被用來優化性能,但是並沒有被moc實現。必須注意,絕不在子類中覆蓋FIANL屬性。
READ WRITE RESET函數可以被繼承。它們也可以是虛函數。當在使用多繼承的類中使用的時候,其必須來自第一個類。
屬性類型可以是任何QVariant支持的屬性,或者是用戶自定義的屬性。在這個例子中,類QDate被看做用戶自定義的類型。
Q_PROPERTY(QDate data READ getDate WRITE setDate)
因為QDate是用戶自定義的,當聲明屬性時,你必須包含<QDate>頭文件。
由於歷史原因,QMap和QList是QVariantMap和QVariantList的同義詞。
使用元對象系統讀寫屬性
一個屬性可以通過QObject::poperty()函數、QObject::setProperty()函數訪問和設置。除了屬性的名字之外不用知道類的別的信息。在下面的代碼段中,調用函數QAbstractButton::setDown()和函數QObject::setProperty()都是設置屬性“down”
QPushButton* button = new QPushButton;
QObject* object = button;
button->setDown(true);
object->setProperty("down" , true);
通過WRITE函數設置屬性值,比上述兩者都好,因為它效率更高而且在編譯時期有更好的診斷。但是這需要你在編譯實際了解整個類(能夠訪問其定義)。通過屬性名訪問屬性,能夠讓你再不了解類的定義的情況訪問或者設置屬性。你可以在運行時期通過QObject,QMetaObject和QMetaProperties查詢類屬性。
QObject *object = ...
const QMetaObject *metaObject = object->metaObject();
int count = metaObject->propertyCount();
for (int i = 0 ; i< count; ++i) {
QMetaProperty metaProperty = metaObject->property(i);
cont char *name = metaProperty.name();
QVariant value = object->property(name);
}
在上述的代碼片段中,QMetaObject::property()被用來獲取定義在某個未知的類中的metaData。屬性的名稱通過metaData獲取,並且將其傳給QObject::property()來獲取屬性值。
假設我們有一個簡單的類MyClass,它繼承自QObject而且在private域中使用了Q_OBJECT。我們想聲明一個屬性用於跟蹤權限值。該屬性的名稱是priority,它的類型是定義在MyClass中的Priority枚舉。
我們使用Q_PROPERTY在private區里聲明屬性。READ函數是priority(),WRITE函數是setPriority()。枚舉類型需要使用Q_ENUM()宏將其注冊到Meta-Object System中。注冊一個枚舉類型使得枚舉可以在setPropert函數中使用。我們也必須提供READ和WRITE函數的聲明。該類的定義如下:
class MyClass : public QObjct
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
public:
explicit MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High , Low , VeryHigh , VeryLow };
Q_ENUM(Priority)
void setPriority(Priority priority)
{
m_priority = priority;
}
Priority priority() const { return m_priority; }
signals:
void priorityChanged(Priority);
private:
Priority m_priority;
};
READ函數是常成員函數而且返回Priority類型。WRITE函數返回void而且只有一個類型為Priority的參數。
給定一個指向MyClass實例的類型為MyClass或者QObject的指針,我們有兩種方式去設置它的priority屬性。
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority" , "VeryHigh");
在這個例子中,定義在MyClass中的枚舉類型是屬性的類型,而且被Q_ENUM()宏注冊在Meta-Object System中。這使得枚舉類型可以在setProperty中通過字符串訪問(string),使用在別的類中定義的枚舉類型,他必須被完全的聲明(i.e. OtherClass::Priority)。而且那個類應該繼承自QObject而且使用Q_ENUM()宏注冊。
一個相似的宏Q_FLAG()。就像Q_ENUM()一樣,它注冊枚舉類型,但是將其標記為一系列的flag,即,可以使用或操作。一個IO類有着Read和Write的枚舉值,而且之后可以在QObject::setProperty傳入Read | Write訪問。Q_FLAG()應該被用來注冊枚舉類型。
動態屬性
QObject::setProperty()也可以被用來在運行時期為類實例添加屬性。當傳入名稱和值調用該函數時。如果屬性名稱已經在類中存在並且傳入的類型與屬性的類型兼容,則屬性值被保存並且返回真,否則值不會被修改,但是函數返回假。但是如果給定的屬性名不存在則新的屬性被添加到類中,當函數仍然返回false。這意味着函數的返回值不能用來確定屬性值看是否被成功的設定。除非你已經知道屬性之前是否存在。
注:動態屬性被添加到每一個實例中。即它們被添加到QObject中而不是QMetaObject中。可以通過傳遞一個空的QVariant給setProperty函數來移除屬性。QVariant的默認構造函數構造一個無效的QVariant對象。
動態屬性可以通過QObject::property()查詢,就像Q_PROPERTY定義的屬性一樣。
屬性和自定義類型
被屬性使用的自定義類型需要使用Q_DECLARE_METATYPE宏注冊。這樣QVariant對象才能夠保存該類的值。這個在動態和靜態屬性都是適用的。
為類添加額外的信息
與屬性系統相對應的是Q_CLASSINFO(name , value)宏。這個宏將添加name-value的到類的元對象中。例如:
Q_CLASSINFO("Version" , "3.0.0")
和被的元對象數據一樣,類信息可以在運行時通過QMetaObject::classInfo函數訪問。
by linannk
2016/6/2 1:33
附:所謂添加屬性到QOject中二不是QMetaObject中的意思是:
假設:有兩個MyClass對象的實例a 與 b,當為a動態添加一個屬性時,b是不會受到影響的。
QMetaObject是所有的MyClass實例所共享的。
關於Q_DECLARE_METATYPE,另一個重要的用途就是用於注冊信號和槽中使用的用戶自定類型。
如果信號和槽使用Qt::QueuedConnection連接,則還需要使用qRegisterMetaType<T>()函數注冊。
此外,Qt的狀態機框架和動畫框架依賴屬性系統。
這份文檔大部分Qt的文檔翻譯,加上部分自己使用Qt中的心得。