qt视窗事件,定时器事件及自定义事件处理源码分析

qt事件机制

由窗口系统或Qt自身产生的,用以响应所发生各类事情的操作。Qt事件是一个QEvent对象,用于描述程序内部或外部发生的动作。

事件主要分为3种:1.视窗事件;2.系统自动发生的计时事件;3.自定义事件。

QT程序是事件驱动的, 程序的每个动作都是由内部某个事件所触发。QT事件的发生和处理成为程序运行的主线,存在于程序整个生命周期。

常见的QT事件类型有:

  1. 键盘事件: 按键按下和松开
  2. 鼠标事件: 鼠标移动,鼠标按键的按下和松开
  3. 拖放事件: 用鼠标进行拖放
  4. 滚轮事件: 鼠标滚轮滚动
  5. 绘屏事件: 重绘屏幕的某些部分
  6. 定时事件: 定时器到时
  7. 焦点事件: 键盘焦点移动
  8. 进入和离开事件: 鼠标移入widget之内,或是移出
  9. 移动事件: widget的位置改变
  10. 大小改变事件: widget的大小改变
  11. 显示和隐藏事件: widget显示和隐藏
  12. 窗口事件: 窗口是否为当前窗口

QT将系统产生的消息转化为QT事件,QT事件被封装为对象,所有的QT事件均继承基类QEvent,用于描述程序内部或外部发生的动作,任意的QObject对象都具备处理QT事件的能力。
请添加图片描述
QEvent是Qt中所有事件的基类,事件对象包含了该次事件所携带的相关参数。其成员函数如下:

QEvent(Type type)
virtual ~QEvent()
void accept()
void ignore(); //忽略该事件,继续传递给父类,或直接调用父类响应函数。
bool isAccepted() const //查询事件是否被处理了
void setAccepted(bool accepted)
bool spontaneous() const
Type type() const

对于多对象情况下,应该根据对象及事件类型,通过加事件忽略(ignore()),提供性能优化。

QEvent类规定了所有事件类型(定义了1000种事件类型),任何一个想要接受并处理事件的对象均须继承自QObject,可以重写QObject::event() 来处理事件,也可以由父类处理。

主要直接子类事件类型有:输入事件:QInputEvent;场景绘制:QGraphicsSceneEvent;获焦事件(QFocusEvent);计时器事件:QTimerEvent;快捷键事件:QShortcutEvent;QMoveEvent;QPaintEvent事件;QHelpEvent事件, 拖拽相关事件:QDropEvent(还继承QMimeSource), QDragMoveEvent,QDragEnterVent;鼠标键盘事件QMouseEvent,QKeyEvent(继承QInputEvent);

QHelpEvent事件扩展了鼠标位置获取接口。

其中,我们在开发中经常用到的就是accept() 和 ignore()函数。当然,accept() 等价于setAccepted(true),ignore() 等价于setAccepted(false)。accept()设置accept标志,表明事件接收者想要处理这个事件,不要传递给其父窗口。ignore() 于此相反,清除accept标志,即表明事件接收者不想处理该事件,请将事件继续往父窗口传递。

事件处理

QCoreApplication::exec()会从本地设备的窗口系统中,捕获消息(MSG),并将他们转换为QEvent类或其子类事件,并且发给接收到这个事件的QObject对象,如果这个事件被接收(返回值为true),则不会继续传给父类,若返回false,则继续传给父类对象。

img

win消息循环

以Windows为例,在windows系统,我们收到的都是“消息”,他是定义在winuser.h里一个结构体类型,在我们编写GUI程序,创建一个原生窗体时,总会要经历两个步骤:

1、注册一个窗体处理函数:
窗体类中,最重要的就是指定了一个窗口处理函数WNDPROC。所有此类型的窗口,收到消息后,都会回调到此处理函数中来执行。

2、创建一个窗体(CreateWindow)
这个函数大致定义了窗体的样式,并且需要与第一步中的窗体处理类关联起来。这样一来,窗体就真正创建好了,并且也可以接收到系统发来的消息。

一般地,我们可以创建很多个窗口,然后使用同一个窗体处理函数,通过创建时的参数来决定上下文,也就是到底是处理的是哪个Window,以及获取一些自定义结构等。

接下来很重要的一点,就是关于消息循环的过程。

首先,用户通过GetMessage、PeekMessage等函数,从消息队列中取出事件,通过DispatchMessage来分发事件。系统将这个事件分发到对应的窗口处理函数WNDPROC中进行处理。

在绝大部分GUI程序中,GetMessage, DispatchMessage是写在一个死循环中的,除非程序退出,否则会一直处理各种事件。

系统将用创建某Window的线程来分发消息。例如窗体1在线程A创建,窗体2在线程B创建,那么它们的WNDPROC则是由不同线程来回调的。一般地,我们也只会在主线程中创建窗体,在各个线程中处理窗口,qt不允许在子线程创建窗口。

Windows事件其实是和线程相关的,也就是说,对于每一个QObject的对象,它必须要有自己所在线程的信息。不同线程的对象是无法直接通信的,要通过事件派发才可以。

Qt事件循环

在Windows中,要处理消息一定要有一个窗体。在Qt中,事件一共有两类,一类是和窗体无关的事件,例如QTimerEvent,另外一类就是常见的窗体事件,如鼠标、键盘、绘制等事件。因此,qt至少有两个WNDPROC,一个处理Timer等事件,一个处理窗口事件。

Qt消息循环开始于QCoreApplication::exec()。用户创建出一个QCoreApplication,或者说更多情况下是QApplication,执行QCoreApplication::exec(),真正的消息循环在QEventLoop类中实现,通过QEventLoop::exec()可以进入一个消息循环的阻塞状态中,也就是不断地PeekMessage-DispatchMessage。

