浅议Qt的事件处理机制 一

深入了解事件处理系统对于每个学习Qt人来说非常重要,可以说,Qt是以事件驱动的UI工具集。 大家熟知Signals/Slots在多线程的实现也依赖于Qt的事件处理机制。

在Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent. 接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件:

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来负责。

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

  1. #include<QApplication>
  2. #include"widget.h"
  3. //Section1
  4. intmain(intargc,char*argv[])
  5. {
  6. QApplicationapp(argc,argv);
  7. Widgetwindow;//Widget继承自QWidget
  8. window.show();
  9. returnapp.exec();//进入Qpplication事件循环,见section2
  10. }
  11. //Section2:
  12. intQApplication::exec()
  13. {
  14. //skipcodes
  15. //简单的交给QCoreApplication来处理事件循环=〉section3
  16. returnQCoreApplication::exec();
  17. }
  18. //Section3
  19. intQCoreApplication::exec()
  20. {
  21. //得到当前Thread数据
  22. QThreadData*threadData=self->d_func()->threadData;
  23. if(threadData!=QThreadData::current()){
  24. qWarning("%s::exec:Mustbecalledfromthemainthread",self->metaObject()->className());
  25. return-1;
  26. }
  27. //检查eventloop是否已经创建
  28. if(!threadData->eventLoops.isEmpty()){
  29. qWarning("QCoreApplication::exec:Theeventloopisalreadyrunning");
  30. return-1;
  31. }
  32. ...
  33. QEventLoopeventLoop;
  34. self->d_func()->in_exec=true;
  35. self->d_func()->aboutToQuitEmitted=false;
  36. //委任QEventLoop处理事件队列循环==>Section4
  37. intreturnCode=eventLoop.exec();
  38. ....
  39. }
  40. returnreturnCode;
  41. }
  42. //Section4
  43. intQEventLoop::exec(ProcessEventsFlagsflags)
  44. {
  45. //这里的实现代码不少,最为重要的是以下几行
  46. Q_D(QEventLoop);//访问QEventloop私有类实例d
  47. try{
  48. //只要没有遇见exit,循环派发事件
  49. while(!d->exit)
  50. processEvents(flags|WaitForMoreEvents|EventLoopExec);
  51. }catch(...){}
  52. }
  53. //Section5
  54. boolQEventLoop::processEvents(ProcessEventsFlagsflags)
  55. {
  56. Q_D(QEventLoop);
  57. if(!d->threadData->eventDispatcher)
  58. returnfalse;
  59. if(flags&DeferredDeletion)
  60. QCoreApplication::sendPostedEvents(0,QEvent::DeferredDelete);
  61. //将事件派发给与平台相关的QAbstractEventDispatcher子类=>Section6
  62. returnd->threadData->eventDispatcher->processEvents(flags);
  63. }

  1. //Section6,QTDIR/src/corelib/kernel/qeventdispatcher_win.cpp
  2. //这段代码是完成与windows平台相关的windowsc++。以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持
  3. //其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX
  4. //QEventDispatcherWin32派生自QAbstractEventDispatcher.
  5. boolQEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlagsflags)
  6. {
  7. Q_D(QEventDispatcherWin32);
  8. if(!d->internalHwnd)
  9. createInternalHwnd();
  10. d->interrupt=false;
  11. emitawake();
  12. boolcanWait;
  13. boolretVal=false;
  14. boolseenWM_QT_SENDPOSTEDEVENTS=false;
  15. boolneedWM_QT_SENDPOSTEDEVENTS=false;
  16. do{
  17. DWORDwaitRet=0;
  18. HANDLEpHandles[MAXIMUM_WAIT_OBJECTS-1];
  19. QVarLengthArray<MSG>processedTimers;
  20. while(!d->interrupt){
  21. DWORDnCount=d->winEventNotifierList.count();
  22. Q_ASSERT(nCount<MAXIMUM_WAIT_OBJECTS-1);
  23. MSGmsg;
  24. boolhaveMessage;
  25. if(!(flags&QEventLoop::ExcludeUserInputEvents)&&!d->queuedUserInputEvents.isEmpty()){
  26. //processqueueduserinputevents
  27. haveMessage=true;
  28. //从处理用户输入队列中取出一条事件
  29. msg=d->queuedUserInputEvents.takeFirst();
  30. }elseif(!(flags&QEventLoop::ExcludeSocketNotifiers)&&!d->queuedSocketEvents.isEmpty()){
  31. //从处理socket队列中取出一条事件
  32. haveMessage=true;
  33. msg=d->queuedSocketEvents.takeFirst();
  34. }else{
  35. haveMessage=PeekMessage(&msg,0,0,0,PM_REMOVE);
  36. if(haveMessage&&(flags&QEventLoop::ExcludeUserInputEvents)
  37. &&((msg.message>=WM_KEYFIRST
  38. &&msg.message<=WM_KEYLAST)
  39. ||(msg.message>=WM_MOUSEFIRST
  40. &&msg.message<=WM_MOUSELAST)
  41. ||msg.message==WM_MOUSEWHEEL
  42. ||msg.message==WM_MOUSEHWHEEL
  43. ||msg.message==WM_TOUCH
  44. #ifndefQT_NO_GESTURES
  45. ||msg.message==WM_GESTURE
  46. ||msg.message==WM_GESTURENOTIFY
  47. #endif
  48. ||msg.message==WM_CLOSE)){
  49. //用户输入事件入队列,待以后处理
  50. haveMessage=false;
  51. d->queuedUserInputEvents.append(msg);
  52. }
  53. if(haveMessage&&(flags&QEventLoop::ExcludeSocketNotifiers)
  54. &&(msg.message==WM_QT_SOCKETNOTIFIER&&msg.hwnd==d->internalHwnd)){
  55. //socket事件入队列,待以后处理
  56. haveMessage=false;
  57. d->queuedSocketEvents.append(msg);
  58. }
  59. }
  60. ....
  61. if(!filterEvent(&msg)){
  62. TranslateMessage(&msg);
  63. //将事件打包成message调用WindowsAPI派发出去
  64. //分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数=>section7
  65. DispatchMessage(&msg);
  66. }
  67. }
  68. }
  69. }while(canWait);
  70. ...
  71. returnretVal;
  72. }

  1. //Section7windows窗口回调函数定义在QTDIR/src/gui/kernel/qapplication_win.cpp
  2. extern"C"LRESULTQT_WIN_CALLBACKQtWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam)
  3. {
  4. ...
  5. //将消息重新封装成QEvent的子类QMouseEvent==>Section8
  6. result=widget->translateMouseEvent(msg);
  7. ...
  8. }

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

