Qt事件机制概览

Posted unclerunning

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt事件机制概览相关的知识,希望对你有一定的参考价值。

Qt事件机制概览

本文内容基于window平台进行Qt事件机制的简要梳理

消息循环

windows窗口所发生的一切都是通过消息传给窗口过程,然后窗口过程以某种形式对消息做出反应,或是把消息传递给DefWindowProc进行默认处理。windows的每个窗口线程都有各自的消息队列,线程可以循环的获取队列中的消息:

while(GetMessage(&msg,NULL,0,0))
  
    TranlateMessage(&msg);
    DispatchMessage(&msg); //--->调用窗口过程
  

从单独一个线程的角度看,他们各自的消息循环都是序列化的:

获取消息—》分发消息到窗口过程—》窗口过程处理消息并返回—》获取消息

窗口过程应尽可能快的处理消息并返回消息循环,否则,在窗口过程卡在一个十分耗时的消息的处理上时,应用程序的这个窗口所在的线程的消息循环就会卡在这里,这就导致这个窗口看上去卡死在桌面上,关也关不了,移也移不动。事实是,在消息循环阻塞时,操作系统能感知用户的操作,并改变窗口句柄在内核中的对象的数据,也会将消息发送到窗口线程的消息队列中。例如,当用户改变窗口大小时,窗口的内核对象会记录窗口改变后的大小、无效区域等信息,然后发送WM_PAINT消息给窗口线程的消息队列,当阻塞的窗口过程从耗时的消息返回后,回到事件循环,继续从消息队列中获取消息,就能获得阻塞时操作系统发送到线程队列中的大量的延时消息,并处理这些消息。

Qt事件循环

简介

int main(int argc, char *argv[])
 
    QApplication a(argc,argv);
    ...
    return a.exec();
 

事件循环一般以exec调用开始,例如QApplication::exec()、QDialog::exec()、QMenu::exec()…,其实他们最后都依赖于QEventLoop来创建一个事件循环:

The QEventLoop class provides a means of entering and leaving an event loop.

At any time, you can create a QEventLoop object and call exec() on it to start a local event loop. From within the event loop, calling exit() will force exec() to return.

QEventLoop是对事件循环的抽象,一个线程可以有多个嵌套的事件循环

  • int QEventLoop::exec()

  • void OnXXXSlot()

    
    QEventLoop loop;
    ...
    loop.exec(); 
    

上面的loop就是一个嵌套的事件循环,loop的嵌套使得OnXXXSlot函数在loop结束之前不会返回,函数栈也就一直存在,所以在函数体中创建的栈对象得以长时间的存在。

局部的QEventLoop和QApplication创建的QEventLoop的功能是没差别的,局部的事件循环嵌套在上一层的事件循环中,可以替代外层的循环处理事件:

  class MyClass : public QWidget
  
    Q_OBJECT //如果不需要信号和槽功能,则可以将Q_OBJECT宏去掉
  public:
    MyClass(QWidget *parent = 0):QWidget(parent)
  protected:
    void mousePressEvent(QMouseEvent * event)
        
          static int level = 0;
          m_label.setText(QString("Enter : %1").arg(++level));
        
  private:
    QLabel m_label;
  ;


  class Widget : public  QWidget
  
  public:
    Widget(QWidget *_p = nullptr) :QWidget(_p) 

  protected:

    void mousePressEvent(QMouseEvent *e)
    
        static int level = 0;
        m_label.setText(QString("Enter : %1").arg(++level));
        //创建并启动一个局部的事件循环作为线程当前的事件循环
        QEventLoop loop;
        loop.exec();
    
  QLabel m_label;
  ;

  int main(int argc, char *argv[])
  
    QApplication a(argc, argv);
    MyClass w;
    Widget cw(&w);
    w.show();
    return a.exec();
  

上面的程序创建了两个窗口,在点击窗口cw之前,只有一个最外层的循环,是QApplication创建的。这时点击w产生的QMouseEvent是通过这个循环传递给w的。第一次点击cw时产生的QMouseEvent也是这个循环传递的。之后,点击w获得的QMouseEvent则是来自于cw的mousePressEvent创建的局部事件循环

