Window系统下开发Qt(10)-- Qt事件

概述

在讨论Qt的事件之前,先叙述两个知识点。

1. 典型的win32程序如下所示:

  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
  { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    ...
  } 
  
  LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM, LPARAM)
  {
        switch(message)
        {
            case WM_DESTORY:
            ...
        }
  }  

因为windows消息机制不是本文的重点,因此,暂不详细讨论windows的消息机制。请阅读相关资料。

2.一个典型的QtGUI应用程序,如下代码所示:

int main(int argc, char** argv)
{
       QApplication app(argc, argv);
       QWidget widget;
       widget.show();
       return app.exec();
}

代码中,实例化了一个QApplication对象,Qt应用程序要求,每个程序必须先构造一个QApplication对象,通过这个对象来让程序跑起来。也就是实例化一个QApplication对象后,就为App建立了一个消息循环。并且,此段代码还实例化了一个QWidget对象,通过show方法,让widget显示出来。

Qt的事件分为两种:

  1. 与用户交互时发生。比如移动鼠标(enterEvent),敲击键盘(keyPressEvent)等。
  2. Qt程序自己发生,比如计时器事件(timerEvent)等。

QApplication的消息循环

以上面的QtGUI例程为例子,从起点开始分析,Qt的消息循环是如何建立起来的。

QApplication的exec的实现如下:

int QApplication::exec()
{
    int r = QGuiApplication::exec();
    QApplication *app = qobject_cast<QApplication*>(QCoreApplication::instance());
    if (app && app->hasDeviceLost())    // TDR has priority
        return 0xdeeedeee;
    return r;
}

可以看到,QApplication的exec内部实现是调用基类的exec。深入内部查看源码发现,最终调用了基类QCoreApplication::exec。

int QCoreApplication::exec()
{
...

    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;
}

QCoreApplication::exec的内部创建了一个QEventLoop,并调用了QEventLoop的exec,正是这个函数开启了消息循环。继续分析QEventLoop::exec的实现。

int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);

...

    // remove posted quit events when entering a new event loop
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);

...

    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);

    ref.exceptionCaught = false;
    return d->returnCode.load();
}

这个方法的主体是 while 循环,在 exit之前,会一直循环调用 processEvents() 方法。看来重点是processEvents,processEvents的实现如下:

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    if (!d->threadData->hasEventDispatcher())
        return false;
    return d->threadData->eventDispatcher.load()->processEvents(flags);
}

processEvents方法转调了eventDispatcher的processEvents。eventDispatcher的类型是QAbstractEventDispatcher,这是个抽象类,因此,很容易想到,这里使用了多态的方式。

的确,在不同的平台上,eventDispatcher的实现类是不一样的,在Windows上的实现是QWindowsGuiEventDispatcher,本篇主要讨论Windows平台。其他的平台暂不讨论。QWindowsGuiEventDispatcher内部又转调QEventDispatcherWin32::processEvents,下面代码是QEventDispatcherWin32::processEvents的实现。 

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
    Q_D(QEventDispatcherWin32);

    if (!d->internalHwnd) {
        createInternalHwnd();
        wakeUp(); // trigger a call to sendPostedEvents()
    }

    d->interrupt.store(false);
    emit awake();

    bool canWait;
    bool retVal = false;
    bool seenWM_QT_SENDPOSTEDEVENTS = false;
    bool needWM_QT_SENDPOSTEDEVENTS = false;
    do {
        DWORD waitRet = 0;
        DWORD nCount = 0;
        HANDLE *pHandles = nullptr;
        if (d->winEventNotifierActivatedEvent) {
            nCount = 1;
            pHandles = &d->winEventNotifierActivatedEvent;
        }
        QVarLengthArray<MSG> processedTimers;
        while (!d->interrupt.load()) {
            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 {
                haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
                if (haveMessage) {
                    if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
                        && isUserInputMessage(msg.message)) {
                        // queue user input events for later processing
                        d->queuedUserInputEvents.append(msg);
                        continue;
                    }
                    if ((flags & QEventLoop::ExcludeSocketNotifiers)
                        && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
                        // queue socket events for later processing
                        d->queuedSocketEvents.append(msg);
                        continue;
                    }
                }
            }
            if (!haveMessage) {
                // no message - check for signalled objects
                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) {
                // WinCE doesn't support hooks at all, so we have to call this by hand :(
                if (!d->getMessageHook)
                    (void) qt_GetMessageHook(0, PM_REMOVE, reinterpret_cast<LPARAM>(&msg));

                if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
                    if (seenWM_QT_SENDPOSTEDEVENTS) {
                        // when calling processEvents() "manually", we only want to send posted
                        // events once
                        needWM_QT_SENDPOSTEDEVENTS = true;
                        continue;
                    }

                    seenWM_QT_SENDPOSTEDEVENTS = true;
                } else if (msg.message == WM_TIMER) {
                    // avoid live-lock by keeping track of the timers we've already sent
                    bool found = false;
                    for (int i = 0; !found && i < processedTimers.count(); ++i) {
                        const MSG processed = processedTimers.constData()[i];
                        found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam = msg.lParam);
                    }
                    if (found)
                        continue;
                    processedTimers.append(msg);
                } else if (msg.message == WM_QUIT) {
                    if (QCoreApplication::instance())
                        QCoreApplication::instance()->quit();
                    return false;
                }

                if (!filterNativeEvent(false, QAbstractNativeEventFilter::WinProcessMsg, ByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            } else if (waitRet - WAIT_OBJECT_0 < nCount) {
                activateEventNotifiers();
       
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值