此文较长,例子较多,可以结合右侧目录进行查看。
何为属性?人有名字,年龄,性别,这就是人的属性。
同样,面向对象编程的世界里,一切皆对象,对象也该有自己特定的属性。
在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])
简单捋一下,声明属性的必备条件:
Q_PROPERTY属性宏,参数分为必须部分和可选部分,接下来讲解,具体各部分含义。
其声明形式如下:
type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
翻译过来,就是有2种形式,带MEMBER和不带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字段为必填项。
形式为:类型+属性名+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]
当属性值发生改变后,会发射一个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支持的任何类型,或者是用户自定义的类型。
如下为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
属性类型为自定义枚举时,可能有以下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);
接下来,分别举例说明。
若属性类型为自定义枚举,则必须使用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()宏注册枚举类型。
比注册单值枚举要稍微麻烦一点。有以下几个注意事项:
代码如下:
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集合,也就是,值可以用或操作来合并。
若属性类型为自定义类型,则必须使用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 中的属性系统》
===================================================
===================================================
业余时间不定期更新一些想法、思考文章,欢迎关注,共同探讨,沉淀技术!