解读Qt 事件处理机制

 

【转自】:http://mobile.51cto.com/symbian-272812.htm

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

本篇来介绍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处理。

复制代码

 64 #include <QApplication> 
 65 #include "widget.h"  
 66 //Section 1  
 67 int main(int argc, char *argv[])  
 68 {  
 69     QApplication app(argc, argv);  
 70     Widget window;  // Widget 继承自QWidget  
 71     window.show();  
 72     return app.exec(); // 进入Qpplication事件循环,见section 2  
 73 }  
 74 // Section 2:   
 75 int QApplication::exec()  
 76 {  
 77    //skip codes  
 78    //简单的交给QCoreApplication来处理事件循环=〉section 3  
 79    return QCoreApplication::exec();  
 80 }  
 81 // Section 3  
 82 int QCoreApplication::exec()  
 83 {  
 84     //得到当前Thread数据  
 85     QThreadData *threadData = self->d_func()->threadData;  
 86     if (threadData != QThreadData::current()) {  
 87         qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());  
 88         return -1;  
 89     }  
 90     //检查event loop是否已经创建  
 91     if (!threadData->eventLoops.isEmpty()) {  
 92         qWarning("QCoreApplication::exec: The event loop is already running");  
 93         return -1;  
 94     }  
 95     ...  
 96     QEventLoop eventLoop;  
 97     self->d_func()->in_exec = true;  
 98     self->d_func()->aboutToQuitEmitted = false;  
 99     //委任QEventLoop 处理事件队列循环 ==> Section 4  
