一、对象、属性和事件是Qt框架的核心(比较理论化,直接翻译了Qt帮助文档)
QObject类构成了Qt对象模型的基础,是许多Qt类的父类。对象模型引入了许多机制,例如允许运行时内省、操作和调用对象中的属性和方法的元对象系统。它还作为Qt事件系统的基础,Qt事件系统是基于qobject的对象之间通信的一种低级方式。Qt的信号和插槽机制提供了另一种高级通信形式。此外,QObject使用QObject::startTimer()提供了一个简单的计时机制。或者,QTimer类为计时器提供高级接口。
1、对象模型:Qt将这些特性添加到c++中:
1)一种非常强大的无缝对象通信机制,称为信号和插槽
2)可查询和可设计的对象属性
3)强大的事件和事件过滤器
4)用于国际化的上下文字符串翻译
5)复杂的时间间隔驱动计时器使在事件驱动GUI中优雅地集成许多任务成为可能
6)层次和可查询的对象树,以一种自然的方式组织对象所有权
7)被保护指针(QPointer),当引用的对象被销毁时,它会自动设置为0,这与普通c++指针不同,后者在对象销毁时变成悬空指针
8)跨越库边界的动态转换
9)支持自定义类型创建
2、元对象系统:Qt的元对象系统为对象间通信、运行时类型信息和动态属性系统提供了信号和插槽机制。
元对象系统基于三件事:
1)QObject类为可以利用元对象系统的对象提供了一个基类
2)类声明的私有部分中的Q_OBJECT宏用于启用元对象特性,例如动态属性、信号和插槽。
3)元对象编译器(moc)为每个QObject子类提供实现元对象特性所需的代码。
在编译过程中,moc工具读取一个c++源文件。如果找到一个或多个包含Q_OBJECT宏的类声明,则生成另一个c++源文件,其中包含每个类的元对象代码。生成的源文件要么#include到类的源文件中,要么(通常)编译并链接到类的实现中。
元对象代码除了提供对象间通信的信号和插槽机制(引入该系统的主要原因)外,还提供了以下附加功能:
1)QObject::metaObject() 返回类的关联元对象
2)QMetaObject::className()在运行时以字符串形式返回类名,而不需要c++编译器对本机运行时类型信息(RTTI)的支持。
3)QObject::inherits() 返回对象是否是继承QObject继承树中指定类的实例。
4)QObject::tr()和QObject::trUtf8()转换字符串以实现国际化。
5)QObject::setProperty()和QObject::property()通过名称动态设置和获取属性。
6)QMetaObject::newInstance()构造类的新实例。
还可以使用qobject_cast()对QObject类执行动态强制转换。qobject_cast()函数的行为类似于标准的c++ dynamic_cast(),其优点是不需要RTTI支持,并且可以跨动态库边界工作。它尝试将其参数转换为尖括号中指定的指针类型,如果对象的类型正确(在运行时确定),则返回非零指针;如果对象的类型不兼容,则返回0。
虽然可以在没有Q_OBJECT宏和元对象代码的情况下使用QObject作为基类,但是如果不使用Q_OBJECT宏,则信号和插槽以及本文描述的其他特性都不可用。从元对象系统的角度来看,一个没有元代码的QObject子类等价于它最近的祖先有元对象代码。这意味着,例如,QMetaObject::className()不会返回类的实际名称,而是祖先的类名称。
3、属性系统:Qt提供了一个复杂的属性系统,类似于一些编译器供应商提供的属性系统。但是,作为一个独立于编译器和平台的库,Qt不依赖于非标准的编译器特性,如__property或[property]。Qt解决方案可以在Qt支持的每个平台上使用任何标准c++编译器。它基于元对象系统,元对象系统还通过信号和插槽提供对象间通信。
设置属性的作用其实就是以另一种方式来获取类的成员的值。并且将成员设置为属性之后,只要修改了该成员的值,那么该成员会自动的发出更新信号,从而实现实时的改变界面数据。
要声明属性,请在继承QObject的类中使用Q_PROPERTY()宏。
这个在使用QML开发界面结合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])
一个完整的例子:
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;
};
4、事件系统:在Qt中,事件是来自抽象QEvent类的对象,它们表示应用程序内部或外部活动中发生的事件,应用程序需要了解这些事件。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关。
1)事件是如何传递的?当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示它。
并通过调用其event()函数将其传递给QObject的特定实例(或其子类之一)。此函数不处理事件本身;基于传递的事件类型,它为特定类型的事件调用事件处理程序,并根据事件是否被接受或忽略发送响应。一些事件,如QMouseEvent和QKeyEvent,来自窗口系统;有些来自其他来源,如QTimerEvent;有些来自应用程序本身。
2)事件类型
大多数事件类型都有特殊的类,特别是QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。每个类都子类化QEvent并添加特定于事件的函数。例如,QResizeEvent添加size()和oldSize(),使小部件能够发现它们的维度是如何更改的。
每个事件都有一个关联的类型,在QEvent:: type中定义,这可以作为运行时类型信息的一个方便的来源,用于快速确定给定事件对象是由哪个子类构造的。
3)事件的处理:如果要处理某个特定类的事件,则需要先继承该类比如继承QPushButton,实现MyPushButton.假设我要处理
QPushButton的点击操作产生的事件,点击操作由两个事件组成mousePressEvent()和mouseReleaseEvent()。那么我就要在MyPushButton类中实现这两个事件。
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
MyPushButton(QWidget *parent = 0);
~MyPushButton();
protected:
void mousePressEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
};
在处理的时候有两种方式:第二种方式就是处理鼠标左键单击,同时将所有其他按钮单击传递给基类QPushButton.
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
//第一种处理方式
if(e->button() == Qt::LeftButton)
qDebug() << "LeftButton Press";
}
void MyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
//第二种方式
if(e->button() == Qt::LeftButton)
qDebug() << "LeftButton Release";
else
QPushButton::mouseReleaseEvent(e);
}
4)事件的过滤:有时,一个对象需要查看传递给另一个对象的事件,可能还需要拦截这些事件。例如,对话框通常希望过滤某些小部件的按键;例如,修改返回键处理。
QObject::installEventFilter()它通过设置一个事件过滤器来实现这一点,使指定的过滤器对象在其QObject::eventFilter()函数中接收目标对象的事件。事件筛选器在目标对象处理事件之前处理事件,允许它根据需要检查和丢弃事件。可以使用QObject::removeEventFilter()函数删除现有的事件过滤器。
当调用filter对象的eventFilter()实现时,它可以接受或拒绝事件,并允许或拒绝事件的进一步处理。如果所有的事件筛选器都允许进一步处理事件(通过每个返回false),则将事件发送到目标对象本身。如果其中一个事件停止处理(返回true),目标和以后的任何事件筛选器都无法看到事件。
还可以通过在QApplication或QCoreApplication对象上安装事件过滤器来过滤整个应用程序的所有事件。在对象特定的过滤器之前调用此类全局事件过滤器。这是非常强大的,但它也减慢了整个应用程序中每个事件的事件交付;通常应该使用其他讨论的技术。
5)发送事件:许多应用程序希望创建和发送自己的事件。通过构造合适的事件对象并使用QCoreApplication::sendEvent()和QCoreApplication::postEvent()发送事件,可以以与Qt自己的事件循环完全相同的方式发送事件。
sendEvent()立即处理事件。当它返回时,事件筛选器和/或对象本身已经处理了该事件。对于许多事件类,有一个名为isaccept()的函数,它告诉您最后调用的处理程序是接受事件还是拒绝事件。
postEvent()将事件发布到队列中,以便稍后分发。下一次运行Qt的主事件循环时,它将调度所有已发布的事件,并进行一些优化。例如,如果有几个调整大小的事件,它们被压缩成一个。这同样适用于paint events: QWidget::update()调用postEvent(),它消除了闪烁并通过避免多次重绘来提高速度。
在对象初始化期间也使用postEvent(),因为post事件通常在对象初始化完成后不久就会被分派。在实现小部件时,重要的是要认识到事件可以在其生命周期的早期交付,因此,在其构造函数中,确保在早期初始化成员变量,以免它可能接收到事件。
要创建自定义类型的事件,您需要定义一个事件号,该事件号必须大于QEvent::User,并且您可能需要子类化QEvent,以便传递关于自定义事件的特定信息。
5、信号和槽:信号和插槽用于对象之间的通信。信号和插槽机制是Qt的核心特性,可能也是与其他框架提供的特性最大的不同之处。信号和插槽是通过Qt的元对象系统实现的。
在GUI编程中,当我们更改一个小部件时,我们通常希望通知另一个小部件。更一般地说,我们希望任何类型的对象都能够彼此通信。例如,如果用户单击Close按钮,我们可能希望调用窗口的Close()函数。
其他工具包使用回调实现这种通信。回调是一个指向函数的指针,所以如果你想让一个处理函数通知你一些事件,你就把一个指针传递给另一个函数(回调)给处理函数。处理函数然后在适当的时候调用回调。虽然使用此方法的成功框架确实存在,但回调可能不直观,并且可能在确保回调参数的类型正确性方面遇到问题。
继承自QObject或其子类之一(例如,QWidget)的所有类都可以包含信号和插槽。当对象以其他对象可能感兴趣的方式改变其状态时,它们会发出信号。这是对象进行通信的全部操作。它不知道或不在乎任何东西是否接收到它发出的信号。这是真正的信息封装,并确保对象可以作为软件组件使用。
插槽可以用来接收信号,但它们也是普通的成员函数。就像对象不知道是否有任何东西接收到它的信号一样,插槽也不知道是否有任何信号连接到它。这确保了可以使用Qt创建真正独立的组件。
您可以将任意多的信号连接到一个插槽,并且一个信号可以连接到任意多的插槽。甚至可以将一个信号直接连接到另一个信号。(当第一个信号发出时,它将立即发出第二个信号。)
1)信号:当对象的内部状态以某种方式发生变化时,对象的客户机或所有者可能会感兴趣,这时就会发出信号。信号是公共访问函数,可以从任何地方发出,但是我们建议只从定义信号及其子类的类发出信号。
当发出信号时,连接到它的插槽通常立即执行,就像普通的函数调用一样。当这种情况发生时,信号和插槽机制完全独立于任何GUI事件循环。一旦所有槽位都返回,那么emit语句后面的代码就会执行。在使用排队连接时,情况略有不同;在这种情况下,emit关键字后面的代码将立即继续,插槽将在稍后执行。
如果多个插槽连接到一个信号,当信号发出时,插槽将依次执行它们已连接的顺序。
信号是由moc自动生成的,不能在.cpp文件中实现。它们永远不会有返回类型(即使用void)。
关于参数的注意事项:我们的经验表明,如果信号和插槽不使用特殊类型,则它们更易于重用。如果QScrollBar::valueChanged()使用一种特殊类型,例如假设的QScrollBar::Range,那么它只能连接到专为QScrollBar设计的插槽。将不同的输入小部件连接在一起是不可能的。
2)槽:当与槽相连的信号发出时,就调用槽。插槽是普通的c++函数,可以正常调用;它们唯一的特殊之处在于信号可以连接到它们。(在新版的Qt中,普通函数也可以作为槽使用了)
由于插槽是普通的成员函数,它们在直接调用时遵循普通的c++规则。但是,作为插槽,任何组件都可以通过信号插槽连接调用它们,无论其访问级别如何。这意味着从任意类的实例发出的信号可能导致在不相关类的实例中调用私有插槽。
您还可以将插槽定义为虚拟的,这在实践中非常有用。
请注意,当与基于qt的应用程序一起编译时,其他定义变量signal或slot的库可能会导致编译器警告和错误。为了解决这个问题,#undef出错的预处理器符号。
3) 使用Qt与第三方信号和插槽
Qt可以与第三方信号/插槽机制一起使用。您甚至可以在同一个项目中使用这两种机制。只需将以下行添加到qmake项目(.pro)文件中。CONFIG += no_keywords
它告诉Qt不要定义moc关键字信号、插槽和emit,因为这些名称将被第三方库使用,例如Boost。然后继续使用no_keywords标志的Qt信号和插槽,只需用相应的Qt宏Q_SIGNALS(或Q_SIGNAL)、Q_SLOT(或Q_SLOT)和Q_EMIT替换源中对Qt moc关键字的所有使用。
4) 基于字符串和基于函数的连接之间的区别
从Qt 5.0开始,Qt提供了用c++编写信号插槽连接的两种不同方式:基于字符串的连接语法和基于函数的连接语法。这两种语法都有优点和缺点。下表总结了它们的不同之处。
字符串 | 函数 | |
运行时 | 编译时 | |
执行隐式类型转换 | √ | |
将信号连接到lambda表达式 | √ | |
将信号连接到参数多于信号的插槽(使用默认参数) | √ | |
连接c++函数到QML函数 | √ |
6、定时器:QObject是所有Qt对象的基类,它在Qt中提供了基本的计时器支持。这个函数返回一个唯一的整数定时器ID。定时器现在会定时触发,直到你用定时器ID显式调用QObject::killTimer()。
要使这种机制发挥作用,应用程序必须在事件循环中运行。使用QApplication::exec()启动一个事件循环。当计时器触发时,应用程序发送QTimerEvent,控制流离开事件循环,直到计时器事件被处理。这意味着当应用程序忙着做其他事情时,计时器不能启动。换句话说:计时器的准确性取决于应用程序的粒度。
在多线程应用程序中,可以在任何具有事件循环的线程中使用计时器机制。要从非gui线程启动事件循环,请使用QThread::exec()。Qt使用对象的线程关联来确定哪个线程将交付QTimerEvent。因此,必须启动和停止对象线程中的所有计时器;无法为另一个线程中的对象启动计时器。
二、Qt 容器类:容器类是隐式共享的,它们是可重入的,并且它们针对速度、低内存消耗和最小内联代码扩展进行了优化,从而减少了可执行文件。此外,在所有用于访问它们的线程都将它们用作只读容器的情况下,它们是线程安全的。
容器类名 | 说明 |
QList |
这是目前最常用的容器类。它存储可由索引访问的给定类型(T)的值列表。在内部,QList使用数组实现,确保基于索引的访问非常快。 可以使用QList::append()和QList::prepend()在列表的任意一端添加项,也可以使用QList::insert()在列表的中间插入项。与任何其他容器类相比,QList经过了高度优化,可以将可执行文件中的代码扩展到尽可能少的代码。QStringList继承自QList |
QLinkedList |
这类似于QList,只不过它使用迭代器而不是整数索引来访问项。在插入一个巨大列表的中间时,它还提供了比QList更好的性能,并且具有更好的迭代器语义。(只要QLinkedList中的项存在,指向该项的迭代器就一直有效,而QList的迭代器在任何插入或删除之后都可能无效。) |
QVector |
它将给定类型的值数组存储在内存中相邻的位置。在向量的前面或中间插入可能非常慢,因为它可能导致大量的项必须由内存中的一个位置移动。 |
QStack |
这是QVector的一个方便的子类,它提供了“后进先出”(LIFO)语义。它添加了QVector中已有的函数中:push()、pop()和top()。 |
QQueue |
这是QList的一个方便的子类,它提供了“先进先出”(FIFO)语义。它添加了QList中已经存在的函数中:enqueue()、dequeue()和head()。 |
QSet |
这提供了一个具有快速查找的单值数学集。 |
QMap |
这提供了一个字典(关联数组),它将类型键的键映射到类型t的值。QMap按密钥顺序存储数据;如果顺序不重要,QHash是一个更快的选择。 |
QMultiMap |
这是QMap的一个方便的子类,它为多值映射提供了一个很好的接口,即一个键可以与多个值关联的映射。 |
QHash |
它的API与QMap几乎相同,但是提供了更快的查找。QHash以任意顺序存储数据。 |
QMultiHash |
这是QHash的一个方便的子类,它为多值散列提供了一个很好的接口。 |
三、Qt进程间通信:Qt提供了几个类来支持进程之间的通信。您还可以使用QProcess类启动和管理外部流程。
Qt提供了在Qt应用程序中实现进程间通信(IPC)的几种方法。
1、TCP/IP:跨平台的Qt网络模块提供了使网络编程可移植和容易的类。它提供了使用特定的应用程序级协议进行通信的高级类(例如,QNetworkAccessManager, QFtp),以及用于实现协议的低级类(例如,QTcpSocket, QTcpServer, QSslSocket)。
2、共享内存:跨平台共享内存类QSharedMemory提供了对操作系统共享内存实现的访问。它允许多个线程和进程安全访问共享内存段。此外,QSystemSemaphore可用于控制对系统共享的资源的访问,以及进程之间的通信。
3、D-Bus protocol:Qt D-Bus模块是一个unix专用库,可以使用D-Bus协议实现IPC。它将Qt的信号和插槽机制扩展到IPC级别,允许一个进程发出的信号连接到另一个进程中的插槽。
4、QProcess类:跨平台类QProcess可用于作为子进程启动外部程序,并与之通信。它提供了用于监视和控制子进程状态的API。QProcess通过继承QIODevice来访问子进程的输入/输出通道。
5、会话管理:在Linux/X11平台中,Qt提供了对会话管理的支持。会话允许将事件传播到进程,例如,在发生关机时通知进程。然后,流程和应用程序可以执行任何必要的操作,如保存打开的文档。
四、Qt线程编程