天天看點

Qt屬性系統(Qt Property System)

作者:音視訊開發老舅

  Qt提供了巧妙的屬性系統,它與某些編譯器支援的屬性系統相似。然而,作為平台和編譯器無關的庫,Qt不能夠依賴于那些非标準的編譯器特性,比如__property 或者 [property]。Qt的解決方案能夠被任何Qt支援的平台下的标準C++編譯器支援。它依賴于元對象系統(Meta_Object Sytstem),元對象系統通過信号和槽提供了對象間通訊的機制。

1. Qt中怎麼聲明屬性?

  QObject中的子類的私有域中使用Q_PROPERTY宏來聲明一個屬性

Qt屬性系統(Qt Property System)
以下是來自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;           

【領QT開發教程學習資料,點選下方連結莬費領取↓↓,先碼住不迷路~】

點選→領取「連結」

2. 對QT中屬性的了解

  一個屬性的表現就像一個普通的資料成員一樣,但是它有額外提供元對象系統通路的特性

(1)如果MEMBER關鍵子沒有被指定,則一個READ通路函數是必須的,它被用來通路資料成員的值。它的傳回類型必須是屬性類型或者屬性類型的常引用。比如,QWidget::focus是一個隻讀的屬性,通過讀函數,QWidget::hadFocus通路。

(2)一個WRITE函數是可選的,它被用來設定資料成員的值。的傳回類型必須是void,而且僅能有一個參數,其類型必須是屬性類型或者是屬性類型的指針類型或者是屬性類型引用。例如,QWidget::enabled有一個WRITE函數,QWidget::setEnabled(bool)。隻讀屬性不需要WRITE函數。比如QWidget::focus就沒有WRITE函數。

(3)如果屬性沒有READ通路函數,則需要用MEMBER指定成員變量,這使得給定的成員變量在沒有建立READ和WRITE的函數下可讀可寫。如果你需要控制變量的通路權限,也可以使用READ和WRITE函數而不僅僅是MEMBER,注意别同時使用。

(4)一個RESET函數頁是可選的,它被用來将屬性設定為上下文指定的預設值,例如,QWidget::cursor有READ和WRITE函數,QWidget::cursor() QWidget::setCursor(),同時它也有一個RESET函數QWidget::unsetCursor(),因為沒有可用的setCursor調用可以确定的将cursor屬性重置為上下文預設的值。RESET函數必須傳回void類型,而且不帶參數。

(5)NOTIFY也是可選的。如果定義了NOTIFY則需要指定一個已經存在的信号,該信号在屬性值發生改變是發射。與屬性相關的信号必須有一個或者零個參數,而且必須與屬性的類型相同。參數為資料成員的新值。NOTIFY信号應該僅僅當屬性值真正的發生改變時發出,以避免被QML重新評估。

(6)REVISION也是可選的,如果包含了該關鍵字,它定義了該屬性和信号被特定版本的API使用通常是QML。如果沒有包含該關鍵字其預設為0。

(7)DESIGNABLE指定了該屬性在GUI編輯器中是否可見(比如QtDesigner)。大多數的屬性是可設計的(DESIGNABLE預設為真)。除了true和false,你還可以指定boolean成員函數。

(8)SCRITABLE屬性指定了該屬性是否可以被script engine通路,其預設為真。除了true和false你還可以指定boolean函數。

(9)STORED屬性指定了該屬性是否是獨立的或者是否依賴于别的屬性。它也指定了當儲存對象屬性時是否會儲存該屬性。大多數的屬性的STORED為真。但是,QWidget::minmunWidth()的STROED為false,因為它的值是從QQWidget::minimumSize()中取得的,它的類型是QSize。

(10)USER指定了屬性是否被設計為使用者可見和可編輯的。正常情況下,每一個對象隻用一個USER屬性(預設為false)。例如,QAbstractButton::clicked對Buttons是可編輯的(checkable)。注,QItemDelegate使用設定和通路函數色設定widget的USER屬性。

(11)CONSTANT的出現表明屬性是一個常量值。對于給點的對象執行個體,每一次READ函數的調用都應該傳回相同的值。對于不同的執行個體該屬性可能會不相同。同時不能有WRITE函數和NOTIFY信号。

(12)FINAL表明該屬性不會再子類中被覆寫。在某些情況下它被用來優化性能,但是并沒有被moc實作。必須注意,絕不在子類中覆寫FIANL屬性。

(13)READ WRITE RESET函數可以被繼承。它們也可以是虛函數。當在使用多繼承的類中使用的時候,其必須來自第一個類。

屬性類型可以是任何QVariant支援的屬性,或者是使用者自定義的屬性。在這個例子中,類QDate被看做使用者自定義的類型。Q_PROPERTY(QDate data READ getDate WRITE setDate)因為QDate是使用者自定義的,當聲明屬性時,你必須包含<QDate>頭檔案。由于曆史原因,QMap和QList是QVariantMap和QVariantList的同義詞。

3. 使用元對象系統讀寫屬性

  一個屬性可以通過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函數的聲明。該類的定義如下:

【領QT開發教程學習資料,點選下方連結莬費領取↓↓,先碼住不迷路~】

點選→領取「連結」

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()應該被用來注冊枚舉類型。

4. 動态屬性

  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函數通路。

附:所謂添加屬性到QOject中二不是QMetaObject中的意思是:
假設:有兩個MyClass對象的執行個體a 與 b,當為a動态添加一個屬性時,b是不會受到影響的。
QMetaObject是所有的MyClass執行個體所共享的。
關于Q_DECLARE_METATYPE,另一個重要的用途就是用于注冊信号和槽中使用的使用者自定類型。
如果信号和槽使用Qt::QueuedConnection連接配接,則還需要使用qRegisterMetaType<T>()函數注冊。
此外,Qt的狀态機架構和動畫架構依賴屬性系統。           

繼續閱讀