显然,QEventLoop会通过内部的一层抽象,来不断从系统获取和处理消息,而这一层抽象,是和线程相关的。所有相同的线程,完全可以共用这层抽象。下面以Qt5.9.5源码来了解此抽象的具体实现。

QT程序中main函数中的app.exec()实际就是启动事件循环:

int main()
{
    QApplication a(argc,argv);
	return a.exec();
}
//实际上执行的是
int QApplication::exec()
{
    return QGuiApplication::exec();
}

int QGuiApplication::exec()
{
#ifndef QT_NO_ACCESSIBILITY
    QAccessible::setRootObject(qApp);
#endif
    return QCoreApplication::exec();
}

int QCoreApplication::exec()
{
    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;
    }
	...
	QEventLoop eventLoop;
	eventLoop.exec();
	...
}

显然最终执行的QCoreApplication的exec()函数,通过开启一个QEventLoop eventLoop来执行主事件循环
QEventLoop的exec()函数实现如下:

int QEventLoop::exec(ProcessEventsFlags flags)
{
 	...
    // 移出app的post事件队列的所有Quit事件
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);
    ...
}

最终执行的是:

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher.load())
        return false;
    // eventDispatcher是个原子操作指针,调用QAbstractEventDispatcher的processEvents
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

其中,eventDispatcher被定义为:

QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;//QAtomicPointer类是一个模板类,它对指针提供与平台无关的原子操作

可见,最终通过线程数据threadData的事件分发器对所有事件进行处理。

事件派发器

QAbstractEventDispatcher是一个处理PeekMessage-DispatchMessage的抽象接口。在win下,Windows上实现的派生类是QEventDispatcherWin32,执行的是:

bool QWindowsGuiEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    const QEventLoop::ProcessEventsFlags oldFlags = m_flags;
    m_flags = flags;
    const bool rc = QEventDispatcherWin32::processEvents(flags);
    m_flags = oldFlags;
    return rc;
}

进而,通过win事件派发器执行:

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherWin32);
    if (!d->internalHwnd) {
        createInternalHwnd();
        wakeUp(); // trigger a call to sendPostedEvents()
    }
    ...
    do {
        ...
        while (!d->interrupt) {
            ...
            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 {
                //从系统里读消息装入msg里,PM_REMOVE表示拿到就删除系统消息
                haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
                ...
            }
            if (!haveMessage) {
                // no message - check for signalled objects
                for (int i=0; i<(int)nCount; i++)
                    pHandles[i] = d->winEventNotifierList.at(i)->handle();
                //判断所有句柄是否有触发的消息
                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) {
                ...
                if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            } 
            ...
            retVal = true;
        }
    } while (canWait);
	...
    return retVal;
}

//其中 
    QList<MSG> queuedUserInputEvents;
    QList<MSG> queuedSocketEvents;

可见,是通过事件派发器的processEvents实现peek和dispatchMessage,消息类型主要有:queuedUserInputEvents,queuedSocketEvents,系统通过PeekMessage来peek(读)消息,并追加到queuedUserInputEvents和queuedSocketEvents链表内。

不可见窗口及消息处理
窗口创建

createInternalHwnd()实现为:

void QEventDispatcherWin32::createInternalHwnd()
{
    Q_D(QEventDispatcherWin32);
    if (d->internalHwnd)
        return;
    d->internalHwnd = qt_create_internal_window(this);
    installMessageHook();
    // start all normal timers
    for (int i = 0; i < d->timerVec.count(); ++i)
        d->registerTimer(d->timerVec.at(i));
}

可见,createInternalHwnd()主要创建了不可见的内部窗口句柄和安装消息钩子只处理套接字消息socketnotifiers,定时器timers消息,自定义消息等事件,并为它注册了一个叫做qt_internal_proc的WNDPROC函数。

而qt_create_internal_window实现为:

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;
    }
#ifdef GWLP_USERDATA
    SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)eventDispatcher);
#else
    SetWindowLong(wnd, GWL_USERDATA, (LONG)eventDispatcher);
#endif
    return wnd;
}

其中,QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext()实现了自定义消息处理函数的注册:

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();
    long result;
    if (!dispatcher) {
        if (message == WM_TIMER)
            KillTimer(hwnd, wp);
        return 0;
    } else if (dispatcher->filterNativeEvent(QByteArrayLiteral("windows_dispatcher_MSG"), &msg, &result)) {
        return result;
    }

#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) {
        // socket notifier message
        int type = -1;
        switch (WSAGETSELECTEVENT(lp)) {
        case FD_READ:
        case FD_ACCEPT:
            type = 0;
            break;
        case FD_WRITE:
        case FD_CONNECT:
            type = 1;
            break;
        case FD_OOB:
            type = 2;
            break;
        case FD_CLOSE:
            type = 3;
            break;
        }
        if (type >= 0) {
            Q_ASSERT(d != 0);
            QSNDict *sn_vec[4] = { &d->sn_read, &d->sn_write, &d->sn_except, &d->sn_read };
            QSNDict *dict = sn_vec[type];

            QSockNot *sn = dict ? dict->value(wp) : 0;
            if (sn == nullptr) {
                d->postActivateSocketNotifiers();
            } else {
                Q_ASSERT(d->active_fd.contains(sn->fd));
                QSockFd &sd = d->active_fd[sn->fd];
                if (sd.selected) {
                    Q_ASSERT(sd.mask == 0);
                    d->doWsaAsyncSelect(sn->fd, 0);
                    sd.selected = false;
                }
                d->postActivateSocketNotifiers();

                // Ignore the message if a notification with the same type was
                // received previously. Suppressed message is definitely spurious.
                const long eventCode = WSAGETSELECTEVENT(lp);
                if ((sd.mask & eventCode) != eventCode) {
                    sd.mask |= eventCode;
                    QEvent event(type < 3 ? QEvent::SockAct : QEvent::SockClose);
                    QCoreApplication::sendEvent(sn->obj, &event);
                }
            }
        }
        return 0;
    } else if (message == WM_QT_ACTIVATENOTIFIERS) {
        Q_ASSERT(d != 0);
        // Postpone activation if we have unhandled socket notifier messages
        // in the queue. WM_QT_ACTIVATENOTIFIERS will be posted again as a result of
        // event processing.
        MSG msg;
        if (!PeekMessage(&msg, d->internalHwnd,
                         WM_QT_SOCKETNOTIFIER, WM_QT_SOCKETNOTIFIER, PM_NOREMOVE)
            && d->queuedSocketEvents.isEmpty()) {
            // register all socket notifiers
            for (QSFDict::iterator it = d->active_fd.begin(), end = d->active_fd.end();
                 it != end; ++it) {
                QSockFd &sd = it.value();
                if (!sd.selected) {
                    d->doWsaAsyncSelect(it.key(), sd.event);
                    // allow any event to be accepted
                    sd.mask = 0;
                    sd.selected = true;
                }
            }
        }
        d->activateNotifiersPosted = false;
        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);
}

