Qt事件机制概览

栏目: 编程工具 · 发布时间: 6年前

内容简介:Qt事件机制概览

Qt事件机制概览

      • 跨线程的信号和槽与事件循环
      • Native widget or Alien widget
      • 创建QApplication的message-only窗口
      • 派发事件的公共基础方法
      • QApplication的创建过程
      • QWidget native QWidget 的创建过程
      • 普通native widget回调过程
      • QApplication的message-only窗口回调过程

本文内容基于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,程序陷入断点,得到如下调用图:

Qt事件机制概览

可以看到,跨线程的槽调用不是同步的,而是异步的调用,保证了一个对象的槽函数一定会在这个对象所属的线程中执行,而不是其他线程。这里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创建,整个流程是这样的:

Qt事件机制概览
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成员,如果没有指定线程,默认为当前线程。

Qt事件机制概览

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

如果当前线程存在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, (LONG_PTR)eventDispatcher)与窗口关联
      */
        d->createEventDispatcher();
    Q_ASSERT(QCoreApplicationPrivate::eventDispatcher != 0);
  ...
    //将刚创建的eventDispatcher存储在d->threadData->eventDispatcher中
    d->threadData->eventDispatcher = QCoreApplicationPrivate::eventDispatcher;
  ...
 }

回调过程调用派发器的 sendPostedEvents 方法处理排队事件:

void QWindowsGuiEventDispatcher::sendPostedEvents()
{
    //派发postEventList中的QEvent
    QEventDispatcherWin32::sendPostedEvents();
    //处理,封装windowSystemEventsQueued中的WindowSystemEvent并派发
    QWindowSystemInterface::sendWindowSystemEvents(m_flags);
}

注意,GUI线程使用的派发器是QWindowsGuiEventDispatcher,所以除了处理postEventList队列之外,还会处理windowSystemEventsQueued。但是非GUI线程使用的派发器是QEventDispatcherWin32,所以非GUI线程就只负责处理他的postEventList队列。

QEventDispatcherWin32::sendPostedEvents 将Post到当前线程QThreadData::postEventList中的QPostEvent包装着的QEvent发送给QPostEvent**指定的对象**。

void QEventDispatcherWin32::sendPostedEvents()
{
    Q_D(QEventDispatcherWin32);
    QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
}
void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)
{
        //只能发送事件给属于该线程的对象
      if(receiver && receiver->d_func()->threadData != data)
       {
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
      }

      ++data->postEventList.recursion;
      QMutexLocker locker(&data->postEventList.mutex);

      data->canWait = (data->postEventList.size() == 0);

    /*
    * 如果线程上没有QPostEvent列队,或者接收者不存在,或者接收者的postedEvents数目为0,则
      返回
    */
      if (data->postEventList.size() == 0 || 
         (receiver && !receiver->d_func()->postedEvents)) {
        --data->postEventList.recursion;
        return;
    }

     data->canWait = true;

    //postEventList是一个特殊的循环队列
    int startOffset = data->postEventList.startOffset;
    int &i = (!event_type && !receiver) ? 
                                data->postEventList.startOffset : startOffset;
    data->postEventList.insertionOffset = data->postEventList.size();

     //负责退栈之后的清理
     CleanUp cleanup(receiver, event_type, data);

  //循环处理
  while (i < data->postEventList.size()) {
       // avoid live-lock:由于当前线程处理的同时,其他线程还可以继续向这个线程
       // 的postEventList中列队事件,所以不能以消费普通的循环队列的方式消费这个
       // 这个队列的事件,这有可能造成活锁
        if (i >= data->postEventList.insertionOffset)
            break;

        const QPostEvent &pe = data->postEventList.at(i);
        ++i;

        if (!pe.event)
            continue;

        if ((receiver && receiver != pe.receiver) || 
            (event_type && event_type != pe.event->type())) 
        {
            data->canWait = false;
            continue;
        }
        ...
        pe.event->posted = false;
        QEvent *e = pe.event;
        QObject * r = pe.receiver;

        --r->d_func()->postedEvents;
        Q_ASSERT(r->d_func()->postedEvents >= 0); 
        const_cast<QPostEvent &>(pe).event = 0;

        struct MutexUnlocker
        {
            QMutexLocker &m;
            MutexUnlocker(QMutexLocker &m) : m(m) { m.unlock(); }
            ~MutexUnlocker() { m.relock(); }
        };
        MutexUnlocker unlocker(locker);

        // will delete the event (with the mutex unlocked)
        QScopedPointer<QEvent> event_deleter(e);

        // after all that work, it's time to deliver the event.
        QCoreApplication::sendEvent(r, e);
  }//  while !

}

