警告

本节包含从C++自动翻译到Python的代码片段,可能包含错误。

属性系统

Qt属性系统的概述。

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
    def colorChanged():
    def spacingChanged():
    def textChanged(newText):
# private
    QColor m_color
    qreal m_spacing
    m_text = QString()

属性类似于类的数据成员,但它具有通过元对象系统访问的附加功能。

  • 如果没有指定MEMBER变量,则需要一个READ访问器函数。它用于读取属性值。理想情况下,为此目的使用一个const函数,并且它必须返回属性的类型或该类型的const引用。例如,QWidget::focus是一个只读属性,带有READ函数QWidget::hasFocus()。如果指定了BINDABLE,则可以编写READ default以从BINDABLE生成READ访问器。

  • 一个 WRITE 访问器函数是可选的。它用于设置属性值。它必须返回 void 并且必须恰好接受一个参数,该参数可以是属性类型或指向该类型的指针或引用。例如,QWidget::enabled 有 WRITE 函数 QWidget::setEnabled()。只读属性不需要 WRITE 函数。例如,QWidget::focus 没有 WRITE 函数。如果你同时指定了 BINDABLEWRITE default,将会从 BINDABLE 生成一个 WRITE 访问器。生成的 WRITE 访问器将 不会 显式地发出任何用 NOTIFY 声明的信号。你应该将信号注册为 BINDABLE 的更改处理程序,例如使用 Q_OBJECT_BINDABLE_PROPERTY

  • 如果没有指定READ访问器函数,则需要MEMBER变量关联。这使得给定的成员变量可读可写,而无需创建READWRITE访问器函数。如果需要控制变量访问,仍然可以使用READWRITE访问器函数(但不能同时使用)来补充MEMBER变量关联。

  • RESET 函数是可选的。它用于将属性设置回其上下文特定的默认值。例如,QWidget::cursor 有典型的 READWRITE 函数,QWidget::cursor() 和 QWidget::setCursor(),它还有一个 RESET 函数,QWidget::unsetCursor(),因为不调用 QWidget::setCursor() 可能意味着重置为上下文特定的光标RESET 函数必须返回 void 并且不接受任何参数。

  • NOTIFY 信号是可选的。如果定义了,它应该指定该类中的一个现有信号,每当属性值发生变化时就会发出该信号。NOTIFY 信号对于 MEMBER 变量必须接受零个或一个参数,该参数必须与属性类型相同。该参数将接收属性的新值。NOTIFY 信号应仅在属性实际发生变化时发出,以避免在 QML 中不必要地重新评估绑定,例如。当通过 Qt API(setPropertyQMetaProperty 等)更改属性时,信号会自动发出,但当直接更改 MEMBER 时则不会发出。

  • 一个 REVISION 编号或 REVISION() 宏是可选的。如果包含,它定义了在API的特定修订版中使用的属性及其通知信号(通常用于暴露给QML)。如果不包含,则默认为0。

  • DESIGNABLE 属性指示该属性是否应在GUI设计工具(例如,Qt Widgets Designer)的属性编辑器中可见。大多数属性都是DESIGNABLE(默认为true)。有效值为true和false。

  • SCRIPTABLE 属性指示此属性是否应通过脚本引擎访问(默认为 true)。有效值为 true 和 false。

  • STORED 属性表示该属性是否应被视为独立存在或依赖于其他值。它还表示在存储对象状态时是否必须保存该属性值。大多数属性都是 STORED(默认为 true),但例如,QWidget::minimumWidth() 的 STORED 为 false,因为它的值仅取自属性 QWidget::minimumSize() 的宽度部分,而 QWidget::minimumSize() 是一个 QSize

  • USER 属性指示该属性是否被指定为类的面向用户或用户可编辑的属性。通常,每个类只有一个 USER 属性(默认值为 false)。例如,QAbstractButton::checked 是(可勾选的)按钮的用户可编辑属性。请注意,QItemDelegate 获取并设置小部件的 USER 属性。

  • BINDABLE bindableProperty 属性表示该属性支持绑定,并且可以通过元对象系统(QMetaProperty)设置和检查该属性的绑定。bindableProperty 命名了一个类型为 QBindable 的类成员,其中 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 是用户定义的,你必须在属性声明中包含 头文件。

