Qt模块 Core1
文章目录
1. 核心功能
Qt Core 给C++语言增加了以下特性
- 信号与槽(signals and slots)机制:用于对象之间无缝通信的强大机制
- 对象属性(object properties):可查询和可设计的对象属性
- 对象树(object trees):对象层级树
- 安全指针(guarded pointers):QPointer
- 跨库边界的动态类型转换:a dynamic cast that works across library boundaries
1.1 元对象系统(The Meta-Object System)
元对象中的元是什么意思?可以简单的将元对象理解为描述对象的对象,类似的,元类型对象可以理解为描述类型的对象
Qt的元对象系统提供了:用于内部对象间通信的信号和槽机制、运行时获得类型信息的能力、动态属性系统
元对象系统基于以下三个东西:
- QObject:一个类想要从元对象系统中获得收益,就必须继承自这个类
- Q_OBJECT宏:放在类声明的private作用域中,用于开启元对象特性,例如动态属性、信号和槽
- 元对象编译器(moc):作用于每一个QObject的子类,提供必要的代码以实现元对象特性
moc会读取C++源文件,如果他发现了有任何类的声明中包含了Q_OBJECT宏,那么它将产生另一些相应的C++源文件,其中包含了用于这些类的元对象代码(meta-object code)。被生成的源文件被#include进类的源文件,或者,更一般的,被编译和链接进这个类的实现中
除了提供信号和槽机制(引入元对象系统的主要原因)之外,元对象系统还提供了下面这些额外的特性:
- QObject::metaObject():该函数返回和类相关联的元对象
- QMetaObject::className():该函数在运行时将类名作为字符串返回
- QObject::inherits():该函数返回是否一个对象是一个被指定的类的实例
- QObject::tr()和QObject::trUtf8():翻译字符串,用于国际化
- QObject::setProperty()和QObject::property():通过属性名动态的设置和获取属性
- QMetaObject::newInstance():构造类的一个新实例
在QObject类上可以使用qobject_cast()去执行动态类型转换,这个函数的行为和标准C++中的dynamic_cast()很像,但是qobject_cast()不需要RTTI支持。这个函数尝试转换他的参数到尖括号中指定的指针类型,失败时返回空指针,成功时返回一个非空的指针
例如,下面假定MyWidget类继承自QWidget并且声明了Q_OBJECT宏
QObject *obj = new MyWidget;
变量obj的类型为QObject,但实际引用的类型为MyWidget,所以我们可以对他进行合适的转换
QWidget *widget = qobject_cast<QWidget *>(obj);
从QObject到QWidget的转换是成功的,因为对象本身的类型是MyWidget,它是QWidget的一个子类。因为我们知道obj是一个MyWidget,所以也可以转换为MyWidget*
MyWidget *myWidget = qobject_cast<MyWidget *>(obj);
转换成MyWidget是也成功的,因为qobject_cast()不区分内置的Qt类型还是自定义的类型
QLabel *label = qobject_cast<QLabel *>(obj);
// label is 0
转换成QLabel是失败的,label被设置为0,这个结果可以在运行时用来处理不同类型的不同对象
if (QLabel *label = qobject_cast<QLabel *>(obj)) {
label->setText(tr("Ping"));
} else if (QPushButton *button = qobject_cast<QPushButton *>(obj)) {
button->setText(tr("Pong!"));
}
一个类继承自QObject但是没有Q_OBJECT宏和元对象代码也是可以的,但是信号和槽机制以及在上面讲到的所有特性都不可用。从元对象系统的角度来看,一个QObject的没有元对象代码的子类相当于离他的最近的具有元对象代码的祖先。这意味着,例如,QMetaObject::className()函数将不会返回类的实际名字,而是他祖先的名字
因此,强烈推荐所有继承自QObject的类都使用Q_OBJECT宏,无论他是否真的需要信号和槽、属性等机制
1.2 属性系统
Qt提供了一些复杂的属性系统,他与编译器厂商提供的类似。然而,作为一个独立于编译器和平台的库,Qt不依赖与非标准的编译器特性,例如__property或[property]。Q提供的解决方案可以被任何Qt支持的平台下的任何标准的C++编译器所支持。它基于Qt中的元对象系统
1.2.1 声明属性
要声明一个属性,在一个继承自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属性的绑定
一个属性的行为像是一个类的数据成员,但是他获得了一些额外的特性通过元对象系统
- READ访问器函数在没有MEMBER变量被指定的情况下是需要的。它用于读取属性值。理想地,一个const函数被用于这个目的,这个函数的返回类型必须是属性的类型或者是对该类型的const引用,例如,QWidget::focus是一个只读属性,指定的READ函数是QWidget::hasFocus().
- WRIETE访问器函数是可选的,他用于设置属性值。这个函数必须返回空并且只有一个参数,参数的类型要么是属性的类型,要么是指向这个类型的指针或引用。例如,QWidget::enabled有一个WRITE函数QWidget::setEnabled()。只读属性不需要WRITE函数,例如,QWidget::focus没有WRITE函数
- MEMBER在没有指定READ访问器函数的情况下需要被关联到一个成员变量。这使得给定的成员变量在不需要READ和WRITE访问器的情况下可读可写。READ或WRITE访问器和MEMBER关联的变量一起使用也是可以的(但是不能同时使用)
- RESET函数是可选的,他用与将属性设置回指定的默认值。例如,QWidget::cursor有READ和WRITE函数QWidget::cursor()和QWidget::setCursor(),并且也有RESET函数QWidget::unsetCursor(),因为调用QWidget::setCursor()不意味着将光标重置为指定位置。RESET函数必须返回void并且不带参数
- NOTIFY信号是可选的。如果被定义,他应该指定他所在类中的一个已存在的信号,这个信号当属性值改变时被发射。用于MEMBER变量的NOTIFY信号必须带有0个或1个参数,这个参数必须和属性有相同的类型,属性的新值将被作为参数传递。NOTIFY信号只应该在属性值真的改变时被发射
- REVISION号是可选的,如果被包含了,那么他定义了属性和他的通知器信号被用于一个特殊的API版本。如果不被包含则默认为0
- DESIGNABLE表明了在GUI设计工具(例如Qt Designer)的属性编辑器中是否这个属性应该被显示。大多数的属性都是DESIGNABLE(默认为true)。他的值为true或false,你可以指定一个boolean类型的成员函数
- SCRIPTABLE表明了这个属性是否应该被脚本引擎访问(默认为true)。他的值为true或false,你可以指定一个boolean类型的成员函数
- STORED表明了这个属性被认为是他自己存在的还是取决于其他值。他也表明了属性值是否必须被存储当存储对象的状态时。大多数的属性都是STORED(默认为true)
- USER表明了是否这个属性被设计为面向用户或用户可编辑的,每个类中只有一个USER属性(默认为false)
- CONSTANT的