Qt源码阅读(四) 事件循环

打印 上一主题 下一主题

主题 741|帖子 741|积分 2238

事件系统

文章为本人理解,如有理解不到位之处,烦请各位指正。
@
目录

Qt的事件循环,应该是所有Qter都避不开的一个点,所以,这篇博客,咱们来了解源码中一些关于Qt中事件循环的部分。
先抛出几个疑问,根据源代码,下面一一进行解析。
什么是事件循环?

对于Qt事件循环个人理解是,事件循环是一个队列去循环处理事件。当队列中有事件时,则去处理事件,如果没有事件时,则会阻塞等待。
事件是如何产生的?

事件的产生可以分为两种:

  • 程序外部产生
  • 程序内部产生
程序外部所产生的事件主要是指系统产生的事件,比如说鼠标按下(MouseButtonPress)、按键按下(KeyPress)等,Qt捕捉系统的事件,然后将系统事件封装成自己的QEvent类,再将事件发送出去。
程序内部产生的事件主要指我们在代码里,手动创建一个事件,然后将事件通过sendEvent/postEvent,来发送到事件循环中。而sendEvent和postEvent区别又在于一个是阻塞的(sendEvent)一个是非阻塞的(postEvent)。
我们结合源码分析,看一下sendEvent和postEvent分别干了什么导致一个是阻塞的一个是非阻塞的。
sendEvent

完整源码如下:
  1. bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
  2. {
  3.         // sendEvent是阻塞调用
  4.     Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());
  5.     if (event)
  6.         event->spont = false;
  7.     return notifyInternal2(receiver, event);
  8. }
复制代码
可以看到,sendEvent是调用了notifyInternal2这个函数
  1. bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
  2. {
  3.         ...
  4.     // Qt enforces the rule that events can only be sent to objects in
  5.     // the current thread, so receiver->d_func()->threadData is
  6.     // equivalent to QThreadData::current(), just without the function
  7.     // call overhead.
  8.     // 事件只能在同一个线程被send
  9.     QObjectPrivate *d = receiver->d_func();
  10.     QThreadData *threadData = d->threadData;
  11.     QScopedScopeLevelCounter scopeLevelCounter(threadData);
  12.     if (!selfRequired)
  13.         return doNotify(receiver, event);
  14.     return self->notify(receiver, event);
  15. }
复制代码
进一步跟踪到其doNotify函数
  1. static bool doNotify(QObject *receiver, QEvent *event)
  2. {
  3.     if (receiver == nullptr) {                        // serious error
  4.         qWarning("QCoreApplication::notify: Unexpected null receiver");
  5.         return true;
  6.     }
  7. #ifndef QT_NO_DEBUG
  8.         // 检查接受线程与当前是否同线程
  9.     QCoreApplicationPrivate::checkReceiverThread(receiver);
  10. #endif
  11.         // QWidget类必须用QApplication
  12.     return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
  13. }
复制代码
再到QCoreApplicationPrivate::notify_helper
  1. bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
  2. {
  3.     // Note: when adjusting the tracepoints in here
  4.     // consider adjusting QApplicationPrivate::notify_helper too.
  5.     Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());
  6.     bool consumed = false;
  7.     bool filtered = false;
  8.     Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);
  9.     // send to all application event filters (only does anything in the main thread)
  10.     if (QCoreApplication::self
  11.         && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
  12.         && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
  13.         filtered = true;
  14.         return filtered;
  15.     }
  16.     // send to all receiver event filters
  17.     if (sendThroughObjectEventFilters(receiver, event)) {
  18.         filtered = true;
  19.         return filtered;
  20.     }
  21.     // deliver the event
  22.     // 直接调用对象的event函数,所以是阻塞的
  23.     consumed = receiver->event(event);
  24.     return consumed;
  25. }