由于历史原因,QMapQList 作为属性类型是 QVariantMapQVariantList 的同义词。

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

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

button = QPushButton()
object = button
button.setDown(True)
object.setProperty("down", True)

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

object = ...
metaobject = object.metaObject()
count = metaobject.propertyCount()
for i in range(0, count):
    metaproperty = metaobject.property(i)
    name = metaproperty.name()
    value = object.property(name)
    ...

在上述代码片段中,property() 用于获取某个未知类中定义的每个属性的metadata。属性名称从元数据中获取,并传递给property()以获取当前object中属性的value

一个简单的例子

假设我们有一个类 MyClass,它继承自 QObject 并且使用了 Q_OBJECT 宏。我们希望在 MyClass 中声明一个属性来跟踪优先级值。该属性的名称将是 priority,其类型将是一个名为 Priority 的枚举类型,该类型在 MyClass 中定义。

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

class MyClass(QObject):

    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
# public
    MyClass(QObject parent = None)
    ~MyClass()
    Priority = { High, Low, VeryHigh, VeryLow }
    Q_ENUM(Priority)
    def setPriority(priority):

        if m_priority == priority:
            return
        m_priority = priority
        priorityChanged.emit(priority)

    def priority():
    { return m_priority; }
# signals
    def priorityChanged(Priority):
# private
    m_priority = Priority()

READ 函数是常量并返回属性类型。WRITE 函数返回 void 并且只有一个属性类型的参数。元对象编译器强制执行这些要求。WRITE 函数中的相等性检查虽然不是强制性的,但这是一个好的做法,因为如果没有变化,通知并可能强制在其他地方重新评估是没有意义的。

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

myinstance = MyClass()
object = myinstance
myinstance.setPriority(MyClass.VeryHigh)
object.setProperty("priority", "VeryHigh")

在示例中,作为属性类型的枚举类型在MyClass中声明,并使用Q_ENUM()宏注册到元对象系统中。这使得枚举值可以作为字符串使用,如在调用setProperty()时。如果枚举类型在另一个类中声明,则需要其完全限定名称(即OtherClass::Priority),并且该类还必须继承QObject,并在那里使用Q_ENUM()宏注册枚举类型。

类似的宏,Q_FLAG(),也是可用的。与Q_ENUM()类似,它注册一个枚举类型,但它将该类型标记为一组标志,即可以一起进行OR操作的值。一个I/O类可能有枚举值ReadWrite,然后setProperty()可以接受Read | WriteQ_FLAG()应该用于注册这个枚举类型。

动态属性

setProperty() 也可以在运行时向类的实例添加属性。当使用名称和值调用它时,如果QObject中存在具有给定名称的属性,并且如果给定值与属性的类型兼容,则该值将存储在属性中,并返回true。如果该值与属性的类型兼容,则属性会更改,并返回false。但是,如果QObject中不存在具有给定名称的属性(即,如果它没有使用Q_PROPERTY()声明),则会自动将具有给定名称和值的新属性添加到QObject中,但仍返回false。这意味着除非您事先知道该属性已经存在于QObject中,否则不能使用返回false来确定是否实际设置了特定属性。

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

动态属性可以使用property()进行查询,就像在编译时使用Q_PROPERTY()声明的属性一样。

属性和自定义类型

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

向类添加附加信息

连接到属性系统的还有一个额外的宏,Q_CLASSINFO(),它可以用来附加额外的名称-对到类的元对象上。例如,这在QML对象类型的上下文中用于标记一个属性为默认属性:

Q_CLASSINFO("DefaultProperty", "content")

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

使用可绑定属性

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

  • QProperty

  • QObjectBindableProperty

  • QObjectComputedProperty .

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

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

另请参阅

元对象系统 信号与槽 Q_DECLARE_METATYPE() QMetaType QVariant Qt 可绑定属性 从 C++ 定义 QML 类型