警告

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

属性系统#

Qt 属性系统的概述。

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

声明属性的要求#

为了声明一个属性,在继承自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 访问器函数是可选的。它是用来设置属性值的。它必须返回空值,并且必须接受一个参数,这个参数可以是属性的类型、指向该类型的指针或对该类型的引用。例如,QWidget::enabled拥有WRITE函数 QWidget::setEnabled()。只读属性不需要WRITE函数。例如,QWidget::focus没有WRITE函数。如果您指定了BINDABLEWRITE default,将从BINDABLE生成一个WRITE访问器。生成的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函数必须返回空值并且不接受任何参数。

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

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

  • DESIGNABLE 属性表示该属性是否应显示在 GUI 设计工具(例如 Qt 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> 的类成员,其中 T 是属性类型。此属性自 Qt 6.0 开始引入。

  • CONSTANT 属性的存在表示属性值是常量。对于给定对象实例,常数属性的 READ 方法必须在每次调用时返回相同的值。此常量值可能因对象的不同实例而不同。常数属性不能有 WRITE 方法或 NOTIFY 信号。

  • FINAL 属性的存在表示派生类不会覆盖该属性。在某些情况下,这可以用于性能优化,但 moc 不强制执行。必须注意永远不要覆盖 FINAL 属性。

  • REQUIRED 属性的存在表示该属性应由类的用户设置。这不由 moc 强制执行,主要用于暴露给 QML 的类。在 QML 中,如果您未设置所有必需的属性,则无法实例化具有必需属性的课程。

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

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

Q_PROPERTY(QDate date READ getDate WRITE setDate)

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

出于历史原因,作为属性类型的 QMapQListQVariantMapQVariantList 的同义词。

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

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

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

通过其 WRITE 访问器访问属性是两个中较好的选择,因为它更快,而且在编译时提供了更好的诊断。但是,以这种方式的属性设置需要在编译时知道相关类的信息。通过属性名访问属性让您可以访问在编译时不了解的类。您可以通过查询其 QObjectQMetaObject ,以及 QMetaProperties 来在运行时 发现 一个类的属性。

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

一个简单的例子#

假设我们有一个从 QObject 派生的类 MyClass,它在私有部分使用了 Q_OBJECT 宏。我们想在 MyClass 中声明一个属性以跟踪优先级值。这个属性的名称将是 priority,其类型将是一个名为 Priority 的枚举类型,该类型在 MyClass 中定义。

我们使用 Q_PROPERTY() 宏在类的私有部分声明这个属性。必需的 READ 函数名为 priority,我们包括了名为 setPriorityWRITE 函数。枚举类型必须使用 Q_ENUM() 宏在元对象系统中注册。注册枚举类型使得枚举值可供使用在 QObjectsetProperty() 调用中。我们还必须为 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 实例的指针或对 MyClass 实例的 QObject 指针,我们有两种方式来设置其优先级属性

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

在示例中,枚举类型作为属性类型在 MyClass 中声明,并使用 Q_ENUM() 宏注册到元对象系统中。这使得枚举值可以作为字符串用于 QObjectsetProperty() 调用中。如果枚举类型是在其他类中声明的,则需要全限定名称(即,OtherClass::Priority),并且该类也需要继承 QObject 并在该类中使用 Q_ENUM() 宏注册枚举类型。

类似的一个宏 Q_FLAG() 也可用。与 Q_ENUM() 类似,它注册枚举类型,但将类型标记为 标志集,即可以进行按位或操作的值。一个 I/O 类可能具有枚举值 ReadWrite,然后 setProperty() 可以接受 Read | Write。应该使用 Q_FLAG() 来注册这个枚举类型。

动态属性#

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

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

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

属性和自定义类型#

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

向类添加更多信息#

与属性系统相关联的还有一个额外的宏 Q_CLASSINFO(),该宏可用于将附加的 名称 对添加到类的元对象中。这在标记属性作为 QML 对象类型上下文中的 默认 属性时使用。

Q_CLASSINFO("DefaultProperty", "content")

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

使用可绑定属性#

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

  • QProperty

  • QObjectBindableProperty

  • QObjectComputedProperty .

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

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

另请参阅

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