Qt中的事件循环

发布于:2025-04-01 ⋅ 阅读:(28) ⋅ 点赞:(0)

      Qt的事件循环是其核心机制之一,它是一种消息处理机制,负责处理各种事件(如用户输入、定时器、网络请求等)的分发和处理。Qt中的事件循环是一个持续运行的循环,负责接收事件并将它们分发给相应的对象进行处理。当没有事件需要处理时,Qt应用程序会进入一个阻塞状态,直到下一个事件到来。它并不会简单地空转消耗CPU资源,而是会进入一种高效的等待状态。Qt支持嵌套事件循环Qt是事件驱动的

      事件的处理方式可以分为两大类

      (1).需要放入事件队列的事件(异步处理):大多数事件是通过事件队列异步处理的,如用户输入事件(鼠标点击、键盘输入等)、窗口系统事件(重绘请求、窗口调整大小等)、定时器事件(QTimer触发的事件)、网络事件(socket通知等)、跨线程的信号槽调用、使用QCoreApplication::postEvent()发送的事件等。这些事件会被放入事件队列,由事件循环在适当的时候取出并处理。

      (2).不需要放入事件队列的事件(同步处理):有些事件是直接同步处理的,不经过事件队列,如使用sendEvent()发送的事件、信号槽直接连接(Qt::DirectConnection)等。

      主事件循环是Qt应用程序的主线程中第一个启动的事件循环。通常位于main()函数的return app.exec()处。主事件循环的运行时间等于整个应用程序的运行时间,退出主事件循环意味着应用程序即将终止。

      不要阻塞事件循环:不要在事件循环中执行耗时的操作,因为这将导致其他事件无法被处理。将耗时操作移到工作线程,或定期调用processEvents()。阻塞Qt事件循环会导致:用户界面完全冻结(如所有用户输入无响应)、新事件无法被处理(堆积在队列中)等。

      QApplication继承QGuiApplication;QGuiApplication继承QCoreApplication:

      (1).非GUI应用程序使用QCoreApplication类来提供其事件循环。

      (2).对于GUI应用程序使用QGuiApplication类来提供其事件循环。

      (3).对于使用Qt Widgets模块的应用程序使用QApplication类来提供其事件循环。

      通常,建议尽早在main()函数中创建QCoreApplication、QGuiApplication或QApplication对象。exec()直到事件循环退出才会返回,例如,调用quit()时。

      1. QCoreApplication

      对于使用Qt的非GUI应用程序,应该只有一个QCoreApplication对象

      QCoreApplication包含主事件循环(main event loop),其中处理和分发(processed and dispatched)来自操作系统(例如,计时器和网络事件)和其他来源的所有事件。它还处理应用程序的初始化和结束(initialization and finalization),以及系统范围和应用程序范围的设置(system-wide and application-wide settings)。

      事件循环通过调用exec()启动。QCoreApplication启动的事件循环也叫作主事件循环。此函数内会调用QEventLoop::exec()。QEventLoop::exec()是通过循环不断地调用QEventLoop::processEvents()来分发事件队列中的事件。长时间运行的操作可以调用processEvents()来保持应用程序响应。

      提供了几个静态函数:QCoreApplication对象可从instance()获得;可以使用sendEvent()发送事件,或使用postEvent()发布到事件队列;可以使用 removePostedEvents()删除待处理事件,或使用sendPostedEvents()调度待处理事件。

      提供了一个quit()槽和一个aboutToQuit()信号。

      exit(int)函数:调用此函数后,应用程序离开主事件循环并从exec()的调用返回。如果事件循环未运行,则此函数不执行任何操作。此函数不是线程安全的。它只能从主线程(QCoreApplication对象正在处理事件的线程)调用。要让应用程序退出另一个线程,使用QCoreApplication::quit(),或者从主线程使用 QMetaMethod::invokeMethod()调用此函数。

      quit()函数:要求应用程序退出。如果应用程序阻止退出,例如,如果无法关闭其中一个窗口,则可以忽略该请求。应用程序可以通过在应用程序级别(application level)处理QEvent::Quit事件或为各个窗口处理QEvent::Close事件来影响此情况。如果退出未被中断,应用程序将退出并返回代码0(成功)。要退出应用程序而不被中断,直接调用exit()。

      QCoreApplication::notify():将事件发送给接收者:receiver->event(event)。返回从接收者的事件处理程序返回的值。注意,此函数用于发送给任何线程中任何对象的所有事件。对于某些类型的事件(例如鼠标和键盘事件),如果接收者对事件不感兴趣(即返回false),则事件将传播到接收者的父级(propagated to the receiver's parent),依此类推,直到顶层对象(top-level object)。

      2. QGuiApplication

      QGuiApplication包含主事件循环,其中处理和分发来自窗口系统(window system)和其他来源的所有事件。它还处理应用程序的初始化和结束,并提供会话管理(session management)。此外,QGuiApplication处理大多数系统范围和应用程序范围的设置。

      对于使用Qt的任何GUI应用程序,无论应用程序在任何给定时间有0、1、2或更多窗口,都只有一个QGuiApplication对象

      QGuiApplication对象可通过QCoreApplication类的静态函数instance()访问,该函数返回一个与全局qApp指针等效的指针。

      QGuiApplication的主要职责范围:

      (1).它使用用户的桌面设置初始化应用程序,例如palette()、font()和styleHints()。它会跟踪这些属性,以防用户全局更改桌面(例如,通过某种控制面板)。

      (2).它执行事件处理,这意味着它从底层窗口系统接收事件并将它们分发到相关的部件(widgets)。你可以使用sendEvent()和postEvent()向窗口发送自己的事件。

      (3).它解析常见的命令行参数并相应地设置其内部状态。

      (4).它通过translate()提供用户可见的字符串的本地化(localization of strings)。

      (5).它提供了一些神奇对象(magical objects),如clipboard()。

      (6).它了解应用程序的窗口。你可以使用topLevelAt()询问哪个窗口位于某个位置,获取topLevelWindows()列表等。

      (7).它管理应用程序的鼠标光标处理。

      (8).它提供对复杂会话管理(session management)的支持。这使得应用程序可以在用户注销时正常终止,如果无法终止,则可以取消关闭过程,甚至可以为将来的会话保留整个应用程序的状态。

      由于QGuiApplication对象执行了大量初始化操作,因此必须在创建与用户界面相关的任何其他对象之前创建它。QGuiApplication还处理常见的命令行参数。因此,在应用程序本身中对argv进行任何解释或修改之前创建它通常是一个好主意。

      qGuiApp:指向唯一应用程序对象的全局指针。仅当该对象是QGuiApplication时才有效。

      3. QApplication:

      QApplication类管理GUI应用程序的控制流和主要设置。

      QApplication专门化(specializes)了QGuiApplication,并增加了一些基于QWidget的应用程序所需的功能。它处理特定于窗口部件(widget)的初始化和结束。

      对于使用Qt的任何GUI应用程序,无论应用程序在任何给定时间有0、1、2或更多窗口,都只有一个QApplication对象。

      QApplication对象可通过QCoreApplication类的静态函数instance()访问,该函数返回一个与全局qApp指针等效的指针。

      QApplication的主要职责包括:

      (1).它使用用户的桌面设置初始化应用程序,例如palette()、font()和doubleClickInterval()。它会跟踪这些属性,以防用户全局更改桌面(例如,通过某种控制面板)。

      (2).它执行事件处理,这意味着它从底层窗口系统接收事件并将它们分发到相关部件。通过使用sendEvent()和postEvent(),你可以将自己的事件发送到部件。

      (3).它解析常见的命令行参数并相应地设置其内部状态。

      (4).它定义应用程序的外观,封装在QStyle对象中。这可以在运行时使用setStyle()进行更改。

      (5).它提供字符串的本地化,这些字符串可通过translation()供用户查看。

      (6).它提供了一些神奇对象,如clipboard()。

      (7).它了解应用程序的窗口。你可以使用widgetAt()询问哪个部件位于某个位置,获取topLevelWidgets()和closeAllWindows()的列表等。

      (8).它管理应用程序的鼠标光标处理。

      由于QApplication对象执行了大量初始化,因此必须在创建与用户界面相关的任何其他对象之前创建它。QApplication还处理常见的命令行参数。因此,在应用程序本身中对argv进行任何解释或修改之前创建它通常是一个好主意。

      qApp:指向唯一应用程序对象的全局指针。它相当于QCoreApplication::instance(),但转换为QApplication指针,因此仅当唯一应用程序对象是QApplication时才有效。

      Qt中的QApplication类的exec函数启动主事件循环:它启动GUI;它处理信号(signals)并在接收到信号时调用适当的槽(slots);它等待直到调用exit函数并返回在exit中设置的值。

      4. 在Qt中,事件是从抽象的QEvent类派生的对象,表示应用程序内部发生的事情,或应用程序需要了解的外部活动所导致的事情。事件可以由QObject子类的任何实例接收和处理,但它们与部件(widgets)特别相关。Qt中每个事件都是一个QEvent的子类对象。

      (1).事件如何传递:

      当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示该事件,并通过调用其event()函数将其传递给QObject的特定实例(或其中一个子类)。

      此函数本身不处理事件;根据传递的事件类型,它调用该特定类型事件的事件处理程序,并根据事件是被接受还是被忽略发送响应。

      某些事件(例如QMouseEvent和QKeyEvent)来自窗口系统;某些事件(例如QTimerEvent)来自其他来源;某些事件来自应用程序本身。

      (2).事件类型:

      大多数事件类型都有特殊类(special classes),特别是QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。每个类都是QEvent的子类,并添加了事件特定的函数。例如,QResizeEvent添加了size()和oldSize(),以使部件能够发现其尺寸是如何改变的。

      有些类支持不止不一种实际事件类型。QMouseEvent支持鼠标按钮按下、双击、移动和其他相关操作。

      每个事件都有一个关联类型,在QEvent::Type中定义,这可以用作运行时类型信息的便捷来源,以快速确定给定事件对象是由哪个子类构建的。

      由于程序需要以多种复杂的方式做出反应,因此Qt的事件传递机制非常灵活。

      (3).事件处理程序:

      传递事件的正常方式是调用虚函数(virtual function)。例如,QPaintEvent通过调用QWidget::paintEvent()传递。此虚函数负责做出适当的反应,通常是重新绘制窗口部件(widget)。如果你没有在虚函数的实现中执行所有必要的工作,则可能需要调用基类的实现。

      如果你想要替换基类的函数,则必须自己实现所有函数。但是,如果你只想扩展基类的功能,则可以实现所需的函数并调用基类以获取你不想处理的任何情况的默认行为。

      有时,没有这样的事件特定函数(event-specific function),或者事件特定函数不够用。最常见的示例涉及Tab键按下。这些对象可以重新实现 QObject::event()(通用事件处理程序),并在常规处理之前或之后执行事件处理,或者它们可以完全替换该函数。

      注意:对于所有未处理的情况,仍会调用QWidget::event(),并且返回值指示事件是否已被处理;true值可防止将事件发送给其他对象。

      (4).事件过滤器:

      有时,一个对象需要查看并可能拦截传递给另一个对象的事件。例如,对话框通常希望过滤某些部件的按键;例如,修改回车键处理(modify Return-key handling)。

      QObject::installEventFilter()函数通过设置事件过滤器来实现这一点,从而使指定的过滤器对象在其QObject::eventFilter()函数中接收目标对象的事件。事件过滤器可以在目标对象之前处理事件,从而允许其根据需要检查和丢弃事件。可以使用QObject::removeEventFilter()函数删除现有的事件过滤器。

      当调用过滤器对象的eventFilter()实现时,它可以接受或拒绝(accept or reject)事件,并允许或拒绝(allow or deny)进一步处理事件。如果所有事件过滤器都允许进一步处理事件(通过每个都返回false),则事件将发送到目标对象本身。如果其中一个停止处理(通过返回true),则目标和任何后续事件过滤器根本看不到该事件。

      (5).发送事件:

      许多应用程序都希望创建和发送自己的事件。你可以按照与Qt自己的事件循环完全相同的方式发送事件,方法是构造合适的事件对象并使用 QCoreApplication::sendEvent()和QCoreApplication::postEvent()发送它们。

      sendEvent()立即处理事件。当它返回时,事件过滤器和/或对象本身已经处理了该事件。对于许多事件类,有一个名为isAccepted()的函数,它会告诉你事件是否被最后调用的处理程序接受或拒绝。

      postEvent()将事件发布到队列中以供稍后调度。下次Qt的主事件循环运行时,它会调度所有已发布的事件,并进行一些优化。例如,如果有多个调整大小事件,则将它们压缩为一个。这同样适用于绘制事件:QWidget::update()调用postEvent(),这消除了闪烁并通过避免多次重绘来提高速度。

      postEvent()也用于对象初始化,因为发布的事件通常会在对象初始化完成后很快分发(dispatched)。在实现部件时,重要的是要意识到事件可以在其生命周期的早期传递,因此,在其构造函数中,一定要尽早初始化成员变量,以免它有可能接收事件。

      要创建自定义类型的事件,你需要定义一个事件编号(event number),该编号必须大于QEvent::User,并且你可能需要子类化QEvent以传递有关自定义事件的特定信息。

      5. QEventLoop类提供了进入和离开事件循环的方法

      你可以随时创建一个QEventLoop对象并在其上调用exec()来启动本地事件循环。在事件循环中,调用exit()将强制exec()返回。

      QEventLoop::exec():

      (1).进入主事件循环并等待,直到调用exit()。返回传递给exit()的值。

      (2).如果指定了标志,则只处理标志允许的类型的事件。

      (3).必须调用此函数来启动事件处理。主事件循环从窗口系统(window system)接收事件,并将这些事件分发给应用程序部件。

      (4).一般而言,在调用exec()之前不能发生任何用户交互。作为一种特殊情况,可以在调用exec()之前使用QMessageBox等模式部件(modal widgets),因为模式部件使用自己的本地事件循环。

      (5).要使你的应用程序执行空闲处理(即,只要没有待处理事件,就执行特殊函数),使用超时时间为0ns的QChronoTimer。可以使用processEvents()实现更复杂的空闲处理方案。

      注:以上整理的内容主要来自于Qt官方文档

      GitHubhttps://github.com/fengbingchun/Qt_Test