Qt属性系统

Qt Core The Property System

简述

Qt提供一个复杂属性系统,类似于其它编译器供应商所提供的(Property System)。然而,作为一个与编译器和平台无关的库,Qt不依赖于那些非标准的编译器特性,如:_property或[property]。Qt的解决方案适用于Qt支持平台下的任何标准C++编译器。它基于元对象系统(Meta Object Sytstem),也通过信号和槽提供对象间通讯机制。

声明属性的要求

为了声明一个属性,在继承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])

以下是摘自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访问函数。如果需要控制变量访问,除了使用MEMBER变量关联外,仍然可以使用READ和WRITE函数(但不要同时使用)

  • 一个RESET函数是可选的,用于将属性设置为上下文指定的默认值。例如:QWidget::cursor有典型的READ和WRITE函数,QWidget::cursor()和QWidget::setCursor(),同时也有一个RESET函数QWidget::unsetCursor(),因为没有QWidget::setCursor()调用将cursor属性重置为上下文默认的值。RESET函数必须返回void类型,并且不带任何参数。

  • 一个NOTIFY信号是可选的。如果定义了NOTIFY,则需要在类中指定一个已存在的信号,该信号在属性值改变时发射。MEMBER变量的NOTIFY信号必须是0个或一个参数,而且必须与属性的类型相同。参数保存的是属性的新值。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)中的宽度部分取得。

  • USER属性指定了在类中属性是否被设计为面向用户或可编辑的。通常情况下,每一个类只有一个USER属性(默认为false)。例如: QAbstractButton::checked是按钮的用户可编辑属性(checkable)。注意:QItemDelegate获取和设置widget的USER属性。

  • CONSTANT属性的存在表明属性是一个常量值。对于给定的object实例,常量属性的READ函数在每次被调用时必须返回相同的值。不同的object实例该常量值可能会不同。一个常量属性不能具有WRITE函数或NOYIFY信号。

  • FINAL属性的存在表明属性不能被派生类所重写。在有些情况下,这可以用于效率优化,但不能被moc强制执行。必须注意不能重写一个FINAL属性。

READ,WRITE,RESET函数能被继承,也能是virtual的。当多重继承时,它们必须来自第一个继承的类。

属性类型可以是QVariant支持的任意类型,或者是用户定义的类型。在这个例子中,类QDate被看作是一个用户定义的类型。

Q_PROPERTY(QDate date READ getDate WRITE setDate)

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

由于历史原因,QMap和QList作为属性类型等同于QVarinatMap和QVarintList。

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

通过元对象系统读写属性

一个属性可以使用通用函数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()来获取当前对象的属性值。

一个简单的例子

假设我们有一个类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_ENUMS()宏一样,类似的宏Q_FLAG()也是可用的,它注册一个枚举类型,但是它把枚举类型作为一个flags集合,也就是,值可以用OR操作来合并。一个I/O类可能具有枚举值Read和Write并且QObject::setProperty()可以接受Read | Write。应使用Q_FLAGS()来注册此枚举类型。

动态属性

QObject::setProperty()也可以用来在运行时期向一个类的实例添加新的属性。当使用一个名称和值调用此函数时,如果QObject中一个给定名称的属性已存在,并且如果给定的值与属性的类型兼容,则值就存储到属性中,并返回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()。用于添加额外的name-value对到类的元对象中。例如:

Q_CLASSINFO("Version", "3.0.0")

和其它meta-data一样,类信息可以在运行时通过meta-object访问,详情见QMetaObject::classInfo() 。
也可参考 Meta-Object System, Signals and Slots, Q_DECLARE_METATYPE(), QMetaType, QVariant

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值