可见,qt_internal_proc主要处理WM_QT_SOCKETNOTIFIER,WM_QT_ACTIVATENOTIFIERS,WM_QT_SENDPOSTEDEVENTS,WM_TIMER消息。

可见窗口及消息处理
窗口创建

我们知道qt_internal_proc是qt在进行消息循环时创建的一个隐藏窗口的窗口处理函数,用来处理自定义消息,定时器消息等。而对于可见窗口时,在窗口创建时,注册了窗口处理函数:qWindowsWndProc。

void QWidget::create(WId window, bool initializeWindow, bool destroyOldWindow)
{
    ...       
    setAttribute(Qt::WA_WState_Created);                        // set created flag
    d->create_sys(window, initializeWindow, destroyOldWindow);
    ...
}

void QWidgetPrivate::create_sys(WId window, bool initializeWindow, bool destroyOldWindow)
{
    ...
    if (q->windowType() != Qt::Desktop || q->testAttribute(Qt::WA_NativeWindow)) {
        win->create();
        // Enable nonclient-area events for QDockWidget and other NonClientArea-mouse event processing.
        if (QPlatformWindow *platformWindow = win->handle())
            platformWindow->setFrameStrutEventsEnabled(true);
    }
    ...
}
...
QWindowsWindowData
    WindowCreationData::create(const QWindow *w, const WindowData &data, QString title) const
{
 	...
    const QString windowClassName = QWindowsContext::instance()->registerWindowClass(w);
    ...
}

QString QWindowsContext::registerWindowClass(const QWindow *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;
    ...
	RegisterClassEx(&wc);//win api实现了窗口注册
}

该消息处理函数为:

//qwindowscontext.cpp
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);//将windows窗口消息转为qt事件
    QWindowsWindow *platformWindow = nullptr;
    const RECT ncCalcSizeFrame = rectFromNcCalcSize(message, wParam, lParam, 0);
    //处理qt窗口消息
    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;
        }
    }
    //若窗口消息没有处理,则让win api 默认处理函数DefWindowProc默认处理该窗口消息
    if (!handled)
        result = DefWindowProc(hwnd, message, wParam, lParam);
	...
    return result;
}

可见,这里利用窗口消息,定义了qt窗口事件类型,其中,windowsEventType主要做Win32消息和Qt事件的映射,实现为:

inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamIn, LPARAM lParamIn)
{
    switch (message) {
    case WM_PAINT:
    case WM_ERASEBKGND:
        return QtWindows::ExposeEvent;
    case WM_CLOSE:
        return QtWindows::CloseEvent;
    case WM_DESTROY:
        return QtWindows::DestroyEvent;
    case WM_ACTIVATEAPP:
        return (int)wParamIn ?
        QtWindows::ActivateApplicationEvent : QtWindows::DeactivateApplicationEvent;
    case WM_MOUSEACTIVATE:
        return QtWindows::MouseActivateWindowEvent;
    case WM_ACTIVATE:
        return  LOWORD(wParamIn) == WA_INACTIVE ?
            QtWindows::DeactivateWindowEvent : QtWindows::ActivateWindowEvent;
    case WM_SETCURSOR:
        return QtWindows::CursorEvent;
    case WM_MOUSELEAVE:
        return QtWindows::MouseEvent;
    case WM_HSCROLL:
        return QtWindows::ScrollEvent;
    case WM_MOUSEWHEEL:
    case WM_MOUSEHWHEEL:
        return QtWindows::MouseWheelEvent;
    case WM_WINDOWPOSCHANGING:
        return QtWindows::GeometryChangingEvent;
    case WM_MOVE:
        return QtWindows::MoveEvent;
    case WM_SHOWWINDOW:
        if (wParamIn)
            return lParamIn == SW_PARENTOPENING ? QtWindows::ShowEventOnParentRestoring : QtWindows::ShowEvent;
        return QtWindows::HideEvent;
    case WM_SIZE:
        return QtWindows::ResizeEvent;
    case WM_NCCREATE:
        return QtWindows::NonClientCreate;
    case WM_NCCALCSIZE:
        return QtWindows::CalculateSize;
    case WM_NCHITTEST:
        return QtWindows::NonClientHitTest;
    case WM_GETMINMAXINFO:
        return QtWindows::QuerySizeHints;
    case WM_KEYDOWN:                        // keyboard event
    case WM_SYSKEYDOWN:
        return QtWindows::KeyDownEvent;
    case WM_KEYUP:
    case WM_SYSKEYUP:
    case WM_CHAR:
        return QtWindows::KeyEvent;
    case WM_IME_CHAR:
        return QtWindows::InputMethodKeyEvent;
    case WM_IME_KEYDOWN:
        return QtWindows::InputMethodKeyDownEvent;
#ifdef WM_INPUTLANGCHANGE
    case WM_INPUTLANGCHANGE:
        return QtWindows::KeyboardLayoutChangeEvent;
#endif // WM_INPUTLANGCHANGE
    case WM_TOUCH:
        return QtWindows::TouchEvent;
    case WM_CHANGECBCHAIN:
    case WM_DRAWCLIPBOARD:
    case WM_RENDERFORMAT:
    case WM_RENDERALLFORMATS:
    case WM_DESTROYCLIPBOARD:
        return QtWindows::ClipboardEvent;
    case WM_IME_STARTCOMPOSITION:
        return QtWindows::InputMethodStartCompositionEvent;
    case WM_IME_ENDCOMPOSITION:
        return QtWindows::InputMethodEndCompositionEvent;
    case WM_IME_COMPOSITION:
        return QtWindows::InputMethodCompositionEvent;
    case WM_IME_REQUEST:
        return QtWindows::InputMethodRequest;
    case WM_IME_NOTIFY:
         switch (int(wParamIn)) {
         case IMN_OPENCANDIDATE:
             return QtWindows::InputMethodOpenCandidateWindowEvent;
         case IMN_CLOSECANDIDATE:
             return QtWindows::InputMethodCloseCandidateWindowEvent;
         default:
             break;
         }
         break;
    case WM_GETOBJECT:
        return QtWindows::AccessibleObjectFromWindowRequest;
    case WM_SETFOCUS:
        return QtWindows::FocusInEvent;
    case WM_KILLFOCUS:
        return QtWindows::FocusOutEvent;
    // Among other things, WM_SETTINGCHANGE happens when the taskbar is moved
    // and therefore the "working area" changes.
    // http://msdn.microsoft.com/en-us/library/ms695534(v=vs.85).aspx
    case WM_SETTINGCHANGE:
        return QtWindows::SettingChangedEvent;
    case WM_DISPLAYCHANGE:
        return QtWindows::DisplayChangedEvent;
    case WM_THEMECHANGED:
#ifdef WM_SYSCOLORCHANGE // Windows 7: Handle color change as theme change (QTBUG-34170).
    case WM_SYSCOLORCHANGE:
#endif
        return QtWindows::ThemeChanged;
    case WM_DWMCOMPOSITIONCHANGED:
        return QtWindows::CompositionSettingsChanged;
#ifndef QT_NO_CONTEXTMENU
    case WM_CONTEXTMENU:
        return QtWindows::ContextMenu;
#endif
    case WM_SYSCOMMAND:
        if ((wParamIn & 0xfff0) == SC_CONTEXTHELP)
            return QtWindows::WhatsThisEvent;
        break;
    case WM_QUERYENDSESSION:
        return QtWindows::QueryEndSessionApplicationEvent;
    case WM_ENDSESSION:
        return QtWindows::EndSessionApplicationEvent;
#if defined(WM_APPCOMMAND)
    case WM_APPCOMMAND:
        return QtWindows::AppCommandEvent;
#endif
    case WM_GESTURE:
        return QtWindows::GestureEvent;
    case WM_DEVICECHANGE:
        return QtWindows::DeviceChangeEvent;
    case WM_DPICHANGED:
        return QtWindows::DpiChangedEvent;
    case WM_ENTERSIZEMOVE:
        return QtWindows::EnterSizeMoveEvent;
    case WM_EXITSIZEMOVE:
        return QtWindows::ExitSizeMoveEvent;
    default:
        break;
    }
    if (message >= WM_NCMOUSEMOVE && message <= WM_NCMBUTTONDBLCLK)
        return QtWindows::NonClientMouseEvent; //
    if ((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST)
         || (message >= WM_XBUTTONDOWN && message <= WM_XBUTTONDBLCLK))
        return QtWindows::MouseEvent;
    return QtWindows::UnknownEvent;
}
可见窗口处理函数

重点是窗口上下文类QWindowsContext的窗口处理函数:

 inline bool windowsProc(HWND hwnd, UINT message, QtWindows::WindowsEventType et, WPARAM wParam, LPARAM lParam, LRESULT *result, QWindowsWindow **platformWindowPtr);

其实现为:

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::MoveEvent:
		platformWindow->handleMoved();
		return true;
    //...
    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;
}

这里会根据windows消息类型来进行分类处理。处理的方式也是统一的,调用handleXXXXEvent()或是tranlateXXXXEvent()。需要二次加工的就要走到tranlateXXXXEvent()二次加工。最终其实都是走到handleXXXXEvent()。而handleXXXXEvent()方法中会将事件包装成一个新的类型(基类为WindowSystemEvent),再统一调用QWindowSystemInterfacePrivate::handleWindowSystemEvent(e)
其中,handleWindowSystemEvent为:

QWindowSystemInterface.cpp
//定义
template<typename Delivery = QWindowSystemInterface::DefaultDelivery>
    static bool handleWindowSystemEvent(WindowSystemEvent *ev);
//实现
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;
}

这里将qt事件按WindowSystemEvent 类型分类加入队列,至此,整个Qt事件和Windows消息循环彻底联系起来。

举例:

以窗口鼠标输入事件为例:

调用QWindowsContext::windowsProc-->translateMouseEvent(window, hwnd, et, msg, result)-->
QWindowSystemInterface::handleEnterEvent(currentWindowUnderMouse, localPosition, globalPosition)-->
QT_DEFINE_QPA_EVENT_HANDLER(void, handleEnterEvent, QWindow *window, const QPointF &local, const QPointF &global)
-->QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::DefaultDelivery>(QWindowSystemInterfacePrivate::WindowSystemEvent *ev)-->
QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(WindowSystemEvent *ev)

其中,宏QT_DEFINE_QPA_EVENT_HANDLER定义如下,用于构造事件类WindowSystemEvent:

