当我们使用Qt程序接收鼠标、键盘事件的时候,大家有没有想过这些事件是怎么来的?当然是从操作系统来的,那Qt又是怎么从操作系统接收到的事件的?下面就通过Qt源码来揭开它的神秘面纱。(为了方便,以Windows为例,Qt版本为msvc2015 Qt5.9.8。)
Windows事件循环
先看看Windows上使用 Windows SDK开启程序事件循环的例子。WinMain是程序的入口,在这个函数中首先指定了窗口过程函数WinPro,就是我们处理消息的函数。使用while循环从消息队列中取出消息。当GetMessage取到WM_QUIT之后返回0,结束程序。
#include <Windows.h>
#include <stdio.h>
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 设计窗口
WNDCLASS wndclass;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.hIcon = LoadIcon(NULL, IDI_HAND);
wndclass.hCursor = LoadCursor(NULL, IDC_HAND);
wndclass.hInstance = hInstance;
// 设置窗口函数
wndclass.lpfnWndProc = WinProc;
wndclass.lpszClassName = "wanglz";
wndclass.lpszMenuName = NULL;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
RegisterClass(&wndclass);
HWND hWnd = CreateWindow("wanglz", "Window Title", WS_OVERLAPPEDWINDOW, 0, 0, 600, 400, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1) {
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
};
LRESULT CALLBACK WinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg) {
case WM_LBUTTONDOWN:
MessageBox(hwnd, "Left Button clicked", "message", 0);
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = BeginPaint(hwnd, &ps);
TextOut(hDC, 0, 0, "Test string", strlen("Test string"));
EndPaint(hwnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
在上面的消息处理函数中,WM_LBUTTONDOWN表示鼠标左键按下,在这里会弹出一个消息框。当系统或其他应用程序请求绘制应用程序窗口的一部分时,将发送 WM_PAINT 消息。
Qt事件循环
Qt的事件循环是通过QCoreApplication、QGuiApplication、QApplication的exec开启的。在main中构造QCoreApplication、QGuiApplication、QApplication的实例时候会把事件分发器创建出来。QCoreApplication和QGuiApplication、QApplication创建事件分发器有点不一样。下面这张图是当使用QCoreApplication是构造过程。
在QCoreApplication::exec中使用了QEventLoop,在源码中可以看到QEventLoop才是真正出来事件的类。
int QCoreApplication::exec()
{
if (!QCoreApplicationPrivate::checkInstance("exec"))
return -1;
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
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;
}
在QEventLoop的exec中使用了while来处理消息。在processEvents中使用了QCoreApplicationPrivate::createEventDispatcher创建的QEventDispatcherWin32分发器。(为了方便查看,省略了大部分代码)。
int QEventLoop::exec(ProcessEventsFlags flags)
{
……
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.load();
}
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->eventDispatcher.load())
return false;
return d->threadData->eventDispatcher.load()->processEvents(flags);
}
继续到QEventDispatcherWin32::processEvents中就发现,原来是在这里注册了窗口,设置了窗口过程函数。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd) {
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
}
d->interrupt = false;
emit awake();
bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt) {
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
MSG msg;
bool haveMessage;
……
if (haveMessage) {
……
if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else if (waitRet - WAIT_OBJECT_0 < nCount) {
d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
} else {
// nothing todo so break
break;
}
retVal = true;
}
……
} while (canWait);
……
return retVal;
}
void QEventDispatcherWin32::createInternalHwnd()
{
Q_D(QEventDispatcherWin32);
if (d->internalHwnd)
return;
d->internalHwnd = qt_create_internal_window(this);
……
}
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
{
QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
if (!ctx->atom)
return 0;
HWND wnd = CreateWindow(ctx->className, // classname
ctx->className, // window name
0, // style
0, 0, 0, 0, // geometry
HWND_MESSAGE, // parent
0, // menu handle
GetModuleHandle(0), // application
0); // windows creation data.
if (!wnd) {
qErrnoWarning("CreateWindow() for QEventDispatcherWin32 internal window failed");
return 0;
}
……
return wnd;
}
QWindowsMessageWindowClassContext
{
QWindowsMessageWindowClassContext();
~QWindowsMessageWindowClassContext();
ATOM atom;
wchar_t *className;
};
QWindowsMessageWindowClassContext::QWindowsMessageWindowClassContext()
: atom(0), className(0)
{
// make sure that multiple Qt's can coexist in the same process
const QString qClassName = QStringLiteral("QEventDispatcherWin32_Internal_Widget")
+ QString::number(quintptr(qt_internal_proc));
className = new wchar_t[qClassName.size() + 1];
qClassName.toWCharArray(className);
className[qClassName.size()] = 0;
WNDCLASS wc;
wc.style = 0;
wc.lpfnWndProc = qt_internal_proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = GetModuleHandle(0);
wc.hIcon = 0;
wc.hCursor = 0;
wc.hbrBackground = 0;
wc.lpszMenuName = NULL;
wc.lpszClassName = className;
atom = RegisterClass(&wc);
if (!atom) {
qErrnoWarning("%s RegisterClass() failed", qPrintable(qClassName));
delete [] className;
className = 0;
}
}
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
if (message == WM_NCCREATE)
return true;
MSG msg;
msg.hwnd = hwnd;
msg.message = message;
msg.wParam = wp;
msg.lParam = lp;
QAbstractEventDispatcher* dispatcher = QAbstractEventDispatcher::instance();
……
return DefWindowProc(hwnd, message, wp, lp);
}
当main函数中是QGuiApplication和QApplication是,情况有一点不一样了。事件分发器的创建变成了在QPA插件(什么是QPA插件)中创建。在这里会创建QWindowsGuiEventDispatcher的一个实例,QWindowsGuiEventDispatcher继承QEventDispatcherWin32。
当Qt使用QWidget创建一个本地窗口的时候,就会调用QPA插件中的QWindowsIntegration::createPlatformWindow来创建窗口。通过下面的代码我们可以看出最终也是调用了系统的API,并设置了窗口函数qWindowsWndProc。
QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) const
{
……
QWindowsWindowData obtained =
QWindowsWindowData::create(window, requested,
QWindowsWindow::formatWindowTitle(window->title()));
……
if (Q_UNLIKELY(!obtained.hwnd))
return Q_NULLPTR;
QWindowsWindow *result = createPlatformWindowHelper(window, obtained);
……
return result;
}
QWindowsWindowData
QWindowsWindowData::create(const QWindow *w,
const QWindowsWindowData ¶meters,
const QString &title)
{
WindowCreationData creationData;
creationData.fromWindow(w, parameters.flags);
QWindowsWindowData result = creationData.create(w, parameters, title);
// Force WM_NCCALCSIZE (with wParam=1) via SWP_FRAMECHANGED for custom margin.
creationData.initialize(w, result.hwnd, !parameters.customMargins.isNull(), 1);
return result;
}
QWindowsWindowData
WindowCreationData::create(const QWindow *w, const WindowData &data, QString title) const
{
……
const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w);
……
result.hwnd = CreateWindowEx(exStyle, classNameUtf16, titleUtf16,
style,
context->frameX, context->frameY,
context->frameWidth, context->frameHeight,
parentHandle, NULL, appinst, NULL);
……
}
QString QWindowsContext::registerWindowClass(const QWindow *w)
{
Q_ASSERT(w);
……
return registerWindowClass(cname, qWindowsWndProc, style, GetSysColorBrush(COLOR_WINDOW), icon);
}
QString QWindowsContext::registerWindowClass(QString cname,
WNDPROC proc,
unsigned style,
HBRUSH brush,
bool icon)
{
……
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = style;
wc.lpfnWndProc = proc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = appInstance;
wc.hCursor = 0;
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;
}
wc.lpszMenuName = 0;
wc.lpszClassName = reinterpret_cast<LPCWSTR>(cname.utf16());
ATOM atom = RegisterClassEx(&wc);
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;
}
qWindowsWndProc函数中使用windowsEventType方法把WM_XXX消息转换转换成了QtWindows::WindowsEventType类型,比如WM_CLOSE转换成QtWindows::CloseEvent,WM_PAINT和WM_ERASEBKGND转换成了QtWindows::ExposeEvent。
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);
QWindowsWindow *platformWindow = nullptr;
const RECT ncCalcSizeFrame = rectFromNcCalcSize(message, wParam, lParam, 0);
const bool handled = QWindowsContext::instance()->windowsProc(hwnd, message, et, wParam, lParam, &result, &platformWindow);
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;
}
然后再使用QWindowsContext::windowsProc把这些消息放到一个叫windowSystemEventQueue的队列中。比如鼠标事件通过QWindowsMouseHandler::translateMouseEventzhong调用 QWindowSystemInterface::handleMouseEvent放到队列中。
bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QtWindows::WindowsEventType et,
WPARAM wParam, LPARAM lParam,
LRESULT *result,
QWindowsWindow **platformWindowPtr)
{
*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;
……
switch (et) {
……
case QtWindows::MouseWheelEvent:
case QtWindows::MouseEvent:
case QtWindows::LeaveEvent:
{
QWindow *window = platformWindow->window();
while (window && (window->flags() & Qt::WindowTransparentForInput))
window = window->parent();
if (!window)
return false;
#if QT_CONFIG(sessionmanager)
return platformSessionManager()->isInteractionBlocked() ? true : d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
#else
return d->m_mouseHandler.translateMouseEvent(window, hwnd, et, msg, result);
#endif
}
……
default:
break;
}
return false;
}
QT_DEFINE_QPA_EVENT_HANDLER(void, handleMouseEvent, QWindow *window, ulong timestamp, const QPointF &local, const QPointF &global, Qt::MouseButtons b,
Qt::KeyboardModifiers mods, Qt::MouseEventSource source)
{
QWindowSystemInterfacePrivate::MouseEvent * e =
new QWindowSystemInterfacePrivate::MouseEvent(window, timestamp, QHighDpi::fromNativeLocalPosition(local, window), QHighDpi::fromNativePixels(global, window), b, mods, source);
QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
}
template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::DefaultDelivery>(QWindowSystemInterfacePrivate::WindowSystemEvent *ev)
{
if (synchronousWindowSystemEvents)
return handleWindowSystemEvent<QWindowSystemInterface::SynchronousDelivery>(ev);
else
return handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(ev);
}
template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(WindowSystemEvent *ev)
{
windowSystemEventQueue.append(ev);
if (QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher())
dispatcher->wakeUp();
return true;
}
当把消息加到队列中之后,使用分发器QAbstractEventDispatcher::wakeUp来分发消息。wakeUp里面就是使用系统的PostMessage发送了一个WM_QT_SENDPOSTEDEVENTS的消息
void QEventDispatcherWin32::wakeUp()
{
Q_D(QEventDispatcherWin32);
d->serialNumber.ref();
if (d->internalHwnd && d->wakeUps.testAndSetAcquire(0, 1)) {
// post a WM_QT_SENDPOSTEDEVENTS to this thread if there isn't one already pending
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
}
}
之前的代码已经展示了internalHwnd的窗口过程函数是qt_internal_proc,我们再回到这个函数,当message等于WM_QT_SENDPOSTEDEVENTS的时候,会调用sendPostedEvents()来分发消息。
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
if (message == WM_NCCREATE)
return true;
MSG msg;
msg.hwnd = hwnd;
msg.message = message;
msg.wParam = wp;
msg.lParam = lp;
……
#ifdef GWLP_USERDATA
QEventDispatcherWin32 *q = (QEventDispatcherWin32 *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
#else
QEventDispatcherWin32 *q = (QEventDispatcherWin32 *) GetWindowLong(hwnd, GWL_USERDATA);
#endif
QEventDispatcherWin32Private *d = 0;
if (q != 0)
d = q->d_func();
if (message == WM_QT_SOCKETNOTIFIER) {
……
return 0;
} else if (message == WM_QT_SENDPOSTEDEVENTS
// we also use a Windows timer to send posted events when the message queue is full
|| (message == WM_TIMER
&& d->sendPostedEventsWindowsTimerId != 0
&& wp == (uint)d->sendPostedEventsWindowsTimerId)) {
const int localSerialNumber = d->serialNumber.load();
if (localSerialNumber != d->lastSerialNumber) {
d->lastSerialNumber = localSerialNumber;
q->sendPostedEvents();
}
return 0;
} else if (message == WM_TIMER) {
Q_ASSERT(d != 0);
d->sendTimerEvent(wp);
return 0;
}
return DefWindowProc(hwnd, message, wp, lp);
}
由于sendPostedEvents是虚函数,最终会调用QWindowsGuiEventDispatcher的sendPostedEvents,里面调用了QWindowSystemInterface::sendWindowSystemEvents,最终就会把事件接入到Qt的事件系统中。
void QWindowsGuiEventDispatcher::sendPostedEvents()
{
QEventDispatcherWin32::sendPostedEvents();
QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}
下面这张图是重写了在MainWindow中重写了mouseReleaseEvent,鼠标释放之后函数调用顺序,可以清楚的看到系统消息是怎么接入到Qt的事件系统中的。
以上都是在Windows上的示例,在Linux下跟Windows类似。