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 中的属性系统》


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

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

业余时间不定期更新一些想法、思考文章,欢迎关注,共同探讨,沉淀技术!

            

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