#define QT_DEFINE_QPA_EVENT_HANDLER(ReturnType, HandlerName, ...) \
    template Q_GUI_EXPORT ReturnType QWindowSystemInterface::HandlerName<QWindowSystemInterface::DefaultDelivery>(__VA_ARGS__); \
    template Q_GUI_EXPORT ReturnType QWindowSystemInterface::HandlerName<QWindowSystemInterface::SynchronousDelivery>(__VA_ARGS__); \
    template Q_GUI_EXPORT ReturnType QWindowSystemInterface::HandlerName<QWindowSystemInterface::AsynchronousDelivery>(__VA_ARGS__); \
    template<typename Delivery> ReturnType QWindowSystemInterface::HandlerName(__VA_ARGS__)

QT_DEFINE_QPA_EVENT_HANDLER(void, handleEnterEvent, QWindow *window, const QPointF &local, const QPointF &global)
{
    if (window) {
        QWindowSystemInterfacePrivate::EnterEvent *e
                = new QWindowSystemInterfacePrivate::EnterEvent(window, QHighDpi::fromNativeLocalPosition(local, window), QHighDpi::fromNativePixels(global, window));
        QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
    }
}

替换后为:

void QWindowSystemInterface::handleEnterEvent<QWindow *window, const QPointF &local, const QPointF &global)
{
 	if (window) 
 	{
        QWindowSystemInterfacePrivate::EnterEvent *e = new QWindowSystemInterfacePrivate::EnterEvent(window, QHighDpi::fromNativeLocalPosition(local, window), QHighDpi::fromNativePixels(global, window));
        QWindowSystemInterfacePrivate::handleWindowSystemEvent<Delivery>(e);
    }
}

可见,根据QWindow,和事件类型,创建了WindowSystemEvent各个子类事件,并压入了

static WindowSystemEventList windowSystemEventQueue。//静态实例

WindowSystemEventList 是一个内部通过原子控制QList链表类。

class WindowSystemEventList {
        QList<WindowSystemEvent *> impl;
        mutable QMutex mutex;
事件传递

我们知道,当程序窗口接收到win端窗口消息后,会转换为WindowSystemEvent视窗系统子类事件并压入到windowSystemEventQueue中:

template<>
bool QWindowSystemInterfacePrivate::handleWindowSystemEvent<QWindowSystemInterface::AsynchronousDelivery>(WindowSystemEvent *ev)
{
    windowSystemEventQueue.append(ev);
    if (QAbstractEventDispatcher *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher())
        dispatcher->wakeUp();
    return true;
}

同时唤醒事件派发器,显然,这里唤醒派发器,肯定的是希望通过派发器及时处理当前视窗系统事件队列windowSystemEventQueue,那么这个又是怎么处理的呢?

派发器唤醒

事件派发器唤醒机制实现为:

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);//发送一个自定义消息WM_QT_SENDPOSTEDEVENTS:1025
    }
}

显然,QWindowSystemInterface类的窗口系统接口,每次进行事件压栈的后,会唤醒派发器,在processEvents里处理这个事件队列,那这个事件队列又是怎么处理的呢?
派发器唤醒(原子唤醒)机制,其实就是给internalHwnd窗口句柄post一个WM_QT_SENDPOSTEDEVENTS异步消息,此时依赖QEventLoop持续拿消息,直到拿到这个自定义消息。

int QEventLoop::exec(ProcessEventsFlags flags)
{
    ...   
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);//被唤醒,将执行事件处理
	...
}

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
	...
	haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);//从这里拿到1025自定义消息
	...
	if(haveMessage )
	{
		...
		if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) 
		{
		     TranslateMessage(&msg);
		     DispatchMessage(&msg);//将消息派发给注册的窗口,对应的窗口处理函数将处理该消息
		}
		...
	}
	...
}

在这里插入图片描述
拿到自定义消息类型1025(WM_USER+1)消息后,执行win api函数DispatchMessage,才会进入qt_internal_proc里,正常调用处理。

系统视窗事件处理

我们回看qt_internal_proc()关于处理WM_QT_SENDPOSTEDEVENTS(消息值为1025)消息的部分,因为这个窗口处理函数注册,自然会收到派发的1025号消息:

LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
    ...
    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;
    }
    ...
}

可知,派发器唤醒,最终会触发一个sendPostedEvents的调用,此时,针对gui层的消息派发sendPostedEvents(),对应实现为:

void QWindowsGuiEventDispatcher::sendPostedEvents()
{
    QEventDispatcherWin32::sendPostedEvents();//处理线程相关的消息队列,这个主要处理自定义事件。
    QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}

显然,调用了QWindowSystemInterface的sendWindowSystemEvents()来发送消息,对应实现为:

bool QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
{
    int nevents = 0;
    while (QWindowSystemInterfacePrivate::windowSystemEventsQueued()) {
        QWindowSystemInterfacePrivate::WindowSystemEvent *event =
            (flags & QEventLoop::ExcludeUserInputEvents) ?
                QWindowSystemInterfacePrivate::getNonUserInputWindowSystemEvent() :
                QWindowSystemInterfacePrivate::getWindowSystemEvent();//从windowSystemEventQueue队列拿消息,类型为WindowSystemEvent
        if (!event)
            break;
        if (QWindowSystemInterfacePrivate::eventHandler) {
            if (QWindowSystemInterfacePrivate::eventHandler->sendEvent(event))
                nevents++;
        } else {
            nevents++;
            QGuiApplicationPrivate::processWindowSystemEvent(event);//处理从队列拿到的消息
        }
     	...
    }
    return (nevents > 0);
}

可见,这里会把windowSystemEventQueue队列全部清空处理,真正处理函数为:

