属性系统

Qt 提供了一个类似于某些编译器供应商提供的复杂的属性系统。然而,作为一个编译器和平台独立的库,Qt 不依赖于非标准编译器特性,如 __property[property]。Qt 的解决方案与 Qt 支持的任何平台上的标准 C++ 编译器一起工作。它基于 元对象系统,该系统还通过 信号和槽 提供对象之间的通信。

声明属性的要求

要声明一个属性,请在继承自 QObject 的类中使用 Q_PROPERTY() 宏。

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

以下是一些从 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。如果指定了 BINDABLE,则可以通过将 BINDABLE 编译生成 READ 访问器。
  • WRITE 访问器函数是可选的。它是用于设置属性值的。它必须返回 void,并且必须接受一个参数,该参数为属性类型或对该类型的指针或引用。例如,QWidget::enabled 具有写入函数 QWidget::setEnabled。只读属性不需要 WRITE 函数。例如,QWidget::focus 没有写入函数。如果您指定了 BINDABLEWRITE default,将生成从 BINDABLE 生成的写入访问器。生成的 WRITE 访问器将 不会 显式发射任何使用 NOTIFY 声明的事件。您应将信号注册为 BINDABLE 的更改处理器,例如使用 Q_OBJECT_BINDABLE_PROPERTY
  • 如果未指定任何 READ 访问器函数,则需要一个 MEMBER 变量关联。这使得给定的成员变量可读且可写,而不需要创建 READWRITE 访问器函数。如果需要控制变量访问,仍然可以在 MEMBER 变量关联(但不是两者同时)的基础上使用 READWRITE 访问器函数。
  • 一个 RESET 函数是可选的。用于将属性值重置为其上下文特定的默认值。例如,QWidget::cursor 具有典型的 READWRITE 函数,即 QWidget::cursor() 和 QWidget::setCursor(),并且还有一个 RESET 函数,即 QWidget::unsetCursor(),因为不调用 QWidget::setCursor() 可以表示 重置为上下文特定的光标。该 RESET 函数必须返回 void 并不接收任何参数。
  • 一个 NOTIFY 信号是可选的。如果定义,它应该指定在类中存在的特定信号,每当属性值变化时,都会发出该信号。《code translate="no">MEMBER 变量的 NOTIFY 信号必须接收零个或一个参数,参数类型必须与属性相同。该参数将取属性的新的值。《code translate="no">NOTIFY 信号应在属性实际上更改时发出,以避免在 QML 中无必要地重新评估绑定。信号在通过 Qt API(如 QObject::setPropertyQMetaProperty)更改属性时会自动发出,但不是在直接更改 MEMBER 时。
  • 一个 REVISION 数字或 REVISION() 宏是可选的。如果包含,它将定义用于特定 API 版本的属性及其通知器信号(通常用于 QML 的公开)。如果不包含,则默认为 0。
  • 《code translate="no">DESIGNABLE 属性指示该属性是否应在 GUI 设计工具(例如 Qt Designer)的属性编辑器中可见。大多数属性都是 DESIGNABLE(默认为 true)。有效的值是 true 和 false。
  • 《code translate="no">SCRIPTABLE 属性指示该属性是否可由脚本引擎访问(默认为 true)。有效的值是 true 和 false。
  • 《code translate="no">STORED 属性指示是否应将属性视为独立存在的或将属性视为依赖于其他值。它还指示在保存对象的州时是否必须保存属性值。大多数属性都是 STORED(默认为 true),但例如,QWidget::minimumWidth() 有 STORED 值为 false,因为它的值只是从属性 QWidget::minimumSize() 的宽度组件中获取的,而 QWidget::minimumSize() 是一个 QSize
  • 《code translate="no">USER 属性指示属性是否指定为类面向用户或可由用户编辑的属性。通常,每个类只有一个 USER 属性(默认为 false)。例如,QAbstractButton::checked 是可检查按钮的用户可编辑属性。请注意,QItemDelegate 获取和设置了小部件的 USER 属性。
  • 属性BINDABLE bindableProperty表示该属性支持绑定,并且可以通过元对象系统(QMetaProperty)来设置和检查对该属性的绑定。将bindableProperty命名为类型为QBindable<T>的类成员,其中T是属性类型。该属性在Qt 6.0中引入。
  • CONSTANT属性的存在表示属性值是常数。对于给定的对象实例,每个时间调用常量属性的READ方法都必须返回相同的值。对于不同实例的对象,这个常量值可能不同。常量属性不能有WRITE方法或NOTIFY信号。
  • FINAL属性的存在表示位置属性不会被派生类覆盖。在某些情况下,这可以用于性能优化,但不会被moc强制执行。必须注意永远不要覆盖一个FINAL属性。
  • REQUIRED属性的存在表示该属性应由类的用户设置。这不是moc强制执行的,主要用于向QML公开的类。在QML中,具有REQUIRED属性的类无法实例化,除非所有REQUIRED属性都已设置。

READWRITERESET函数可以继承。它们也可以是虚拟的。当它们在多重继承的类中被继承时,它们必须来自第一个继承的类。

属性类型可以是QVariant支持的任何类型,也可以是用户定义的类型。在这个例子中,类QDate被认为是用户定义的类型。

Q_PROPERTY(QDate date READ getDate WRITE setDate)

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