100     int returnCode = eventLoop.exec();  
101     ....  
102     }  
103     return returnCode;  
104 }  
105 // Section 4  
106 int QEventLoop::exec(ProcessEventsFlags flags)  
107 {  
108    //这里的实现代码不少,最为重要的是以下几行  
109    Q_D(QEventLoop); // 访问QEventloop私有类实例d  
110         try {  
111         //只要没有遇见exit,循环派发事件  
112         while (!d->exit)  
113             processEvents(flags | WaitForMoreEvents | EventLoopExec);  
114     } catch (...) {}  
115 }  
116 // Section 5  
117 bool QEventLoop::processEvents(ProcessEventsFlags flags)  
118 {  
119     Q_D(QEventLoop);  
120     if (!d->threadData->eventDispatcher)  
121         return false;  
122     if (flags & DeferredDeletion)  
123         QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);  
124     //将事件派发给与平台相关的QAbstractEventDispatcher子类 =>Section 6  
125     return d->threadData->eventDispatcher->processEvents(flags);  
126 }  
127    
128 // Section 6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp     
129 // 这段代码是完成与windows平台相关的windows c++。 以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持     
130 // 其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX     
131 // QEventDispatcherWin32派生自QAbstractEventDispatcher.     
132 bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)     
133 {     
134     Q_D(QEventDispatcherWin32);     
135     if (!d->internalHwnd)     
136         createInternalHwnd();     
137     d->interrupt = false;     
138     emit awake();     
139     bool canWait;     
140     bool retVal = false;     
141     bool seenWM_QT_SENDPOSTEDEVENTS = false;     
142     bool needWM_QT_SENDPOSTEDEVENTS = false;     
143     do {     
144         DWORD waitRet = 0;     
145         HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];     
146         QVarLengthArray<MSG> processedTimers;     
147         while (!d->interrupt) {     
148             DWORD nCount = d->winEventNotifierList.count();     
149             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);     
150             MSG msg;     
151             bool haveMessage;     
152             if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {     
153                 // process queued user input events     
154                 haveMessage = true;     
155                 //从处理用户输入队列中取出一条事件     
156                 msg = d->queuedUserInputEvents.takeFirst();     
157             } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {     
158                 // 从处理socket队列中取出一条事件     
159                 haveMessage = true;     
160                 msg = d->queuedSocketEvents.takeFirst();     
161             } else {     
162                 haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);     
163                 if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)     
164                     && ((msg.message >= WM_KEYFIRST     
165                          && msg.message <= WM_KEYLAST)     
166                         || (msg.message >= WM_MOUSEFIRST     
167                             && msg.message <= WM_MOUSELAST)     
168                         || msg.message == WM_MOUSEWHEEL     
169                         || msg.message == WM_MOUSEHWHEEL     
170                         || msg.message == WM_TOUCH     
171 #ifndef QT_NO_GESTURES     
172                         || msg.message == WM_GESTURE     
173                         || msg.message == WM_GESTURENOTIFY     
174 #endif     
175                         || msg.message == WM_CLOSE)) {     
176                     // 用户输入事件入队列,待以后处理     
177                     haveMessage = false;     
178                     d->queuedUserInputEvents.append(msg);     
179                 }     
180                 if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)     
181                     && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {     
182                     // socket 事件入队列,待以后处理     
183                     haveMessage = false;     
184                     d->queuedSocketEvents.append(msg);     
185                 }     
186             }     
187             ....     
188                 if (!filterEvent(&msg)) {     
189                     TranslateMessage(&msg);     
190                     //将事件打包成message调用Windows API派发出去     
191                        //分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 => section 7                         
192                   DispatchMessage(&msg);     
193                 }     
194             }                  
195         }     
196     } while (canWait);     
197       ...     
198     return retVal;     
199 }    
200 // Section 6,QTDIR\src\corelib\kernel\qeventdispatcher_win.cpp  
201 // 这段代码是完成与windows平台相关的windows c++。 以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持  
202 // 其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX  
203 // QEventDispatcherWin32派生自QAbstractEventDispatcher.  
204 bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)  
205 {  
206     Q_D(QEventDispatcherWin32);  
207     if (!d->internalHwnd)  
208         createInternalHwnd();  
209     d->interrupt = false;  
210     emit awake();  
211     bool canWait;  
212     bool retVal = false;  
213     bool seenWM_QT_SENDPOSTEDEVENTS = false;  
214     bool needWM_QT_SENDPOSTEDEVENTS = false;  
215     do {  
216         DWORD waitRet = 0;  
217         HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];  
218         QVarLengthArray<MSG> processedTimers;  
219         while (!d->interrupt) {  
220             DWORD nCount = d->winEventNotifierList.count();  
221             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);  
222             MSG msg;  
223             bool haveMessage;  
224             if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {  
225                 // process queued user input events  
226                 haveMessage = true;  
227                 //从处理用户输入队列中取出一条事件  
228                 msg = d->queuedUserInputEvents.takeFirst();  
229             } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {  
230                 // 从处理socket队列中取出一条事件  
231                 haveMessage = true;  
232                 msg = d->queuedSocketEvents.takeFirst();  
233             } else {  
234                 haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);  
235                 if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)  
236                     && ((msg.message >= WM_KEYFIRST  
237                          && msg.message <= WM_KEYLAST)  
238                         || (msg.message >= WM_MOUSEFIRST  
239                             && msg.message <= WM_MOUSELAST)  
240                         || msg.message == WM_MOUSEWHEEL  
241                         || msg.message == WM_MOUSEHWHEEL  
242                         || msg.message == WM_TOUCH  
243 #ifndef QT_NO_GESTURES  
244                         || msg.message == WM_GESTURE  
245                         || msg.message == WM_GESTURENOTIFY  
246 #endif  
247                         || msg.message == WM_CLOSE)) {  
248                     // 用户输入事件入队列,待以后处理  
249                     haveMessage = false;  
250                     d->queuedUserInputEvents.append(msg);  
251                 }  
252                 if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)  
253                     && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {  
254                     // socket 事件入队列,待以后处理  
255                     haveMessage = false;  
256                     d->queuedSocketEvents.append(msg);  
257                 }  
258             }  
259             ....  
260                 if (!filterEvent(&msg)) {  
261                     TranslateMessage(&msg);  
262                     //将事件打包成message调用Windows API派发出去  
263                        //分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 => section 7                      
264                   DispatchMessage(&msg);  
265                 }  
266             }               
267         }  
268     } while (canWait);  
269       ...  
270     return retVal;  
271 }   
272  
273 // Section 7 windows窗口回调函数 定义在QTDIR\src\gui\kernel\qapplication_win.cpp     
274 extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)     
275 {     
276    ...     
277    //将消息重新封装成QEvent的子类QMouseEvent ==> Section 8     
278     result = widget->translateMouseEvent(msg);         
279    ...     
280 }     
281      
282 // Section 7 windows窗口回调函数 定义在QTDIR\src\gui\kernel\qapplication_win.cpp  
283 extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
284 {  
285    ...  
286    //将消息重新封装成QEvent的子类QMouseEvent ==> Section 8  
287     result = widget->translateMouseEvent(msg);      
288    ...  
289 } 

复制代码

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

【转】Qt 事件处理机制 (下篇)

转自:http://mobile.51cto.com/symbian-272816.htm

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

继续我们上一篇文章继续介绍,Qt 事件处理机制 (上篇) 介绍了Qt框架的事件处理机制:事件的产生、分发、接受和处理,并以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理,函数调用栈如下:

1 main(int, char **)   
2 QApplication::exec()   
3 QCoreApplication::exec()   
4 QEventLoop::exec(ProcessEventsFlags )   
5 QEventLoop::processEvents(ProcessEventsFlags )   
6 QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)

本文将介绍Qt app在视窗系统回调后,事件又是怎么一步步通过QApplication分发给最终事件的接受和处理者QWidget::event, (QWidget继承Object,重载其虚函数event),以下所有的讨论都将嵌入在源码之中。

