Qt 鼠标事件触发到转发到传到接收对象全过程剖析

Qt 事件机制

【1】事件

事件是可以被控件识别的操作。如按下确定按钮、选择某个单选按钮或复选框。

每种控件有自己可识别的事件,如窗体的加载、单击、双击等事件,编辑框(文本框)的文本改变事件等等。

事件就是用户对窗口上各种组件的操作。

【2】Qt事件

由窗口系统或Qt自身产生的,用以响应所发生各类事情的操作。具体点,Qt事件是一个QEvent对象,用于描述程序内部或外部发生的动作。

【3】Qt事件产生类型

1、键盘或鼠标事件:用户按下或松开键盘或鼠标上的按键时,就可以产生一个键盘或者鼠标事件。

2、绘制事件:某个窗口第一次显示的时候,就会产生一个绘制事件,用来告诉窗口需要重新绘制它本身,从而使得该窗口可见。

3、QT事件:Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent。

【4】Qt事件分类

基于事件如何被产生与分发,可以把事件分为三类:

1、Spontaneous 事件

由窗口系统产生,它们被放到系统队列中,通过事件循环逐个处理。

本类事件通常是Windows System把从系统得到的消息,比如鼠标按键、键盘按键等, 放入系统的消息队列中。 Qt事件循环的时候读取这些事件,转化为QEvent,再依次逐个处理。

2、Posted 事件

由Qt或应用程序产生,它们被Qt组成队列,再通过事件循环处理。

调用QApplication::postEvent()来产生一个posted类型事件。例如:QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数。

其实现的原理是new出一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理。

3、Send事件

由Qt或应用程序产生,但它们被直接发送到目标对象。

调用QApplication::sendEvent()函数来产生一个send类型事件。

send 类型事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是这种方式。

【5】QObject类

QObject三大职责

1、内存管理

2、内省(intropection)

3、事件处理机制

任何一个想要接受并处理事件的对象均须继承自QObject,可以重写QObject::event() 来处理事件,也可以由父类处理。

【6】事件处理与过滤

Qt提供了5个级别来处理和过滤事件。

1、我们可以重新实现特定的event handler。

重新实现像mousePressEvent(), keyPressEvent()和paintEvent()这样的event Handler是目前处理event最普通的方式。

2、我们可以重新实现QObject::event()。

通过重新实现event(),我们可以在事件到达特定的event handler之前对它们作出处理。

这个方法主要是用来覆写Tab键的缺省实现,也可以用来处理不同发生的事件类型,对它们,就没有特定的event handler。

当重新实现event()的时候,我们必须调用基类的event()来处理我们不显式处理的情况。

3、我们可以安装一个event filter到一个单独的QObject。

一旦一个对象用installEventFilter注册了, 发到目标对象的所有事件都会先发到监测对象的eventFilter()。

如果同一个object安装了多个event filter, filter会依次被激活, 从最近安装的回到第一个。

4、我们可以在QApplication对象上安装event filter。

一旦一个event filter被注册到qApp(唯一的QApplication对象), 程序里发到每个对象的每个事件在发到其他event filter之前,都要首先发到eventFilter()。

这个方法对debugging非常有用,也可以用来处理发到disable的widget上的事件, QApplication通常会丢弃它们。

5、我们可以子类化QApplication并重新实现notify()。

Qt调用QApplication::notify()来发出事件,在任何event filter得到之前, 重新实现这个函数是得到所有事件的唯一方法。

event filter通常更有用, 因为可以有任意数目且同时存在的event filter, 但是只有一个notify()函数。

【7】事件过滤器

Qt创建QEvent事件对象后,会调用QObject的event()函数来分发事件。

但有时,你可能需要在调用event()函数之前做一些自己的操作,比如,对话框上某些组件可能并不需要响应回车键按下的事件,此时,你就需要重新定义组件的event()函数。

如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用组件的event()函数。

QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的声明如下:

virtual bool QObject::eventFilter (QObject * watched, QEvent * event)

在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的声明如下:

void QObject::installEventFilter ( QObject * filterObj)

这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。

这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。

例如,textField.installEventFilter(obj),则如果有事件发送到textField组件时,会先调用obj->eventFilter()函数,然后才会调用textField.event()。

