zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Qt之属性系统

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

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


(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访问函数。如果需要控制变量访问,仍然可以使用READ和WRITE函数而不仅仅是MEMBER(但别同时使用)。

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

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

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

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

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

属性类型可以是QVariant支持的任何类型,或者是用户定义的类型。在这个例子中,类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”。


通过WRITE操作器来设置属性值比上述两者都好,因为它效率更高而且在编译时期有更好的诊断。但是这需要你在编译实际了解整个类(能够访问其定义)。通过名称访问属性,能够让你在编译时访问不了解的类。你可以在运行时期通过QObject、QMetaObject和QMetaProperties查询类属性。


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的声明看起来应该是这样的:


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属性:


myinstance- setPriority(MyClass::VeryHigh); object- setProperty("priority", "VeryHigh");

在此例子中,定义在MyClass中的枚举类型是属性的类型,而且被Q_ENUM()宏注册在元对象系统中。这使得枚举值可以在调用setProperty()时做为字符串使用。如果枚举类型在其它类中声明,那么需要使用枚举的全名(例如:OtherClass::Priority),而且这个类也必须从QObject派生,并且使用Q_ENUM()宏注册枚举类型。

另一个简单的宏Q_FLAGS()也是可用的,就像Q_ENUMS(),它注册一个枚举类型,但是它把枚举类型作为一个flag集合,也就是,值可以用OR操作来合并。一个I/O类可能具有枚举值Read和Write并且QObject::setProperty()可以接受Read | Write。应使用Q_FLAGS()来注册此枚举类型。


QObject::setProperty()也可以用来在运行时期向一个类的实例添加新的属性。当使用一个名字和值调用它时,如果QObject中一个指定名称的属性已经存在,并且如果给定的值与属性的类型兼容,那么,值就被存储到属性中,然后返回true。如果值与属性类型不兼容,属性的值就不会发生改变,会返回false。但是如果QObject中一个指定名称的属性不存在(例如:未用Q_PROPERTY()声明),一个带有指定名称和值的新属性就被自动添加到QObject中,但是依然会返回false。这意味着返回值不能用于确定一个属性是否被设置值,除非事先知道这个属性已经存在于QObject中。

注意:态属性被添加到每一个实例中,即:它们被添加到QObject中,而不是QMetaObject。一个属性可以从一个实例中删除,通过传入属性名和非法的QVariant值给QObject::setProperty()。默认的QVariant构造器会构造一个非法的QVariant。

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


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


与属性系统相对应的是一个附加宏 - Q_CLASSINFO()。用于添加name-value对到类的元对象中。例如:


和其它meta-data一样,类信息可以在运行时通过meta-object访问,详情见:QMetaObject::classInfo() 。


在Qt之QSS(样式表语法)一节讲过关于“设置对象属性”的用法,里面有一条很重要的原则是:任何可被识别的Q_PROPERTY都可以使用qproperty-语法设置。 这里需要对Qt属性系统有一定的了解,详见:Qt之属性系统。
QSS可以定制应用程序的外观,无需关注Qt样式背后的魔力。从非常轻微到极其复杂的调整,样式表都可以做到。对于一个完全定制和独特的用户体验,QtQuick和QGraphicsView是更好的选择。
PySide教程:PySide中使用Qt“.NET研究”属性 PySide提供了 Propery 函数定义一种属性,该属性可以同时作为Qt和Python的属性(我们知道,一般在Python下定义的属性,并不是Qt属性)。而这个属性的Setter和getter也可以用Python函数定义。
PySide教程:PySide中使用Qt属性 PySide提供了 Propery 函数定义一种属性,该属性可以同时作为Qt和Python的属性(我们知道,一般在Python下定义的属性,并不是Qt属性)。而这个属性的Setter和getter也可以用Python函数定义。