The Property System

Qt提供一个复杂的属性系统, 类似于某些编译器供应商提供的属性系统. 然而, 作为一个与编译器和平台无关的库, Qt不依赖非标准的编译器功能, 如 __property[property]. Qt的解决方案可以在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]
             [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;

属性行为与类数据成员类似, 但是它可以使用 元对象系统访问.

  • READ 如果未指定 MEMBER 变量, 必须提供读函数. 函数用于读取属性值. 理想情况下, 这个函数用const修饰, 它返回属性类型或const引用. 如., QWidget::focus 是一个只读属性, READ 函数是 QWidget::hasFocus().
  • WRITE 函数用于设置属性值, 只接受一个参数, 返回值是void. 函数参数可以是属性类型的指针或引用. 如., QWidget::enabledWRITE 函数是 QWidget::setEnabled(). 只读属性不需要WRITE函数. 如., QWidget::focus 没有 WRITE 函数.
  • MEMBER 没有 READ 函数时, 必须关联变量. 这个标识表示成员变量支持读写, 无需创建 READWRITE 函数. 如果需要控制变量访问, 除MEMBER变量外, 可以使用 READWRITE 函数 (但是两者不能同时存在).
  • RESET 可选函数. 这个函数将属性设置为默认值. 如., QWidget::cursorREAD 函数(QWidget::cursor())和 WRITE 函数(QWidget::setCursor()), 它也有 RESET 函数(QWidget::unsetCursor()), 由于没有调用 QWidget::setCursor()函数, 所以QWidget::cursor设置默认值. RESET 函数返回类型是void, 不带参数.
  • NOTIFY 可选信号. 这个信号必须是类中现有信号. 这个值改变时会发出此信号. MEMBER变量的NOTIFY 信号必须有0或1个参数, 这个参数与属性类型相同. 参数是属性的新值. 属性改变后才会发送 NOTIFY 信号, 这样做是避免QML中不必要地重新评估绑定. 若没有显示设置MEMBER属性, Qt会自动发送这个信号.
  • REVISION 可选数字. 这个关键字表明属性和信号用于特定版本的API (常用于 QML). 若没有显示指定, 默认是0.
  • DESIGNABLE 可选标识. 属性是否在GUI设计工具 (如., Qt Designer) 显示. 多数属性是 DESIGNABLE (默认 true). 你可以使用返回值是bool类型的函数, 替代true和false.
  • SCRIPTABLE 可选标识. 属性是否关联脚本引擎(默认true). 你可以使用返回值是bool类型的函数, 替代true和false.
  • STORED 可选标识. 属性是独立存在, 还是依赖其他值. 它还表明存储对象状态时, 是否必须保存属性值. 多数属性是 STORED (默认 true), 少数例外, 如., QWidget::minimumWidth() 的 STORED 是 false, 这个函数的返回值是 QWidget::minimumSize()值( QSize )的分量.
  • USER 可选标识. 属性对于用户是否可编辑. 通常, 每个类仅有一个 USER 属性 (默认 false). 如., QAbstractButton::checked 是用户可编辑的属性. 注意: QItemDelegate 读写widget的 USER 值.
  • CONSTANT 可选标识. 属性值是否是常数. 对于给定对象实例, READ函数每次返回值必须相同. 对于对象的不同实例, 这个数值可能不同. 具有该标识的属性没有WRITE函数和NOTIFY信号.
  • FINAL 可选标识. 属性是否可被派生类覆盖. 这个标识用于性能优化, 但是这个标识不会被moc强制遵守. 必须小心FINAL属性.
  • REQUIRED 可选标识. 指示这个属性必须被类用户赋值. moc不会强制遵守, 这个属性对于QML而言很有用. 在QML中, 用户必须对所有具有REQUIRED标识的属性赋值, 才能实例化类.

READ, WRITERESET 函数可以被继承. 它们可以是虚函数. 对于多重继承, 这些函数必须来自第一个继承类.

属性类型可以是 QVariant支持的任意类型, 或用户自定义类型. 如下所示, QDate 被认为是用户自定义类型.


  Q_PROPERTY(QDate date READ getDate WRITE setDate)

由于 QDate 是用户自定义类型, 你必须在属性声明处包含头文件 <QDate>.

由于历史原因, QMap, QListQVariantMap, QVariantList的同义词.

使用元对象系统读写属性

仅知道属性名, 不知道类的其他信息时, 调用 QObject::property() 和 QObject::setProperty()读写属性. 下列代码片段, 调用 QAbstractButton::setDown() 和 QObject::setProperty() 都是设置属性 "down".


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

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

两种访问属性的方式, 调用 WRITE 函数更好, 因为WRITE函数执行速度更快, 编译时更多检查, 但是这种方式要求你在编译时必须知道这个类. 调用QObject::setProperty()可以让你编译时不知道类信息. 你可以在运行时, 查询类的 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 宏. 我们想在类中声明一个属性priority并追踪其值. 这个属性的类型是MyClass类定义的枚举类型 Priority.

我们在类中声明Priority时, 使用 Q_PROPERTY() 宏. 其中, READ 函数是 priority, WRITE 函数是 setPriority. NOTIFY信号是priorityChanged. 枚举类型必须使用Q_ENUM()宏向 元对象系统注册. 注册枚举类型后, 可以调用 QObject::setProperty()访问枚举名称. 我们也必须提供 READWRITE 函数. 最后, 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 指针, 我们有两种方式设置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_ENUM()类似的宏Q_FLAG(). 这个宏也注册一个枚举类型, 但是它将这个类型作为一组标识, 即值使用OR运算. 一个I/O类可能具有 ReadWrite 枚举值, QObject::setProperty() 接受 Read | Write. Q_FLAG() 用于注册此类枚举.

动态属性

QObject::setProperty() 也可用作运行时向类示例添加新属性. 调用QObject::setProperty(),传入参数名称和值, 若 QObject 调用QObject::setProperty(), 传入参数名称和值, 若QObject中存在给定名称的属性, 且属性类型和值兼容, 这个值存入属性, 返回true; 若这个值与属性类型不兼容, 不改变属性值, 返回false. 若QObject中不存在给定名称的属性(即., 未使用 Q_PROPERTY()声明), 新的属性和值自动加入 QObject中, 但是仍然返回false. 这意味着不能通过返回值(false)确定属性值是否修改成功, 除非你知道 QObject已存在该属性.

注意: 动态属性基于类实例添加, 即., 它们添加到 QObject, 而不是 QMetaObject. 调用QObject::setProperty(), 并传入名称和无效的 QVariant, 删除属性. QVariant 默认构造函数是一个无效的 QVariant.

调用 QObject::property()动态查询属性, 就像编译时使用 Q_PROPERTY()声明属性一样.

属性和自定义类型

属性可以使用自定义类型, 自定义类型使用 Q_DECLARE_METATYPE() 宏注册, 以便将其值存储到 QVariant 对象中. 通过这种方式, 自定义类型即可在类定义时, 作为 Q_PROPERTY() 宏中的静态属性, 又可在运行时, 创建动态属性.

向类添加附加信息

连接属性系统的是Q_CLASSINFO()宏, 这个宏支持在类的元对象上添加名值对数据, 如:


  Q_CLASSINFO("Version", "3.0.0")

像其他元数据一样, 类信息可以在运行时通过元对象访问. 详见 QMetaObject::classInfo() for details.

参见 Meta-Object System, Signals and Slots, Q_DECLARE_METATYPE(), QMetaType, QVariant.