QEventLoop

当这样做时:


 ... 
 QEventLoop loop
 loop.exec();
 ...

就创建了一个事件循环,那么QEventLoop::exec干了什么?

先看构造函数

QEventLoop::QEventLoop(QObject *parent)
    : QObject(*new QEventLoopPrivate, parent)

    Q_D(QEventLoop);
    //QApplication是所有线程共享的对象,全局且唯一  
    if (!QCoreApplication::instance()) 
        qWarning("QEventLoop: Cannot be used without QApplication");
     else if (!d->threadData->eventDispatcher.load()) 
    //如果当前线程还没有事件派发器,那就创建一个
        QThreadPrivate::createEventDispatcher(d->threadData);
    

两点,一,QApplication是所有线程共享的对象,全局且唯一 ;二,一个线程有且只有一个eventDispatcher,如果不存在,则创建一个。而且,由于QEventLoop不能在QApplication之前创建,所以,如果QEventLoop是在GUI线程中构造,那么eventDispatcher早在QApplication构造时就被创建了,所以免了自己创建eventDispatcher的步骤。如果QEventLoop是在非GUI线程中构造呢?这种情况肯定是存在的,因为非GUI线程可能也需要处理事件,这些事件不是来自可见窗口,而是来自自己或其他线程。例如,使用跨线程的信号和槽。下面看看在非GUI下的QEventLoop:

class MyThread :public QThread

    void run() Q_DECL_OVERRIDE
        //start a event loop
        this->exec();
    

;

int main(int argc, char *argv[])

    QApplication a(argc, argv);

    MyThread t;
    t.start();

    return a.exec();

现在,程序有两个线程,一个是GUI线程,一个是线程t。GUI线程有一个事件循环,在a.exec中创建并启动,线程t也有一个事件循环,在t.exec中创建并启动。

就像在GUI线程中的事件循环需要使用一个事件派发器一样,任何一个线程中的事件循环都需要一个派发器。GUI线程中的事件派发器是在构造QApplication时创建的,是一个QWindowsGuiEventDispatcher类的派发器,在这个派发器的构造函数中同时还创建了一个message-only窗口。

QWindowsGuiEventDispatcher::QWindowsGuiEventDispatcher(QObject *parent) :
    QEventDispatcherWin32(parent), m_flags(0)

    setObjectName(QStringLiteral("QWindowsGuiEventDispatcher"));
    //创建 message-only 窗口
    createInternalHwnd(); 

对于需要事件循环的非GUI线程,message-only窗口是不可或缺的,因为没有他,线程就没有消息队列,何谈消息循环,除非Qt使用另外的机制而非消息循环机制来支持非GUI线程的事件循环,不过这完全没必要。我们来看看这些步骤在非GUI线程中是怎么完成的:

第一步,创建一个事件派发器

事件派发器在t.start中被创建:

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);
    data->threadId = reinterpret_cast<Qt::HANDLE>(GetCurrentThreadId());
  ...
    if (data->eventDispatcher.load()) // custom event dispatcher set?
        data->eventDispatcher.load()->startingUp();
    else
        createEventDispatcher(data); //创建事件派发器
  ...

创建的是一个QEventDispatcherWin32类的事件派发器,它并不像QWindowsGuiEventDispatcher一样在构造的同时还创建 message-only 窗口。

第二步,创建一个 message-only 窗口

如果在启动事件循环的过程中发现当前的事件派发器还没有创建 message-only 窗口的话,那就会为其创建一个这样的窗口。

bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)

      Q_D(QEventDispatcherWin32);

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

对比GUI线程创建事件派发器和message-only窗口的一步到位,非GUI线程采用延迟的方式来处理。为什么要这样做呢?像GUI线程一步到位不行吗?当然可以,但是没必要,因为创建一个 message-only 窗口是要占用内核资源的,GUI线程一定需要一个消息循环来实现事件循环,所以一步到位的创建没毛病,但是非GUI线程可能根本就不需要一个事件循环,所以,白白浪费资源干嘛呢?

