概述
在讨论Qt的事件之前,先叙述两个知识点。
1. 典型的win32程序如下所示:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
...
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM, LPARAM)
{
switch(message)
{
case WM_DESTORY:
...
}
}
因为windows消息机制不是本文的重点,因此,暂不详细讨论windows的消息机制。请阅读相关资料。
2.一个典型的QtGUI应用程序,如下代码所示:
int main(int argc, char** argv)
{
QApplication app(argc, argv);
QWidget widget;
widget.show();
return app.exec();
}
代码中,实例化了一个QApplication对象,Qt应用程序要求,每个程序必须先构造一个QApplication对象,通过这个对象来让程序跑起来。也就是实例化一个QApplication对象后,就为App建立了一个消息循环。并且,此段代码还实例化了一个QWidget对象,通过show方法,让widget显示出来。
Qt的事件分为两种:
- 与用户交互时发生。比如移动鼠标(enterEvent),敲击键盘(keyPressEvent)等。
- Qt程序自己发生,比如计时器事件(timerEvent)等。
QApplication的消息循环
以上面的QtGUI例程为例子,从起点开始分析,Qt的消息循环是如何建立起来的。
QApplication的exec的实现如下:
int QApplication::exec()
{
int r = QGuiApplication::exec();
QApplication *app = qobject_cast<QApplication*>(QCoreApplication::instance());
if (app && app->hasDeviceLost()) // TDR has priority
return 0xdeeedeee;
return r;
}
可以看到,QApplication的exec内部实现是调用基类的exec。深入内部查看源码发现,最终调用了基类QCoreApplication::exec。
int QCoreApplication::exec()
{
...
threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
threadData->quitNow = false;
if (self)
self->d_func()->execCleanup();
return returnCode;
}
QCoreApplication::exec的内部创建了一个QEventLoop,并调用了QEventLoop的exec,正是这个函数开启了消息循环。继续分析QEventLoop::exec的实现。
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
...
// remove posted quit events when entering a new event loop
QCoreApplication *app = QCoreApplication::instance();
if (app && app->thread() == thread())
QCoreApplication::removePostedEvents(app, QEvent::Quit);
...
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.load();
}
这个方法的主体是 while 循环,在 exit之前,会一直循环调用 processEvents() 方法。看来重点是processEvents,processEvents的实现如下:
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher())
return false;
return d->threadData->eventDispatcher.load()->processEvents(flags);
}
processEvents方法转调了eventDispatcher的processEvents。eventDispatcher的类型是QAbstractEventDispatcher,这是个抽象类,因此,很容易想到,这里使用了多态的方式。
的确,在不同的平台上,eventDispatcher的实现类是不一样的,在Windows上的实现是QWindowsGuiEventDispatcher,本篇主要讨论Windows平台。其他的平台暂不讨论。QWindowsGuiEventDispatcher内部又转调QEventDispatcherWin32::processEvents,下面代码是QEventDispatcherWin32::processEvents的实现。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd) {
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
}
d->interrupt.store(false);
emit awake();
bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
DWORD nCount = 0;
HANDLE *pHandles = nullptr;
if (d->winEventNotifierActivatedEvent) {
nCount = 1;
pHandles = &d->winEventNotifierActivatedEvent;
}
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt.load()) {
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage) {
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
&& isUserInputMessage(msg.message)) {
// queue user input events for later processing
d->queuedUserInputEvents.append(msg);
continue;
}
if ((flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// queue socket events for later processing
d->queuedSocketEvents.append(msg);
continue;
}
}
}
if (!haveMessage) {
// no message - check for signalled objects
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {
// a new message has arrived, process it
continue;
}
}
if (haveMessage) {
// WinCE doesn't support hooks at all, so we have to call this by hand :(
if (!d->getMessageHook)
(void) qt_GetMessageHook(0, PM_REMOVE, reinterpret_cast<LPARAM>(&msg));
if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
if (seenWM_QT_SENDPOSTEDEVENTS) {
// when calling processEvents() "manually", we only want to send posted
// events once
needWM_QT_SENDPOSTEDEVENTS = true;
continue;
}
seenWM_QT_SENDPOSTEDEVENTS = true;
} else if (msg.message == WM_TIMER) {
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = 0; !found && i < processedTimers.count(); ++i) {
const MSG processed = processedTimers.constData()[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam = msg.lParam);
}
if (found)
continue;
processedTimers.append(msg);
} else if (msg.message == WM_QUIT) {
if (QCoreApplication::instance())
QCoreApplication::instance()->quit();
return false;
}
if (!filterNativeEvent(false, QAbstractNativeEventFilter::WinProcessMsg, ByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();