当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。

我们可以把Qt的事件传递看成链状:如果子类没有处理这个事件,就会继续向其他类传递。其实,Qt的事件对象都有一个accept()函数和ignore()函数。

正如它们的名字,前者用来告诉Qt,事件处理函数"接收"了这个事件,不要再传递;后者则告诉Qt,事件处理函数"忽略"了这个事件,需要继续传递,寻找另外的接受者。

在事件处理函数中,可以使用isAccepted()来查询这个事件是不是已经被接收了。

事实上,我们很少使用accept()和ignore()函数,而是像上面的示例一样,如果希望忽略事件,只要调用父类的响应函数即可。

记得我们曾经说过,Qt中的事件大部分是protected的,因此,重写的函数必定存在着其父类中的响应函数,这个方法是可行的。

为什么要这么做呢?因为我们无法确认父类中的这个处理函数没有操作,如果我们在子类中直接忽略事件,Qt不会再去寻找其他的接受者,那么父类的操作也就不能进行,这可能会有潜在的危险。

不过,事情也不是绝对的。在一个情形下,我们必须使用accept()和ignore()函数,那就是在窗口关闭的时候。

如果你在窗口关闭时需要有个询问对话框,那么就需要这么去写:

复制代码
 1 void MainWindow::closeEvent(QCloseEvent * event)
 2 {
 3     if (continueToClose())
 4     {
 5         event->accept();
 6     }
 7     else
 8     {
 9         event->ignore();
10     }
11 }

non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver. Qt GUI程序,由QApplication来负责。

【8】事件和信号的区别

Qt的事件很容易和信号槽混淆。signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理;

而对于事件,Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件接着再进行处理。

但是,必要的时候,Qt的事件也是可以不进入事件队列,而是直接处理的。并且,事件还可以使用"事件过滤器"进行过滤。

比如一个按钮对象, 我们使用这个按钮对象的时候, 我们只关心它被按下的信号, 至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的。

但是如果我们要重载一个按钮的时候,我们就要面对event了。 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候。

总结的说,Qt的事件和Qt中的signal不一样。 后者通常用来使用widget, 而前者用来实现widget。 如果我们使用系统预定义的控件,那我们关心的是信号,如果自定义控件我们关心的是事件。

【9】自定义事件

为什么需要自定义事件?

事件既可用于同步也可用于异步(依赖于你是调用sendEvent()或是postEvents()),函数调用或是槽调用总是同步的。事件的另外一个好处是它可以被过滤。

阻塞型事件:事件发送后需要等待处理完成

