Qt屬性系統詳細使用教程

此文較長,例子較多,可以結合右側目錄進行查看。

何爲屬性?人有名字,年齡,性別,這就是人的屬性。

同樣,面向對象編程的世界裏,一切皆對象,對象也該有自己特定的屬性。

在Qt中,QObject實現了對於屬性的支持,那麼派生於QObject的對象,都可以很容易的擁有自己的屬性。

注:

使用Qt屬性,則必須繼承於QObject

一、屬性的聲明

讓我們看看幫助文檔中關於屬性聲明的定義:

Q_PROPERTY(type name
         (READ getFunction [WRITE setFunction] |
          MEMBER memberName [(READ getFunction | WRITE setFunction)])
         [RESET resetFunction]
         [NOTIFY notifySignal]
         [REVISION int]
         [DESIGNABLE bool]
         [SCRIPTABLE bool]
         [STORED bool]
         [USER bool]
         [CONSTANT]
         [FINAL])

簡單捋一下,聲明屬性的必備條件:

  • 繼承於QObject
  • 使用Q_OBJECT宏
  • 使用Q_PROPERTY宏

Q_PROPERTY屬性宏,參數分爲必須部分和可選部分,接下來講解,具體各部分含義。

二、屬性聲明之必須部分

其聲明形式如下:

type name 
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])

翻譯過來,就是有2種形式,帶MEMBER和不帶MEMBER字段;

1. 不帶MEMBER字段

形式爲:類型+屬性名+READ+get函數+WRITE+set函數,其中WRITE+set函數爲可選。

我們定義MyObject類,聲明age屬性,可以像如下書寫:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int age READ getAge WRITE setAge)
    
public:
    int getAge() const
    {
        return _age;
    }
    void setAge(int value)
    {
        _age = value;
    }
private:
    int _age;
};

訪問age屬性,當調用setProperty設置屬性時,setAge()函數會被調用,以將18保存到_age變量;調用property獲取屬性時,getAge()函數會被調用,以獲取_age變量值。可以說屬性實際承載者是我們定義的成員變量。

QObject *obj = new MyObject();
obj->setProperty("age", 18);  // _age == 18
int value = obj->property("age").toInt(); // value == 18

若不寫WRITE字段,如下:

Q_PROPERTY(int age READ getAge)

則調用setProperty()設置屬性時,不會調用setAge(),此時_age值不變,新屬性值被丟棄。
而調用property()時與原來一致。

注:

READ字段爲必填項。

2. 帶MEMBER字段

形式爲:類型+屬性名+MEMBER+成員變量名+READ+get函數+WRITE+set函數,其中READ+get函數+WRITE+set函數爲可選。

我們仍以MyObject類爲例進行說明,聲明score屬性,可以像如下書寫:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int score MEMBER _score)
    
private:
    int _score;
};

後續設置屬性就是指setProperty(),獲取屬性是指property(),下面不再贅述。

這樣寫,我們設置和獲取屬性都是成功的。小夥伴可能會覺得奇怪,此處並沒有指定get、set函數,值是如何設置到_score變量,並還能正常獲取的呢?

這是因爲我們使用MEMBER關鍵字,Qt會自動生成一對getter/setter函數用於訪問_score。

下面,如果加上READ、WRITE字段,如下:

Q_PROPERTY(int score MEMBER _score READ getScore WRITE setScore)

那麼我們設置屬性時就會調用setScore(),獲取屬性時就會調用getScore()。

如果只加READ,如下:

Q_PROPERTY(int score MEMBER _score READ getScore)

則設置屬性時不調setScore(),調用默認生成的setter();獲取屬性時調getScore()。

如果只加WRITE,如下:

Q_PROPERTY(int score MEMBER _score WRITE setScore)

則設置屬性時調setScore(),獲取屬性時不調getScore(),調用默認生成的getter()。

這個Qt屬性聲明還是相當靈活的。

