QObject三大核心功能:信号与槽,内存管理,事件处理
总览
1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent
事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()
会触发QTimerEvent. 用户的程序可还以自己定制事件。
2、谁来接受和处理事件:答案是QObject。在Qt的内省机制剖析一文已经介绍QObject 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。
3、谁来负责分发事件:对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类Receiver。对于Qt GUI程序,由QApplication来负责。
4、每个线程可以有它的事件循环。
初始线程开始它的事件循环需使用QCoreApplication::exec()
,别的线程开始它的事件循环需要用QThread::exec()
,像QCoreApplication一样,QThreadr提供了exit(int)函数,一个quit() slot。
自定义事件
5种级别的事件过滤
1、重载特定事件函数。
比如:mousePressEvent(),keyPressEvent(), paintEvent() 。
2、重载QObject::event()。
我们可以在事件被特定的事件处理函数处理之前(像keyPressEvent())处理它。
这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时。
3、安装事件过滤器。
比如用 objA 过滤 objB 的事件,即事件到达 objB 之前,先交由 objA 处理。
安装事件过滤器
调用objB->installEventFilter(objA)
重载objA::eventFilter()
4、在QApplication上安装事件过滤器。
一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过QApplication这个eventFilter()。
5、继承QApplication类,并重载notify()函数。
Qt使用notify()来分发事件。要想在任何事件处理器捕获事件之前捕获事件,唯一的方法就是重新实现QApplication的notify()方法。这是一个最原始的检查事件不响应的终极办法。
事件处理优先级
事件循环模型
事件的产生、处理流程
事件的产生
事件的两种来源:
1. 一种是系统产生的。Spontaneous events
通常是window system把从系统得到的消息,比如鼠标按键,键盘按键等,放入系统的消息队列中,Qt事件循环的时候读取这些事件,转化为QEvent,再依次处理。
系统底层事件是通过抽象事件分发器QAbstractEventDispatcher
整合进Qt的事件循环的。QAbstractEventDispatcher
接受窗口系统以及其他源中的事件。它对事件的传递提供了一种精细控制的能力如下:
QAbstractEventDispatcher
*QEventDispatcherUNIX // 默认的glib不可用时,就用这个
QEventDispatcherX11
QEventDispatcherQWS
QEventDispatcherQPA
*QEventDispatcherGlib // 使用glib事件循环,有助于和Gtk的集成
QGuiEventDispatcherGlib
QWSEventDispatcherGlib
*QEventDispatcherWin32 // Qt 创建一个带回调函数的隐藏窗口来处理事件
QGuiEventDispatcherWin32
*QEventDispatcherMac
*...
2. 一种是由Qt应用程序程序自身产生的。
程序产生事件有两种方式:
一种方式是同步的,调用QApplication::sendEvent()
,bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
函数。 这时候事件不会放入队列,而是直接被派发和处理,QWidget::repaint()
函数用的就是这种方式。
发送按键"X"的事件到 mainWin 窗口
QKeyEvent event(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X", 0);
QApplication::sendEvent(mainWin, &event);
bool mainWinQObject::event(QEvent *e)
{
switch (e->type()) {
......
case QEvent::KeyPress:
// 处理事件消息
break;
}
}
一种是异步的,调用QApplication::postEvent()
,事件会进入到事件队列中,等待receiver接收。 例如QWidget::update()
函数,当需要重新绘制屏幕时,程序调用update()
函数,new出来一个paintEvent,调用QApplication::postEvent()
,将其放入Qt的消息队列中,等待依次被处理。
发送按键"X"的事件到 mainWin 窗口:
QApplication::postEvent(mainWin,
new QKeyEvent(QEvent::KeyPress, Qt::Key_X, Qt::NoModifier, "X", 0));
这会将该事件放入Qt自己的事件队列中,事件循环QEventLoop空闲时会判断该队列是否为空。最终使用 sendEvent() 依次派发事件队列中的这些事件。
注意一:postEvent
的事件是异步的,需要在堆上申请空间。
注意二:每一个线程有一个事件队列。
事件的调度
两种调度方式,一种是同步的sendEvent
,一种是异步postEvent
。
调用QApplication::sendEvent的时候,消息会立即被处理,是同步的。 实际上QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节。
Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环。 该循环可以简化的描述为如下的代码:
while ( !app_exit_loop )
{
while( !postedEvents ) { processPostedEvents() } // 先处理Qt事件队列中的事件
while( !qwsEvnts ){ qwsProcessEvents(); } // 处理系统消息队列中的消息
while( !postedEvents ) { processPostedEvents() }
}
先处理Qt事件队列中的事件,直至为空。 再处理系统消息队列中的消息,直至为空,在处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理。
事件的派发和处理
事件的派发是从bool QCoreApplication::notify(QObject *receiver, QEvent *event)
开始的,因为QAppliction也是继承自QObject,所以先检查QAppliation对象,如果有事件过滤器安装在qApp上,先调用这些事件过滤器。 接下来QApplication::notify()
会过滤或合并一些事件(比如失效widget的鼠标事件会被过滤掉,而同一区域重复的绘图事件会被合并)。 之后,会执行receiver->event(event)
,事件被送到reciver::event() 处理。
当QApplication开始析构时,QApplication::notify()
,不再派发消息。
在reciver::event()
中,先检查有无事件过滤器安装在reciever上。 若有,则调用之。 接下来,根据QEvent的类型,调用相应的特定事件处理函数。 一些常见的事件都有特定事件处理函数,比如mousePressEvent(),focusOutEvent(),resizeEvent(),paintEvent(),resizeEvent()
等等。
事件的转发
子类未处理的事件会一层一层传递给父类进行处理,直到最顶层,若无处理,QEvent将停止转发。
参考文章:QT事件传递与事件过滤器
事件过滤器实现
事件过滤器void QObject::installEventFilter(QObject *obj)
是这样实现的:
在所有Qt对象的基类: QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObject (qobjA)给另一个QObject (qobjB)安装了事件过滤器(objB->installEventFilter(objA)
)之后,qobjB会把qobjA的指针保存在eventFilters中。 在qobjB处理事件之前,会先去检查eventFilters列表,如果非空,就先调用列表中对象的eventFilter()函数。
事件过滤器函数eventFilter()
,如果返回true,则表示该事件已经被处理完毕,Qt将直接返回,进行下一事件的处理; 如果返回false,事件将接着被送往剩下的事件过滤器或是目标对象进行处理。
注意:事件过滤器限定被监视对象与监视对象生存在同一线程中
多线程事件循环
- 线程中的事件循环,使得线程可以使用那些需要事件循环的非GUI类(如,QTimer,QTcpSocket,QProcess)。
- 可以把任何线程的signals连接到特定线程的slots,也就是说信号-槽机制是可以跨线程使用的。
- 对于在QApplication之前创建的对象,QObject::thread()返回0,这意味着主线程仅为这些对象处理投递事件,不会为没有所属线程的对象处理另外的事件。
- 可以用
QObject::moveToThread(QThread *targetThread)
来改变QObject和QObject孩子们的线程亲缘关系,假如QObject对象有父亲,它不能移动这种关系。 -
在另一个线程(而不是创建它的那个线程)中delete QObject对象是不安全的。除非你可以保证在同一时刻对象不在处理事件。可以用
QObject::deleteLater()
,它会投递一个DeferredDelete事件,这会被对象线程的事件循环最终选取到。 - 假如没有事件循环运行,事件不会分发给对象。举例来说,假如你在一个线程中创建了一个QTimer对象,但从没有调用过exec(),那么QTimer就不会发射它的timeout()信号,对deleteLater()也不会工作。(这同样适用于主线程)。你可以手工使用线程安全的函数
QCoreApplication::postEvent()
,在任何时候,给任何线程中的任何对象投递一个事件,事件会在那个创建了对象的线程中通过事件循环派发。 -
事件过滤器在所有线程中也被支持,不过它限定被监视对象与监视对象生存在同一线程中。类似地,
QCoreApplication::sendEvent
(不是postEvent()),仅用于在调用此函数的线程中向目标对象投递事件。 - QObject和所有它的子类是非线程安全的。这包括整个的事件投递系统。需要牢记的是,当你正从别的线程中访问对象时,事件循环可以向你的QObject子类投递事件。假如你调用一个不生存在当前线程中的QObject子类的函数时,你必须用mutex来保护QObject子类的内部数据,否则会遭遇灾难或非预期结果。
- QThread对象(像其它的对象一样)生存在创建它的那个线程中,即父线程中,而不是当
QThread::run()
被调用时创建的那个线程。一般来讲,在你的QThread子类中提供slots是不安全的,除非你用mutex保护了你的成员变量。另一方面,你可以安全的从QThread::run()
的实现中发射信号,因为信号发射是线程安全的。
Qt 多线程之逐线程事件循环 下篇
事件源码分析
以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理。
1、创建一个QApplication和MyWidget,并注册一个事件过滤器
#include <QApplication>
#include "widget.h"
//Section 1
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyWidget window; // MyWidget继承自QWidget
OtherWidget other; // OtherWidget继承自QWidget
window.installEventFilter(&other);
window.show();
return app.exec(); // 进入Qpplication事件循环,见section 2
}
事件源码流程图解
循环调用QEventLoop::processEvents
事件循环调用与平台相关的QAbstractEventDispatcher子类的processEvents接口
实现Windows消息循环
不断从Windows系统中PeekMessage,然后分发给Windows系统,再由Windows系统找到对应的目标窗口,将消息分发到目标线程的消息回调中WndProc。
调用与平台相关的QAbstractEventDispatcher子类的processEvents接口
Windows消息分发
执行到QT注册的消息回调QtWndProc
系统消息转换为QEvent消息层层传递到最后的MyWidget::event
QEvent传递过程最后总体简图-Windows平台为例
事件循环总体简图2、QApplication::exec()
int QApplication::exec()
{
//skip codes
//简单的交给QCoreApplication来处理事件循环=〉section 3
return QCoreApplication::exec();
}
3、QCoreApplication::exec()
int QCoreApplication::exec()
{
//得到当前Thread数据
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
//检查event loop是否已经创建
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
...
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
//委任QEventLoop 处理事件队列循环 ==> Section 4
int returnCode = eventLoop.exec();
....
}
return returnCode;
}
4、QEventLoop::exec
只要QEventloop未退出,则循环调用processEvents
派发事件。
int QEventLoop::exec(ProcessEventsFlags flags)
{
//这里的实现代码不少,最为重要的是以下几行
Q_D(QEventLoop); // 访问QEventloop私有类实例d
try {
//只要没有遇见exit,循环派发事件
while (!d->exit)
processEvents(flags | WaitForMoreEvents | EventLoopExec);
} catch (...) {}
}
5、QEventLoop::processEvents
将事件派发给与平台相关的QAbstractEventDispatcher子类
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop); // 访问QEventloop私有类实例d
if (!d->threadData->eventDispatcher)
return false;
if (flags & DeferredDeletion)
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
// 将事件派发给与平台相关的QAbstractEventDispatcher子类 =>Section 6
return d->threadData->eventDispatcher->processEvents(flags);
}
6、具体与平台相关的QAbstractEventDispatcher子类的processEvents
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
// 拿到QEventDispatcherWin32私有类QEventDispatcherWin32Private实例d
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd)
createInternalHwnd();
d->interrupt = false;
emit awake();
bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
QVarLengthArray<MSG> processedTimers;
// 如下循环实现了Windows的消息循环机制
/*
while(PeekMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg); //转换
DispatchMessage(&Msg); //分发
}
*/
while (!d->interrupt) {
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// 处理排队的用户输入事件
haveMessage = true;
// 从处理用户输入队列中取出一条事件
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// 从处理socket队列中取出一条事件
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
// 从系统消息队列中取消息。设置了PM_REMOVE,消息被取出后从消息队列中删除
// PeekMessage从消息队列中取不到消息,直接返回,线程不会被阻塞
// GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
&& ((msg.message >= WM_KEYFIRST
&& msg.message <= WM_KEYLAST)
|| (msg.message >= WM_MOUSEFIRST
&& msg.message <= WM_MOUSELAST)
|| msg.message == WM_MOUSEWHEEL
|| msg.message == WM_MOUSEHWHEEL
|| msg.message == WM_TOUCH
#ifndef QT_NO_GESTURES
|| msg.message == WM_GESTURE
|| msg.message == WM_GESTURENOTIFY
#endif
|| msg.message == WM_CLOSE)) {
haveMessage = false;
// 用户输入事件入队列,待以后处理
d->queuedUserInputEvents.append(msg);
}
if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
haveMessage = false;
// socket 事件入队列,待以后处理
d->queuedSocketEvents.append(msg);
}
}
....
if (!filterEvent(&msg)) {
// 转换:将虚拟键值信息转换为字符信息
TranslateMessage(&msg);
// 分发
// 将事件打包成message调用Windows API派发出去
// 分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 => section 7
// DispatchMessage()函数将消息再给windows系统,由windows系统找到目标窗口并分发给该窗口,
// 调用消息对应的窗口过程函数,即窗口的WinPro函数,让WinPro函数处理
// qt的窗口处理程序为QtWndProc
DispatchMessage(&msg);
}
}
} while (canWait);
...
return retVal;
}
7、窗口过程函数 QtWndProc
Windows窗口回调函数定义在QTDIR\src\gui\kernel\qapplication_win.cpp
。
关键:通过QApplication::widgetAt(curPos.x, curPos.y)
,取得鼠标点击的坐标所在的QWidget指针,然后再调用QWidget指针所指对象的translateMouseEvent
,将消息分发到对应的QWidget中。
QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
// 检查message是否属于Qt可转义的鼠标事件
if (qt_is_translatable_mouse_event(message)) {
if (QApplication::activePopupWidget() != 0) {
POINT curPos = msg.pt;
// 取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例
QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);
if (w)
widget = (QETWidget*)w;
}
if (!qt_tabletChokeMouse) {
// 对,就在这里。Windows的回调函数将鼠标事件分发给了Qt Widget
// 即我们在main函数里创建的widget实例
result = widget->translateMouseEvent(msg);
...
}
8、translateMouseEvent
该函数所在与Windows平台相关,主要职责就是把Windows格式打包的鼠标事件解包、翻译成QApplication、QWidget可识别的QMouseEvent事件。
bool QETWidget::translateMouseEvent(const MSG &msg)
{
//.. 这里很长的代码给以忽略
// 让我们看一下sendMouseEvent的声明
// widget是事件的接受者; e是封装好的QMouseEvent
// ==> Section 2-3
res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, qt_last_mouse_receiver);
}
9、sendMouseEvent
至此与平台相关代码处理完毕。 根据事件类型调用sendEvent
或者sendSpontaneousEvent
将事件同步直接发送给接受者。
bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,
QWidget *alienWidget, QWidget *nativeWidget,
QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,
bool spontaneous)
{
// MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。
// sendSpontaneousEvent()与sendEvent的代码实现几乎相同,除了将QEvent的属性标记为spontaneous不同。
// spontaneous事件是由应用程序之外产生的事件,比如一个系统事件。
// 显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是spontaneous事件
if (spontaneous)
result = QApplication::sendSpontaneousEvent(receiver, event); ==〉Section 2-4
else
result = QApplication::sendEvent(receiver, event);
}
10、sendSpontaneousEvent
// Section 2-4 C:\Qt\4.7.1-Vs\src\corelib\kernel\qcoreapplication.h
inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{
//将event标记为自发事件
//进一步调用 2-5 QCoreApplication::notifyInternal
if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false;
}
11、notifyInternal
bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
{
// 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持
...
// 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,
// 也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
// 注意,跨线程的事件需要借助Event Loop来派发
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
++threadData->loopLevel;
bool returnValue;
QT_TRY {
// 哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6
returnValue = notify(receiver, event);
} QT_CATCH (...) {
--threadData->loopLevel;
QT_RETHROW;
}
}
12、QApplication::notify
QCoreApplication::notify
和它的重载函数QApplication::notify
在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:任何线程的任何对象的所有事件在发送时都会调用notify函数。
bool QApplication::notify(QObject *receiver, QEvent *e)
{
//代码很长,最主要的是一个大大的Switch,Case
..
switch ( e->type())
{
...
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
...
// 让自己私有类(d是私有类的句柄)来进一步处理 ==> Section 2-7
res = d->notify_helper(w, w == receiver ? mouse : &me);
e->spont = false;
break;
}
...
}
13、notify_helper
先经过事件过滤器处理,优先让OtherWidget处理感兴趣的消息,再将事件传递给接收对象MyWidget的event
函数。
bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
...
// 向事件过滤器即前面的OtherWidget发送该事件,优先让OtherWidget处理感兴趣的消息
// 这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。
// 如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤,
// 允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
// 如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。
if (sendThroughObjectEventFilters(receiver, e))
// 事件过滤器返回true后,事件将不会传递给目标对象receiver
return true;
// 递交事件给目标对象receiver => Section 2-8
bool consumed = receiver->event(e);
e->spont = false;
}
14、QWidget::event或者children's_QWidget::event
QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类
bool QWidget::event(QEvent *event)
{
...
switch(event->type()) {
case QEvent::MouseButtonPress:
// Don't reset input context here. Whether reset or not is
// a responsibility of input method. reset() will be
// called by mouseHandler() of input method if necessary
// via mousePressEvent() of text widgets.
#if 0
resetInputContext();
#endif
// mousePressEvent是虚函数,QWidget的子类可以通过重载重新定义mousePress事件的行为
mousePressEvent((QMouseEvent*)event);
break;
}
参考1:Qt 事件处理机制 (上篇)
参考2:Qt 事件处理机制 (下篇)
网友评论