特性系统

Qt 提供类似于某些编译器供应商所提供的完备特性系统。不管怎样,作为编译器无关和平台无关库,Qt 并不依赖非标准编译器特征像 __property or [property] 。Qt 解决方案工作于 any Qt 支持的各平台标准 C++ 编译器。它基于 元对象系统 还提供对象间通信凭借 信号和槽 .

声明特性的要求

要声明特性,使用 Q_PROPERTY () 宏在类继承 QObject .

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)
					

这里是展示如何将成员变量导出作为 Qt 特性的范例,使用 MEMBER 关键词。注意 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;
					

A property behaves like a class data member, but it has additional features accessible through the 元对象系统 .

  • A READ accessor function is required if no MEMBER variable was specified. It is for reading the property value. Ideally, a const function is used for this purpose, and it must return either the property's type or a const reference to that type. e.g., QWidget::focus is a read-only property with READ function, QWidget::hasFocus (). If a BINDABLE is specified, you can write READ default to have the READ accessor generated from the BINDABLE .
  • A WRITE accessor function is optional. It is for setting the property value. It must return void and must take exactly one argument, either of the property's type or a pointer or reference to that type. e.g., QWidget::enabled 拥有 WRITE function QWidget::setEnabled (). Read-only properties do not need WRITE functions. e.g., QWidget::focus has no WRITE function. If you specify both a BINDABLE and WRITE default WRITE accessor will be generated from the BINDABLE . The generated WRITE accessor will not explicitly emit any signal declared with NOTIFY . You should register the signal as change handler to the BINDABLE , for example using Q_OBJECT_BINDABLE_PROPERTY .
  • A MEMBER variable association is required if no READ accessor function is specified. This makes the given member variable readable and writable without the need of creating READ and WRITE accessor functions. It's still possible to use READ or WRITE accessor functions in addition to MEMBER variable association (but not both), if you need to control the variable access.
  • A RESET function is optional. It is for setting the property back to its context specific default value. e.g., QWidget::cursor has the typical READ and WRITE functions, QWidget::cursor () 和 QWidget::setCursor (), and it also has a RESET function, QWidget::unsetCursor (), since no call to QWidget::setCursor () can mean reset to the context specific cursor RESET function must return void and take no parameters.
  • A NOTIFY signal is optional. If defined, it should specify one existing signal in that class that is emitted whenever the value of the property changes. NOTIFY 信号对于 MEMBER variables must take zero or one parameter, which must be of the same type as the property. The parameter will take the new value of the property. The NOTIFY signal should only be emitted when the property has really been changed, to avoid bindings being unnecessarily re-evaluated in QML, for example. The signal is emitted automatically when the property is changed via the Qt API ( QObject::setProperty , QMetaProperty , etc.), but not when the MEMBER is changed directly.
  • A REVISION number or REVISION() macro is optional. If included, it defines the property and its notifier signal to be used in a particular revision of the API (usually for exposure to QML). If not included, it defaults to 0.
  • The DESIGNABLE attribute indicates whether the property should be visible in the property editor of GUI design tool (e.g., Qt Designer ). Most properties are DESIGNABLE (default true). Valid values are true and false.
  • The SCRIPTABLE attribute indicates whether this property should be accessible by a scripting engine (default true). Valid values are true and false.
  • The STORED attribute indicates whether the property should be thought of as existing on its own or as depending on other values. It also indicates whether the property value must be saved when storing the object's state. Most properties are STORED (default true), but e.g., QWidget::minimumWidth () has STORED false, because its value is just taken from the width component of property QWidget::minimumSize (), which is a QSize .
  • The USER attribute indicates whether the property is designated as the user-facing or user-editable property for the class. Normally, there is only one USER property per class (default false). e.g., QAbstractButton::checked is the user editable property for (checkable) buttons. Note that QItemDelegate gets and sets a widget's USER 特性。
  • The BINDABLE bindableProperty attribute indicates that the property supports bindings , and that it is possible to set and inspect bindings to this property via the meta object system ( QMetaProperty ). bindableProperty names a class member of type QBindable <T>, where T is the property type. This attribute was introduced in Qt 6.0.
  • The presence of the CONSTANT attribute indicates that the property value is constant. For a given object instance, the READ method of a constant property must return the same value every time it is called. This constant value may be different for different instances of the object. A constant property cannot have a WRITE method or a NOTIFY signal.
  • The presence of the FINAL attribute indicates that the property will not be overridden by a derived class. This can be used for performance optimizations in some cases, but is not enforced by moc. Care must be taken never to override a FINAL 特性。
  • The presence of the REQUIRED attribute indicates that the property should be set by a user of the class. This is not enforced by moc, and is mostly useful for classes exposed to QML. In QML, classes with REQUIRED properties cannot be instantiated unless all REQUIRED properties have been set.

The READ , WRITE ,和 RESET functions can be inherited. They can also be virtual. When they are inherited in classes where multiple inheritance is used, they must come from the first inherited class.

The property type can be any type supported by QVariant , or it can be a user-defined type. In this example, class QDate is considered to be a user-defined type.

Q_PROPERTY(QDate date READ getDate WRITE setDate)
					