跨线程的信号和槽与事件循环

TestOb.h

#ifndef TESTOB
#define TESTOB

#include <QObject>

class TestOb : public QObject

    Q_OBJECT

public:
    TestOb(QObject *parent = 0) :QObject(parent)

    public slots :
        void updateNumber(int num)
            m_num = num;
            sendChangedeSignal(m_num);
        
signals:
    void sendChangedeSignal(int num);

private:
    int  m_num = 0;
;

#endif

testUI.h

#ifndef TESTUI
#define TESTUI

#include <QtWidgets/QApplication>
#include <QLabel>
#include <QGridLayout>


class TestUI : public QWidget

    Q_OBJECT

public:
    TestUI(QWidget *parent = 0) :QWidget(parent), m_pLabel(nullptr), m_layout(this)

        setGeometry(600, 200, 50, 200);
        m_pLabel = new QLabel(this);
        m_pLabel->setText("0");
        m_layout.addWidget(m_pLabel);
        setLayout(&m_layout);
    

    public slots :
        void updateNumber(int num)
            if (m_pLabel)
                m_pLabel->setText(QString("%1").arg(num));

            emit sendChangedeSignal(m_pLabel->text().toInt() + 1);
        
signals:
    void sendChangedeSignal(int num);
protected:
    void mousePressEvent(QMouseEvent * event)
        emit sendChangedeSignal(m_pLabel->text().toInt() + 1);
    
private:
    QGridLayout m_layout;
    QLabel  *m_pLabel;
;

#endif

Mythread.h

#ifndef MYTHREAD
#define MYTHREAD

#include <QThread>
#include <QEventLoop>
#include "testOb.h"

class MyThread :public QThread

    Q_OBJECT

    void run() Q_DECL_OVERRIDE

        TestOb Ob;
        QObject::connect(this, SIGNAL(sendChangedeSignal_1(int)), 
                         &Ob, SLOT(updateNumber(int)));
        QObject::connect(&Ob, SIGNAL(sendChangedeSignal(int)), 
                         this, SLOT(updateNumber_1(int)));

        //start a event loop in this thread
        QEventLoop loop;
        loop.exec();
    

    public slots:
    void updateNumber(int num)
        emit sendChangedeSignal_1(num);
    
    void updateNumber_1(int num)
        emit sendChangedeSignal(num);
    
signals:
    void sendChangedeSignal(int num);
    void sendChangedeSignal_1(int num);

;

#endif
#include "Mythread.h"
#include "testUI.h"

int main(int argc, char *argv[])

    QApplication a(argc, argv);

    MyThread t;//线程t,在gui线程中创建

    TestUI ui; //ui界面,在gui线程中创建
    QObject::connect(&ui, SIGNAL(sendChangedeSignal(int)), 
                     &t, SLOT(updateNumber(int)));
    QObject::connect(&t, SIGNAL(sendChangedeSignal(int)), 
                     &ui, SLOT(updateNumber(int)));
    ui.show();

    //启动线程
    t.start();

    return a.exec();

解释下上面的实验代码:

GUI线程创建了ui,创建了一个线程t,ui和t同属于GUI线程。然后建立了ui–>t以及t–>ui的两个同线程的信号和槽链接。然后开始运行t线程。在线程t的 run方法中,创建了ob对象,然后建立了t–>ob以及ob–>t的跨线程的信号和槽链接,接着启动一个事件循环。程序主要做的就是:点击ui,发出信号,将信号转给同属于GUI线程的对象t的槽,t的槽又将信号发给属于非GUI线程的ob,ob记下信号值,然后将信号发给t,t又发给ui进行回显。这里从t–>ob以及从ob–>t的链接是跨线程的。

在TestOb的updateNumber槽上打上断点,运行程序,点击一下ui,程序陷入断点,得到如下调用图:

可以看到,跨线程的槽调用不是同步的,而是异步的调用,保证了一个对象的槽函数一定会在这个对象所属的线程中执行,而不是其他线程。这里ob属于t线程,所以ob的槽函数就是在t线程中执行的。

