Qt:属性系统

1059 篇文章 285 订阅

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属性。注意,必须指定一个通知信号来允许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;

属性的行为类似于类数据成员,但它具有可通过元对象系统访问的其他功能:

  • READ getFunction(读访问函数)

    • 如果没有指定成员变量,则需要读访问函数,用于读取属性值
    • 理想情况下,使用const函数来实现此目的,它必须返回属性的类型或该类型的const引用。例如,QWidget::focus是READ函数QWidget::hasFocus()的只读属性。
  • WRITE setFunction(写访问函数)

    • 是可选的
    • 它用于设置属性值。
    • 它必须返回void并且必须只有一个参数,可以是属性的类型,也可以是指向该类型的指针或引用。例如,QWidget::enabled具有写函数QWidget::setEnabled()。
    • 只读属性不需要写函数。例如,QWidget::focus没有写函数。
  • MEMBER memberName()

    • 如果没有指定读访问器函数,则需要关联成员变量。这使得给定的成员变量可读可写,而不需要创建读写访问器函数。
    • 如果需要控制变量访问,除了关联成员变量之外,还可以使用读或写访问函数(但不能两者都使用)。
  • RESET resetFunction

    • 可选的。
    • 用于将属性设置回其上下文特定的默认值
    • 这个RESET函数必须返回void并且不带参数。
    • 例如,QWidget::cursor有典型的读和写函数,QWidget::cursor()和QWidget::setCursor(),它也有一个重置函数,QWidget::unsetCursor(),因为没有调用QWidget::setCursor()可能意味着重置上下文特定的游标。
  • [NOTIFY notifySignal]

    • 可选的
    • 如果定义了,它应该指定类中的一个现有信号,每当属性的值发生变化时就发出这个信号。
    • 成员变量的通知信号必须取0或者1个参数,参数必须与属性的类型相同。该参数将接受属性的新值。只有当属性真正被更改时才发出通知信号
    • 例如,为了避免在QML中不必要地重新计算绑定。当需要没有显式setter的成员属性时,Qt会自动发出信号。
  • [REVISION int | REVISION(int[, int])]

    • 这个宏是可选的
    • 如果包含的话,它定义了在API的特定修订版中使用的属性及其通知程序信号(通常用于暴露于QML中)。如果不包括,则默认为0。
  • [DESIGNABLE bool]

    • 指示属性是否应该在GUI设计工具的属性编辑器中可见
    • 大多数属性是DESIGNABLE(默认为true)
  • [SCRIPTABLE bool]

    • 指示脚本引擎是否应访问此属性(默认为true)
  • [STORED bool]

    • STORED属性指示应该认为该属性是单独存在的,还是依赖于其他值。
    • 它还指示在存储对象的状态时是否必须保存属性值。
    • 大多数属性被存储(默认为true),但是,例如,QWidget::minimumWidth()被存储为false,因为它的值是从属性QWidget::minimumSize()的宽度组件中获取的,它是一个QSize。
  • [USER bool]

    • USER属性指示该属性是指定为类的面向用户属性还是用户可编辑属性。
    • 通常,每个类只有一个USER属性(默认为false)。
    • 例如,QAbstractButton::checked是(可检查的)按钮的用户可编辑属性。请注意,QItemDelegate获取和设置小部件的用户属性。
  • [BINDABLE bindableProperty]

    • 可绑定的bindableProperty属性表明该属性支持绑定,并且可以通过元对象系统(QMetaProperty)设置和检查到该属性的绑定。
    • bindableProperty命名类型为QBindable的类成员,其中T是属性类型。这个属性是Qt 6.0中引入的。
  • [CONSTANT]

    • CONSTANT属性的存在表明该属性值是常量。
    • 对于给定的对象实例,常量属性的READ方法在每次调用时都必须返回相同的值。
    • 这个常量值对于对象的不同实例可能不同。
    • 常量属性不能有写方法或通知信号。
  • [FINAL]

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

    • REQUIRED属性的存在表明该属性应该由类的用户设置。moc并没有强制执行这一点,这对于公开给QML的类非常有用。
    • 在QML中,除非设置了所有必需的属性,否则不能实例化具有必需属性的类。

READ, WRITE, RESET功能可以继承。它们也可以是虚拟的。当它们在使用多重继承的类中继承时,它们必须来自第一个继承的类。

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

Q_PROPERTY(QDate date READ getDate WRITE setDate)

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

由于历史原因,QMap和QList作为属性类型是QVariantMap和QVariantList的同义词。

用元对象系统读写属性

可以使用通用函数QObject::property()QObject::setProperty()来读取和写入属性,除了属性的名称之外,不需要知道任何关于所属类的信息。

在下面的代码片段中,对QAbstractButton::setDown()的调用和对QObject::setProperty()的调用都设置了属性“down”。

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

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

当然,更好的方法是通过属性的写访问器访问属性,它的速度更快,在编译时提供更好的诊断,但是通过这种方式设置属性需要您在编译时了解类。

通过名称访问属性可以访问编译时不知道的类。

通过查询类的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()用于获取有关某个未知类中定义的每个属性的元数据。从元数据中获取属性名称,并将其传递给QObject :: property()以获取当前对象中属性的值。

一个简单的例子

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

我们在类的私有部分用Q_PROPERTY()宏声明该属性,所需的读函数名为priority,包含名为setPriority的写函数。枚举类型必须使用Q_ENUM()宏在元对象系统中注册。注册枚举类型可以使枚举器名称在调用QObject::setProperty()时可用。我们还必须为读和写函数提供自己的声明。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)
    {
        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实例的指针或一个指向MyClass实例的QObject的指针,我们有两种方法来设置它的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_FLAG()也可用。与Q_ENUM()类似,它注册了一个枚举类型,但它将该类型标记为一组标记,即可以在一起或一起的值。一个I/O类可能有读和写的枚举值,然后QObject::setProperty()可以接受读|写。应该使用Q_FLAG()来注册这个枚举类型。

动态特性

QObject :: setProperty()也可以在运行时用于向类的实例添加新属性。当使用名称和值调用它时

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

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

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

属性和自定义类型

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

向类中添加其他信息

与属性系统相连的是一个额外的宏Q_CLASSINFO(),可用于附加名称 --价值与类的元对象配对,例如:

Q_CLASSINFO("Version", "3.0.0")

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

另请参阅元对象系统信号和槽Q_DECLARE_METATYPE()QMetaTypeQVariant

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值