因为 QDate is user-defined, you must include the <QDate> header file with the property declaration.

For historical reasons, QMap and QList as property types are synonym of QVariantMap and QVariantList .

采用元对象系统读写特性

A property can be read and written using the generic functions QObject::property () 和 QObject::setProperty (), without knowing anything about the owning class except the property's name. In the code snippet below, the call to QAbstractButton::setDown () and the call to QObject::setProperty () both set property "down".

QPushButton *button = new QPushButton;
QObject *object = button;
button->setDown(true);
object->setProperty("down", true);
					

Accessing a property through its WRITE accessor is the better of the two, because it is faster and gives better diagnostics at compile time, but setting the property this way requires that you know about the class at compile time. Accessing properties by name lets you access classes you don't know about at compile time. You can discover a class's properties at run time by querying its 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);
    ...
}
					

In the above snippet, QMetaObject::property () is used to get metadata about each property defined in some unknown class. The property name is fetched from the metadata and passed to QObject::property () 以获取 value of the property in the current 对象 .

简单范例

Suppose we have a class MyClass , which is derived from QObject and which uses the Q_OBJECT macro. We want to declare a property in MyClass to keep track of a priority value. The name of the property will be priority , and its type will be an enumeration type named 优先级 , which is defined in MyClass .

We declare the property with the Q_PROPERTY () macro in the private section of the class. The required READ function is named priority , and we include a WRITE function named setPriority . The enumeration type must be registered with the 元对象系统 使用 Q_ENUM () macro. Registering an enumeration type makes the enumerator names available for use in calls to QObject::setProperty (). We must also provide our own declarations for the READ and WRITE functions. The declaration of MyClass then might look like this:

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)
    {
        if (m_priority == priority)
            return;
        m_priority = priority;
        emit priorityChanged(priority);
    }
    Priority priority() const
    { return m_priority; }
signals:
    void priorityChanged(Priority);
private:
    Priority m_priority;
};
					

The READ function is const and returns the property type. The WRITE function returns void and has exactly one parameter of the property type. The meta-object compiler enforces these requirements. The equality check in the WRITE function, while not mandatory, is good practice as there is no point in notifying and potentially forcing re-evaluation in other places if nothing has changed.

Given a pointer to an instance of MyClass or a pointer to a QObject that is an instance of MyClass , we have two ways to set its priority property:

MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
					

In the example, the enumeration type that is the property type is declared in MyClass and registered with the 元对象系统 使用 Q_ENUM () macro. This makes the enumeration values available as strings for use as in the call to setProperty (). Had the enumeration type been declared in another class, its fully qualified name (i.e., OtherClass::Priority) would be required, and that other class would also have to inherit QObject and register the enumeration type there using the Q_ENUM () 宏。

A similar macro, Q_FLAG (), is also available. Like Q_ENUM (), it registers an enumeration type, but it marks the type as being a set of flags , i.e., values that can be OR'd together. An I/O class might have enumeration values 读取 and 写入 and then QObject::setProperty () could accept Read | Write . Q_FLAG () should be used to register this enumeration type.

动态特性

QObject::setProperty () can also be used to add new properties to an instance of a class at runtime. When it is called with a name and a value, if a property with the given name exists in the QObject , and if the given value is compatible with the property's type, the value is stored in the property, and true is returned. If the value is not compatible with the property's type, the property is not changed, and false is returned. But if the property with the given name doesn't exist in the QObject (i.e., if it wasn't declared with Q_PROPERTY ()), a new property with the given name and value is automatically added to the QObject , but false is still returned. This means that a return of false can't be used to determine whether a particular property was actually set, unless you know in advance that the property already exists in the QObject .

注意, dynamic properties are added on a per instance basis, i.e., they are added to QObject , not QMetaObject . A property can be removed from an instance by passing the property name and an invalid QVariant value to QObject::setProperty (). The default constructor for QVariant constructs an invalid QVariant .

Dynamic properties can be queried with QObject::property (), just like properties declared at compile time with Q_PROPERTY ().

特性和自定义类型

特性使用的自定义类型需要注册使用 Q_DECLARE_METATYPE () 宏以便它们的值可以存储在 QVariant objects. This makes them suitable for use with both static properties declared using the Q_PROPERTY () macro in class definitions and dynamic properties created at run-time.

把额外信息添加到类

连接到特性系统的是额外宏, Q_CLASSINFO (),可以用于附加额外 namevalue pairs to a class's meta-object. This is used for instance to mark a property as the default one in the context of QML 对象类型 :

Q_CLASSINFO("DefaultProperty", "content")
					

像其它元数据,类信息可以在运行时透过元对象访问。见 QMetaObject::classInfo () 了解细节。

Using Bindable Properties

Three different types can be used to implement bindable properties:

The first one is a general class for bindable properties. The latter two can only be used inside a QObject .

For more information, including examples, see the classes mentioned above and the general tips on implementing and using bindable properties .

另请参阅 元对象系统 , 信号和槽 , Q_DECLARE_METATYPE (), QMetaType , QVariant , Qt Bindable Properties ,和 从 C++ 定义 QML 类型 .