出于历史原因,将QMapQList作为属性类型与QVariantMapQVariantList同义。

使用元对象系统阅读和写入属性

可以使用通用函数QObject::property()和QObject::setProperty()来读取和写入属性,而无需了解除了属性名称之外的所有拥有类。在下面的代码片段中,调用QAbstractButton::setDown()和调用QObject::setProperty()都设置属性"down"。

QPushButton *button = new QPushButton;
QObject *object = button;

button->setDown(true);
object->setProperty("down", true);

通过其WRITE访问器访问属性是两种选择中较好的一个,因为它更快且在编译时提供更好的诊断,但这种方式设置属性要求你在编译时知道关于类的信息。通过名称访问属性允许你在编译时不知道关于类的信息。你可以通过查询对象的QObjectQMetaObjectQMetaProperties来在运行时发现一个类的属性。

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()获取有关在某个未知类中定义的每个属性的元数据。属性名称从元数据中检索出来,并传递给QObject::property(),以获取当前对象中属性的值。

一个简单的示例

假设我们有一个从 QObject 派生的类 MyClass,并且使用了 Q_OBJECT 宏。我们希望在 MyClass 中声明一个属性来追踪优先级值。该属性的名字为 priority,其类型为一个名为 Priority 的枚举类型,该枚举类型定义在 MyClass 中。

我们在类的私有部分使用 Q_PROPERTY() 宏声明该属性。需要的 READ 函数名为 priority,我们包含了名为 setPriorityWRITE 函数。枚举类型必须使用 Q_ENUM() 宏注册到 Meta-Object System 中。注册枚举类型使得枚举名字可以在调用 QObject::setProperty() 时使用。我们还需要提供自己的 READWRITE 函数的声明。因此,MyClass 的声明可能如下所示:

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

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

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

    void setPriority(Priority priority)
    {
        if (m_priority == priority)
            return;

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

signals:
    void priorityChanged(Priority);

private:
    Priority m_priority;
};

READ 函数是常函数,并返回属性的类型。WRITE 函数返回空值,并且确实有一个参数是该属性的类型。元对象编译器会强制这些要求。在 WRITE 函数中的等价检查虽然不是强制性的,但作为准则,如果没有任何变化就通知其他地方并可能强制重新评估是没有意义的。

给定一个指向 MyClass 实例的指针或者指向一个 QObject 的指针,该指针是一个 MyClass 的实例,我们有两种方式设置其优先级属性:

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

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

在示例中,枚举类型是属性类型,并在 MyClass 中声明,使用 Q_ENUM() 宏注册到 Meta-Object System 中。这使得枚举值可以作为字符串使用,例如在调用 setProperty() 中。如果枚举类型在另一个类中声明,将需要使用其全称(即,OtherClass::Priority),并且那个其他类也必须继承自 QObject 并使用 Q_ENUM() 宏在该处注册枚举类型。

还有一个类似的宏 Q_FLAG() 也可用。和 Q_ENUM() 一样,它也会注册一个枚举类型,但它将类型标记为 标志 的集合,即可以按位或的值。一个 I/O 类可能具有 ReadWrite 这样的枚举值,并且然后 QObject::setProperty() 可以接受 Read | Write。应该使用 Q_FLAG() 来注册这个枚举类型。

动态属性

QObject::setProperty()函数也可以在运行时向类的实例添加新的属性。当它被调用时带上一个名称和一个值,如果给定名称的属性存在于QObject中,并且给定的值与属性的类型兼容,那么该值会被存储在属性中,并返回true。如果值与属性类型不兼容,属性不会被更改,并返回false。但如果有给定名称的属性不存在于QObject中(即,如果没有用Q_PROPERTY()()声明),则会自动向QObject添加一个新的具有给定名称和值的属性,但仍然返回false。这意味着不能仅通过返回值为false来判断特定属性是否实际上被设置了,除非你知道属性已经在QObject中存在。

请注意,动态属性是基于每个实例添加的,即,它们被添加到QObject,而不是QMetaObject。可以通过传递属性名称和一个无效的QVariant值到QObject::setProperty()来从实例中移除属性。默认构造器会创建一个无效的QVariant

可以使用QObject::property()查询动态属性,就如同使用Q_PROPERTY()编译时声明的属性一样。

属性和自定义类型

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

向类添加更多信息

与属性系统相关联的一个额外宏是Q_CLASSINFO(),它可以用来将额外的名称对附加到类的元对象上。例如,它可以用来标示QML Object Types上下文中的属性为默认属性。

Q_CLASSINFO("DefaultProperty", "content")

与其他元数据一样,类信息可以通过元对象在运行时访问;有关详情,请参阅QMetaObject::classInfo()。

使用可绑定属性

可用于实现可绑定属性的三种不同类型:

第一个是一个绑定性属性的通用类。后两个只能用于QObject内部。

有关更多信息,包括示例,请参阅上述类及其实现和使用的通用提示可绑定属性

另请参见元对象系统信号和槽Q_DECLARE_METATYPE(),QMetaTypeQVariantQt可绑定属性,以及从C++定义QML类型

© 2024 Qt公司有限公司。此处包含的文档贡献归各自的拥有者所有。本提供的文档根据自由软件基金会发布的《GNU自由文档许可证》第1.3版条款授权使用。Qt及其相关标志是Qt公司芬兰及或全球其他国家的商标。