Qt属性系统及Q_PROPERTY宏的使用

0.前言

像某些编译器提供的平台特有的属性系统(Property System)一样,Qt也提供了一个复杂的属性系统。当然,作为一个跨平台框架,Qt没有依赖那些非标准的编译器特性,比如:__property或者[property]。Qt的解决方案适用于Qt支持平台下的任何标准C++编译器。它基于元对象系统(Meta Object Sytstem),该系统还提供了信号槽用于对象间通信。(本文主要翻译自文档:https://doc.qt.io/qt-5/properties.html

属性系统在QtWidgets中用的没那么多,可能在做属性动画或者自定义控件时会用到。但在Qt Quick框架中,我们从C++注册QML的类型,自定义属性是必不可少的。

1.声明属性的要求

要声明属性,需要继承QObject并使用Q_PROPERTY()宏。

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])

(MEMBER是Qt5新增的,Qt4文档中没有,参照:https://doc.qt.io/archives/qt-4.8/properties.html) 

如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信号必须被指定,这样才能被QML使用。

    Q_PROPERTY(QColor color MEMBER m_color NOTIFY colorChanged)
    Q_PROPERTY(qreal spacing MEMBER m_spacing NOTIFY spacingChanged)
    Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged)
    ...
signals:
    void colorChanged();
    void spacingChanged();
    void textChanged(const QString &newText);

private:
    QColor  m_color;
    qreal   m_spacing;
    QString m_text;

一个属性的行为就像一个类的数据成员,但它有通过元对象系统访问的附加功能。 

  • 如果MEMBER关键字没有被指定,则一个READ访问函数是必须的。它被用来读取属性值。理想的情况下,一个const函数用于此目的,并且它必须返回的是属性类型或const引用。比如:QWidget::focus是一个只读属性,通过READ函数QWidget::hasFocus()访问。
  • 一个WRITE访问函数是可选的,用于设置属性的值。它必须返回void并且只能接受一个参数,属性的类型是类型指针或引用,例如:QWidget::enabled具有WRITE函数QWidget::setEnabled()。只读属性不需要WRITE函数,例如:QWidget::focus没有WRITE函数。
  • 如果READ访问函数没有被指定,则MEMBER变量关联是必须的。这使得给定的成员变量可读和可写,而不需要创建READ和WRITE访问函数。如果需要控制变量访问,仍然可以使用READ和WRITE函数而不仅仅是MEMBER(但别同时使用)。
  • 一个RESET函数是可选的,用于将属性设置为上下文指定的默认值。例如:QWidget::cursor有READ和WRITE函数QWidget::cursor()和QWidget::setCursor(),同时也有一个RESET函数QWidget::unsetCursor(),因为没有可用的QWidget::setCursor()调用可以确定的将cursor属性重置为上下文默认的值。RESET函数必须返回void类型,并且不带任何参数。
  • 一个NOTIFY信号是可选的。如果定义了NOTIFY,则需要在类中指定一个已存在的信号,该信号在属性值发生改变时发射。与MEMBER变量相关的NOTIFY信号必须有零个或一个参数,而且必须与属性的类型相同。参数保存的是属性的新值。NOTIFY信号应该仅当属性值真正的发生变化时发射,以避免被QML重新评估。例如:当需要一个没有显式setter的MEMBER属性时,Qt会自动发射信号。
  • 一个REVISION数字是可选的。如果包含了该关键字,它定义了属性并且通知信号被特定版本的API使用(通常是QML);如果没有包含,它默认为0。
  • DESIGNABLE属性指定了该属性在GUI设计器(例如:Qt Designer)里的编辑器中是否可见。大多数的属性是DESIGNABLE (默认为true)。除了true或false,你还可以指定boolean成员函数。
  • SCRIPTABLE属性表明这个属性是否可以被一个脚本引擎操作(默认是true)。除了true或false,你还可以指定boolean成员函数。
  • STORED属性表明了该属性是否是独立存在的还是依赖于其它属性。它也表明在保存对象状态时,是否必须保存此属性的值。大多数属性是STORED(默认为true)。但是例如:QWidget::minmunWidth()的STROED为false,因为它的值从QWidget::minimumSize()(类型为QSize)中的width部分取得。
  • USER属性指定了属性是否被设计为用户可见和可编辑的。通常情况下,每一个类只有一个USER属性(默认为false)。例如: QAbstractButton::checked是(checkable)buttons的用户可修改属性。注意:QItemDelegate获取和设置widget的USER属性。
  • CONSTANT属性的出现表明属性是一个常量值。对于给定的object实例,常量属性的READ函数在每次被调用时必须返回相同的值。对于不同的object实例该常量值可能会不同。一个常量属性不能具有WRITE函数或NOYIFY信号。
  • FINAL属性的出现表明属性不能被派生类所重写。有些情况下,这可以用于效率优化,但不能被moc强制执行。必须注意不能覆盖一个FINAL属性。

