Qt--QCoreApplication、QGuiApplication、QApplication
2017-11-23 10:18
323 查看
Q:当我们使用Qtcreator创建Qt Core Application、Qt Widgets Application、Qt Quick Application时,会发现main函数中所使用的QXxxApplication类不同,创建Qt Core Application时使用的是QCoreApplication,创建Qt Widgets Application时使用的是QApplication,创建Qt Quick Application时使用的是QGuiApplication,那么它们有什么区别和联系呢?
从继承关系来看,QApplication继承自QGuiApplication,QGuiApplication继承自QCoreApplication,所以它们的功能是逐步递增的。
从所处模块来看,QCoreApplication定义在core模块中,为应用程序提供了一个非gui的事件循环;QGuiApplication定义在gui模块中,提供了额外的gui相关的设置,比如桌面设置,风格,字体,调色板,剪切板,光标;QApplication定义在widgets模块中,是QWidget相关的,能设置双击间隔,按键间隔,拖拽距离和时间,滚轮滚动行数等,能获取桌面,激活的窗口,模式控件,弹跳控件等。
具体使用哪个取决于你的应用程序,如果你的应用程序是无界面的,直接使用QCoreApplication即可,如果是gui相关,但没有使用widgets模块的就使用QGuiApplication,否则使用QApplication。
下面具体介绍下QCoreApplication中几个重要的接口
我们首先示例了一个QCoreApplication或其派生类的对象,这个对象是全局唯一的,qApp指针指向这个对象。QCoreApplication初始化时会获取库所在路径,并创建一个事件派遣类。看下它的构造函数:
嗯,就是new了一个QCoreApplicationPrivate私有类,然后调用init函数,在init函数中最重要的一行就是createEventDispatcher();这个方法会根据不同的平台去创建不同的事件派遣类。
定义了一个QEventLoop 事件循环类,调用了eventLoop.exec(),转过去发现如下语句:
这就是我们要找的应用程序事件循环了,只要exit标识为false,会一直调用processEvents处理事件
而processEvents中就是调用事件派遣类的processEvents,不同平台下有不同的实现,比如windows下是通过PeekMessage然后DispatchMessage,而glib下就是g_main_context_iteration了。
那么exit接口实现也很简单,就是将exit标识置true,就会退出事件循环。
我追踪了下源码,简述下sendEvent的主要调用过程:
notify->doNotify->QCoreApplicationPrivate::notify_helper
通过这个函数我们就很明白Qt的事件处理过程了,先是通过应用程序对象QCoreApplication的事件过滤eventFilter,然后是接受者的事件过滤eventFilter,最后调用event,然后是细分的event,如mousePressEvent,timerEvent等。sendEvent是同步事件处理,即发送事件后会立即调用事件处理函数
线程数据里有个postEventList,postEvent会将事件添加addEvent到接受者所在线程的postEventList,然后唤醒wakeUp接受者线程的事件派遣类eventDispatcher,在线程的事件循环中去处理事件。所以postEvent是一个异步事件处理,post事件后不会立即得到处理,而是需要在接受者线程的事件循环获取到这个事件,然后才处理。
区别和联系
QCoreApplication、QGuiApplication、QApplication区别和联系:从继承关系来看,QApplication继承自QGuiApplication,QGuiApplication继承自QCoreApplication,所以它们的功能是逐步递增的。
从所处模块来看,QCoreApplication定义在core模块中,为应用程序提供了一个非gui的事件循环;QGuiApplication定义在gui模块中,提供了额外的gui相关的设置,比如桌面设置,风格,字体,调色板,剪切板,光标;QApplication定义在widgets模块中,是QWidget相关的,能设置双击间隔,按键间隔,拖拽距离和时间,滚轮滚动行数等,能获取桌面,激活的窗口,模式控件,弹跳控件等。
具体使用哪个取决于你的应用程序,如果你的应用程序是无界面的,直接使用QCoreApplication即可,如果是gui相关,但没有使用widgets模块的就使用QGuiApplication,否则使用QApplication。
下面具体介绍下QCoreApplication中几个重要的接口
QCoreApplication
在Qt的应用程序中,在main函数中,我们一般会这么写:int main(int argc, char* argv[]){ QCoreApplication app(argc, argv); //... return app.exec(); }
我们首先示例了一个QCoreApplication或其派生类的对象,这个对象是全局唯一的,qApp指针指向这个对象。QCoreApplication初始化时会获取库所在路径,并创建一个事件派遣类。看下它的构造函数:
构造函数
QCoreApplication::QCoreApplication(int &argc, char **argv #ifndef Q_QDOC , int _internal #endif ) #ifdef QT_NO_QOBJECT : d_ptr(new QCoreApplicationPrivate(argc, argv, _internal)) #else : QObject(*new QCoreApplicationPrivate(argc, 4000 argv, _internal)) #endif { d_func()->q_ptr = this; d_func()->init(); #ifndef QT_NO_QOBJECT QCoreApplicationPrivate::eventDispatcher->startingUp(); #endif } void QCoreApplicationPrivate::init() { Q_Q(QCoreApplication); initLocale(); Q_ASSERT_X(!QCoreApplication::self, "QCoreApplication", "there should be only one application object"); QCoreApplication::self = q; // Store app name (so it's still available after QCoreApplication is destroyed) if (!coreappdata()->applicationNameSet) coreappdata()->application = appName(); QLoggingRegistry::instance()->init(); #ifndef QT_NO_LIBRARY // Reset the lib paths, so that they will be recomputed, taking the availability of argv[0] // into account. If necessary, recompute right away and replay the manual changes on top of the // new lib paths. QStringList *appPaths = coreappdata()->app_libpaths.take(); QStringList *manualPaths = coreappdata()->manual_libpaths.take(); if (appPaths) { if (manualPaths) { // Replay the delta. As paths can only be prepended to the front or removed from // anywhere in the list, we can just linearly scan the lists and find the items that // have been removed. Once the original list is exhausted we know all the remaining // items have been added. QStringList newPaths(q->libraryPaths()); for (int i = manualPaths->length(), j = appPaths->length(); i > 0 || j > 0; qt_noop()) { if (--j < 0) { newPaths.prepend((*manualPaths)[--i]); } else if (--i < 0) { newPaths.removeAll((*appPaths)[j]); } else if ((*manualPaths)[i] != (*appPaths)[j]) { newPaths.removeAll((*appPaths)[j]); ++i; // try again with next item. } } delete manualPaths; coreappdata()->manual_libpaths.reset(new QStringList(newPaths)); } delete appPaths; } #endif #ifndef QT_NO_QOBJECT // use the event dispatcher created by the app programmer (if any) if (!eventDispatcher) eventDispatcher = threadData->eventDispatcher.load(); // otherwise we create one if (!eventDispatcher) createEventDispatcher(); Q_ASSERT(eventDispatcher); if (!eventDispatcher->parent()) { eventDispatcher->moveToThread(threadData->thread); eventDispatcher->setParent(q); } threadData->eventDispatcher = eventDispatcher; eventDispatcherReady(); #endif #ifdef QT_EVAL extern void qt_core_eval_init(QCoreApplicationPrivate::Type); qt_core_eval_init(application_type); #endif processCommandLineArguments(); qt_call_pre_routines(); qt_startup_hook(); #ifndef QT_BOOTSTRAPPED if (Q_UNLIKELY(qtHookData[QHooks::Startup])) reinterpret_cast<QHooks::StartupCallback>(qtHookData[QHooks::Startup])(); #endif #ifndef QT_NO_QOBJECT is_app_running = true; // No longer starting up. #endif }
嗯,就是new了一个QCoreApplicationPrivate私有类,然后调用init函数,在init函数中最重要的一行就是createEventDispatcher();这个方法会根据不同的平台去创建不同的事件派遣类。
exec
QCoreApplication类中大部分都是static静态方法,看看exec这个方法int QCoreApplication::exec() { if (!QCoreApplicationPrivate::checkInstance("exec")) return -1; 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; } if (!threadData->eventLoops.isEmpty()) { qWarning("QCoreApplication::exec: The event loop is already running"); return -1; } 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()->in_exec = false; if (!self->d_func()->aboutToQuitEmitted) emit self->aboutToQuit(QPrivateSignal()); self->d_func()->aboutToQuitEmitted = true; sendPostedEvents(0, QEvent::DeferredDelete); } return returnCode; }
定义了一个QEventLoop 事件循环类,调用了eventLoop.exec(),转过去发现如下语句:
while (!d->exit.loadAcquire()) processEvents(flags | WaitForMoreEvents | EventLoopExec);
这就是我们要找的应用程序事件循环了,只要exit标识为false,会一直调用processEvents处理事件
bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher.load()) return false; return d->threadData->eventDispatcher.load()->processEvents(flags); }
而processEvents中就是调用事件派遣类的processEvents,不同平台下有不同的实现,比如windows下是通过PeekMessage然后DispatchMessage,而glib下就是g_main_context_iteration了。
那么exit接口实现也很简单,就是将exit标识置true,就会退出事件循环。
sendEvent
再来看看sendEvent和postEvent是如何工作的我追踪了下源码,简述下sendEvent的主要调用过程:
notify->doNotify->QCoreApplicationPrivate::notify_helper
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event) { // send to all application event filters (only does anything in the main thread) if (QCoreApplication::self && receiver->d_func()->threadData->thread == mainThread() && QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) return true; // send to all receiver event filters if (sendThroughObjectEventFilters(receiver, event)) return true; // deliver the event return receiver->event(event); }
通过这个函数我们就很明白Qt的事件处理过程了,先是通过应用程序对象QCoreApplication的事件过滤eventFilter,然后是接受者的事件过滤eventFilter,最后调用event,然后是细分的event,如mousePressEvent,timerEvent等。sendEvent是同步事件处理,即发送事件后会立即调用事件处理函数
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); // if this is one of the compressible events, do compression if (receiver->d_func()->postedEvents && self && self->compressEvent(event, receiver, &data->postEventList)) { return; } if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) { // remember the current running eventloop for DeferredDelete // events posted in the receiver's thread. // Events sent by non-Qt event handlers (such as glib) may not // have the scopeLevel set correctly. The scope level makes sure that // code like this: // foo->deleteLater(); c8ff // qApp->processEvents(); // without passing QEvent::DeferredDelete // will not cause "foo" to be deleted before returning to the event loop. // If the scope level is 0 while loopLevel != 0, we are called from a // non-conformant code path, and our best guess is that the scope level // should be 1. (Loop level 0 is special: it means that no event loops // are running.) int loopLevel = data->loopLevel; int scopeLevel = data->scopeLevel; if (scopeLevel == 0 && loopLevel != 0) scopeLevel = 1; static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel; } // delete the event on exceptions to protect against memory leaks till the event is // properly owned in the postEventList QScopedPointer<QEvent> eventDeleter(event); data->postEventList.addEvent(QPostEvent(receiver, event, priority)); eventDeleter.take(); event->posted = true; ++receiver->d_func()->postedEvents; data->canWait = false; locker.unlock(); QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire(); if (dispatcher) dispatcher->wakeUp(); }
线程数据里有个postEventList,postEvent会将事件添加addEvent到接受者所在线程的postEventList,然后唤醒wakeUp接受者线程的事件派遣类eventDispatcher,在线程的事件循环中去处理事件。所以postEvent是一个异步事件处理,post事件后不会立即得到处理,而是需要在接受者线程的事件循环获取到这个事件,然后才处理。
话外: repaint和update的区别
看到这,我们也应该明白了通常的repaint和update的区别了,repaint就是sendEvent立即引起重绘,调用paintEvent,update是postEvent到线程事件队列中,如果同一时间段有很多update事件,会合并这些update,只调用paintEvent一次。总结
至此,我们了解了QCoreApplication、QGuiApplication、QApplication的区别和联系,知道了QCoreApplication在初始化和exec时搞了什么鬼,通过分析sendEvent和postEvent摸清了Qt的事件路由QCoreApplication::eventFilter->接受者eventFilter->接受者event->接受者细分event->接受者父对象
相关文章推荐
- [QT]QApplication和QCoreApplication的用法
- QApplication、QGuiApplication和QCoreApplication三者的区别与联系
- QT源码查看001-QApplication和QCoreApplication
- 解决 #include "QtGui/QApplication" No such file or directory
- QCoreApplication和QApplication的用法及区别
- Qt学习之系列[9] – QCoreApplication:processEvents()可能会引起递归,导致栈溢出崩溃
- Qt工程文件中QT -= core gui与Config-=qt的区别
- Qt 自定义事件详细实例(继承QEvent,然后QCoreApplication::postEvent()、sendEvent())
- Creating a GUI application using Qt
- QT5 编译报错 QtGui/QApplication: No such file or direc
- QCoreApplication与QApplication区别
- QApplication和QCoreApplication的用法
- Qt4项目迁移Qt5项目一问题解决方法:#include <QtGui/QApplication> ---> No such file or directory
- QApplication和QCoreApplication的用法
- Qt5编译时出现<QtGui/QApplication> ---> No such file or directory错误解决办法
- QT 问题:QPixmap: Must construct a QGuiApplication before a QPixmap
- QT5出现问题:QtGui/QApplication: No such file or directory
- QApplication与QCoreApplication
- DrawTest for QT GUI Application(QT波形绘制实例)
- Qt4项目迁移Qt5项目一问题解决方法:#include <QtGui/QApplication> ---> No such file or directory