1.元对象系统
元对象系统(Meta-Object System)是Qt框架中一个非常重要的机制,它提供了一种在运行时动态获取对象信息的方法,并且能够实现对象之间的通信。
元对象系统的核心是QObject类提供的QMetaObject对象。每个QObject对象都有一个对应的QMetaObject成员,它包含了该对象的类型信息、信号和槽信息、属性信息、枚举信息等。这些信息在运行时可以被动态获取,从而实现了许多高级特性,如信号和槽、元数据反射等。
在使用元对象系统时,要在QObject的子类中添加Q_OBJECT宏来启用元对象特性。这个宏会自动生成一些元对象信息,并且会将这些信息注册到Qt的元对象系统中。当对象被实例化时,它的元对象信息也会被创建并注册到元对象系统中。
元对象编译器( Meta-Object Compile, Moc)为QObject的子类提供了实现元对象特性所必须得代码。
我们还可以使用qobject_cast来检查一个对象是否是某个特定类型或其派生类的实例。这是通过比较对象的元对象来实现的,如果元对象的名称与目标类型相同或目标类型是元对象所对应类型的基类,那么就认为这个对象是目标类型的实例。这种方法比C++中的dynamic_cast更高效,因为它只涉及元对象的比较,而不需要进行运行时类型检查。
元对象系统涉及的主要类有下面几个:
- QMetaObject 元对象类
- QMetaClassInfo 封装了类的信息
- QMetaMethod 封装了类中的方法信息
- QMetaProperty 封装了类中的属性信息
2.元对象与属性系统
Qt提供了一个复杂的属性系统,作为一个独立于编译器和平台的库,它不依赖于任何非标准编译器功能。Qt可与其支持的每个平台上的C++编译器配合使用。属性系统是基于元对象系统实现的。下面是属性声明的通用公式:
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])
属性声明举例:
Q_PROPERTY(bool focus READ hasFocus)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
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变量,则需要READ访问器函数。用于读取属性值。理想情况下,const函数用于此目的,并且它必须返回属性的类型或对该类型的const引用。例如,QWidget::focus是一个带有read函数的只读属性,QWidget::hasFocus()。
-
WRITE
访问器函数是可选的。用于设置属性值。它只接受一个参数,要么是属性的类型,要么是指向该类型的指针或引用。例如,QWidget::enabled具有WRITE函数QWidget::setEnabled()。只读属性不需要WRITE函数。例如,QWidget::focus没有WRITE功能。
-
MEMBER
如果未指定READ访问器函数,则需要MEMBER变量关联。这使得给定的成员变量可读写,而无需创建READ和WRITE访问器函数。如果需要控制变量访问,除了MEMBER变量关联之外,还可以使用READ或WRITE访问器函数(但不能同时使用这两种函数)。
-
RESET
重置功能是可选的。它用于将属性设置回其上下文特定的默认值。例如,QWidget::cursor具有典型的READ和WRITE函数,QWidget::cursors()和QWidgep::setCursor(),RESET函数必须返回void,并且不接受任何参数。
-
NOTIFY
通知是可选的。如果使用该关键词,则应该指定该类中的一个现有信号,该信号在属性值更改时发出。MEMBER变量的NOTIFY信号必须为零或一个参数,该参数必须与属性的类型相同。该参数将采用属性的新值。NOTIFY信号应该只在属性真正更改时发出,以避免在QML中不必要地重新评估绑定。当没有显式setter的MEMBER属性需要时,Qt会自动发出该信号。
-
REVISION
修订号是可选的。如果包括在内,它定义了要在API的特定版本中使用的属性及其通知信号(通常用于暴露于QML)。如果不包括在内,则默认为0。
-
DESIGNABLE
表示属性是否应在GUI设计工具(例如Qt Designer)的属性编辑器中可见。大多数属性都是DESIGNABLE(默认为true)。可以指定一个布尔成员函数。
-
SCRIPTABLE
表示脚本引擎是否应该访问此属性(默认为true)。可以指定一个布尔成员函数。
-
STORED
表示属性应该被认为是单独存在还是依赖于其他值。它还指示在存储对象的状态时是否必须保存属性值。大多数属性都是STORED(默认为true),但例如,QWidget::minimumWidth的STORED为false,因为它的值只是取自属性QWidget::minimumSize的宽度组件,该属性是QSize。
-
USER
表示该属性是指定为类的面向用户属性还是用户可编辑属性。通常,每个类只有一个USER属性(默认值为false)。例如,QAbstractButton::checked是用户可编辑属性。请注意,QItemDelegate获取并设置小部件的USER属性。
-
CONSTANT
表示属性值是常量。对于给定的对象实例,常量属性的READ方法每次调用时都必须返回相同的值。对于对象的不同实例,该常数值可能不同。常量属性不能具有WRITE方法或NOTIFY信号。
-
FINAL
表示派生类不会覆盖该属性。在某些情况下,这可以用于性能优化,但moc不会强制执行。必须注意决不能覆盖FINAL属性。
通过元对象系统访问属性:
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);
...
}
2.1 动态属性
通过QObject子类对象的setProperty函数可以设置一个不存在的属性,该属性将存储在QObject 子类对象中,而不是QMetaObject中,不能通过元对象系统访问。这类属性称为动态属性。如果绑定动态属性的数据类型是自定义类型,则需要通过Q_DECLARE_METATYPE()宏声明,这样属性值才能通过QVariant包装存储。
3. 元对象与信号槽
信号和槽是Qt框架的重要特性之一,用来在对象之间通信。元对象系统为信号槽的实现提供了底层支持。
3.1 信号槽应用场景
在GUI编程中,当触发某个事件时,我们通常希望通知另一个对象。更普遍地说,我们希望所有对象之间都能够相互交流。例如,用户单击Close按钮,我们可能希望调用窗口的Close函数。其他框架使用回调来实现这种通信,回调是指向函数的指针。虽然使用这种方法的成功框架确实存在,但回调可能是不直观的,并且在确保回调参数的类型正确性方面可能会遇到问题。
在Qt中,使用信号和槽在对象间通信,当特定事件发生时,会发出一个信号。QWidget 有许多预定义的信号,我们也可以继承QWidget实现自定义UI组件,并在子类中添加我们自己的信号。槽是响应特定信号而调用的函数。QWidget有许多预定义的槽函数,但通常的做法是子类化QWidget,并添加自己的槽函数,以便处理相应的信号。
3.2 信号槽的连接
信号槽机制是类型安全的:信号的签名必须与接收插槽的签名匹配。基于字符串的SIGNAL和SLOT语法将在运行时检测类型不匹配。信号和槽是松散耦合的:发出信号的类既不知道也不关心哪个插槽接收信号。Qt的信号槽机制确保,如果将信号连接到槽函数,槽函数将在正确的时间使用信号的参数进行调用。信号槽可以采用任何类型的任意数量的参数。它们是完全类型安全的。
所有继承自QObject或其子类之一(例如,QWidget)的类都可以包含信号槽。当对象状态改变时,它们会发出信号。这就是对象所做的所有通信。它不知道也不关心是否有任何对象正在接收它发出的信号。这是真正的信息封装,并确保对象可以用作软件组件。
槽函数可以用于接收信号,但它们也是正常的成员函数。就像一个对象不知道是否有任何对象接收它的信号一样,一个槽函数也不知道是否有任何信号连接到它。这确保了可以用Qt创建真正独立的组件。
我们可以将任意数量的信号连接到单个槽函数,也可以将信号连接到任意数量的槽函数。甚至可以将一个信号直接连接到另一个信号。(无论何时发出第一个信号,都会立即发出第二个信号。)信号和槽共同构成了强大的组件编程机制。下图是信号槽的连接示例:
![信号槽连接示意图](https://img-blog.csdnimg.cn/c18909bc65874f23b7b978277396fabb.png#pic_center)
3.3 信号槽要点总结
- 信号是Public函数,可以在任何地方发射信号,但是推荐只在定义信号的类中发射信号。
- 根据信号槽连接方式的不同(参见Qt::ConnectionType),槽函数会被立即调用,也可能延迟调用。
- 几个槽函数连接到一个信号,当信号发射时,这些槽函数将按照连接的顺序依次调用。
- 信号由moc自动生成,不得在.cpp文件中实现。它们永远不能有返回类型(即使用void)。
- 信号参数注意事项:经验表明,如果信号和槽函数是无参数的,它们将更易于重用。
本篇介绍了元对象系统及其应用的部分内容,下一篇会继续这个话题,并给出一个反射的应用示例,感谢大家的支持。
以上就是本篇全部内容了,欢迎吐槽评论!