Qt属性系统详解

本文介绍了Qt中的属性系统,包括如何声明属性、属性的行为特性和如何通过元数据对象系统读写属性等内容。此外,还详细解释了如何使用Q_PROPERTY宏、不同函数的作用以及如何处理自定义类型。

Qt提供了一个绝妙的属性系统。跟那些由编译器提供的属性差不多。然而,作为一个独立于编译器和平台的库,Qt不依赖于非标准的编译特性,比如__property 或[property]。Qt可以在任何平台上的标准编译器下编译。Qt属性系统基于元数据对象系统--就是那个提供了对象内置信号和槽通讯机制的家伙。


声明属性需要什么


要声明一个属性,需在继承自QObject的类中使用Q_PROPERTY()宏。
Q_PROPERTY(type name
   READ getFunction
   [WRITE setFunction]
   [RESET resetFunction]
   [NOTIFY notifySignal]
   [DESIGNABLE bool]
   [SCRIPTABLE bool]
   [STORED bool]
   [USER bool]
   [CONSTANT]
   [FINAL])

下面是一些典型的声明属性的示例:

 Q_PROPERTY(bool focus READ hasFocus)
 Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
 Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
  • 一个属性的行为就像类的数据成员,但是它还具有附加的特性,这些特性可以被元数据对象系统操作。这些特性是:
    需要一个READ访问器函数。用于读属性的值。理想情况下,有一个不变的函数用于此目的,并且它必须返回属性的类型的值或指针或引用。例如,QWidget::focus是一个只读的属性,它对应一个读函数:QWidget::hasFocus()。
  • 一个可选的WRITE访问器函数。它用于设置属性的值。它必须返回空并且至少具有一个参数,参数是属性类型的值或指针或引用。例如:QWidget::enabled具有WRITE函数QWidget::setEnable()。只读属性不需要写函数。例如,QWidget::focus没有对应的写函数。
  • 一个可选的RESET函数。用于设置属性的值到它的默认值。例如:QWidget::cursor具有典型的READ和WRITE函数,QWidget::cursor()和QWidget::setCursor(),并且它也具有一个RESET函数,QWidget::unsetCursor()。RESET函数必须返回void并且不带有任何参数。
  • 一个可选的NOTIFY信号。如果被定义了,信号将在属性的值改变时发出。信号必须带有一个参数,这个参数的类型必须与属性相同;参数保存的是属性的新值。
  • 一个DESIGNABLE变量表明此属性是否在界面设计器的属性编辑器中出现。大多数属性是可见的,除了为这个变量传入true或false,你还可以指定一个bool型的成员函数。
  • SCRIPTABLE变量表明这个属性是否可以被一个脚本引擎操作(默认是true)。你也可以赋予它true或false或bool型函数。
  • STORED变量表明了属性是否被认为是独立存在还是依赖于其它的值而存在。它也表明是否在保存对象状态时保存此属性的值。大多数属性都是需要保存的,但是,如QWidget::minimumWidth()就是不被保存的,因为它的值是从另一个属性QWidget::minimumSize()得来的。
  • USER变量表明属性是否被设计为面向用户的或用户可修改的类属性。通常,每个类只有一个USER属性。例如,QAbstractButton::checked是按钮类的用户可修改属性。注意QItemDelegate获取和设置widget的USER属性。
  • CONSTANT的出现表明属性的值是不变的。对于一个object实例,常量属性的READ方法在每次被调用时必须返回相同的值。此常量值可能在不同的object实例中不相同。一个常量属性不能具有WRITE方法或NOYIFY信号。
  • FINAL变量的出现表明属性不能被派生类所重写。有些情况下,这可以用于效率优化,但不是被moc强制的。程序员必须永远注意不能重写一个FINAL属性。

READ,WRITE和RESET函数都可以被继承。它们也可以是虚函数。当它们在被多重继承中被继承时,它们必须出现在第一个被继承的类中。

属性的类型可以是被QVariant支持的所有类型,也可以是用户定义的类型。在下面的例子中,类QDate被当作用户自定义类型。
Q_PROPERTY(QDate data READ getDate WRITE setDate)
因为QDate是用户定义的,你必须包含<QDate>头文件。

对于QMap,QList和QValueList属性,属性的值是一个QVariant,它包含整个list或map。注意Q_PROPERTY字符串不能包含逗号,因为逗号会划分宏的参数。因此,你必须使用QMap作为属性的类型而不是QMap<QString,QVariant>。为了保持一致性,也需要用QList和QValueList而不是QList<QVariant>和QValueList<QVariant>。


