此文較長,例子較多,可以結合右側目錄進行查看。
何爲屬性?人有名字,年齡,性別,這就是人的屬性。
同樣,面向對象編程的世界裏,一切皆對象,對象也該有自己特定的屬性。
在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_FLAGS、Q_FLAG、Q_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
參考鏈接:
===================================================
===================================================
業餘時間不定期更新一些想法、思考文章,歡迎關注,共同探討,沉澱技術!