基本屬性使用,我們已經get到了,接下來,我們來錦上添花。

三、屬性聲明之可選部分

可選字段如下:

[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL]

1. NOTIFY字段

當屬性值發生改變後,會發射一個NOTIFY字段聲明的信號。

我們可以像如下書寫:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int age READ getAge WRITE setAge NOTIFY ageChanged)
    
public:
    int getAge() const
    {
        return _age;
    }
    void setAge(int value)
    {
        _age = value;
        emit ageChanged(value);
    }
signals:
    void ageChanged(int value);
private:
    int _age;
};

當age屬性值發生改變後,發射ageChanged信號。由於信號是我們手動發射,所以對信號的參數沒有限制。

另外我們還可以讓信號由Qt系統自動發射,即使用MEMBER字段。如下:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int score MEMBER _score NOTIFY scoreChanged)
signals:
    void scoreChanged(int value);
private:
    int _score;
};

當score屬性值發生改變後,自動發射scoreChanged信號。但是對信號定義有限制,即信號參數個數只能爲0個或者1個,且參數類型必須與屬性類型相同,參數保存的是屬性改變後的新值。

其他的可選字段,不是很常用,大家就自行研究吧。

四、支持的屬性類型

Qt屬性類型可以是QVariant支持的任何類型,或者是用戶自定義的類型。

1. 內置類型屬性

如下爲QVariant支持的內置類型。

enum Type {
    Invalid = QMetaType::UnknownType,
    Bool = QMetaType::Bool,
    Int = QMetaType::Int,
    UInt = QMetaType::UInt,
    LongLong = QMetaType::LongLong,
    ULongLong = QMetaType::ULongLong,
    Double = QMetaType::Double,
    Char = QMetaType::QChar,
    Map = QMetaType::QVariantMap,
    List = QMetaType::QVariantList,
    String = QMetaType::QString,
    StringList = QMetaType::QStringList,
    ByteArray = QMetaType::QByteArray,
    BitArray = QMetaType::QBitArray,
    Date = QMetaType::QDate,
    Time = QMetaType::QTime,
    DateTime = QMetaType::QDateTime,
    Url = QMetaType::QUrl,
    Locale = QMetaType::QLocale,
    Rect = QMetaType::QRect,
    RectF = QMetaType::QRectF,
    Size = QMetaType::QSize,
    SizeF = QMetaType::QSizeF,
    Line = QMetaType::QLine,
    LineF = QMetaType::QLineF,
    Point = QMetaType::QPoint,
    PointF = QMetaType::QPointF,
    RegExp = QMetaType::QRegExp,
    RegularExpression = QMetaType::QRegularExpression,
    Hash = QMetaType::QVariantHash,
    EasingCurve = QMetaType::QEasingCurve,
    Uuid = QMetaType::QUuid,
#if QT_CONFIG(itemmodel)
    ModelIndex = QMetaType::QModelIndex,
    PersistentModelIndex = QMetaType::QPersistentModelIndex,
#endif
    LastCoreType = QMetaType::LastCoreType,

    Font = QMetaType::QFont,
    Pixmap = QMetaType::QPixmap,
    Brush = QMetaType::QBrush,
    Color = QMetaType::QColor,
    Palette = QMetaType::QPalette,
    Image = QMetaType::QImage,
    Polygon = QMetaType::QPolygon,
    Region = QMetaType::QRegion,
    Bitmap = QMetaType::QBitmap,
    Cursor = QMetaType::QCursor,
    KeySequence = QMetaType::QKeySequence,
    Pen = QMetaType::QPen,
    TextLength = QMetaType::QTextLength,
    TextFormat = QMetaType::QTextFormat,
    Matrix = QMetaType::QMatrix,
    Transform = QMetaType::QTransform,
    Matrix4x4 = QMetaType::QMatrix4x4,
    Vector2D = QMetaType::QVector2D,
    Vector3D = QMetaType::QVector3D,
    Vector4D = QMetaType::QVector4D,
    Quaternion = QMetaType::QQuaternion,
    PolygonF = QMetaType::QPolygonF,
    Icon = QMetaType::QIcon,
    LastGuiType = QMetaType::LastGuiType,