属性类型可以是QVariant支持的任何类型,或者是用户定义的类型。下面我们把QDate类看作是一个用户定义的类型:

Q_PROPERTY(QDate date READ getDate WRITE setDate)

因为QDate是用户自定义的,当声明属性时,必须包含<QDate>头文件。

对于QMap、QList和QValueList属性,属性的值是一个QVariant,它包含整个list或map。注意:Q_PROPERTY字符串不能包含逗号,因为逗号会分割宏的参数。因此,你必须使用QMap作为属性的类型而不是QMap<QString,QVariant>。为了保持一致性,也需要用QList和QValueList而不是QList<QVariant>和QValueList<QVariant>。

2.使用元对象系统读写属性

一个属性可以使用常规函数QObject::property()和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);
    const char *name = metaproperty.name();
    QVariant value = object->property(name);
    ...
}

上面的代码片段中,QMetaObject::property()用于获取未知类中每个属性的metadata。从metadata中获取属性名,然后传给QObject::property()来获取当前对象的属性值。 

3.一个简单的例子

假设我们有一个类MyClass,它从QObject派生并且在其private区域使用了Q_OBJECT宏。我们想在MyClass类中声明一个属性来追踪一个priority值。属性的名称是priority,它的类型是定义在MyClass中的Priority枚举。

我们在类的private区域使用Q_PROPERTY()来声明属性。READ函数名为priority,并且我们包含一个名为setPriority的WRITE函数,枚举类型必须使用Q_ENUM()注册到元对象系统中。注册一个枚举类型使得枚举的名字可以在调用QObject::setProperty()时使用。我们还必须为READ和WRITE函数提供我们自己的声明。

MyClass的声明看起来应该是这样的:

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

public:
    MyClass(QObject *parent = 0);
    ~MyClass();

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

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

signals:
    void priorityChanged(Priority);

private:
    Priority m_priority;
};

READ函数是const的并且返回属性的类型。WRITE函数返回void并且具有一个属性类型的参数。元对象编译器强制做这些事情。

给定一个指向MyClass实例的指针,或一个指向QObject(MyClass实例)的指针时,我们有两种方法来设置priority属性:

MyClass *myinstance = new MyClass;
QObject *object = myinstance;

myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");

在此例子中,定义在MyClass中的枚举类型是属性的类型,而且被Q_ENUM()宏注册在元对象系统中。这使得枚举值可以在调用setProperty()时做为字符串使用。如果枚举类型在其它类中声明,那么需要使用枚举的全名(例如:OtherClass::Priority),而且这个类也必须从QObject派生,并且使用Q_ENUM()宏注册枚举类型。

另一个简单的宏Q_FLAGS()也是可用的,就像Q_ENUMS(),它注册一个枚举类型,但是它把枚举类型作为一个flag集合,也就是,值可以用OR操作来合并。一个I/O类可能具有枚举值Read和Write并且QObject::setProperty()可以接受Read | Write。应使用Q_FLAGS()来注册此枚举类型。

4.动态属性

QObject::setProperty()也可以用来在运行时期向一个类的实例添加新的属性。当使用一个名字和值调用它时,如果QObject中一个指定名称的属性已经存在,并且如果给定的值与属性的类型兼容,那么,值就被存储到属性中,然后返回true。如果值与属性类型不兼容,属性的值就不会发生改变,会返回false。但是如果QObject中一个指定名称的属性不存在(例如:未用Q_PROPERTY()声明),一个带有指定名称和值的新属性就被自动添加到QObject中,但是依然会返回false。这意味着返回值不能用于确定一个属性是否被设置值,除非事先知道这个属性已经存在于QObject中。

注意:态属性被添加到每一个实例中,即:它们被添加到QObject中,而不是QMetaObject。一个属性可以从一个实例中删除,通过传入属性名和非法的QVariant值给QObject::setProperty()。默认的QVariant构造器会构造一个非法的QVariant。