复制代码
然后我们可以看到主要有几个流程:

  • 判断QCoreApplication有没有安装事件过滤器,有就把信号发送到事件过滤器里,由事件过滤器对事件进行处理。
    1. // send to all application event filters (only does anything in the main thread)
    2. if (QCoreApplication::self
    3.     && receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
    4.     && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
    5.     filtered = true;
    6.     return filtered;
    7. }
    复制代码
  • 判断事件接受对象,有没有安装事件过滤器,有就将信号发送到事件过滤器。
    1. // send to all receiver event filters
    2. if (sendThroughObjectEventFilters(receiver, event)) {
    3.     filtered = true;
    4.     return filtered;
    5. }
    复制代码
    具体遍历事件接受对象所安装的事件过滤器的代码如下:
    1. bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event)
    2. {
    3.     if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) {
    4.         for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) {
    5.             QObject *obj = receiver->d_func()->extraData->eventFilters.at(i);
    6.             if (!obj)
    7.                 continue;
    8.             if (obj->d_func()->threadData != receiver->d_func()->threadData) {
    9.                 qWarning("QCoreApplication: Object event filter cannot be in a different thread.");
    10.                 continue;
    11.             }
    12.             if (obj->eventFilter(receiver, event))
    13.                 return true;
    14.         }
    15.     }
    16.     return false;
    17. }
    复制代码
    我们可以看到,只要事件被一个事件过滤器所成功处理,那么后续的事件过滤器就不会被响应。同时,参看Qt帮助手册中有提及到:
    If multiple event filters are installed on a single object, the filter that was installed last is activated first.
    后插入的事件过滤器会被优先响应。 具体安装事件过滤器,我们在后面进行分析。
  • 直接调用事件接受对象的event函数进行处理。因为是直接调用的对象的event,所以说,sendEvent函数会阻塞等待。
    1.     // deliver the event
    2.     // 直接调用对象的event函数,所以是阻塞的
    3.     consumed = receiver->event(event);
    4.     return consumed
    复制代码
postEvent

完整代码如下:
  1. void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
  2. {
  3.     Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());
  4.         // 事件的接收者不能为空
  5.     if (receiver == nullptr) {
  6.         qWarning("QCoreApplication::postEvent: Unexpected null receiver");
  7.         delete event;
  8.         return;
  9.     }
  10.         // 对事件接受对象所在线程的事件处理列表上锁
  11.     auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
  12.     if (!locker.threadData) {
  13.         // posting during destruction? just delete the event to prevent a leak
  14.         delete event;
  15.         return;
  16.     }
  17.     QThreadData *data = locker.threadData;
  18.     // if this is one of the compressible events, do compression
  19.     // 将重复的事件,进行压缩
  20.     if (receiver->d_func()->postedEvents
  21.         && self && self->compressEvent(event, receiver, &data->postEventList)) {
  22.         Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
  23.         return;
  24.     }
  25.     if (event->type() == QEvent::DeferredDelete)
  26.         receiver->d_ptr->deleteLaterCalled = true;
  27.     if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
  28.         // remember the current running eventloop for DeferredDelete
  29.         // events posted in the receiver's thread.
  30.         // Events sent by non-Qt event handlers (such as glib) may not
  31.         // have the scopeLevel set correctly. The scope level makes sure that
  32.         // code like this:
  33.         //     foo->deleteLater();
  34.         //     qApp->processEvents(); // without passing QEvent::DeferredDelete
  35.         // will not cause "foo" to be deleted before returning to the event loop.
  36.         // If the scope level is 0 while loopLevel != 0, we are called from a
  37.         // non-conformant code path, and our best guess is that the scope level
  38.         // should be 1. (Loop level 0 is special: it means that no event loops
  39.         // are running.)
  40.         int loopLevel = data->loopLevel;
  41.         int scopeLevel = data->scopeLevel;
  42.         if (scopeLevel == 0 && loopLevel != 0)
  43.             scopeLevel = 1;
  44.         static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
  45.     }
  46.     // delete the event on exceptions to protect against memory leaks till the event is
  47.     // properly owned in the postEventList
  48.     QScopedPointer<QEvent> eventDeleter(event);
  49.     Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
  50.     data->postEventList.addEvent(QPostEvent(receiver, event, priority));
  51.     eventDeleter.take();
  52.     event->posted = true;
  53.     ++receiver->d_func()->postedEvents;
  54.     data->canWait = false;
  55.     locker.unlock();
  56.     QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
  57.     if (dispatcher)
  58.         dispatcher->wakeUp();
  59. }