    SizePolicy = QMetaType::QSizePolicy,

    UserType = QMetaType::User,
    LastType = 0xffffffff // need this so that gcc >= 3.4 allocates 32 bits for Type
};

除了容器類型外,其他的類型基本都可以直接定義使用,如下:

Q_PROPERTY(QDate date READ getDate WRITE setDate)

注意:

Q_PROPERTY字符串不能包含逗號,因爲逗號會分割宏的參數。

所以,對於QMap、QList和QHash等容器類型屬性,必須使用QMap作爲屬性的類型而不是QMap<QString,QVariant>,同樣,也只能使用QList和QHash,而不是QList和QHash<QString,QVariant>。

2. 自定義枚舉屬性

屬性類型爲自定義枚舉時,可能有以下2種需求:

  • 有些枚舉是單值,如:
enum Priority { High, Low, VeryHigh, VeryLow };
xxx(High);
  • 而有的可以將多個枚舉值進行或運算,多值,如:
enum Align
{
    Left = 0x01,
    Right = 0x02,
    HCenter = 0x04,
    Top = 0x08,
    Bottom = 0x10,
    VCenter = 0x20
};
xxx(Left|Top);

接下來,分別舉例說明。

(1)使用Q_ENUM註冊單值枚舉

若屬性類型爲自定義枚舉,則必須使用Q_ENUM()註冊到元對象系統中。

使用Q_ENUM()註冊枚舉類型,如下:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged) 

public:
    enum Priority { High, Low, VeryHigh, VeryLow };
    Q_ENUM(Priority)

    void setPriority(Priority priority)
    {
        _priority = priority;
        emit priorityChanged(priority);
    }
    Priority priority() const
    {
        return _priority;
    }

signals:
    void priorityChanged(Priority priority);

private:
    Priority _priority;
};

由於已經註冊在元對象系統中,所以在setProperty()時,可以直接用枚舉值的字符串形式作爲實參,如下:

QObject *object = new MyObject();
object->setProperty("priority", "VeryHigh"); // _priority == MyObject::VeryHigh
QVariant temp = object->property("priority");
MyObject::Priority pri = temp.value<MyObject::Priority>(); // pri == MyObject::VeryHigh

此時,字符串"VeryHigh"會在內部自動轉換爲枚舉值VeryHigh,並通過調用setPriority(),將m_priority設置爲枚舉值VeryHigh。

如果枚舉類型在其它類中聲明,那麼需要使用枚舉的全名(例如:OtherClass::Priority),而且這個類也必須從QObject派生,並且使用Q_ENUM()宏註冊枚舉類型。

(2)使用Q_FLAG註冊多值枚舉

比註冊單值枚舉要稍微麻煩一點。有以下幾個注意事項:

  • 使用Q_DECLARE_FLAGSQ_FLAGQ_DECLARE_OPERATORS_FOR_FLAGS三個宏。
  • 將枚舉值定義爲2的n次方,保證或操作不會出現交叉,如 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20。
  • 不使用原枚舉類型,而是使用Q_FLAG(xx)定義的xx類型。

代碼如下:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Aligns aligns READ aligns WRITE setAligns NOTIFY alignsChanged) // 測試多值枚舉

public:
    enum Align
    {
        Left = 0x01,
        Right = 0x02,
        HCenter = 0x04,
        Top = 0x08,
        Bottom = 0x10,
        VCenter = 0x20
    };
    Q_DECLARE_FLAGS(Aligns, Align)
    Q_FLAG(Aligns)

    void setAligns(Aligns aligns)
    {
        _aligns = aligns;
        emit alignsChanged(aligns);
    }
    Aligns aligns() const
    {
        return _aligns;
    }