QWindowSystemInterface::sendWindowSystemEvents 将Post到 windowSystemEventsQueued 上的WindowSystemEvent封装成QEvent,然后发送给 特定的对象

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;
        nevents++;

       //进行封装派发
        QGuiApplicationPrivate::processWindowSystemEvent(event);
        delete event;
    }

    return (nevents > 0);
}

派发事件的公共基础方法

掠过细节不谈,瞧瞧与搞Qt界面开发的 程序员 最接近的一个相关函数:

virtual bool QApplication::notify(QObject *receiver, QEvent *e)

bool QApplication::notify(QObject *receiver, QEvent *e)
 {
    if (Q_UNLIKELY(!receiver)) {
            return true;
        }

  ... 
    switch (e->type()) {
        ... //省略各种事件case...

        //以滚轮事件为例:  
        case QEvent::Wheel:
        {
            //进行强制类型转换
            QWidget* w = static_cast<QWidget *>(receiver);
            QWheelEvent* wheel = static_cast<QWheelEvent*>(e);

            // ignore wheel events when a popup (QComboBox) is open.
            if (const QWidget *popup = QApplication::activePopupWidget()) {
                if (w->window() != popup)
                    return true;
            }

            QPoint relpos = wheel->pos();
            bool eventAccepted = wheel->isAccepted();

            if (e->spontaneous() && wheel->phase() == Qt::ScrollUpdate)
                QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos);

           /*
           * 循环,直到事件被某个接收者w正确的处理
           */
            while (w) {
               //事件拷贝到变量we
                QWheelEvent we(relpos, wheel->globalPos(), wheel->pixelDelta(), 
                               wheel->angleDelta(), wheel->delta(), 
                               wheel->orientation(), wheel->buttons(),
                               wheel->modifiers(), wheel->phase(), wheel->source());
                we.spont = wheel->spontaneous();

               /*
               * 将事件传递下去,进行处理,如果返回值res为真且事件被接收,或者到了顶层窗口,
               * 或者w窗口设置了Qt::WA_NoMousePropagation,则停止事件的传递,退出循环。
               * 完成对该事件的处理。否则,将事件转发给w的父窗口处理:
               * w = w->parentWidget();
               */
                res = d->notify_helper(w, w == receiver ? wheel : &we);
                eventAccepted = ((w == receiver) ? wheel : &we)->isAccepted();
                e->spont = false;
                if ((res && eventAccepted)
                    || w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
                    break;

                relpos += w->pos();
                w = w->parentWidget();
            }   // while (w)!

            wheel->setAccepted(eventAccepted);
        } break;  

        ... //省略各种事件case...

    } //switch (e->type())! 


        return res;
 }

上面省略了很多细节和大量的case,如:

case QEvent::MouseButtonPress:
    case QEvent::MouseButtonRelease:
    case QEvent::MouseButtonDblClick:
    case QEvent::MouseMove:
    ...
    case QEvent::ShortcutOverride:
    case QEvent::KeyPress:
    case QEvent::KeyRelease:
    ...

不过,上面的代码也足以说明 QApplication::notify的主要功能了,再来看看notify_helper:

bool QApplicationPrivate::notify_helper(QObject receiver, QEvent e)

bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
{ 
    /*
    * 首先将事件交由安装到QApplication上的事件过滤器进行处理。如果事件被过滤器处理并返回
    * true,则从这里直接返回true
    */
    // send to all application event filters
    if (sendThroughApplicationEventFilters(receiver, e))
        return true;

    if (receiver->isWidgetType()) {
        QWidget *widget = static_cast<QWidget *>(receiver);
      ...

     /*
     * 在事件交由receiver的event处理之前,先将它交给安装到receiver上的过滤器处理。如果事件
     * 被过滤器处理并返回true,则从这里直接返回true
     */   
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, e))
        return true;

    /*
    * 最后,交给receiver的event进行处理,返回event的返回值
    */  
    // deliver the event
    bool consumed = receiver->event(e);
    QCoreApplicationPrivate::setEventSpontaneous(e, false);
    return consumed;
}

事件是否被正确处理由QEvent的 m_accept 字段的值和notify_helper的返回值共同决定。从notify_helper的代码中可以看到,只要安装到应用程序或安装到receiver上的过滤器对象中的任意一个的eventFilter函数返回值为true,事件就不会传递给receiver,无论QEvent的m_accept字段的值是什么,这点都是可以保证的。值得一提的是,QEvent的m_accept字段的值初始化为true,如下:

class Q_CORE_EXPORT QEvent
{  
  ...
    private:
  ...
        ushort m_accept : 1;
  ...
} 

QEvent::QEvent(Type type)
        : ...m_accept(true)...
    {}

所以,我们现在知道notify主要做了什么:

notify将事件转发下去,先将事件交给安装到QApplication上的过滤器处理,如果没有这样的过滤器,就将事件交给安装到接收对象receiver上的过滤器处理,如果没有这样的过滤器,则将事件交给receiver的event进行处理,而receiver的event可以被重写以完成定制的处理功能…,event可以选择将事件分派给专门化的处理器函数进行处理,这样的函数有mousePressEvent,dropEvent,customEven…,不一而足,且和event一样可以被重写定制。如果事件在一轮循环处理中被接收(QEvent的m_accept字段的值为true,这是默认情况,除非你在处理过程中主动ignore这个事件)且从notify_helper返回true,一般就可以结束这个事件的处理循环,否则,就将receiver的父对象设置为下一轮循环的receiver继续该事件的处理过程,直到事件被明确的处理,或者已经到达了顶层窗口。哦,对了,差点忘了说了,notify也是可以重写定制的,要想有更大的监视权,可以继承QApplication,重写notify!

source code

QApplication的创建过程

QWidget [native QWidget ] 的创建过程

Qt5-QWidget::show Qt5-QWidget::setVisible Qt5-QWidget::create Qt5-QWidgetPrivate::create_sys Qt5-QWindow::create Qt5-QWindowPrivate::create Qt5-QWindowsIntegration::createPlatformWindow Qt5-QWindowsWindowData::create 调用Win32API,注册窗口类,创建窗口 Qt5-WindowCreationData::create

普通native widget回调过程

Qt5-qWindowsWndPro Qt5-QWindowsContext::windowsProc

Qt5-QAbstractEventDispatcher::filterNativeEvent Qt5-QWindowsContext::findPlatformWindow(HWND hwnd) Qt5-QWindowSystemInterface::handleNativeEvent QGuiApplicationPrivate::processNativeEvent Qt5-QWindow::nativeEvent

Qt5-QWindowsMouseHandler::translateMouseEvent

QApplication的message-only窗口回调过程


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

电商运营之道:策略、方法与实践

电商运营之道:策略、方法与实践

吴伟定、姚金刚、周振兴、郑琰 / 机械工业出版社 / 2015-9-1 / 49

电商运营之道:策略、方法与实践是一本电商的运营指南,适合所有的电商从业人员阅读,也适合打算进入或打算在电商行业创业的读者朋友阅读。分别从策略、方法与实践三个方面全景式展示电商运营的内在商业规律与管理逻辑。一起来看看 《电商运营之道:策略、方法与实践》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具