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的存在表明了属性值为常量。对于一个给定的对象实例,一个const属性的READ方法在每次调用时必须返回相同的值。常量值在不同对象的实例中是不同的。const属性不能有WRITE方法或NOTIFY信号
- FINAL的存在表明了该属性不能被继承的类重写。这在一些形况下被用于性能优化,但是不会被moc强制执行。需要注意的是不要去重写FINAL属性
READ、WRITE和RESET函数可以被继承,他们也可以是虚函数。当用于被使用多重继承的类中时,他们必须来自第一个被继承的类
属性的类型可以为任意被QVariant支持的类型,或者为一个用户自定义的类型。在下面的例子中,类QDate被考虑为一个用户自定义的类型
Q_PROPERTY(QDate date READ getDate WRITE setDate)
因为QDate是自定义的,所以你必须包含<QDate>头文件
由于历史原因,QMap和QList作为属性类型,他们是QVariantMap和QVariantList的同义词
1.2.2 使用元对象系统读写属性
使用泛型函数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和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);
...
}
在上面的代码中,QMetaObject::property()被用来获得每一个被定义的属性的metadata。属性名从metadata中得到然后传递给QObject::property()去得到属性值
1.2.3 一个简单的例子
假设我们有一个类MyClass,他继承于QObject并且声明了Q_OBJECT宏。我们想在MyClass中声明一个属性去追踪一个属性值。属性名为priority,类型为被定义在MyClass中的枚举类型Priority
我们使用宏Q_PROPERTY()在类private部分中声明属性。READ函数被命名为priority,WRITE函数被命名为setPriority。枚举类型在元对象系统中被注册,使用宏Q_ENUM()。注册枚举类型使枚举值可以在调用QObject::setProperty()时使用。我们必须提供我们我们自己的用于READ和WRITE函数的声明。MyClass类的声明如下:
class MyClass : public QObject
{
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实例的指针或一个指向MyClass实例的QObject类型的指针,我们有两种方式去设置他的priority属性
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
在这个例子中,枚举类型被声明在MyClass中并且在元对象系统中使用Q_ENUM()宏注册。这使得枚举值可以在调用setProperty()时作为字符串使用。如果枚举类型是在另一个类中声明的,则需要它的完全限定名(即OtherClass::Priority),同时这个类也需要继承自QObject并且使用Q_ENUM()宏注册枚举类型
一个相似的宏,Q_FLAG(),也是可用的。像Q_ENUM()一样