signals:
    void alignsChanged(Aligns align);

private:
    Aligns _aligns;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(MyObject::Aligns)

測試代碼如下:

QObject *object = new MyObject();
object->setAligns(MyObject::Left|MyObject::Bottom); // _aligns == MyObject::Left|MyObject::Bottom
object->setProperty("aligns", "Left|Top"); // _aligns == MyObject::Left|MyObject::Top
QVariant temp = object->property("aligns");
MyObject::Aligns aligns = temp.value<MyObject::Aligns>(); // aligns == MyObject::Left|MyObject::Top

使用方式與Q_ENUM()類似,也是註冊枚舉類型,但它是把枚舉類型作爲一個flag集合,也就是,值可以用或操作來合併。

3. 自定義類型屬性

若屬性類型爲自定義類型,則必須使用Q_DECLARE_METATYPE宏註冊該類型,使其能夠放入QVariant,如下:

class Person
{
public:
    int varA;
    float varB;
    QString varC;
};
Q_DECLARE_METATYPE(Person)

然後,聲明此類型屬性如下:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Person person READ getPerson WRITE setPerson NOTIFY personChanged)

public:
    Person getPerson() const
    {
        return _person;
    }
    void setPerson(const Person &person)
    {
        _person = person;
        emit personChanged(person);
    }

signals:
    void personChanged(Person person);

private:
    Person _person;
};

測試代碼,如下:

Person person;
person.varA = 10;
person.varB = 5.0;
person.varC = "test";

QObject *object = new MyObject();
object->setProperty("person", QVariant::fromValue(person)); // _person.varA==10,_person.varB==5.0,_person.varC=="test"
QVariant temp = object->property("person");
Person result = temp.value<Person>(); // result.varA==10,result.varB==5.0,result.varC=="test"

注意:

如果需要使用MEMBER字段聲明自定義類型屬性,則需要爲自定義類型重載!=操作符,可能是在設置或者獲取屬性時,調用Qt自身生成的setter/getter,會間接調用!=進行比較,所以必須要實現,否則編譯報錯:

在這裏插入圖片描述

添加重載!=操作符後的,代碼如下:

class Person
{
public:
    Person() { }

    bool operator!=(const Person& person)
    {
        if (person.varA == varA &&
            person.varB == varB &&
            person.varC == varC)
            return false;
        else
            return true;
    }

    int varA;
    float varB;
    QString varC;
};
Q_DECLARE_METATYPE(Person)

此時,再使用MEMBER關鍵字聲明Person類型屬性,則不會再報錯,可以正常運行。聲明屬性代碼如下:

class MyObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(Person person MEMBER _person NOTIFY personChanged)

public:

signals:
    void personChanged(Person person);

private:
    Person _person;
};

測試代碼,如下:

Person person;
person.varA = 10;
person.varB = 5.0;
person.varC = "test";

QObject *object = new MyObject();
object->setProperty("person", QVariant::fromValue(person)); // _person.varA==10,_person.varB==5.0,_person.varC=="test"
QVariant temp = object->property("person");
Person result = temp.value<Person>(); // result.varA==10,result.varB==5.0,result.varC=="test"

五、靜態屬性與動態屬性

使用Q_PROPERTY宏定義的屬性,就是靜態屬性。

後期使用setProperty()動態添加的屬性,就是動態屬性。

無論靜態還是動態,都可以按如下方式,獲取所有的屬性名和屬性值。如下:

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);
    ...
}

本文工程代碼地址:

https://gitee.com/bailiyang/cdemo/tree/master/Qt/40Property/Property

參考鏈接:

《Qt 之屬性系統》

《枚舉與 QFlags》

《Qt中的枚舉變量》

《Qt 中的屬性系統》


===================================================

===================================================

業餘時間不定期更新一些想法、思考文章,歡迎關注,共同探討,沉澱技術!

            

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章