void QGuiApplicationPrivate::processWindowSystemEvent(QWindowSystemInterfacePrivate::WindowSystemEvent *e)
{
    switch(e->type) {
    case QWindowSystemInterfacePrivate::Mouse:
        QGuiApplicationPrivate::processMouseEvent(static_cast<QWindowSystemInterfacePrivate::MouseEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Wheel:
        QGuiApplicationPrivate::processWheelEvent(static_cast<QWindowSystemInterfacePrivate::WheelEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Key:
        QGuiApplicationPrivate::processKeyEvent(static_cast<QWindowSystemInterfacePrivate::KeyEvent *>(e));
        break;
    ...
    case QWindowSystemInterfacePrivate::Enter:
        QGuiApplicationPrivate::processEnterEvent(static_cast<QWindowSystemInterfacePrivate::EnterEvent *>(e));
        break;
    case QWindowSystemInterfacePrivate::Leave:
        QGuiApplicationPrivate::processLeaveEvent(static_cast<QWindowSystemInterfacePrivate::LeaveEvent *>(e));
        break;
	...
    default:
        qWarning() << "Unknown user input event type:" << e->type;
        break;
    }
}

其中,MouseEvent,WheelEvent ,keyEvent,EnterEvent ,LeaveEvent 事件基类都是WindowSystemEvent,那么qt又是怎么把WindowSystemEvent转换为QEvent类型的呢?

举例

我们以输入事件为例:

QGuiApplicationPrivate::processEnterEvent(static_cast<QWindowSystemInterfacePrivate::EnterEvent *>(e));
void QGuiApplicationPrivate::processEnterEvent(QWindowSystemInterfacePrivate::EnterEvent *e)
{
    if (!e->enter)
        return;
    if (e->enter.data()->d_func()->blockedByModalWindow) {
        // a modal window is blocking this window, don't allow enter events through
        return;
    }
    currentMouseWindow = e->enter;
    QEnterEvent event(e->localPos, e->localPos, e->globalPos);//QEnterEvent事件基类为QEvent。
    QCoreApplication::sendSpontaneousEvent(e->enter.data(), &event);//SpontaneousEvent系统事件,视窗消息派发
}

可见,在处理qt中间的WindowSystemEvent时,拿到了事件局部和全局位置,构建了QEvent事件,再次进行了派发。

再经过一次次事件派发后,最终执行sendEvent(QObject *receiver, QEvent *event);

inline bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{  if (event) event->spont = false; return notifyInternal2(receiver, event); }

notifyInternal2主要对QCoreApplication应用类的所有QObject对象进行通知。

bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
    bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
    if (!selfRequired)
        return doNotify(receiver, event);
    return self->notify(receiver, event);//
}

进行一个通知:

bool QApplication::notify(QObject *receiver, QEvent *e)
{
    ...
    // 处理视窗消息
    if (!receiver->isWidgetType()) 
    {
     	res = d->notify_helper(receiver, e);
    } 
}

在执行事件过滤助手,真正传给窗口。

bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{
     // deliver the event
    bool consumed = receiver->event(e);//执行QObject的event事件
}

//执行指定的窗口的event事件。

bool QWidget::event(QEvent *event)
{
    switch (event->type()) 
    {
    case QEvent::Enter:
#if QT_CONFIG(statustip)
        if (d->statusTip.size()) {
            QStatusTipEvent tip(d->statusTip);
            QApplication::sendEvent(const_cast<QWidget *>(this), &tip);
        }
#endif
        enterEvent(event);
		...
    }       
}

最终事件执行结束。

定时器事件注册与派发

上述事件派发主要用于自定义事件或者视窗系统事件派发,那定时器事件又是怎么注册派发的
首先看时怎么开启定时器的:

void QTimer::start()
{
    if (id != INV_TIMER)                        // stop running timer
        stop();
    nulltimer = (!inter && single);
    id = QObject::startTimer(inter, Qt::TimerType(type));
}

而startTimer的具体实现为:

int QObject::startTimer(int interval, Qt::TimerType timerType)
{
    Q_D(QObject);
	...
    int timerId = d->threadData->eventDispatcher.load()->registerTimer(interval, timerType, this);
    if (!d->extraData)
        d->extraData = new QObjectPrivate::ExtraData;
    d->extraData->runningTimers.append(timerId);
    return timerId;
}

可见,这里通过事件派发器,进行了一个定时器注册,我们再看下这个定时器注册函数。

void QEventDispatcherWin32Private::registerTimer(WinTimerInfo *t)
{
    Q_ASSERT(internalHwnd);
    Q_Q(QEventDispatcherWin32);
    bool ok = false;
    calculateNextTimeout(t, qt_msectime());
    uint interval = t->interval;
    if (interval == 0u) {
        // optimization for single-shot-zero-timer
        QCoreApplication::postEvent(q, new QZeroTimerEvent(t->timerId));
        ok = true;
    } else if (interval < 20u || t->timerType == Qt::PreciseTimer) {
        // 3/2016: Although MSDN states timeSetEvent() is deprecated, the function
        // is still deemed to be the most reliable precision timer.
        t->fastTimerId = timeSetEvent(interval, 1, qt_fast_timer_proc, DWORD_PTR(t), TIME_CALLBACK_FUNCTION | TIME_PERIODIC | TIME_KILL_SYNCHRONOUS);
        ok = t->fastTimerId;
    }

    if (!ok) {
        // user normal timers for (Very)CoarseTimers, or if no more multimedia timers available
        ok = SetTimer(internalHwnd, t->timerId, interval, 0);
    }
    if (!ok)
        qErrnoWarning("QEventDispatcherWin32::registerTimer: Failed to create a timer");
}

到这里就一目了然了,通过win api 函数timeSetEvent,设置一个毫秒级定时器(小于20ms)及定时器处理函数。或者直接给内部不可见窗口句柄定义一个常规定时器,让不可见窗口处理函数(qt_internal_proc)处理定时器消息。

事件线程相关性

当我们创建一个QObject时,它会与创建自己所在的线程绑定(无论是否指定父对象,只有未指定父对象的QObject对象才可以通过moveToThread切换到指定线程)。它参与的消息循环,其实是它所在线程的消息循环,如上图所示。假如某个线程没有默认的QThread::exec(),那么该线程上的QObject则无法接收到事件。另外,如果两个不同线程的QObject需要相互通信,那么只能通过QueuedConnection的方式,异步通知对方线程,在下一轮消息循环处理QObject的消息。

QObject的线程相关性默认会和它的parent保持一致。如果一个QObject没有parent,那么可以通过moveToThread,将它的线程相关性切换到指定线程。

了解QObject的线程相关性非常重要,很多初学者常常分不清一个多线程中哪些QObject应该由主线程创建,哪些应该由工作线程创建,我的观点是,它参与哪个消息循环,就由哪个来创建。

正因为这样的特性,我们才可以理解什么叫做AutoConnection。通过AutoConnect连接的两个QObject,如果是在同一个线程,那么可以直接调用(DirectConnection),如果不是在同一个线程,那么就通过事件通知的方式(QueuedConnection)来调用。通过信号和槽、事件或者QueuedConnection方式来进行线程间的通讯,尤其是与UI线程通讯,永远是最优雅的方式之一。

QThreadData:线程资源

QEventDispatcherWin32是跟着线程走的,所以没有必要每个QEventLoop都存一个。事实上,它存放在一个叫做QThreadData的结构中:

class QThreadData
{
public:
    QThreadData(int initialRefCount = 1);
    ~QThreadData();
    static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
    
private:
    QAtomicInt _ref;
    
public:
    int loopLevel;
    int scopeLevel;

    QStack<QEventLoop *> eventLoops;
    QPostEventList postEventList;
    QAtomicPointer<QThread> thread;
    QAtomicPointer<void> threadId;
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
    QVector<void *> tls;
    FlaggedDebugSignatures flaggedSignatures;

    bool quitNow;
    bool canWait;
    bool isAdopted;
    bool requiresCoreApplication;
};

其中主要有个事件派发器eventDispatcher,即QEventDispatcherWin32实例,eventLoops,这个是嵌套的消息循环,以及loopLevel,是它嵌套的层数(如QEventLoop::exec里面调用QEventLoop:exec)。里面还有个postEventList,表示当前的Qt事件队列,thread表示它当前所在的线程,他通常是QAdoptedThread的一个实例,以及一个_ref引用计数。
QThreadData奇妙在,它是跟着线程走的。在QThreadData::current中我们可以看到:

QThreadData *QThreadData::current(bool createIfNecessary)
{
    qt_create_tls();
    QThreadData *threadData = reinterpret_cast<QThreadData *>(TlsGetValue(qt_current_thread_data_tls_index));
    if (!threadData && createIfNecessary) {
        threadData = new QThreadData;
        // This needs to be called prior to new AdoptedThread() to
        // avoid recursion.
        TlsSetValue(qt_current_thread_data_tls_index, threadData);
        QT_TRY {
            threadData->thread = new QAdoptedThread(threadData);
        } QT_CATCH(...) {
            TlsSetValue(qt_current_thread_data_tls_index, 0);
            threadData->deref();
            threadData = 0;
            QT_RETHROW;
        }
        threadData->deref();
        threadData->isAdopted = true;
        threadData->threadId.store(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));

        if (!QCoreApplicationPrivate::theMainThread) {
            QCoreApplicationPrivate::theMainThread = threadData->thread.load();
            // TODO: is there a way to reflect the branch's behavior using
            // WinRT API?
        } else {
            HANDLE realHandle = INVALID_HANDLE_VALUE;
            DuplicateHandle(GetCurrentProcess(),
                    GetCurrentThread(),
                    GetCurrentProcess(),
                    &realHandle,
                    0,
                    FALSE,
                    DUPLICATE_SAME_ACCESS);
            qt_watch_adopted_thread(realHandle, threadData->thread);
        }
    }
    return threadData;
}

