上次研究了一下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 消息的处理,我们必然要关注的一个,那就是回调函数。
拿着这两个线索,花了一点时间,简单梳理一下,不难发现这里边的调用。以下调用非必要的会省略掉参数
1. 初始化 QWidget
会初始化 QWidgetPrivate
,在 QWidgetPrivate
的 init()
中会调用 QWidget::create()
;
2. 接着在 QWidget::create()
中调用 QWidgetPrivate::create_sys()
,在这个方法中,会创建一个 QWindow
,在创建之后如果 QWidget
是显示的,会调用 QWindow::setVisible(true)
;
3. 在 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 QWindowFlags
by 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 消息循环的联系。
在这中间省略的很多其他细节,包括注册窗口,反注册,具体的事件处理规则,还有一些防止事件错误发送的保护机制,都是很好的研究内容……