【Qt源码笔记】Qt事件与Windows消息循环的联系
上次研究了一下Qt是如何对Win32初始化程序进行包装的。这次研究下Qt的事件循环和Windows消息循环之间的联系。
上次说到QApplication注册了一个qt_internal_proc方法来处理消息循环,但是在这个方法中并没有看到一些关于Qt事件的蛛丝马迹。例如鼠标事件、键盘事件等。
其实在qt_internal_proc方法中有个调用值得注意:sendPostedEvents()。如果在我们自己Demo的鼠标事件中打个断点,不难发现,就是从这个调用来到我们的mousePressEvent()的。但是如果我们查看堆栈信息,按图索骥,会发现:
bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags){ int nevents = 0;
while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) { QWindowSystemInterfacePrivate::WindowSystemEvent *event = (flags & QEventLoop::ExcludeUserInputEvents) ? QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() : QWindowSystemInterfacePrivate::getWindowSystemEvent(); if (!event) break;
if (QWindowSystemInterfacePrivate::eventHandler) { if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event)) nevents++; } else { nevents++; QGuiApplicationPrivate::processWindowSystemEvent(event); }
// Record the accepted state for the processed event // (excluding flush events). This state can then be // returned by flushWindowSystemEvents(). if (event->type != QWindowSystemInterfacePrivate::FlushEvents) QWindowSystemInterfacePrivate::eventAccepted.store(event->eventAccepted);
delete event; }
return (nevents > 0);}在上边可以看到,这个最原始的事件就是从getXXXXXEvent()方法中得到的,而这个方法是从一个事件队列中取事件。
getWindowSystemEvent()方法中的内容是这样的:
QWindowSystemInterfacePrivate::WindowSystemEvent * QWindowSystemInterfacePrivate::getWindowSystemEvent(){ return windowSystemEventQueue.takeFirstOrReturnNull();}可以说这个事件队列就是我们要关注的焦点。那事件是如何被添加到这个队列里的,这里暂时按下不表,先记住他的名字windowSystemEventQueue。
###从QWidget谈起
回过头来想,鼠标键盘事件其实都是依托于窗口的,但其实QApplication本身并不属于窗体,我们如果想在程序中加入一些可视的窗口,就要自己做个QWidget或者是QMainWindow等等。所以可以得出一个大概的结论,这些事件的接收处理必然和QWidget有着千丝万缕的联系。另外关于Win32消息的处理,我们必然要关注的一个,那就是回调函数。
拿着这两个线索,花了一点时间,简单梳理一下,不难发现这里边的调用。以下调用非必要的会省略掉参数
- 初始化
QWidget会初始化QWidgetPrivate,在QWidgetPrivate的init()中会调用QWidget::create(); - 接着在
QWidget::create()中调用QWidgetPrivate::create_sys(),在这个方法中,会创建一个QWindow,在创建之后如果QWidget是显示的,会调用QWindow::setVisible(true); - 在
QWindow::setVisible(true)中调用QWindow::create(),这个方法中没有别的只是转调QWindowPrivate::create()。
void QWindowPrivate::create(bool recursive) { Q_Q(QWindow); if (platformWindow) return;
if (q->parent()) q->parent()->create();
platformWindow = QGuiApplicationPrivate::platformIntegration()->createPlatformWindow(q);Q_ASSERT(platformWindow);
if (!platformWindow) { qWarning() << "Failed to create platform window for" << q << "with flags" << q->flags(); return;}
QObjectList childObjects = q->children();for (int i = 0; i < childObjects.size(); i ++) { QObject *object = childObjects.at(i); if (!object->isWindowType()) continue;
QWindow *childWindow = static_cast<QWindow *>(object); if (recursive) childWindow->d_func()->create(recursive);
// The child may have had deferred creation due to this window not being created // at the time setVisible was called, so we re-apply the visible state, which // may result in creating the child, and emitting the appropriate signals. if (childWindow->isVisible()) childWindow->setVisible(true);
if (QPlatformWindow *childPlatformWindow = childWindow->d_func()->platformWindow) childPlatformWindow->setParent(this->platformWindow);}
QPlatformSurfaceEvent e(QPlatformSurfaceEvent::SurfaceCreated);QGuiApplication::sendEvent(q, &e);}
4. 在这个方法中,可以看到`createPlatformWindow()`,顾名思义,会创建一个平台相关的*Window*。这里的实际调用是`QWindowsIntegration::createPlatformWindow()`。 而在这个方法中,我们会看到这个语句`QWindowsWindowData::create(window, requested, window->title());`这里的`create()`是一个静态方法。 5. 在`create()`中会搞出一个`WindowCreationData`,这个结构体在**qwindowswindow.cpp**中,可以看到在定义上边的注释,没错,`create()`中会调用`WindowCreationData::create()`来创建一个*system handle*。
/*!
\class WindowCreationData
\brief Window creation code.
This struct gathers all information required to create a window.Window creation is split in 3 steps:
\list\li fromWindow() Gather all required information\li create() Create the system handle.\li initialize() Post creation initialization steps.\endlist
The reason for this split is to also enable changing the QWindowFlagsby calling:
\list\li fromWindow() Gather information and determine new system styles\li applyWindowFlags() to apply the new window system styles.\li initialize() Post creation initialization steps.\endlist
Contains the window creation code formerly in qwidget_win.cpp.
\sa QWindowCreationContext\internal\ingroup qt-lighthouse-win*/
```
6. 在WindowCreationData::create()中会发现一个非常熟悉的一段代码
const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w);
7. 这段代码是得到一个QWindowsContext实例,调用它的registerWindowClass()方法。而在QWindowsContext::registerWindowClass()中,我们会看到这段代码
return registerWindowClass(cname, qWindowsWndProc, style, GetSysColorBrush(COLOR_WINDOW), icon);,在这里我们就会看到qWindowsWndProc,其实这个就是最终跟每个QWidget的事件相关的回调方法,这里暂时按下不表,先观察这个重载方法的内容:
QString QWindowsContext::registerWindowClass(QString cname, WNDPROC proc, unsigned style, HBRUSH brush, bool icon){ // since multiple Qt versions can be used in one process // each one has to have window class names with a unique name // The first instance gets the unmodified name; if the class // has already been registered by another instance of Qt then // add an instance-specific ID, the address of the window proc. static int classExists = -1;
const HINSTANCE appInstance = static_cast<HINSTANCE>(GetModuleHandle(0)); if (classExists == -1) { WNDCLASS wcinfo; classExists = GetClassInfo(appInstance, reinterpret_cast<LPCWSTR>(cname.utf16()), &wcinfo); classExists = classExists && wcinfo.lpfnWndProc != proc; }
if (classExists) cname += QString::number(reinterpret_cast<quintptr>(proc));
if (d->m_registeredWindowClassNames.contains(cname)) // already registered in our list return cname;
#ifndef Q_OS_WINCE WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX);#else WNDCLASS wc;#endif wc.style = style; wc.lpfnWndProc = proc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = appInstance; wc.hCursor = 0;#ifndef Q_OS_WINCE wc.hbrBackground = brush; if (icon) { wc.hIcon = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE)); if (wc.hIcon) { int sw = GetSystemMetrics(SM_CXSMICON); int sh = GetSystemMetrics(SM_CYSMICON); wc.hIconSm = static_cast<HICON>(LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, sw, sh, 0)); } else { wc.hIcon = static_cast<HICON>(LoadImage(0, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED)); wc.hIconSm = 0; } } else { wc.hIcon = 0; wc.hIconSm = 0; }#else if (icon) { wc.hIcon = (HICON)LoadImage(appInstance, L"IDI_ICON1", IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); } else { wc.hIcon = 0; }#endif
wc.lpszMenuName = 0; wc.lpszClassName = reinterpret_cast<LPCWSTR>(cname.utf16());#ifndef Q_OS_WINCE ATOM atom = RegisterClassEx(&wc);#else ATOM atom = RegisterClass(&wc);#endif
if (!atom) qErrnoWarning("QApplication::regClass: Registering window class '%s' failed.", qPrintable(cname));
d->m_registeredWindowClassNames.insert(cname); qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << ' ' << cname << " style=0x" << hex << style << dec << " brush=" << brush << " icon=" << icon << " atom=" << atom; return cname;}到这里,就看到了注册窗口的基本套路RegisterClass(),就算是彻底把跟Qt事件相关的消息循环回调找到了。
现在再来看一下刚才说的qWindowsWndProc,这里边的内容,其实比较简短:
extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ LRESULT result; const QtWindows::WindowsEventType et = windowsEventType(message, wParam, lParam); const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result); if (QWindowsContext::verbose > 1 && lcQpaEvents().isDebugEnabled()) { if (const char *eventName = QWindowsGuiEventDispatcher::windowsMessageName(message)) { qCDebug(lcQpaEvents) << "EVENT: hwd=" << hwnd << eventName << hex << "msg=0x" << message << "et=0x" << et << dec << "wp=" << int(wParam) << "at" << GET_X_LPARAM(lParam) << GET_Y_LPARAM(lParam) << "handled=" << handled; } } if (!handled) result = DefWindowProc(hwnd, message, wParam, lParam); return result;}在这里主要做了一些微小的工作,对消息分类把消息处理成QtWindow::WindowEventType类型,便于后续处理,具体逻辑在windowsEventType()方法中,主要是做Win32消息和Qt事件的映射。然后就是调用QWindowsContext::windowsProc()处理消息。特定情况下输出debug信息。在处理消息的时候会得到处理结果,对于没有处理的调用DefWindowProc()做默认处理。
如果想看Win32消息和Qt事件对应的关系映射,在上边说到的windowEventType()方法中是最快的,基本涵盖了大部分,但是要注意有一些名字对不上,因为到这里其实分类还不是QEvent,而是一个中间类型
现在来重点关注一下windowProc()方法。
bool QWindowsContext::windowsProc(HWND hwnd, UINT message, QtWindows::WindowsEventType et, WPARAM wParam, LPARAM lParam, LRESULT *result){ *result = 0;
MSG msg; msg.hwnd = hwnd; // re-create MSG structure msg.message = message; // time and pt fields ignored msg.wParam = wParam; msg.lParam = lParam; msg.pt.x = msg.pt.y = 0; if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) { msg.pt.x = GET_X_LPARAM(lParam); msg.pt.y = GET_Y_LPARAM(lParam); // For non-client-area messages, these are screen coordinates (as expected // in the MSG structure), otherwise they are client coordinates. if (!(et & QtWindows::NonClientEventFlag)) { ClientToScreen(msg.hwnd, &msg.pt); } } else {#ifndef Q_OS_WINCE GetCursorPos(&msg.pt);#endif }
// Run the native event filters. long filterResult = 0; QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance(); if (dispatcher && dispatcher->filterNativeEvent(d->m_eventType, &msg, &filterResult)) { *result = LRESULT(filterResult); return true; }
QWindowsWindow *platformWindow = findPlatformWindow(hwnd); if (platformWindow) { filterResult = 0; if (QWindowSystemInterface::handleNativeEvent(platformWindow->window(), d->m_eventType, &msg, &filterResult)) { *result = LRESULT(filterResult); return true; } } if (et & QtWindows::InputMethodEventFlag) { QWindowsInputContext *windowsInputContext = ::windowsInputContext(); // Disable IME assuming this is a special implementation hooking into keyboard input. // "Real" IME implementations should use a native event filter intercepting IME events. if (!windowsInputContext) { QWindowsInputContext::setWindowsImeEnabled(platformWindow, false); return false; } switch (et) { case QtWindows::InputMethodStartCompositionEvent: return windowsInputContext->startComposition(hwnd); case QtWindows::InputMethodCompositionEvent: return windowsInputContext->composition(hwnd, lParam); case QtWindows::InputMethodEndCompositionEvent: return windowsInputContext->endComposition(hwnd); case QtWindows::InputMethodRequest: return windowsInputContext->handleIME_Request(wParam, lParam, result); default: break; } } // InputMethodEventFlag //... if (platformWindow) { // Suppress events sent during DestroyWindow() for native children. if (platformWindow->testFlag(QWindowsWindow::WithinDestroy)) return false; if (QWindowsContext::verbose > 1) qCDebug(lcQpaEvents) << "Event window: " << platformWindow->window(); } else { qWarning("%s: No Qt Window found for event 0x%x (%s), hwnd=0x%p.", __FUNCTION__, message, QWindowsGuiEventDispatcher::windowsMessageName(message), hwnd); return false; }
switch (et) { case QtWindows::KeyboardLayoutChangeEvent: if (QWindowsInputContext *wic = windowsInputContext()) wic->handleInputLanguageChanged(wParam, lParam); // fallthrough intended. case QtWindows::KeyDownEvent: case QtWindows::KeyEvent: case QtWindows::InputMethodKeyEvent: case QtWindows::InputMethodKeyDownEvent: case QtWindows::AppCommandEvent:#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result);#else return d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result);#endif //... case QtWindows::MouseWheelEvent: case QtWindows::MouseEvent: case QtWindows::LeaveEvent:#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);#else return d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);#endif case QtWindows::TouchEvent:#if !defined(Q_OS_WINCE) && !defined(QT_NO_SESSIONMANAGER) return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);#else return d->m_mouseHandler.translateTouchEvent(platformWindow->window(), hwnd, et, msg, result);#endif case QtWindows::FocusInEvent: // see QWindowsWindow::requestActivateWindow(). case QtWindows::FocusOutEvent: handleFocusEvent(et, platformWindow); return true; case QtWindows::ShowEventOnParentRestoring: // QTBUG-40696, prevent Windows from re-showing hidden transient children (dialogs). if (!platformWindow->window()->isVisible()) { *result = 0; return true; } break; //... } //... return false;}本着太长不看的原则,我把一些相似的都省略掉了。这里就能看到在这里会根据消息类型来进行分类处理。处理的方式也是统一的,调用handleXXXXEvent()或是tranlateXXXXEvent()。需要二次加工的就要走到tranlateXXXXEvent()二次加工。最终其实都是走到handleXXXXEvent()。而handleXXXXEvent()方法中会将事件包装成一个新的类型,再统一调用QWindowSystemInterfacePrivate::handleWindowSystemEvent(e)PS:这是个静态方法,这个静态方法中需要关注postWindowSystemEvent()。
现在来看postWindowSystemEvent():
void QWindowSystemInterfacePrivate::postWindowSystemEvent(WindowSystemEvent *ev){ windowSystemEventQueue.append(ev); QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher(); if (dispatcher) dispatcher->wakeUp();}看到了非常熟悉的一个队列windowSystemEventQueue,就是在这里将事件加入队列,至此整个Qt事件和Windows消息循环彻底联系起来……
其实这只是一个添加事件、获取事件的简单流程,仅仅为了研究Qt事件和Windows消息循环的联系。 在这中间省略的很多其他细节,包括注册窗口,反注册,具体的事件处理规则,还有一些防止事件错误发送的保护机制,都是很好的研究内容……
如果觉得文章不错,欢迎赞赏
微信
支付宝