其中qt_create_tls() 会利用win api TlsAlloc()分配TLS(线程局部存储区Thread Local Storage) 索引变量,再利用TlsGetValue()获取指定索引变量的线程存储区内存资源指针。
QAdoptedThread为线程实例,
而QObject构造流程为:

QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_D(QObject);
    d_ptr->q_ptr = this;
    d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current();
    d->threadData->ref();
    .....
}

QObject对象,若parent存在,且parent的thread为空时,将与parent的threadData一致,否则将调用QThreadData::current获取一个新的TLS的QThreadData,并指定一个QAdoptedThread的线程实例。

QThreadData与QThread
QThread::QThread(QObject *parent)
    : QObject(*(new QThreadPrivate), parent)
{
    Q_D(QThread);
    // fprintf(stderr, "QThreadData %p created for thread %p\n", d->data, this);
    d->data->thread = this;
}

QThreadPrivate::QThreadPrivate(QThreadData *d)
    : QObjectPrivate(), running(false), finished(false),
      isInFinish(false), interruptionRequested(false),
      exited(false), returnCode(-1),
      stackSize(0), priority(QThread::InheritPriority), data(d)
{
	...
    if (!data)
        data = new QThreadData;
}

可以看到,新建一个QThread时,在QThreadPrivate构造时,会建立了一个新的QThreadData。并设置QThreadData关联的线程为this。但在构造过程中,并没有设置TLS。
而在线程start启动时,调用了win api CreateThread函数创建线程,同时函数指针为:QThreadPrivate::start

void QThread::start(Priority priority)
{
	// MSVC -MD or -MDd or MinGW build
    d->handle = (Qt::HANDLE) CreateThread(NULL, d->stackSize, (LPTHREAD_START_ROUTINE)QThreadPrivate::start, this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));
}