复制代码

  1 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg)   
  2 bool QApplicationPrivate::sendMouseEvent(...)   
  3 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)   
  4 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)   
  5 bool QApplication::notify(QObject *receiver, QEvent *e)   
  6 bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)   
  7 bool QWidget::event(QEvent *event)   
  8  
  9 // (续上文Section 7) Section 2-1:     
 10 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)      
 11 {     
 12    ...     
 13    //检查message是否属于Qt可转义的鼠标事件     
 14    if (qt_is_translatable_mouse_event(message)) {     
 15         if (QApplication::activePopupWidget() != 0) {                 
 16             POINT curPos = msg.pt;     
 17             //取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例     
 18             QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);     
 19             if (w)     
 20                 widget = (QETWidget*)w;     
 21         }     
 22         if (!qt_tabletChokeMouse) {     
 23             //对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget      
 24             // => Section 2-2     
 25             result = widget->translateMouseEvent(msg);            
 26      ...     
 27 }     
 28 // Section 2-2  $QTDIR\src\gui\kernel\qapplication_win.cpp     
 29 //该函数所在与Windows平台相关,主要职责就是把已windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent,QWidget.     
 30 bool QETWidget::translateMouseEvent(const MSG &msg)     
 31 {     
 32      //.. 这里很长的代码给以忽略       
 33       // 让我们看一下sendMouseEvent的声明     
 34      // widget是事件的接受者; e是封装好的QMouseEvent     
 35      // ==> Section 2-3     
 36      res = QApplicationPrivate::sendMouseEvent(widget, &e, alienWidget, this, &qt_button_down, qt_last_mouse_receiver);     
 37 }     
 38 // Section 2-3 $QTDIR\src\gui\kernel\qapplication.cpp     
 39 bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,     
 40                                          QWidget *alienWidget, QWidget *nativeWidget,     
 41                                          QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,     
 42                                          bool spontaneous)     
 43 {     
 44      //至此与平台相关代码处理完毕     
 45      //MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同,
 46                    除了将QEvent的属性spontaneous标记不同。 这里是解释什么spontaneous事件:如果事件由应用程序之外产生的,比如一个系统事件。 
 47                显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是   spontaneous事件     
 48        
 49     if (spontaneous)     
 50         result = QApplication::sendSpontaneousEvent(receiver, event);  ==〉Section 2-4     
 51     else    
 52         result = QApplication::sendEvent(receiver, event);     
 53 }    
 54 
 55 // Section 2-4 C:\Qt\4.7.1-Vs\src\corelib\kernel\qcoreapplication.h     
 56 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)     
 57 {      
 58     //将event标记为自发事件     
 59      //进一步调用 2-5 QCoreApplication::notifyInternal     
 60     if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false;      
 61 }     
 62 // Section 2-5:  $QTDIR\gui\kernel\qapplication.cpp     
 63 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)     
 64 {     
 65          
 66     // 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持     
 67      ...     
 68      // 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
 69                                           注意,跨线程的事件需要借助Event Loop来派发     
 70      QObjectPrivate *d = receiver->d_func();     
 71     QThreadData *threadData = d->threadData;     
 72     ++threadData->loopLevel;     
 73     bool returnValue;     
 74     QT_TRY {     
 75         //哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6     
 76         returnValue = notify(receiver, event);     
 77     } QT_CATCH (...) {     
 78         --threadData->loopLevel;     
 79         QT_RETHROW;     
 80     }     
 81 }     
 82 // Section 2-6:  $QTDIR\gui\kernel\qapplication.cpp     
 83 // QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
 84                                                        任何线程的任何对象的所有事件在发送时都会调用notify函数。     
 85 bool QApplication::notify(QObject *receiver, QEvent *e)     
 86 {     
 87    //代码很长,最主要的是一个大大的Switch,Case     
 88    ..     
 89    switch ( e->type())     
 90    {     
 91     ...     
 92     case QEvent::MouseButtonPress:     
 93     case QEvent::MouseButtonRelease:     
 94     case QEvent::MouseButtonDblClick:     
 95     case QEvent::MouseMove:     
 96      ...     
 97         //让自己私有类(d是私有类的句柄)来进一步处理 ==> Section 2-7     
 98         res = d->notify_helper(w, w == receiver ? mouse : &me);     
 99         e->spont = false;     
100         break;     
101     }     
102     ...     
103 }     
104 // Section 2-7:  $QTDIR\gui\kernel\qapplication.cpp     
105 bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)     
106 {     
107     ...     
108     // 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。     
109    //如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤,
110                          允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
111                          如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。     
112     if (sendThroughObjectEventFilters(receiver, e))     
113         return true;     
114      // 递交事件给receiver  => Section 2-8     
115     bool consumed = receiver->event(e);     
116     e->spont = false;     
117 }     
118 // Section 2-8  $QTDIR\gui\kernel\qwidget.cpp     
119 // QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget.     
120 bool QWidget::event(QEvent *event)     
121 {     
122     ...     
123     switch(event->type()) {     
124     case QEvent::MouseButtonPress:     
125         // Don't reset input context here. Whether reset or not is     
126         // a responsibility of input method. reset() will be     
127         // called by mouseHandler() of input method if necessary     
128         // via mousePressEvent() of text widgets.     
129 #if 0     
130         resetInputContext();     
131 #endif     
132         //mousePressEvent是虚函数,QWidget的子类可以通过重载重新定义mousePress事件的行为     
133         mousePressEvent((QMouseEvent*)event);     
134         break;        
135 }   

复制代码

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值