通过元数据对象系统读写属性

一个属性可以使用常规函数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和QMetaProerties。

 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类中声明一个属性来持续追踪一个Priorty值。属性的值叫做priority,并且它的类型是一个在类MyClass中定义的叫做Priority的枚举。

我们在类的private区使用Q_PROPERTY()来声明属性。READ函数叫做priority,并且我们包含一个WRITE函数叫做setPriority。枚举类型必须使用Q_ENUMS()注册到元数据对象系统中。注册一个枚举类型使得枚举的名字可以在调用QObject::setProperty()时使用。我们还必须为READ和WRITE函数提供我们自己的声明。MyClass的声明看起来应该是这样的:

 class MyClass : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(Priority priority READ priority WRITE setPriority)
     Q_ENUMS(Priority)
 public:
     MyClass(QObject *parent = 0);
     ~MyClass();
     enum Priority { High, Low, VeryHigh, VeryLow };
     void setPriority(Priority priority);
     Priority priority() const;
 };

READ函数是const的并且返回属性的类型。WRITE函数返回void并且具有一个属性类型的参数。元数据对象编译器强制做这些事情。

在有了一个指向MyClass实例的指针时,我们有两种方法来设置priority属性:

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

在此例子中,枚举类型在MyClass中声明并被使用Q_ENUMS()注册到元数据对象系统中。这使得枚举值可以在调用setProperty()时做为字符串使用。如果枚举类型是在其它类中声明的,那么我们就需要用枚举的全名(如OtherClass::Priority),并且这个其它类也必须从QObject中派生并且也要注册枚举类型。
另一个简单的Q_FLAGS()也是可用的。就像Q_ENUMS(),它注册一个枚举类型,但是它把枚举类型作为一个flag的集合,也就是,值可以用OR操作来合并。一个I/O类可能具有枚举值Read和Write并且QObject::setProperty()可以接受 Read|Write。此时应使用Q_FLAGS()来注册枚举值。

动态属性

Qobject::setProperty()也可以用来在运行时向一个类的实例添加新的属性。当使用一个名字和值调用它时,如果一个对应的属性已经存在,并且如果值的类型与属性的类型兼容,那么值就被存储到属性中,然后返回true。如果值类型不兼容,属性的值就不会发生改变,就会返回false。但是如果对应名字的属性不存在,那么一个新的属性就诞生了,以传入的名字为名,以传入的值为值,但是依然会返回false。这表示返回值不能用于确定一个属性是否被设置值,除非你已经知道这个属性已经存在于QObject中了。
注意动态属性被添加到单个实现的基础中,也就是,被添加到QObject,而不是QMetaObject。一个属性可以从一个实例中删除,通过传入属性的名字和非法的QVariant值给QObject::setProperty()。默认的QVariant构造器构造一个非法的QVariant。
动态属性可用QObject::property()来查询,就行使用Q_PROPERTY()声明的属性一样。

属性和自定义类型

被属性使用的自定义类型需要使用Q_DECLARE_METATYPE()宏注册,以使它们的值能被保存在QVariant对象中。这使得它们可以用于被Q_PROPERTY()声明的静态类型中,也可以被用于动态类型中。