实际上,跨线程的槽是以事件的形式异步调用的,调用事件的派发使用

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)

完成,这个函数将从这个线程的post事件队列(不是线程的消息队列而是QThreadData::postEventList)中取出事件,然后派送给出去:

bool QObject::event(QEvent *e)

  ....
      case QEvent::MetaCall:
        
            QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e);

            QConnectionSenderSwitcher sw(this, const_cast<QObject*>(mce->sender()), mce->signalId());

            //转到qt_static_metacall,根据下标调用相应的槽函数
            mce->placeMetaCall(this); 
            break;
        

  ...
void QMetaCallEvent::placeMetaCall(QObject *object)

    if (slotObj_) 
        slotObj_->call(object, args_);
     else if (callFunction_ && method_offset_ <= object->metaObject()->methodOffset()) 
        //调用qt_static_metacall
        callFunction_(object, QMetaObject::InvokeMetaMethod, method_relative_, args_);
     else 
        QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, method_offset_ + method_relative_, args_);
    
void TestOb::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)

    if (_c == QMetaObject::InvokeMetaMethod) 
        TestOb *_t = static_cast<TestOb *>(_o);
        Q_UNUSED(_t)
        switch (_id) 
        case 0: _t->sendChangedeSignal((*reinterpret_cast< int(*)>(_a[1]))); break;
         //调用updateNumber  
        case 1: _t->updateNumber((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        
     else if (_c == QMetaObject::IndexOfMethod) 
        int *result = reinterpret_cast<int *>(_a[0]);
        void **func = reinterpret_cast<void **>(_a[1]);
        
            typedef void (TestOb::*_t)(int );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&TestOb::sendChangedeSignal)) 
                *result = 0;
            
        
    

那么qt在什么地方决定这个信号槽调用要以事件的形式进行异步而不是同步的调用呢?毕竟我们没有明确的告诉qt这个信号槽的链接是异步的啊。这就要从信号的发射说起,信号发射的主要实现在这里:

void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)

  ...
    ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
    const QObjectPrivate::ConnectionList *list;
    list = &connectionLists->at(signal_index);

  do // !
        QObjectPrivate::Connection *c = list->first;
        if (!c) continue;
        // We need to check against last here to ensure that signals added
        // during the signal emission are not emitted in this emission.
        QObjectPrivate::Connection *last = list->last;
    do // !
         if (!c->receiver)
            continue;
        QObject * const receiver = c->receiver;

        //发送方和接收方是否在同一个线程中
        const bool receiverInSameThread = 
        currentThreadId == receiver->d_func()->threadData->threadId;

        // determine if this connection should be sent immediately or
        // put into the event queue
        if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
            || (c->connectionType == Qt::QueuedConnection))
          
            //列队发送
            queued_activate(sender, signal_index, c, argv ?
                            argv : empty_argv, locker);
                continue;
            else if (c->connectionType == Qt::BlockingQueuedConnection)
            //postEvent
                locker.unlock();
                if (receiverInSameThread) 
                    qWarning("Qt: Dead lock detected while activating a"   
                             "BlockingQueuedConnection: "
                             "Sender is %s(%p), receiver is %s(%p)",
                    sender->metaObject()->className(), sender,
                    receiver->metaObject()->className(), receiver);
                
                QSemaphore semaphore;
                QMetaCallEvent *ev = c->isSlotObject ?
                    new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, 
                                       argv ? argv : empty_argv, &semaphore) :
                    new QMetaCallEvent(c->method_offset, c->method_relative, 
                                       c->callFunction, sender, signal_index, 0, 0, 
                                       argv ? argv : empty_argv, &semaphore);
                //post到事件队列中    
                QCoreApplication::postEvent(receiver, ev);
                semaphore.acquire();
                locker.relock();
                continue;
            

         //直接调用关联的槽
          QConnectionSenderSwitcher sw;
          if (receiverInSameThread) 
              sw.switchSender(receiver, sender, signal_index);
            
         const QObjectPrivate::StaticMetaCallFunction callFunction = c->callFunction;
         const int method_relative = c->method_relative;
         if (c->isSlotObject) 
           ...
            QScopedPointer<QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
             obj->call(receiver, argv ? argv : empty_argv);
           ...
         else if (callFunction && c->method_offset <= 
                   receiver->metaObject()->methodOffset()) 
           ...
            callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, 
                         argv ? argv : empty_argv);
           ...
         else 
                const int method = method_relative + c->method_offset;
           ...
             metacall(receiver, QMetaObject::InvokeMetaMethod, method, 
                      argv ? argv : empty_argv);
           ...
         

    while(c != last && (c = c->nextConnectionList) != 0); //!

    if (connectionLists->orphaned)
            break;

  while (list != &connectionLists->allsignals &&
        //start over for all signals;
        ((list = &connectionLists->allsignals), true));//!