在下文中,我们将进一步讨论当我们收到来在Windows的回调后,事件又是怎么一步步打包成QEvent并通过QApplication分发给最终事件的接受和处理者QObject::event.

在Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent. 接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件:

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来负责。

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

  1. #include<QApplication>
  2. #include"widget.h"
  3. //Section1
  4. intmain(intargc,char*argv[])
  5. {
  6. QApplicationapp(argc,argv);
  7. Widgetwindow;//Widget继承自QWidget
  8. window.show();
  9. returnapp.exec();//进入Qpplication事件循环,见section2
  10. }
  11. //Section2:
  12. intQApplication::exec()
  13. {
  14. //skipcodes
  15. //简单的交给QCoreApplication来处理事件循环=〉section3
  16. returnQCoreApplication::exec();
  17. }
  18. //Section3
  19. intQCoreApplication::exec()
  20. {
  21. //得到当前Thread数据
  22. QThreadData*threadData=self->d_func()->threadData;
  23. if(threadData!=QThreadData::current()){
  24. qWarning("%s::exec:Mustbecalledfromthemainthread",self->metaObject()->className());
  25. return-1;
  26. }
  27. //检查eventloop是否已经创建
  28. if(!threadData->eventLoops.isEmpty()){
  29. qWarning("QCoreApplication::exec:Theeventloopisalreadyrunning");
  30. return-1;
  31. }
  32. ...
  33. QEventLoopeventLoop;
  34. self->d_func()->in_exec=true;
  35. self->d_func()->aboutToQuitEmitted=false;
  36. //委任QEventLoop处理事件队列循环==>Section4
  37. intreturnCode=eventLoop.exec();
  38. ....
  39. }
  40. returnreturnCode;
  41. }
  42. //Section4
  43. intQEventLoop::exec(ProcessEventsFlagsflags)
  44. {
  45. //这里的实现代码不少,最为重要的是以下几行
  46. Q_D(QEventLoop);//访问QEventloop私有类实例d
  47. try{
  48. //只要没有遇见exit,循环派发事件
  49. while(!d->exit)
  50. processEvents(flags|WaitForMoreEvents|EventLoopExec);
  51. }catch(...){}
  52. }
  53. //Section5
  54. boolQEventLoop::processEvents(ProcessEventsFlagsflags)
  55. {
  56. Q_D(QEventLoop);
  57. if(!d->threadData->eventDispatcher)
  58. returnfalse;
  59. if(flags&DeferredDeletion)
  60. QCoreApplication::sendPostedEvents(0,QEvent::DeferredDelete);
  61. //将事件派发给与平台相关的QAbstractEventDispatcher子类=>Section6
  62. returnd->threadData->eventDispatcher->processEvents(flags);
  63. }

  1. //Section6,QTDIR/src/corelib/kernel/qeventdispatcher_win.cpp
  2. //这段代码是完成与windows平台相关的windowsc++。以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持
  3. //其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX
  4. //QEventDispatcherWin32派生自QAbstractEventDispatcher.
  5. boolQEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlagsflags)
  6. {
  7. Q_D(QEventDispatcherWin32);
  8. if(!d->internalHwnd)
  9. createInternalHwnd();
  10. d->interrupt=false;
  11. emitawake();
  12. boolcanWait;
  13. boolretVal=false;
  14. boolseenWM_QT_SENDPOSTEDEVENTS=false;
  15. boolneedWM_QT_SENDPOSTEDEVENTS=false;
  16. do{
  17. DWORDwaitRet=0;
  18. HANDLEpHandles[MAXIMUM_WAIT_OBJECTS-1];
  19. QVarLengthArray<MSG>processedTimers;
  20. while(!d->interrupt){
  21. DWORDnCount=d->winEventNotifierList.count();
  22. Q_ASSERT(nCount<MAXIMUM_WAIT_OBJECTS-1);
  23. MSGmsg;
  24. boolhaveMessage;
  25. if(!(flags&QEventLoop::ExcludeUserInputEvents)&&!d->queuedUserInputEvents.isEmpty()){
  26. //processqueueduserinputevents
  27. haveMessage=true;
  28. //从处理用户输入队列中取出一条事件
  29. msg=d->queuedUserInputEvents.takeFirst();
  30. }elseif(!(flags&QEventLoop::ExcludeSocketNotifiers)&&!d->queuedSocketEvents.isEmpty()){
  31. //从处理socket队列中取出一条事件
  32. haveMessage=true;
  33. msg=d->queuedSocketEvents.takeFirst();
  34. }else{
  35. haveMessage=PeekMessage(&msg,0,0,0,PM_REMOVE);
  36. if(haveMessage&&(flags&QEventLoop::ExcludeUserInputEvents)
  37. &&((msg.message>=WM_KEYFIRST
  38. &&msg.message<=WM_KEYLAST)
  39. ||(msg.message>=WM_MOUSEFIRST
  40. &&msg.message<=WM_MOUSELAST)
  41. ||msg.message==WM_MOUSEWHEEL
  42. ||msg.message==WM_MOUSEHWHEEL
  43. ||msg.message==WM_TOUCH
  44. #ifndefQT_NO_GESTURES
  45. ||msg.message==WM_GESTURE
  46. ||msg.message==WM_GESTURENOTIFY
  47. #endif
  48. ||msg.message==WM_CLOSE)){
  49. //用户输入事件入队列,待以后处理
  50. haveMessage=false;
  51. d->queuedUserInputEvents.append(msg);
  52. }
  53. if(haveMessage&&(flags&QEventLoop::ExcludeSocketNotifiers)
  54. &&(msg.message==WM_QT_SOCKETNOTIFIER&&msg.hwnd==d->internalHwnd)){
  55. //socket事件入队列,待以后处理
  56. haveMessage=false;
  57. d->queuedSocketEvents.append(msg);
  58. }
  59. }
  60. ....
  61. if(!filterEvent(&msg)){
  62. TranslateMessage(&msg);
  63. //将事件打包成message调用WindowsAPI派发出去
  64. //分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数=>section7
  65. DispatchMessage(&msg);
  66. }
  67. }
  68. }
  69. }while(canWait);
  70. ...
  71. returnretVal;
  72. }

  1. //Section7windows窗口回调函数定义在QTDIR/src/gui/kernel/qapplication_win.cpp
  2. extern"C"LRESULTQT_WIN_CALLBACKQtWndProc(HWNDhwnd,UINTmessage,WPARAMwParam,LPARAMlParam)
  3. {
  4. ...
  5. //将消息重新封装成QEvent的子类QMouseEvent==>Section8
  6. result=widget->translateMouseEvent(msg);
  7. ...
  8. }

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

在下文中,我们将进一步讨论当我们收到来在Windows的回调后,事件又是怎么一步步打包成QEvent并通过QApplication分发给最终事件的接受和处理者QObject::event.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值