<think>我们正在讨论Qt属性系统。用户要求了解其原理和使用方法。根据之前的对话历史,用户已经对Qt对象模型有基本了解,现在需要深入属性系统属性系统Qt元对象系统的一部分,允许在类中声明属性,这些属性可以在运行时被查询和修改,支持数据绑定、样式设置等。 原理部分: 1. 属性声明:通过Q_PROPERTY宏在类声明中定义属性。这个宏会被moc处理,生成相应的元数据。 2. 元对象系统:在编译时,moc会读取Q_PROPERTY宏,生成包含属性信息的元对象代码(在moc生成的文件中)。每个属性包括名称、类型、读写函数、通知信号等。 3. 运行时访问:通过QObject的property()和setProperty()方法,或者通过元对象系统(QMetaObject)来访问属性。 使用指南: 1. 声明属性:在QObject派生类的头文件中使用Q_PROPERTY宏。 2. 实现读写函数和通知信号(如果需要)。 3. 使用属性:通过字符串名称访问属性(动态属性),或者通过元对象系统。 注意:动态属性(在运行时添加的属性)也是可能的,但这里主要讨论静态声明的属性。 我们将按照以下结构回答: 一、属性系统原理 1.1 声明与元数据生成 1.2 运行时支持 二、属性声明详解(Q_PROPERTY宏参数) 三、使用指南 3.1 声明和实现属性 3.2 访问属性(静态和动态) 3.3 在QML中使用属性 四、注意事项 五、示例代码 然后生成相关问题。 参考之前的引用,我们提到过属性系统(设计特点中的第3点),现在展开。</think>### Qt属性系统原理及使用指南 Qt属性系统是元对象系统的核心组件,允许为QObject派生类添加动态属性,支持运行时查询、修改和监控。以下是详细原理和使用方法: --- #### 一、属性系统原理 1. **元数据编译机制** - 使用`Q_PROPERTY`宏声明属性(如`Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged)`) - **moc编译器**解析宏并生成元数据代码(存储在`moc_*.cpp`中),包含: - 属性名称、类型、读写权限 - READ/WRITE函数指针 - NOTIFY信号指针 - 属性约束(如`CONSTANT`)[^1] 2. **运行时动态访问** - 通过`QObject::property("name")`和`setProperty("name", value)`访问属性 - 元对象系统自动处理类型转换(如`QVariant`与基本类型的互转) - 属性变化时触发绑定的信号槽(如`NOTIFY`信号)[^2] 3. **内存结构** ```mermaid graph LR A[QObject] --> B[QMetaObject] B --> C[属性元数据表] C --> D[属性1: 名称/类型/READ/WRITE/NOTIFY] C --> E[属性2: ...] ``` --- #### 二、属性声明详解(Q_PROPERTY宏参数) | 参数 | 作用 | 示例 | |-------------|--------------------------------------|---------------------------| | `READ` | 读取函数(必须) | `int width() const` | | `WRITE` | 写入函数(可选) | `void setWidth(int)` | | `NOTIFY` | 属性变化信号(可选) | `void widthChanged()` | | `RESET` | 重置为默认值(可选) | `void resetWidth()` | | `CONSTANT` | 标记不可变属性(如常量) | `Q_PROPERTY(..., CONSTANT)`| | `FINAL` | 禁止子类重写 | `Q_PROPERTY(..., FINAL)` | --- #### 三、使用指南 1. **声明与实现属性** ```cpp // 头文件 class Widget : public QWidget { Q_OBJECT Q_PROPERTY(int borderWidth READ borderWidth WRITE setBorderWidth NOTIFY borderWidthChanged) public: int borderWidth() const { return m_width; } void setBorderWidth(int w) { if (w != m_width) { m_width = w; emit borderWidthChanged(); // 触发信号 } } signals: void borderWidthChanged(); private: int m_width = 1; }; ``` 2. **动态访问属性** ```cpp Widget widget; // 通过函数访问 widget.setBorderWidth(10); int w = widget.borderWidth(); // 通过字符串动态访问 widget.setProperty("borderWidth", 20); QVariant v = widget.property("borderWidth"); ``` 3. **在QML中绑定属性** ```qml // QML文件 Rectangle { width: parent.width // 自动绑定到C++属性 border.width: myWidget.borderWidth // 属性变化时自动更新 onBorderWidthChanged: console.log("宽度变化") } ``` 4. **添加动态属性** ```cpp // 运行时添加属性(无需Q_PROPERTY) QObject *obj = new QObject; obj->setProperty("dynamicColor", QColor("red")); QColor color = obj->property("dynamicColor").value<QColor>(); ``` --- #### 四、注意事项 1. **类型安全** - `setProperty()`使用`QVariant`,需确保类型兼容 - 错误类型会导致运行时警告:`QMetaProperty::write: ...`[^2] 2. **性能优化** - 频繁访问时优先使用READ/WRITE函数(避免字符串解析) - 常量属性用`CONSTANT`标记(跳过元对象查询) 3. **线程安全** - 属性修改默认在对象所属线程 - 跨线程修改需使用`QueuedConnection`连接信号槽 --- #### 五、示例:自定义进度条 ```cpp class ProgressBar : public QWidget { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) public: int value() const { return m_value; } void setValue(int v) { if (v != m_value && v >= 0 && v <= 100) { // 范围约束 m_value = v; update(); // 重绘UI emit valueChanged(); } } signals: void valueChanged(); private: int m_value = 0; }; ``` --- ### 总结 Qt属性系统通过`Q_PROPERTY`宏和元对象系统实现动态属性管理,支持: 1. 编译时声明类型安全的属性 2. 运行时动态查询/修改 3. 自动触发信号实现数据绑定 4. 与QML无缝集成 通过合理使用READ/WRITE函数和NOTIFY信号,可构建响应式UI和数据模型[^1][^2]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值