关联到某个信号的所有槽都在这里的得到处理,要么立即被调用,这是发送者和接收者在同一个线程中时的默认处理方式;要么列队发送,这是发送者和接收者不在同一个线程中时的默认处理方式。

static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv,
                            QMutexLocker &locker)

  ...
     QMetaCallEvent *ev = c->isSlotObject ?
     new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
     new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, 
                        sender, signal, nargs, types, args);

    //post事件到接收者所属线程的QThreadData::postEventList队列中
     QCoreApplication::postEvent(c->receiver, ev);
  ...

来看看这个作为基础设施postEvent函数:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)

     if (receiver == 0) 
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    

    QThreadData * volatile * pdata = &receiver->d_func()->threadData;
    QThreadData *data = *pdata;
    if (!data) 
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    

      // lock the post event mutex
    data->postEventList.mutex.lock();
      // if object has moved to another thread, follow it
    while (data != *pdata) 
        data->postEventList.mutex.unlock();

        data = *pdata;
        if (!data) 
            // posting during destruction? just delete the event to prevent a leak
            delete event;
            return;
        
        //尝试获取锁
        data->postEventList.mutex.lock();
    

  //防止资源泄漏
   QMutexUnlocker locker(&data->postEventList.mutex);

    //如果这个事件和post队列中的事件可以合并,那就合并,提高事件吞吐量
      // if this is one of the compressible events, do compression
   if (receiver->d_func()->postedEvents
        && self && self->compressEvent(event, receiver, &data->postEventList)) 
        return;
   

  //如果是一个删除事件并且接收者和发送者位于同一线程,则在event中记录当前线程的事件循环层次
   if (event->type() == QEvent::DeferredDelete && data == QThreadData::current())
   
        // remember the current running eventloop for DeferredDelete
        // events posted in the receiver's thread
        static_cast<QDeferredDeleteEvent *>(event)->level = data->loopLevel;
    

    // delete the event on exceptions to protect against memory leaks till the 
    // event is properly owned in the postEventList
    QScopedPointer<QEvent> eventDeleter(event);

    //将事件添加到postEventList中
    data->postEventList.addEvent(QPostEvent(receiver, event, priority));
    eventDeleter.take();
    event->posted = true;
    ++receiver->d_func()->postedEvents;
    data->canWait = false;
    locker.unlock();

    //唤醒接收者所在线程的事件派发器:
    //所谓唤醒,其实就是利用windows的消息循环机制,向接收者线程的dispatcher的message-only窗
    //口句柄发送一个1025号消息。dispatcher的线程的消息循环Peek到这条消息后,就会去处理他的队
    //列消息
    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    if (dispatcher)
        dispatcher->wakeUp();

模态窗口

有时候,需要一个窗口屏蔽掉其他窗口的消息,在windows上,这样的窗口叫模态窗口。创建模态窗口后,在关闭它之前,不允许用户操作其他窗口。模态窗口可以和局部事件循环结合,从而允许在栈上创建一个模态窗口:

class Widget : public  QWidget

public:
    Widget(QWidget *_p = nullptr) :QWidget(_p) 