动态属性可用QObject::property()来查询,就像使用Q_PROPERTY()声明的属性一样。

5.属性和自定义类型

被属性使用的自定义类型需要使用Q_DECLARE_METATYPE()宏注册,以便它们的值能被保存在QVariant对象中。这使得它们适用于在类定义时使用Q_PROPERTY()宏声明的静态属性,以及运行时创建的动态属性。

6.为类添加附加信息

与属性系统相对应的是一个附加宏 - Q_CLASSINFO()。用于添加name-value对到类的元对象中。例如:

Q_CLASSINFO("Version", "3.0.0")

和其它meta-data一样,类信息可以在运行时通过meta-object访问,详情见:QMetaObject::classInfo() 。

7.自己写的示例

#include <QObject>

//展示自定义class/struct作为属性
class MyData
{
public:
    QString _str;
    //需要定义!=
    bool operator !=(const MyData&other){
        return _str!=other._str;
    }
};
Q_DECLARE_METATYPE(MyData)

//要声明属性,需要继承QObject并使用Q_PROPERTY()宏
class MyClass : public QObject
{
    Q_OBJECT
    //最常见的声明方式
    Q_PROPERTY(int number READ getNumber WRITE setNumber NOTIFY numberChanged)
    //或者Qt5这样写,直接绑定一个成员,声明下notify信号
    Q_PROPERTY(int age MEMBER _age NOTIFY ageChanged)
    Q_PROPERTY(MyData data MEMBER _data NOTIFY dataChanged)
public:
    explicit MyClass(QObject *parent = nullptr):QObject(parent){}

    //可以在set get函数中进行数据过滤
    int getNumber() const {
        return _number;
    }
    void setNumber(int number){
        if(number!=_number){
            _number=number;
            emit numberChanged();
        }
    }

signals:
    void numberChanged();
    void ageChanged();
    void dataChanged();

private:
    int _number=0;
    int _age=0;
    MyData _data{"gongjianbo1992"};
};
#include <QDebug>
#include "MyClass.h"

void test_property()
{
    MyClass mc;
    mc.setProperty("number",123);
    mc.setProperty("age",18);
    qDebug()<<mc.property("number").toInt()
           <<mc.property("age").toInt();

    mc.setProperty("data",QVariant::fromValue(MyData{"new data"}));
    qDebug()<<mc.property("data").value<MyData>()._str;

    //动态属性
    mc.setProperty("newproperty",1992);
    qDebug()<<mc.property("newproperty").toInt();
}
//输出结果:
//123 18
//"new data"
//1992

 8.参考

官方文档:https://doc.qt.io/qt-5/properties.html

参考博客(翻译的文档):https://blog.csdn.net/liang19890820/article/details/52022714

Qt使用`Q_PROPERTY`定义属性非常简单。`Q_PROPERTY`用于在中声明一个属性,这样可以通过元对象系统(Meta-Object System)来访问和修改这些属性。以下是使用`Q_PROPERTY`定义属性的步骤和示例: 1. **包含必要的头文件**:确保包含了`QObject`头文件,因为`Q_PROPERTY`是`QObject`的一部分。 2. **继承自QObject**:要使用`Q_PROPERTY`必须继承自`QObject`。 3. **使用Q_OBJECT**:在的私有部分添加`Q_OBJECT`,以便启用元对象特性。 4. **使用Q_PROPERTY声明属性**:在的私有部分使用`Q_PROPERTY`声明属性。 下面是一个简单的示例,展示了如何在Qt使用`Q_PROPERTY`定义属性: ```cpp #include <QObject> class MyClass : public QObject { Q_OBJECT Q_PROPERTY(int myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged) public: explicit MyClass(QObject *parent = nullptr) : QObject(parent), m_myProperty(0) {} int myProperty() const { return m_myProperty; } void setMyProperty(int value) { if (m_myProperty != value) { m_myProperty = value; emit myPropertyChanged(m_myProperty); } } signals: void myPropertyChanged(int newValue); private: int m_myProperty; }; ``` 在这个示例中: - `Q_PROPERTY(int myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)`声明了一个名为`myProperty`的属性。 - `READ myProperty`指定了读取属性的方法。 - `WRITE setMyProperty`指定了设置属性的方法。 - `NOTIFY myPropertyChanged`指定了属性变化时发出的信号。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值