复制代码

  • 判断事件接收对象是否为空
    1. // 事件的接收者不能为空
    2. if (receiver == nullptr) {
    3.     qWarning("QCoreApplication::postEvent: Unexpected null receiver");
    4.     delete event;
    5.     return;
    6. }
    复制代码
  • 将事件接收对象所在线程的post事件列表上锁,如果已经被锁了,就把事件删除掉,并返回,防止泄露。
    1. // 对事件接受对象所在线程的事件处理列表上锁
    2. auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    3. if (!locker.threadData) {
    4.     // posting during destruction? just delete the event to prevent a leak
    5.     delete event;
    6.     return;
    7. }
    复制代码
  • 将一些可以压缩的事件进行压缩,及多个事件压缩成只推送最后的一个事件。Qt界面的update就是这个操作,为了防止多次刷新导致卡顿,短时间内多次的调用update可能只会刷新一次
    1. // if this is one of the compressible events, do compression
    2. // 将重复的事件,进行压缩
    3. if (receiver->d_func()->postedEvents
    4.     && self && self->compressEvent(event, receiver, &data->postEventList)) {
    5.     Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
    6.     return;
    7. }
    复制代码
  • 将事件插入接收对象所在线程的post事件列表中,并唤醒线程的事件调度器,来进行事件的处理。所以postEvent是非阻塞的,因为其只是把事件插入了线程的事件列表,唤醒事件调度器之后便返回
    1.     // delete the event on exceptions to protect against memory leaks till the event is
    2.     // properly owned in the postEventList
    3.     QScopedPointer<QEvent> eventDeleter(event);
    4.     Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
    5.     data->postEventList.addEvent(QPostEvent(receiver, event, priority));
    6.     eventDeleter.take();
    7.     event->posted = true;
    8.     ++receiver->d_func()->postedEvents;
    9.     data->canWait = false;
    10.     locker.unlock();
    11.     QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
    12.     if (dispatcher)
    13.         dispatcher->wakeUp();
    复制代码
事件是如何处理的?

在Qt中,事件的接收者都是QObject,而QObject中事件处理是调用event函数。如果当时对象不处理某个事件,就会将其转发到父类的event进行处理。
而事件的处理,主要分为三个部分:
所以,在这一章节,我们同样一步一步的分析这三个点。
事件循环是怎么遍历的?
  1. int main(int argc, char *argv[])
  2. {
  3.     QApplication a(argc, argv);
  4.     MainWindow w;
  5.     w.show();
  6.     return a.exec();
  7. }
复制代码
上面是一个经典的QtGUI程序的main函数,调用a.exec()
  1. int QCoreApplication::exec()
  2. {
  3.     ...
  4.    
  5.     threadData->quitNow = false;
  6.     QEventLoop eventLoop;
  7.     self->d_func()->in_exec = true;
  8.     self->d_func()->aboutToQuitEmitted = false;
  9.     int returnCode = eventLoop.exec();
  10.    
  11.     ...
  12. }
复制代码
而看QApplication::exec的源码,实际上就是开启了一个事件循环(QEventLoop)。同样,我们去看QEventLoop::exec的源码,进一步看处理事件的步骤是什么。
  1. int QEventLoop::exec(ProcessEventsFlags flags)
  2. {
  3.     ...
  4.     while (!d->exit.loadAcquire())
  5.         processEvents(flags | WaitForMoreEvents | EventLoopExec);
  6.     ref.exceptionCaught = false;
  7.     return d->returnCode.loadRelaxed();
  8. }
复制代码
上面可以看到,QEvenLoop::exec里,是一个while循环,循环的去调用processEvent,而且设置了WaitForMoreEvents就是说,如果没有事件,就阻塞等待。
  1. void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int ms)
  2. {
  3.     // ### Qt 6: consider splitting this method into a public and a private
  4.     //           one, so that a user-invoked processEvents can be detected
  5.     //           and handled properly.
  6.     QThreadData *data = QThreadData::current();
  7.     if (!data->hasEventDispatcher())
  8.         return;
  9.     QElapsedTimer start;
  10.     start.start();
  11.     while (data->eventDispatcher.loadRelaxed()->processEvents(flags & ~QEventLoop::WaitForMoreEvents)) {
  12.         if (start.elapsed() > ms)
  13.             break;
  14.     }
  15. }
复制代码
阅读processEvent,其调用了线程的事件调度器QAbstrctEventDispatcher,而这个类是一个抽象基类,根据不同的平台,有不同的实现,我们以windows下(QEventDispatcherWin32)的为例,接着分析事件处理的流程。
  1. bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
  2. {
  3.     Q_D(QEventDispatcherWin32);
  4.         ...
  5.     // To prevent livelocks, send posted events once per iteration.
  6.     // QCoreApplication::sendPostedEvents() takes care about recursions.
  7.     sendPostedEvents();
  8.     ...
  9. }
  10. void QEventDispatcherWin32::sendPostedEvents()
  11. {
  12.     Q_D(QEventDispatcherWin32);
  13.     if (d->sendPostedEventsTimerId != 0)
  14.         KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
  15.     d->sendPostedEventsTimerId = 0;
  16.     // Allow posting WM_QT_SENDPOSTEDEVENTS message.
  17.     d->wakeUps.storeRelaxed(0);
  18.     QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed());
  19. }
