剖析Qt 事件的产生、分发、接受、处理流程

Windows上Qt事件处理机制详解:

1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的

keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent. 用户的程序还可以自己定制事件。

2、谁来接受和处理事件:答案是QObject。QObject 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。

3、谁来负责分发事件:对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver. 对于Qt GUI程序,由QApplication来负责。

接下来,将通过对代码的解析来看看QT是如何利用event loop从事件队列中获取用户输入事件,又是如何将事件转义成QEvents,并分发给相应的QObject处理。

#include <QApplication>     

 

 
  1. #include "widget.h"

  2. //Section 1

  3. int main(int argc, char *argv[])

  4. {

  5. QApplication app(argc, argv);

  6. Widget window; // Widget 继承自QWidget

  7. window.show();

  8. return app.exec(); // 进入QApplication事件循环,见section 2

  9. }  <span style="font-family: Arial, Helvetica, sans-serif;"> </span>

 
  1. // Section 2:

  2. int QApplication::exec()

  3. {

  4. //skip codes

  5. //简单的交给QCoreApplication来处理事件循环=〉section 3

  6. return QCoreApplication::exec();

  7. }

  8. // Section 3

  9. int QCoreApplication::exec()

  10. {

  11. //得到当前Thread数据,确保在同一个线程

  12. QThreadData *threadData = self->d_func()->threadData;

  13. if (threadData != QThreadData::current()) {

  14. qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());

  15. return -1;

  16. }

  17. //检查event loop是否已经创建

  18. if (!threadData->eventLoops.isEmpty()) {

  19. qWarning("QCoreApplication::exec: The event loop is already running");

  20. return -1;

  21. }

  22. ...

  23. QEventLoop eventLoop;

  24. self->d_func()->in_exec = true;

  25. self->d_func()->aboutToQuitEmitted = false;

  26. //委任QEventLoop 处理事件队列循环 ==> Section 4

  27. int returnCode = eventLoop.exec();

  28. ....

  29. }

  30. return returnCode;

  31. }

  32. // Section 4

  33. int QEventLoop::exec(ProcessEventsFlags flags)

  34. {

  35. //这里的实现代码不少,最为重要的是以下几行

  36. Q_D(QEventLoop); // 访问QEventloop私有类实例d

  37. try {

  38. //只要没有遇见exit,循环派发事件

  39. while (!d->exit)

  40. processEvents(flags | WaitForMoreEvents | EventLoopExec);

  41. } catch (...) {}

  42. }

  43. // Section 5

  44. bool QEventLoop::processEvents(ProcessEventsFlags flags)

  45. {

  46. Q_D(QEventLoop);

  47. if (!d->threadData->eventDispatcher)

  48. return false;

  49. if (flags & DeferredDeletion)

  50. QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);

  51. //将事件派发给与平台相关的QAbstractEventDispatcher子类 =>Section 6

  52. return d->threadData->eventDispatcher->processEvents(flags);

  53. }

  54. // Section 6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp

  55. // 这段代码是完成与windows平台相关的windows c++。 以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持

  56. // 其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX

  57. // QEventDispatcherWin32派生自QAbstractEventDispatcher.

  58. bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)

  59. {

  60. Q_D(QEventDispatcherWin32);

  61. if (!d->internalHwnd)

  62. createInternalHwnd();

  63. d->interrupt = false;

  64. emit awake();

  65. bool canWait;

  66. bool retVal = false;

  67. bool seenWM_QT_SENDPOSTEDEVENTS = false;

  68. bool needWM_QT_SENDPOSTEDEVENTS = false;

  69. do {

  70. DWORD waitRet = 0;

  71. HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];

  72. QVarLengthArray<MSG> processedTimers;

  73. while (!d->interrupt) {

  74. DWORD nCount = d->winEventNotifierList.count();

  75. Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);

  76. MSG msg;

  77. bool haveMessage;

  78. if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {

  79. // process queued user input events

  80. haveMessage = true;

  81. //从处理用户输入队列中取出一条事件

  82. msg = d->queuedUserInputEvents.takeFirst();

  83. } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {

  84. // 从处理socket队列中取出一条事件

  85. haveMessage = true;

  86. msg = d->queuedSocketEvents.takeFirst();

  87. } else {

  88. haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);

  89. if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)

  90. && ((msg.message >= WM_KEYFIRST

  91. && msg.message <= WM_KEYLAST)

  92. || (msg.message >= WM_MOUSEFIRST

  93. && msg.message <= WM_MOUSELAST)

  94. || msg.message == WM_MOUSEWHEEL

  95. || msg.message == WM_MOUSEHWHEEL

  96. || msg.message == WM_TOUCH

  97. #ifndef QT_NO_GESTURES

  98. || msg.message == WM_GESTURE

  99. || msg.message == WM_GESTURENOTIFY

  100. #endif

  101. || msg.message == WM_CLOSE)) {

  102. // 用户输入事件入队列,待以后处理

  103. haveMessage = false;

  104. d->queuedUserInputEvents.append(msg);

  105. }

  106. if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)

  107. && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {

  108. // socket 事件入队列,待以后处理

  109. haveMessage = false;

  110. d->queuedSocketEvents.append(msg);

  111. }

  112. }

  113. ....

  114. if (!filterEvent(&msg)) {

  115. TranslateMessage(&msg);

  116. //将事件打包成message调用Windows API派发出去

  117. //分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 => section 7

  118. DispatchMessage(&msg);

  119. }

  120. }

  121. }

  122. } while (canWait);

  123. ...

  124. return retVal;

  125. }

  126. // Section 7 windows窗口回调函数 定义在QTDIR\src\gui\kernel\qapplication_win.cpp

  127. extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

  128. {

  129. ...

  130. //将消息重新封装成QEvent的子类QMouseEvent ==> Section 8

  131. result = widget->translateMouseEvent(msg);

  132. ...

  133. }