protected:

    void mousePressEvent(QMouseEvent *e)
    
        MyClass w;
        //设置窗口为模态窗口,模态窗口一定是个native widget
        w.setAttribute(Qt::WA_ShowModal, true);
        //创建QWindow+QWindowsWindow,调用Win32API创建窗口并显示窗口
        w.show();
        //开启局部事件循环
        QEventLoop loop;
        loop.exec();
    

;

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();

Native widget or Alien widget

Native widget是指拥有windows窗口句柄的widget,占用了内核资源,Alien widget是指依附在某个Native widget上的子窗口,没有windows窗口句柄,不占用内核资源。在qt4.4之前,所有的widget都是Native widget,而且伴随着被人诟病的闪烁现象。qt4.4之后,使用了Alien widget,子窗口默认是一个Alien widget,除非必要,qt不会创建一个Native widget。qt想尽可能地把对窗口的处理从内核转移到qt上,从而拥有更大的自主权。widget的Alien widget和Native widget属性是可以配置的,如果你确实需要一个Native widget,你可以对属性进行显示的配置

setAttribute(Qt::WA_NativeWindow);

创建Native widget

如果一个窗口是Alien widget,没有窗口句柄,那他怎么得到windows的消息呢?不能,可以肯定的是一个没有窗口句柄的Alien widget是无法被操作系统感知的。所以,在windows之上,qt建立了自己的窗口系统。然而,qt程序逃避不了的一个事实是:它需要获取windows的消息。要获取操作系统消息的前提是拥有窗口句柄,所以,qt的窗口程序一般是这样的:

一个Native widget作为顶层窗口,一些Alien widget窗口作为顶层窗口的后代,依附其上。

这样形式的qt程序,对于操作系统来说,它只看到了Native widget,所以它就认为消息是传递给这个Native widget的;对于qt的窗口系统来说,它看到的是依附于Native widget上绘制出来的数目众多的Alien widget,qt内部再确定该事件正真的目的地是哪个窗口。

对,没毛病,windows的观点没错,qt的观点就更对了。

创建一个Native widget就意味着需要注册一个窗口类或者使用同名的已经注册过的窗口类,然后创建一个内核窗口对象,并返回窗口句柄。

QWidget注册窗口类时使用的窗口过程为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); 
...
    //qt不感兴趣的消息,直接交由DefWindowProc处理
    if (!handled)
        result = DefWindowProc(hwnd, message, wParam, lParam);
    return result;

例如,当你点击窗口时,windows感知到了用户对native widget的点击操作,并将消息发送给native widget所在的线程的消息队列,在qt的消息循环中能够GetMessage获取消息,qt使用GetMessage的非阻塞版PeekMessage获取线程队列中的消息,然后通过DispatchMessage将消息转给Native widget的这个窗口过程qWindowsWndProc

窗口过程将消息映射到系统事件类型WindowsEventType,case各种类型,最后将封装好的WindowSystemEvent格式的系统事件放入QWindowSystemInterfacePrivate的全局系统事件队列中。

对,一般都是将事件放入windowSystemEventQueue队列中排队。然后就一步步的返回了,最后从DispatchMessage回到事件循环。

如果遇到qt不干兴趣的消息,他不会将其放入windowSystemEventQueue队列,而是从windowsProc返回false,交给DefWindowProc进行默认处理。

void QWindowSystemInterfacePrivate::handleWindowSystemEvent(
  QWindowSystemInterfacePrivate::WindowSystemEvent *ev)

    if (synchronousWindowsSystemEvents) 
    //同步处理
        QGuiApplicationPrivate::processWindowSystemEvent(ev);//直接处理
     else 
    //异步处理

        windowSystemEventQueue.append(ev);//消息排队

        QAbstractEventDispatcher 
          *dispatcher = QGuiApplicationPrivate::qt_qpa_core_dispatcher();
        if (dispatcher)
            dispatcher->wakeUp();//发送WM_QT_SENDPOSTEDEVENTS(1025)给QApplication的
                                //message-only窗口
    

上面提到的是qt不感兴趣的消息和会放入windowSystemEventQueue队列中的消息。其实,还有一些消息可以通过安装的nativeEventFilter进行过滤。