而QThreadPrivate::start(void *arg)函数实现为:

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
{
    QThread *thr = reinterpret_cast<QThread *>(arg);
    QThreadData *data = QThreadData::get2(thr);
    qt_create_tls();
    TlsSetValue(qt_current_thread_data_tls_index, data);

    QThread::setTerminationEnabled(false);
    {
        QMutexLocker locker(&thr->d_func()->mutex);
        data->quitNow = thr->d_func()->exited;
    }

    if (data->eventDispatcher.load()) // custom event dispatcher set?
        data->eventDispatcher.load()->startingUp();
    else
        createEventDispatcher(data);

    emit thr->started(QThread::QPrivateSignal());
    QThread::setTerminationEnabled(true);
    thr->run();
    finish(arg);
    return 0;
}

可知,只有真正启动的时候,才会创建tls。并通过TlsSetValue设置给了QThread创建的QThreadData实例。同时会检查QThreadData是否存在派发器,若不存在,则创建createEventDispatcher(data);为QThread的QThreadData添加事件派发器。

如果不是通过QThread创建的QThreadData(即通过QThreadData::current来创建的)默认是没有event dispatcher的,所以你无法对一个孤立的QObject分发事件。QCoreApplication并没有继承QThread,它是通过QThreadData::current创建了tls,且设置了event dispatcher来实现消息的分发。

int QCoreApplication::exec()
{
    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;
    }
	...
	QEventLoop eventLoop;
	eventLoop.exec();
	...
}
//在processEvents中进行加载事件派发器。
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->eventDispatcher.load())
        return false;
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

至此,可知qt的消息循环是是通过事件派发器的processEvents()完成的,且是线程相关的。每个线程都有自己独立线程tls,派发器等。

事件过滤

Qt提供了5个级别来处理和过滤事件。

  1. 我们可以重新实现特定的event handler:重新实现像mousePressEvent(), keyPressEvent()和paintEvent()这样的event Handler是目前处理event最普通的方式。

  2. 我们可以重新实现QObject::event():通过重新实现event(),我们可以在事件到达特定的event handler之前对它们作出处理。

  3. 安装事件过滤器

    一个对象用installEventFilter注册了, 发到目标对象的所有事件都会先发到监测对象的eventFilter()。一个object安装了多个event filter, filter会依次被激活, 从最近安装的回到第一个。

    在QApplication对象上安装eventfilter。一旦一个eventfilter被注册到qApp(唯一的QApplication对象), 程序里发到每个对象的每个事件在发到其他eventfilter之前,都要首先发到eventFilter()。

    void QObject::installEventFilter ( QObject * filterObj);

    这个函数接收一个QObject对象,发生到这个对象的事件,都会先传递到filterObj定义的eventFilter()函数里。

    qApp->installEventFilter(filterObj);qApp->removeEventFilter(filterObj);//qApp的接收到的窗口事件先让filterObj处理。

事件和信号的区别

Qt的事件很容易和信号槽混淆。signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理;(只有队列连接时,才会通过事件派发方式,通过事件循环执行)
而对于事件,Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件接着再进行处理。
Qt的事件和Qt中的signal不一样。 后者通常用来使用widget, 而前者用来实现widget。 如果我们使用系统预定义的控件,那我们关心的是信号,如果自定义控件我们关心的是事件。
对某些事件可以不处理,而信号与槽一旦绑定,是需要处理这个信号的。

自定义事件

定义自定义事件类型ID(范围0~65535),由于QT的系统事件ID都在0~1000,所有自定义事件ID范围要在1001~65535之内(QEvent::User的值为1000)。不然会覆盖。

    void customEvent(QEvent *event) override;

sendEvent或者postEvent的区别:
sendEvent:是同步执行,它一发送,就会通过notify直接执行相关事件,并且它是在栈区创建。
postEvent:是异步执行,它一发送,事件就会通过主线程的QEventLoop把它从输入事件队列queuedSocketEvents加到执行事件队列,然后按照顺序执行,并且它在堆区创建。
首先要定义一个事件,并指定事件类型ID:

// .h文件:
const int MyEventId = QEvent::User + 6;// 定义事件类型id
class MyEvent :public QEvent
{
public:
    MyEvent(QObject *parent, QString val):QEvent(QEvent::Type(MyEventId)),m_val(val),m_parent(parent)
    {
    }
private:
    QObject* m_parent;
    QString m_val;
};

自定义一个事件后,将这个事件用于QObject里:

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();
    void customEvent(QEvent *event) override;
    bool eventFilter(QObject *watched, QEvent *event) override;
private:
    Ui::Widget *ui;
};
//.cpp文件
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->installEventFilter(this);//当前对象接收的事件让当前类的eventfilter处理。
    connect(ui->pushButton,&QPushButton::clicked,this,[&](){
    #if 1
        //1
        MyEvent* myevent = new MyEvent(this,"message");
        QApplication::postEvent(this,myevent);//将自定义事件发给当前对象。
    #else
        //2
        MyEvent myevent(this,"message");
        QApplication::sendEvent(this,&myevent);
    #endif
    });
}

Widget::~Widget()
{
    delete ui;
}
// 专门处理自定义事件
void Widget::customEvent(QEvent *event)
{
    if (event->type() == MyEventId)
      {
          MyEvent* e = dynamic_cast<MyEvent*>(event);
          if (e)
          {
              qDebug()<<"customEvent接收事件:自定义事件:参数:"+e->m_val;
          }
      }
    return QWidget::customEvent(event);
}
// 事件过滤器处理事件。
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == MyEventId)
    {
        MyEvent* e = dynamic_cast<MyEvent*>(event);
        if (e)
        {
            qDebug()<<"eventFilter接收事件:自定义事件:参数:" + e->m_val;
        }
    }
    return QWidget::eventFilter(watched,event);
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值