static\] bool QCoreApplication::sendEvent(QObject \*receiver, QEvent \*event) 事件生命周期由应用程序自身管理,同时支持栈事件对象和堆事件对象的发送。 非阻塞型发送:事件发送后立刻返回,事件被发送到事件队列等待处理 \[static\] void QCoreApplication::postEvent(QObject \*receiver, QEvent \*event, int priority = Qt::NormalEventPriority) 只能发送堆事件对象,事件被处理后由Qt平台销毁 当我们在main()函数的末尾调用QApplication::exec()时,程序进入了Qt的事件循环,大概来讲,事件循环如下面所示: ![复制代码](https://file.jishuzhan.net/article/1768980304967503874/410a034e41f4342238d2140cbb0b0f27.webp) ``` 1 while (!exit_was_called) 2 { 3 while (!posted_event_queue_is_empty) 4 { 5 process_next_posted_event(); 6 } 7 while (!spontaneous_event_queue_is_empty) 8 { 9 process_next_spontaneous_event(); 10 } 11 while (!posted_event_queue_is_empty) 12 { 13 process_next_posted_event(); 14 } 15 } ``` ![复制代码](https://file.jishuzhan.net/article/1768980304967503874/410a034e41f4342238d2140cbb0b0f27.webp) Qt事件循环的过程 首先,事件循环处理所有的posted事件,直到队列空。然后再处理所有spontaneous事件,最后它处理所有的因为处理spontaneous事件而产生的posted事件。 send 事件并不在事件循环内处理,它们都直接被发送到了目标对象。 当一个widget第一次可见,或被遮挡后再次变为可见,窗口系统产生一个(spontaneous) paint事件,要求程序重绘widget。 事件循环最终会从事件队列中捡选这个事件并把它分发到那个需要重画的widget。 并不是所有的paint事件都是由窗口系统产生的。当你调用QWidget::update()去强行重画widget,这个widget会post 一个paint事件给自己。这个paint事件被放入队列,最终被事件循环分发之。 如果等不及事件循环去重画一个widget, 理论上,应该直接调用paintEvent()强制进行立即的重画。但实际上这不总是可行的,因为paintEvent()函数是protected的(很可能访问不了)。 它也绕开了任何存在的事件过滤器。因为这些原因,Qt提供了一个机制,直接sending事件而不是posting。 QWidget::repaint()就使用了这个机制来强制进行立即重画。 posting 相对于sending的一个优势是,它给了Qt一个压缩(compress)事件的机会。 假如你在一个widget上连续地调用update() 十次,因update()而产生的这十个事件,将会自动地被合并为一个单独的事件,但是QPaintEvents事件附带的区域信息也合并了。 可压缩的事件类型包括:paint、move、resize、layout hint、language change。 最后要注意,你可以在任何时候调用QApplication::sendPostedEvent(),强制Qt产生一个对象的posted事件。 【10】事件转发 对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget,直到最顶层窗口。 比如:事件可能最先发送给QCheckBox, 如果QCheckBox没有处理, 那么由QGroupBox接着处理; 如果QGroupBox仍然没有处理, 再送到QDialog, 因为QDialog已经是最顶层widget, 所以如果QDialog再不处理, QEvent将停止转发。 如何判断一个事件是否被处理了呢? Qt中和事件相关的函数通过两种方式相互通信。 QApplication::notify(), QObject::eventFilter(), QObject::event() 通过返回bool值来表示是否已处理。"真"表示已经处理, "假"表示事件需要继续传递。 另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识。这种方式只用于event() 函数和特定事件处理函数之间的沟通。 而且只有用在某些类别事件上是有意义的, 这些事件就是上面提到的那些会被转发的事件, 包括: 鼠标、滚轮、按键等事件。 【11】事件的传播(propogation) 如果事件在目标对象上得不到处理,事件向上一层进行传播,直到最顶层的widget为止。 如果得到事件的对象,调用了accept(),则事件停止继续传播;如果调用了ignore(),事件向上一级继续传播。 Qt对自定义事件处理函数的默认返回值是accept(),但默认的事件处理函数是ingore()。 因此,如果要继续向上传播,调用QWidget的默认处理函数即可。到此为止的话,不必显式调用accept()。 但在event处理函数里,返回true表示accept,返回false表示向上级传播。 但closeEvent是个特殊情形,accept表示quit,ignore表示取消,所以最好在closeEvent显式调用accept和ignore。 【12】事件产生 事件产生详细过程: ![复制代码](https://file.jishuzhan.net/article/1768980304967503874/410a034e41f4342238d2140cbb0b0f27.webp) ``` 1 // section 1-1 2 int main(int argc, char *argv[]) 3 { 4 QApplication a(argc, argv); 5 MainWindow w; 6 w.show(); 7 8 return a.exec(); 9 } 10 11 // section 1-2 12 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp) 13 int QApplication::exec() 14 { 15 return QGuiApplication::exec(); 16 } 17 18 // section 1-3 19 // 源码路径:($QTDIR\Src\qtbase\src\gui\kernel\qguiapplication.cpp) 20 int QGuiApplication::exec() 21 { 22 #ifndef QT_NO_ACCESSIBILITY 23 QAccessible::setRootObject(qApp); 24 #endif 25 return QCoreApplication::exec(); 26 } 27 28 // section 1-4 29 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp) 30 int QCoreApplication::exec() 31 { 32 if (!QCoreApplicationPrivate::checkInstance("exec")) 33 return -1; 34 35 QThreadData *threadData = self->d_func()->threadData; 36 if (threadData != QThreadData::current()) 37 { 38 qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className()); 39 return -1; 40 } 41 if (!threadData->eventLoops.isEmpty()) 42 { 43 qWarning("QCoreApplication::exec: The event loop is already running"); 44 return -1; 45 } 46 47 threadData->quitNow = false; 48 QEventLoop eventLoop; 49 self->d_func()->in_exec = true; 50 self->d_func()->aboutToQuitEmitted = false; 51 int returnCode = eventLoop.exec(); 52 threadData->quitNow = false; 53 if (self) 54 { 55 self->d_func()->in_exec = false; 56 if (!self->d_func()->aboutToQuitEmitted) 57 { 58 emit self->aboutToQuit(QPrivateSignal()); 59 } 60 self->d_func()->aboutToQuitEmitted = true; 61 sendPostedEvents(0, QEvent::DeferredDelete); 62 } 63 64 return returnCode; 65 } 66 67 // section 1-5 68 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp) 69 // 声明:int exec(ProcessEventsFlags flags = AllEvents); 70 int QEventLoop::exec(ProcessEventsFlags flags) 71 { 72 Q_D(QEventLoop); 73 // we need to protect from race condition with QThread::exit 74 QMutexLocker locker(&static_cast(QObjectPrivate::get(d->threadData->thread))->mutex); 75 if (d->threadData->quitNow) 76 { 77 return -1; 78 } 79 80 if (d->inExec) 81 { 82 qWarning("QEventLoop::exec: instance %p has already called exec()", this); 83 return -1; 84 } 85 86 struct LoopReference 87 { 88 QEventLoopPrivate *d; 89 QMutexLocker &locker; 90 91 bool exceptionCaught; 92 LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true) 93 { 94 d->inExec = true; 95 d->exit.storeRelease(false); 96 ++d->threadData->loopLevel; 97 d->threadData->eventLoops.push(d->q_func()); 98 locker.unlock(); 99 } 100 101 ~LoopReference() 102 { 103 if (exceptionCaught) 104 { 105 qWarning("Qt has caught an exception thrown from an event handler. Throwing\n" 106 "exceptions from an event handler is not supported in Qt. You must\n" 107 "reimplement QApplication::notify() and catch all exceptions there.\n"); 108 } 109 locker.relock(); 110 QEventLoop *eventLoop = d->threadData->eventLoops.pop(); 111 Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error"); 112 Q_UNUSED(eventLoop); // --release warning 113 d->inExec = false; 114 --d->threadData->loopLevel; 115 } 116 }; 117 LoopReference ref(d, locker); 118 119 // remove posted quit events when entering a new event loop 120 QCoreApplication *app = QCoreApplication::instance(); 121 if (app && app->thread() == thread()) 122 { 123 QCoreApplication::removePostedEvents(app, QEvent::Quit); 124 } 125 126 while (!d->exit.loadAcquire()) 127 { 128 processEvents(flags | WaitForMoreEvents | EventLoopExec); 129 } 130 131 ref.exceptionCaught = false; 132 return d->returnCode.load(); 133 } 134 135 // section 1-6 136 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp) 137 bool QEventLoop::processEvents(ProcessEventsFlags flags) 138 { 139 Q_D(QEventLoop); 140 if (!d->threadData->eventDispatcher.load()) 141 { 142 return false; 143 } 144 return d->threadData->eventDispatcher.load()->processEvents(flags); 145 } 146 147 // section 1-7 148 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp) 149 // 这段代码是完成与windows平台相关的windows c++。 150 // 以跨平台著称的Qt同时也提供了对Symiban、Unix等平台的消息派发支持, 151 // 分别封装在QEventDispatcherSymbian和QEventDIspatcherUNIX。 152 // QEventDispatcherWin32继承QAbstractEventDispatcher。 153 bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) 154 { 155 Q_D(QEventDispatcherWin32); 156 157 if (!d->internalHwnd) 158 { 159 createInternalHwnd(); 160 wakeUp(); // trigger a call to sendPostedEvents() 161 } 162 163 d->interrupt = false; 164 emit awake(); 165 166 bool canWait; 167 bool retVal = false; 168 bool seenWM_QT_SENDPOSTEDEVENTS = false; 169 bool needWM_QT_SENDPOSTEDEVENTS = false; 170 do 171 { 172 DWORD waitRet = 0; 173 HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1]; 174 QVarLengthArray processedTimers; 175 while (!d->interrupt) 176 { 177 DWORD nCount = d->winEventNotifierList.count(); 178 Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1); 179 180 MSG msg; 181 bool haveMessage; 182 183 if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) 184 { 185 // process queued user input events 186 haveMessage = true; 187 msg = d->queuedUserInputEvents.takeFirst(); // 逐个处理用户输入队列中的事件 188 } 189 else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) 190 { 191 // process queued socket events 192 haveMessage = true; 193 msg = d->queuedSocketEvents.takeFirst(); // 逐个处理socket队列中的事件 194 } 195 else 196 { 197 haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE); 198 if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents) 199 && ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) 200 || (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) 201 || msg.message == WM_MOUSEWHEEL 202 || msg.message == WM_MOUSEHWHEEL 203 || msg.message == WM_TOUCH 204 #ifndef QT_NO_GESTURES 205 || msg.message == WM_GESTURE 206 || msg.message == WM_GESTURENOTIFY 207 #endif 208 || msg.message == WM_CLOSE)) 209 { 210 // queue user input events for later processing 211 haveMessage = false; 212 d->queuedUserInputEvents.append(msg); // 用户输入事件入队列,待以后处理 213 } 214 if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers) 215 && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) 216 { 217 // queue socket events for later processing 218 haveMessage = false; 219 d->queuedSocketEvents.append(msg); // socket 事件入队列,待以后处理 220 } 221 } 222 if (!haveMessage) 223 { 224 // no message - check for signalled objects 225 for (int i = 0; i < (int)nCount; i++) 226 { 227 pHandles[i] = d->winEventNotifierList.at(i)->handle(); 228 } 229 waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE); 230 if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) 231 { 232 // a new message has arrived, process it 233 continue; 234 } 235 } 236 if (haveMessage) 237 { 238 // WinCE doesn't support hooks at all, so we have to call this by hand :( 239 if (!d->getMessageHook) 240 { 241 (void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg); 242 } 243 244 if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) 245 { 246 if (seenWM_QT_SENDPOSTEDEVENTS) 247 { 248 // when calling processEvents() "manually", we only want to send posted 249 // events once 250 needWM_QT_SENDPOSTEDEVENTS = true; 251 continue; 252 } 253 seenWM_QT_SENDPOSTEDEVENTS = true; 254 } 255 else if (msg.message == WM_TIMER) 256 { 257 // avoid live-lock by keeping track of the timers we've already sent 258 bool found = false; 259 for (int i = 0; !found && i < processedTimers.count(); ++i) 260 { 261 const MSG processed = processedTimers.constData()[i]; 262 found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam); 263 } 264 if (found) 265 { 266 continue; 267 } 268 processedTimers.append(msg); 269 } 270 else if (msg.message == WM_QUIT) 271 { 272 if (QCoreApplication::instance()) 273 { 274 QCoreApplication::instance()->quit(); 275 } 276 return false; 277 } 278 279 if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) 280 { 281 // 将事件打包成message调用Windows API派发出去 282 TranslateMessage(&msg); 283 // 分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数。 284 DispatchMessage(&msg); 285 } 286 } 287 else if (waitRet - WAIT_OBJECT_0 < nCount) 288 { 289 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0)); 290 } 291 else 292 { 293 // nothing todo so break 294 break; 295 } 296 retVal = true; 297 } 298 299 // still nothing - wait for message or signalled objects 300 canWait = (!retVal 301 && !d->interrupt 302 && (flags & QEventLoop::WaitForMoreEvents)); 303 if (canWait) 304 { 305 DWORD nCount = d->winEventNotifierList.count(); 306 Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1); 307 for (int i = 0; i < (int)nCount; i++) 308 { 309 pHandles[i] = d->winEventNotifierList.at(i)->handle(); 310 } 311 312 emit aboutToBlock(); 313 waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE); 314 emit awake(); 315 if (waitRet - WAIT_OBJECT_0 < nCount) 316 { 317 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0)); 318 retVal = true; 319 } 320 } 321 } while (canWait); 322 323 if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0) 324 { 325 // when called "manually", always send posted events 326 sendPostedEvents(); 327 } 328 329 if (needWM_QT_SENDPOSTEDEVENTS) 330 { 331 PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0); 332 } 333 334 return retVal; 335 } 336 337 // section1~7的过程:Qt进入QApplication的event loop,经过层层委任, 338 // 最终QEventLoop的processEvent将通过与平台相关的AbstractEventDispatcher的子类QEventDispatcherWin32 339 // 获得用户的输入事件,并将其打包成message后,通过标准的Windows API传递给Windows OS。 340 // Windows OS得到通知后回调QtWndProc,至此事件的分发与处理完成了一半的路程。 341 // 事件的产生、分发、接受和处理,并以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event 342 // Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理,函数调用栈如下: 343 // 1.main(int, char **) 344 // 2.QApplication::exec() 345 // 3.QCoreApplication::exec() 346 // 4.QEventLoop::exec(ProcessEventsFlags ) 347 // 5.QEventLoop::processEvents(ProcessEventsFlags ) 348 // 6.QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags) ``` 【13】事件分发 事件分发详细过程: ``` 1 // 1.QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg) 2 // 2.bool QApplicationPrivate::sendMouseEvent(...) 3 // 3.inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) 4 // 4.bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event) 5 // 5.bool QApplication::notify(QObject *receiver, QEvent *e) 6 // 6.bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) 7 // 7.bool QWidget::event(QEvent *event) 8 // 下面介绍Qt app在视窗系统回调后,事件又是怎么一步步通过QApplication分发给最终事件的接受和处理者QWidget::event, 9 // QWidget继承自Object,重载其虚函数event。 10 // section 2-1 11 // windows窗口回调函数 12 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 13 { 14 // ... 15 // 检查message是否属于Qt可转义的鼠标事件 16 if (qt_is_translatable_mouse_event(message)) 17 { 18 if (QApplication::activePopupWidget() != 0) 19 { // in popup mode 20 POINT curPos = msg.pt; 21 // 取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例 22 QWidget* w = QApplication::widgetAt(curPos.x, curPos.y); 23 if (w) 24 { 25 widget = (QETWidget*)w; 26 } 27 } 28 29 if (!qt_tabletChokeMouse) 30 { 31 // 对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget 32 // => Section 2-2 33 result = widget->translateMouseEvent(msg); // mouse event 34 } 35 } 36 37 // ... 38 } 39 40 // section 2-2 ($QTDIR\src\gui\kernel\qapplication_win.cpp) 41 // 该函数所在与Windows平台相关,主要职责就是把已用windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent。 42 bool QETWidget::translateMouseEvent(const MSG &msg) 43 { 44 // ...这里有很长的一段代码可以忽略 45 // 让我们看一下sendMouseEvent的声明 46 // widget是事件的接受者;e是封装好的QMouseEvent 47 // ==> Section 2-3 48 res = QApplicationPrivate::sendMouseEvent(target, 49 &e, alienWidget, this, &qt_button_down, 50 qt_last_mouse_receiver); 51 } 52 53 // section 2-3 54 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp) 55 bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event, 56 QWidget *alienWidget, QWidget *nativeWidget, 57 QWidget **buttonDown, QPointer &lastMouseReceiver, 58 bool spontaneous) 59 { 60 // ... 61 // 至此与平台相关代码处理完毕 62 // MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 63 // sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同 64 // 除了将QEvent的属性spontaneous标记不同。 这里是解释什么是spontaneous事件:事件由应用程序之外产生的,比如一个系统事件。 65 // 显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是spontaneous事件 66 if (spontaneous) 67 { 68 result = QApplication::sendSpontaneousEvent(receiver, event); 69 } 70 else 71 { 72 result = QApplication::sendEvent(receiver, event);//TODO 73 } 74 75 ... 76 77 return result; 78 } 79 80 // section 2-4 81 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp) 82 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) 83 { 84 // 将event标记为自发事件 85 // 进一步调用 2-5 QCoreApplication::notifyInternal 86 if (event) 87 { 88 event->spont = true; 89 } 90 return self ? self->notifyInternal(receiver, event) : false; 91 } 92 93 // section 2-5: 94 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp) 95 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event) 96 { 97 // 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持 98 // ... 99 // 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象, 100 // 也就是说receiver->d_func()->threadData应该等于QThreadData::current()。 101 // 注意,跨线程的事件需要借助Event Loop来派发 102 QObjectPrivate *d = receiver->d_func(); 103 QThreadData *threadData = d->threadData; 104 ++threadData->loopLevel; 105 106 // 哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6 107 QT_TRY 108 { 109 returnValue = notify(receiver, event); 110 } 111 QT_CATCH (...) 112 { 113 --threadData->loopLevel; 114 QT_RETHROW; 115 } 116 117 ... 118 119 return returnValue; 120 } 121 122 // section 2-6: 123 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp) 124 // QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的: 125 // 任何线程的任何对象的所有事件在发送时都会调用notify函数。 126 bool QCoreApplication::notify(QObject *receiver, QEvent *event) 127 { 128 Q_D(QCoreApplication); 129 // no events are delivered after ~QCoreApplication() has started 130 if (QCoreApplicationPrivate::is_app_closing) 131 { 132 return true; 133 } 134 135 if (receiver == 0) 136 { // serious error 137 qWarning("QCoreApplication::notify: Unexpected null receiver"); 138 return true; 139 } 140 141 #ifndef QT_NO_DEBUG 142 d->checkReceiverThread(receiver); 143 #endif 144 145 return receiver->isWidgetType() ? false : d->notify_helper(receiver, event); 146 } 147 148 // section 2-7: 149 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp) 150 // notify 调用 notify_helper() 151 bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event) 152 { 153 // send to all application event filters 154 if (sendThroughApplicationEventFilters(receiver, event)) 155 { 156 return true; 157 } 158 // 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。 159 //如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤 160 //允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。 161 //如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。 162 if (sendThroughObjectEventFilters(receiver, event)) 163 { 164 return true; 165 } 166 // deliver the event 167 // 递交事件给receiver => Section 2-8 168 return receiver->event(event); 169 } 170 171 // section 2-8 172 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qwidget.cpp) 173 // QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget. 174 bool QWidget::event(QEvent *event) 175 { 176 // ... 177 switch (event->type()) 178 { 179 case QEvent::MouseMove: 180 mouseMoveEvent((QMouseEvent*)event); 181 break; 182 183 case QEvent::MouseButtonPress: 184 // Don't reset input context here. Whether reset or not is 185 // a responsibility of input method. reset() will be 186 // called by mouseHandler() of input method if necessary 187 // via mousePressEvent() of text widgets. 188 #if 0 189 resetInputContext(); 190 #endif 191 mousePressEvent((QMouseEvent*)event); 192 break; 193 } 194 // ... 195 } ``` ![复制代码](https://file.jishuzhan.net/article/1768980304967503874/410a034e41f4342238d2140cbb0b0f27.webp) 【14】Qt5.3.2版本事件机制源码调试 事件产生于分发调试堆栈图如下: ![](https://file.jishuzhan.net/article/1768980304967503874/dbb18ca26c31960a4afff15fe46fb188.webp)

相关推荐
永霖光电_UVLED7 小时前
KAIST 团队研发出高效、超高分辨率的红色微米发光二极管(Micro-LED)显示器
计算机外设
春日见1 天前
车辆动力学:前后轮车轴
java·开发语言·驱动开发·docker·计算机外设
PHOSKEY1 天前
光子精密QM系列闪测仪在鼠标电路板部件质量控制中的核心应用
计算机外设
墩墩冰1 天前
计算机图形学 分析选择缓冲区中的数字
计算机外设
UI设计兰亭妙微2 天前
中车株州所显示器界面设计
计算机外设·界面设计
墩墩冰2 天前
计算机图形学 多视区的显示
计算机外设
墩墩冰2 天前
计算机图形学 GLU库中的二次曲面函数
计算机外设
墩墩冰2 天前
计算机图形学 利用鼠标实现橡皮筋技术
计算机外设
企鹅侠客4 天前
鼠标键盘按键统计工具
计算机外设·键盘·鼠标
华一精品Adreamer5 天前
便携式显示器供应链与成本结构:挑战与机遇
计算机外设