这么说来,Native widget的窗口过程没有对消息进行有效的回应嘛。确实是这样的,这个回调过程主要负责将消息封装成系统事件,然后将其放入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);
    

Windows消息有两种传送方式,一种是Post语义的传送方式,还有一种是Send语义的传送方式,Post将消息排队到线程的消息队列中,Send则直接将消息传送到窗口的窗口过程。

将系统事件排队之后,需要保证线程消息队列中一定存在且只存在一条到d->internalHwnd窗口的1025号消息WM_QT_SENDPOSTEDEVENTS

WM_USER = 0x400 = 1024

消息范围说 明
0 ~ WM_USER – 1系统消息
WM_USER ~ 0x7FFF自定义窗口类整数消息
WM_APP ~ 0xBFFF应用程序自定义消息
0xC000 ~ 0xFFFF应用程序字符串消息
0xFFFF为以后系统应用保留

创建QApplication的message-only窗口

在定义一个QApplication的变量时,构造函数就在内部创建一个专门用来处理qt事件的message-only窗口,这个窗口有窗口句柄,但是不显示。这个窗口类注册的窗口过程是qt_internal_proc, 通过qt_create_internal_window创建,整个流程是这样的:



static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)

    QWindowsMessageWindowClassContext *ctx = qWindowsMessageWindowClassContext();
    if (!ctx->atom)
        return 0;
#ifdef Q_OS_WINCE
    HWND parent = 0;
#else
    HWND parent = HWND_MESSAGE; //message-only
#endif
    HWND wnd = CreateWindow(ctx->className,    // classname
                            ctx->className,    // window name
                            0,                 // style
                            0, 0, 0, 0,        // geometry
                            parent,            // parent
                            0,                 // menu handle
                            qWinAppInst(),     // application
                            0);                // windows creation data.

    if (!wnd) 
        qErrnoWarning("%s: CreateWindow() for QEventDispatcherWin32 internal window failed", Q_FUNC_INFO);
        return 0;
    

  //关联一个32位的值: 事件派发器的地址eventDispatcher
#ifdef GWLP_USERDATA
    SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)eventDispatcher);
#else
    SetWindowLong(wnd, GWL_USERDATA, (LONG)eventDispatcher);
#endif

    return wnd;

qWindowsMessageWindowClassContext构造完成的同时注册了窗口类,这个窗口类的窗口过程代码如下:

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_SENDPOSTEDEVENTS //WM_QT_SENDPOSTEDEVENTS
               // we also use a Windows timer to send posted events when the message 
               //queue is full
               || (message == WM_TIMER  //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);

QAbstractEventDispatcher::instance(QThread *thread)获取存储在指定线程的TLS中的QThreadData的eventDispatcher成员,如果没有指定线程,默认为当前线程。



于是,上面的代码的意图就很明确了:

如果当前线程存在dispatcher,则先使用当前线程的dispatcher对原始消息调用安装到dispatcher上的nativeEventFilter进行过滤处理。对未过滤的消息使用message-only窗口关联的dispatcher。但其实,这两个dispatcher实际上是同一个。

就拿GUI线程来说:

QCoreApplication::init()
 
  ...
     // use the event dispatcher created by the app programmer (if any)
    if (!QCoreApplicationPrivate::eventDispatcher)
        QCoreApplicationPrivate::eventDispatcher = 
      d->threadData->eventDispatcher.load();
    // otherwise we create one
    if (!QCoreApplicationPrivate::eventDispatcher)
      /*
      *  创建一个QWindowsGuiEventDispatcher;
      *  调用createInternalHwnd创建一个message-only窗口;
      *  SetWindowLongPtr(wnd, GWLP_USERDATA, (LON

以上是关于Qt事件机制概览的主要内容,如果未能解决你的问题,请参考以下文章

Qt消息机制和事件

BroadcastReceiver机制-Android12

BroadcastReceiver机制-Android12

Qt ------ 事件处理机制

Qt 事件处理机制

QT开发——QT事件处理机制