从Section 1~Section7, Qt进入QApplication的event loop,经过层层委任,最终QEventloop的processEvent将通过与平台相关的QAbstractEventDispatcher的子类QEventDispatcherWin32获得用户的用户输入事件,并将其打包成message后,通过标准Windows API ,把消息传递给了Windows OS,Windows OS得到通知后回调QtWndProc,  至此事件的分发与处理完成了一半的路程。

 

 

 
  1. // (续上文Section 7) Section 2-1:

  2. QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

  3. {

  4. ...

  5. //检查message是否属于Qt可转义的鼠标事件

  6. if (qt_is_translatable_mouse_event(message)) {

  7. if (QApplication::activePopupWidget() != 0) {

  8. POINT curPos = msg.pt;

  9. //取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例

  10. QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);

  11. if (w)

  12. widget = (QETWidget*)w;

  13. }

  14. if (!qt_tabletChokeMouse) {

  15. //对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget

  16. // => Section 2-2

  17. result = widget->translateMouseEvent(msg);

  18. ...

  19. }

  20. // Section 2-2 $QTDIR\src\gui\kernel\qapplication_win.cpp

  21. //该函数所在与Windows平台相关,主要职责就是把已windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent,QWidget.

  22. bool QETWidget::translateMouseEvent(const MSG &msg)

  23. {

  24. //.. 这里很长的代码给以忽略

  25. // 让我们看一下sendMouseEvent的声明

  26. // widget是事件的接受者; e是封装好的QMouseEvent

  27. // ==> Section 2-3

  28. res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, qt_last_mouse_receiver);

  29. }

  30. // Section 2-3 $QTDIR\src\gui\kernel\qapplication.cpp

  31. bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,

  32. QWidget *alienWidget, QWidget *nativeWidget,

  33. QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,

  34. bool spontaneous)

  35. {

  36. //至此与平台相关代码处理完毕

  37. //MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同,

  38. 除了将QEvent的属性spontaneous标记不同。 这里是解释什么spontaneous事件:如果事件由应用程序之外产生的,比如一个系统事件。

  39. 显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是 spontaneous事件

  40.  
  41. if (spontaneous)

  42. result = QApplication::sendSpontaneousEvent(receiver, event); ==〉Section 2-4

  43. else

  44. result = QApplication::sendEvent(receiver, event);

  45. }

  46. // Section 2-4 C:\Qt\4.7.1-Vs\src\corelib\kernel\qcoreapplication.h

  47. inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)

  48. {

  49. //将event标记为自发事件

  50. //进一步调用 2-5 QCoreApplication::notifyInternal

  51. if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false;

  52. }

  53. // Section 2-5: $QTDIR\gui\kernel\qapplication.cpp

  54. bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)

  55. {

  56.  
  57. // 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持

  58. ...

  59. // 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,也就是说receiver->d_func()->threadData应该等于QThreadData::current()。

  60. 注意,跨线程的事件需要借助Event Loop来派发

  61. QObjectPrivate *d = receiver->d_func();

  62. QThreadData *threadData = d->threadData;

  63. ++threadData->loopLevel;

  64. bool returnValue;

  65. QT_TRY {

  66. //哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6

  67. returnValue = notify(receiver, event);

  68. } QT_CATCH (...) {

  69. --threadData->loopLevel;

  70. QT_RETHROW;

  71. }

  72. }

  73. // Section 2-6: $QTDIR\gui\kernel\qapplication.cpp

  74. // QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:

  75. 任何线程的任何对象的所有事件在发送时都会调用notify函数。

  76. bool QApplication::notify(QObject *receiver, QEvent *e)

  77. {

  78. //代码很长,最主要的是一个大大的Switch,Case

  79. ..

  80. switch ( e->type())

  81. {

  82. ...

  83. case QEvent::MouseButtonPress:

  84. case QEvent::MouseButtonRelease:

  85. case QEvent::MouseButtonDblClick:

  86. case QEvent::MouseMove:

  87. ...

  88. //让自己私有类(d是私有类的句柄)来进一步处理 ==> Section 2-7

  89. res = d->notify_helper(w, w == receiver ? mouse : &me);

  90. e->spont = false;

  91. break;

  92. }

  93. ...

  94. }

  95. // Section 2-7: $QTDIR\gui\kernel\qapplication.cpp

  96. bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)

  97. {

  98. ...

  99. // 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。

  100. //如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤,

  101. 允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。

  102. 如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。

  103. if (sendThroughObjectEventFilters(receiver, e))

  104. return true;

  105. // 递交事件给receiver => Section 2-8

  106. bool consumed = receiver->event(e);

  107. e->spont = false;

  108. }

  109. // Section 2-8 $QTDIR\gui\kernel\qwidget.cpp

  110. // QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget.

  111. bool QWidget::event(QEvent *event)

  112. {

  113. ...

  114. switch(event->type()) {

  115. case QEvent::MouseButtonPress:

  116. // Don't reset input context here. Whether reset or not is

  117. // a responsibility of input method. reset() will be

  118. // called by mouseHandler() of input method if necessary

  119. // via mousePressEvent() of text widgets.

  120. #if 0

  121. resetInputContext();

  122. #endif

  123. //mousePressEvent是虚函数,QWidget的子类可以通过重载重新定义mousePress事件的行为

  124. mousePressEvent((QMouseEvent*)event);

  125. break;

  126. }

 
  1. main(int, char **)   
  2. QApplication::exec()   
  3. QCoreApplication::exec()   
  4. QEventLoop::exec(ProcessEventsFlags )   
  5. QEventLoop::processEvents(ProcessEventsFlags )   
  6. QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)  
  7. QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg)   
  8. bool QApplicationPrivate::sendMouseEvent(...)   
  9. inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)   
  10. bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)   bool QApplication::notify(QObject *receiver, QEvent *e)   
  11. bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)   
  12. bool QWidget::event(QEvent *event)   
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值