复制代码
可以看到,事件调度器最终还是调用了QCoreApplication的sendPostEvents
  1. void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
  2.                                                QThreadData *data)
  3. {
  4.     if (event_type == -1) {
  5.         // we were called by an obsolete event dispatcher.
  6.         event_type = 0;
  7.     }
  8.     if (receiver && receiver->d_func()->threadData != data) {
  9.         qWarning("QCoreApplication::sendPostedEvents: Cannot send "
  10.                  "posted events for objects in another thread");
  11.         return;
  12.     }
  13.     ...
  14.     // Exception-safe cleaning up without the need for a try/catch block
  15.     struct CleanUp {
  16.         QObject *receiver;
  17.         int event_type;
  18.         QThreadData *data;
  19.         bool exceptionCaught;
  20.         inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
  21.             receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
  22.         {}
  23.         inline ~CleanUp()
  24.         {
  25.             if (exceptionCaught) {
  26.                 // since we were interrupted, we need another pass to make sure we clean everything up
  27.                 data->canWait = false;
  28.             }
  29.             --data->postEventList.recursion;
  30.             if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
  31.                 data->eventDispatcher.loadRelaxed()->wakeUp();
  32.             // clear the global list, i.e. remove everything that was
  33.             // delivered.
  34.             if (!event_type && !receiver && data->postEventList.startOffset >= 0) {
  35.                 const QPostEventList::iterator it = data->postEventList.begin();
  36.                 data->postEventList.erase(it, it + data->postEventList.startOffset);
  37.                 data->postEventList.insertionOffset -= data->postEventList.startOffset;
  38.                 Q_ASSERT(data->postEventList.insertionOffset >= 0);
  39.                 data->postEventList.startOffset = 0;
  40.             }
  41.         }
  42.     };
  43.     CleanUp cleanup(receiver, event_type, data);
  44.     while (i < data->postEventList.size()) {
  45.        ...
  46.         // first, we diddle the event so that we can deliver
  47.         // it, and that no one will try to touch it later.
  48.         pe.event->posted = false;
  49.         QEvent *e = pe.event;
  50.         QObject * r = pe.receiver;
  51.         --r->d_func()->postedEvents;
  52.         Q_ASSERT(r->d_func()->postedEvents >= 0);
  53.         // next, update the data structure so that we're ready
  54.         // for the next event.
  55.         const_cast<QPostEvent &>(pe).event = nullptr;
  56.         locker.unlock();
  57.         const auto relocker = qScopeGuard([&locker] { locker.lock(); });
  58.         QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
  59.         // after all that work, it's time to deliver the event.
  60.         QCoreApplication::sendEvent(r, e);
  61.         // careful when adding anything below this point - the
  62.         // sendEvent() call might invalidate any invariants this
  63.         // function depends on.
  64.     }
  65.     cleanup.exceptionCaught = false;
  66. }
复制代码
我们一个一个的分块分析:

  • 判断是否在一个线程
    1. if (receiver && receiver->d_func()->threadData != data) {
    2.     qWarning("QCoreApplication::sendPostedEvents: Cannot send "
    3.              "posted events for objects in another thread");
    4.     return;
    5. }
    复制代码
  • 一个有意思的异常安全的处理,不需要try/catch块
    1. // Exception-safe cleaning up without the need for a try/catch block
    2. struct CleanUp {
    3.     QObject *receiver;
    4.     int event_type;
    5.     QThreadData *data;
    6.     bool exceptionCaught;
    7.     inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
    8.         receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
    9.     {}
    10.     inline ~CleanUp()
    11.     {
    12.         if (exceptionCaught) {
    13.             // since we were interrupted, we need another pass to make sure we clean everything up
    14.             data->canWait = false;
    15.         }
    16.         --data->postEventList.recursion;
    17.         if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
    18.             data->eventDispatcher.loadRelaxed()->wakeUp();
    19.         // clear the global list, i.e. remove everything that was
    20.         // delivered.
    21.         if (!event_type && !receiver && data->postEventList.startOffset >= 0) {
    22.             const QPostEventList::iterator it = data->postEventList.begin();
    23.             data->postEventList.erase(it, it + data->postEventList.startOffset);
    24.             data->postEventList.insertionOffset -= data->postEventList.startOffset;
    25.             Q_ASSERT(data->postEventList.insertionOffset >= 0);
    26.             data->postEventList.startOffset = 0;
    27.         }
    28.     }
    29. };
    30. CleanUp cleanup(receiver, event_type, data);
    复制代码
