searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

【Qt源码笔记】Qt事件与Windows消息循环的联系

2023-07-20 07:04:45
80
0

上次研究了一下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 消息循环的联系。
在这中间省略的很多其他细节,包括注册窗口,反注册,具体的事件处理规则,还有一些防止事件错误发送的保护机制,都是很好的研究内容……

0条评论
0 / 1000
Harper
17文章数
0粉丝数
Harper
17 文章 | 0 粉丝
原创

【Qt源码笔记】Qt事件与Windows消息循环的联系

2023-07-20 07:04:45
80
0

上次研究了一下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 消息循环的联系。
在这中间省略的很多其他细节,包括注册窗口,反注册,具体的事件处理规则,还有一些防止事件错误发送的保护机制,都是很好的研究内容……

文章来自个人专栏
Qt 杂谈
8 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0