定义了一个结构体CleanUp,结构体的析构函数(~CleanUp)保存了函数退出时需要执行的清理操作。然后在栈上创建了一个结构体对象,遍历事件列表时,异常退出,那么就会调用自动调用~CleanUp的析构函数。

  • 将事件发送出去(sendEvent)
    1. while (i < data->postEventList.size()) {
    2.        ...
    3.         // first, we diddle the event so that we can deliver
    4.         // it, and that no one will try to touch it later.
    5.         pe.event->posted = false;
    6.         QEvent *e = pe.event;
    7.         QObject * r = pe.receiver;
    8.         --r->d_func()->postedEvents;
    9.         Q_ASSERT(r->d_func()->postedEvents >= 0);
    10.         // next, update the data structure so that we're ready
    11.         // for the next event.
    12.         const_cast<QPostEvent &>(pe).event = nullptr;
    13.         locker.unlock();
    14.         const auto relocker = qScopeGuard([&locker] { locker.lock(); });
    15.         QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
    16.         // after all that work, it's time to deliver the event.
    17.         QCoreApplication::sendEvent(r, e);
    18.         // careful when adding anything below this point - the
    19.         // sendEvent() call might invalidate any invariants this
    20.         // function depends on.
    21.     }
    复制代码
可以看到,核心还是调用sendEvent将事件发送出去,而前面我们对sendEvent的源码分析我们可以看到,事件先是经过事件过滤器,再经过对象的event函数,来进行事件的处理。所以就引出我们的下一个话题:事件过滤器
事件过滤器

在实际应用中,我们经常要将某一个窗口部件的某个事件如鼠标滑轮滚动拦截,然后执行我们自己想要的操作。这个时候,我们就可以用到事件过滤器(EventFilter**) **
首先,我们需要自己编写一个eventFilter函数,
  1. bool Class::eventFilter(QObject* watcher, QEvent* event)
  2. {
  3.         //以过滤鼠标滚轮事件为例
  4.     if (object == m_watcherObject && event->type() == QEvent::Wheel) {
  5.             // do something
  6.         return true;      
  7.     }
  8.     QWidget::eventFilter(watcher, event);
  9. }
复制代码
然后,我们需要为要拦截的某个窗口部件,安装事件过滤器
  1. void Class::initUI()
  2. {
  3.         QWidget* m_watcherObject = new QWidget(this);
  4.     // 为对象安装一个事件过滤器
  5.         m_watcherObject->installEventFilterr(this);
  6. }
  7. initUI();
复制代码
那么一个对象安装的多个事件过滤器,会以什么样的顺序触发呢?我们在前面的讲过,后安装的事件过滤器会先触发,这一点,我们可以在源码里得到佐证:
  1. void QObject::installEventFilter(QObject *obj)
  2. {
  3.     Q_D(QObject);
  4.     if (!obj)
  5.         return;
  6.     if (d->threadData != obj->d_func()->threadData) {
  7.         qWarning("QObject::installEventFilter(): Cannot filter events for objects in a different thread.");
  8.         return;
  9.     }
  10.     if (!d->extraData)
  11.         d->extraData = new QObjectPrivate::ExtraData;
  12.     // clean up unused items in the list
  13.     d->extraData->eventFilters.removeAll((QObject*)nullptr);
  14.     d->extraData->eventFilters.removeAll(obj);
  15.     d->extraData->eventFilters.prepend(obj);
  16. }
复制代码
可以清楚的看到,事件过滤器,是以prepend的形式被添加进事件过滤器列表的。
那么,当有鼠标滚轮事件触发的时候,我们可以看到sendEvent会优先走到事件过滤器里,如果eventFilter返回一个true,那么事件就不会被继续派发,否则,将会将事件发送到其他的事件过滤器里进行处理,如果其他的事件过滤器均对该事件不进行处理,那么事件将会继续往下派发,走到事件的处理函数event
event

接下来,就到了事件处理的最后一站,event函数,这个函数比较简单,我们可以自己重写这个函数,对事件进行自定义的处理。
  1. bool Class::event(QEvent *e)
  2. {
  3.     switch (e->type()) {
  4.     case QEvent::Whell:
  5.         // do something
  6.         return true;
  7.     default:
  8.         if (e->type() >= QEvent::User) {
  9.             customEvent(e);
  10.             break;
  11.         }
  12.         return false;
  13.     }
  14.     return true;
  15. }
复制代码
夹带私货时间


  • 之前有说到processEvent,添加一个小经验。当我们有时候不得不在主线程循环执行很耗时的操作的时候,这个时候,界面就会刷新不过来,就会导致界面卡顿,影响使用。但是,我们可以在这个循环里,手动调用qApp->processEvent(),这样就可以手动调用处理掉所有的事件,就可以解决卡顿的问题

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连全瓷种植牙齿制作中心

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表