Qt事件补充 - 针对书《C++ GUI Qt 4编程》_sujingclg的博客-程序员秘密

QEventLoop类

QEventLoop类提供了一种进入和离开事件循环的方式。任何时候用户都可创建一个QEventLoop对象并调用exec()以开启一个局部事件循环。在此事件循环中调用exit()可强制让exec()返回。

int QEventLoop::exec(ProcessEventsFlags flags = AllEvents)

进入主事件循环并等待直到调用exit()。返回值为传入exit()的值。如果指定了flags的值,则只有flags所指定的类型的事件可被处理。必须调用此函数才能开启事件处理。主事件循环从窗口系统接收事件并将这些事件分发给应用程序的各个窗口部件。

一般来说,在调用exec()之前将没有任何用户交互。一个特例是,模态窗口组件如QMessageBox可在调用exec()前使用,因为模态窗口组件使用它们自己的局部事件循环。

可使用一个0超时的QTimer以使应用程序执行闲置处理(例如,无论是否有待处理事件(pending events)都执行一个指定的函数)。使用processEvents()可实现更复杂的闲置处理机制。

void QEventLoop::exit(int returnCode = 0)

在调用此函数后,开启事件循环的exec()函数将返回returnCode的值,同时事件循环结束。依惯例,returnCode为0时说明成功,非0值表示有错误。当returnCode为0时,告诉事件循环以正常方式结束,等同于调用QEventLoop::quit()。
注意:不同于C语言库函数中的同名函数,此函数返回给调用者,说明事件处理结束。

bool QEventLoop::isRunning() const

如果事件循环正在运行则返回true,否则返回false。事件循环运行在调用exec()和调用exit()之间。

bool QEventLoop::processEvents(ProcessEventsFlags flags = AllEvents)

处理匹配flags的待处理事件,直到没有更多的事件需要处理。若待处理事件被处理则返回true,否则返回false。

此函数在不许用户输入的条件下指示耗时运行的进度时尤其有用,比如传入ExcludeUserInputEvents标识时。

此函数是对QAbstractEventDispatcher::processEvents()的简单封装。

void QEventLoop::processEvents(ProcessEventsFlags flags, int maxTime)

重载的函数,需在maxTime毫秒内处理待处理事件,或直到没有更多事件需要被处理。
注意:
1. 此函数不会连续处理事件;它在所有可处理的事件被处理后返回。
2. 指定flags为WaitForMoreEvents无意义且会被忽略。

QAbstractEventDispatcher类

QAbstractEventDispatcher类提供了管理Qt的事件循环的接口。

一个事件分发器从窗口系统和其他源头接收事件,然后将接收到的事件发送给QCoreApplication或QApplication的实例以进行处理和转发。QAbstractEventDispatcher对事件的递送提供了精细的控制。

对事件处理进行简单控制可使用QCoreApplication::processEvents()。

若要对应用程序的事件循环进行更为精细的控制,可先调用QAbstractEventDispatcher的instance()静态成员函数,然后由其返回的QAbstractEventDispatcher对象调用相应的方法即可。如果用户想使用自定义的派生自QAbstractEventDispatcher类的实例,必须在默认的事件分发器被安装之前调用QCoreApplication的setEventDispatcher()方法或QThread的setEventDispatcher()方法来安装它。

通过调用QCoreApplication的exec()方法和exit()方法可开启和停止主事件循环。QEventLoop用于创建局部事件循环。

执行耗时操作的程序可调用QAbstractEventDispatcher的processEvents()方法,并传入由位或运算符连接的各种QEventLoop::ProcessEventsFlag,以控制应递送哪些事件。

QAbstractEventDispatcher也允许将外部事件循环与Qt事件循环集成在一起。

自定义事件:

实现Qt自定义事件需要继承QEvent类,并对此进行扩展(同其它类库的使用相似)。继承QEvent最重要的是需要提供一个QEvent::Type类型的参数,作为自定义事件的类型值。这个type是用户在处理事件时用于识别事件类型的代号,可以看作是对象间的一种通讯机制。比如在event()函数中(P130),使用QEvent::type()函数获得传入事件的类型,然后与目标类型QEvent::KeyPress做对比。

QEvent::Type是QEvent定义的一个枚举,因此可以为自定义事件的类型指定一个int值。但是自定义事件的type值不能和已经存在的type值重复,否则会有不可预料的错误发生,因为系统会将你新增加的事件当做系统事件进行派发和调用。在Qt中,系统保留0–999的值,因此自定义事件的type值要大于999。Qt定义了两个边界值来避免用户记忆此数值:QEvent::User和QEvent::MaxUser,自定义事件的type应该在这两个值的范围之间。其中,QEvent::User的值是1000,QEvent::MaxUser的值是65535(2^16)。为避免用户自定义事件之间出现重复的type值,可使用Qt提供的registerEventType()函数,进行自定义事件的注册。该函数签名如下:

  static int QEvent::registerEventType(int hint = -1);

由于此函数是静态的,可以在自定义事件类的构造函数中直接调用。函数返回向系统注册的新的Type值。若hint为用户设置的值,则当其合法(无覆盖)时直接返回这个值,否则系统会自动分配一个合法值并返回。当hint为默认值-1时,直接返还一个合法值。此函数是线程安全的,不必另外添加同步。

用户可以在QEvent子类中添加自己的事件所需要的数据,然后发送此事件。Qt中提供了两种事件发送方式:QCoreApplication的static public函数sendEvent()和postEvent()。两个函数的函数原型如下:

  bool QCoreApplication::sendEvent(QObject * receiver, QEvent * event);
  void QCoreApplication::postEvent(QObject * receiver, QEvent * event, int priority = Qt::NormalEventPriority);
  1. sendEvent()通过调用QApplication::notify(), 直接进入了事件的派发和处理环节,将传入的事件对象event发送给接收者,不走事件队列。sendEvent()会把事件直接发送给notify(),之后由notify()把事件event发送给接收者receiver->event(event)。sendEvent()和notify()的返回值就是事件处理函数(event handler)的返回值。在事件被发送的时候,event对象并不会被销毁,因此通常在栈上创建event对象。
  2. postEvent()把event追加到事件队列中,然后立即返回。这个过程最终也要调用notify()把事件分发给参数中的接收者(receiver->event(event))。因为post事件队列会持有事件对象,并且在事件被发送后将其delete掉,因此必须使用new运算符在堆上创建event对象。此函数是线程安全的。

在事件传递的过程中,权限是由大到小的,不算sendEvent()和postEvent()和事件循环,notify()具有最大控制权,因为它最先见到event,我们可以重写notify()开始的这个处理链上的函数(notify() → eventFilter() → event() → event处理器)来响应event,从而进行相应的操作。此外,还可以通过重写notify()函数来影响事件的传递过程。

创建一个事件类型并将其post到事件队列的代码如下:

  const QEvent::Type MyEvent = (QEvent::Type)1234;  //QCustomEvent构造函数的参数是QEvent::Type类型。
  QApplication::postEvent(obj, new QCustomEvent(MyEvent));  //事件必须是QCustomEvent(或其子类)的类型。

之后是处理所发送的自定义事件,这需要重写QObject::customEvent()函数,该函数接收一个QEvent对象的指针作为参数。用户可以通过转换传入的Qevent对象的类型来对不同的事件进行处理,或将其分发给对应的事件处理函数(类似于event()):

void CustomWidget::customEvent(QEvent *event) {
    CustomEvent *customEvent = static_cast<CustomEvent *>(event);
    // ... }

当然,也可以在event()函数中直接处理:

  bool CustomWidget::event(QEvent *event) {
      if (event->type() == MyCustomEventType) {
          CustomEvent *myEvent = static_cast<CustomEvent *>(event);
          // processing...
          return true; }
      return QWidget::event(event);}

事件:

Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发。事件可以由程序生成,也可以在程序外部生成(与外设的交互、操作系统生成等)。

当事件发生时,Qt将创建一个事件对象。Qt中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。这类似于Win32 API中SDK函数中的DispatchMessage()函数。

与信号不同,事件并不是一产生就被分发。事件产生之后被加入到一个队列中(这里队列的含义同数据结构中的概念,先进先出),该队列即被称为事件队列。事件分发器遍历事件队列,如果发现事件队列中有事件,那么就把这个事件发送给它的目标对象。这个循环被称作事件循环。

Qt程序需要在main()函数中创建一个QCoreApplication(或QApplication)对象,并使用此对象调用exec()函数以开启事件循环。随后,程序将进入事件循环来监听应用程序的事件。

调用QCoreApplication::exec()函数意味着进入了主事件循环。可把事件循环理解为一个无限循环,直到调用QCoreApplication::exit()或者QCoreApplication::quit()后,事件循环才真正退出。

事件循环是可以嵌套的,子层的事件循环执行exec()的时候,父层事件循环就处于中断状态;当子层事件循环跳出exec()后,父层事件循环才能继续循环下去。另外,子层事件循环具有父层事件循环的几乎所有功能。Qt会把事件送到当前生效的那个事件循环队列中去,所以用户在主线程中执行各种exec()(如QMessageBox::exec(),QEventLoop::exec())的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是GUI界面仍然能够正常响应。如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出。

事件循环的嵌套机制使得向非阻塞式API提供阻塞机制成为可能:使用QEventLoop类创建新的事件循环,通过调用QEventLoop::exec()函数,重新进入新的事件循环(子事件循环),给QEventLoop::quit()槽函数发送信号则退出这个事件循环。示例代码如下:

  QEventLoop eventLoop; 
  connect(netWorker, &NetWorker::finished, 
          &eventLoop, &QEventLoop::quit); 
  QNetworkReply *reply = netWorker->get(url); 
  replyMap.insert(reply, FetchWeatherInfo); 
  eventLoop.exec();

由于QNetworkReply没有提供阻塞式API,并且要求只有一个事件循环。上述代码使得局部的QEventLoop代替全局事件循环被网络访问阻塞,当网络响应完成时,这个局部的事件循环就会退出。

三种类型的事件:

基于事件如何被产生与分发,可以把事件分为三类:

  • Spontaneous事件,由窗口系统产生(如重画窗口paint事件),它们被放到系统队列中,通过事件循环逐个处理。
  • Posted事件,由Qt或是应用程序产生,它们被Qt组成队列,再通过事件循环处理。
  • Send事件,由Qt或是应用程序产生,但它们被直接发送到目标对象。

首先,事件循环处理事件队列中的事件(Posted事件),直到队列空。然后再处理系统消息队列中的事件(spontaneous事件),最后它处理所有的因为处理spontaneous事件而产生的posted事件。send事件并不在事件循环内处理,它们都直接被发送到了目标对象。

posting相对于sending的一个优势是,它给了Qt一个压缩(compress)事件的机会。假如你在一个widget上连续地调用update()十次,因update()而产生的这十个事件,将会自动地被合并为一个单独的事件(但此时QPaintEvents事件附带的区域信息也将被合并)。合并工作由notify()执行。可压缩的事件类型包括:paint, move, resize, layout hint, language change。

注意,你可以在任何时候调用QApplication::sendPostedEvent(),强制Qt产生一个对象的posted事件。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sujingclg/article/details/81417314

智能推荐

“12306”是如何支撑百万QPS的?_机器学习算法与Python学习-公众号的博客-程序员秘密

来源:掘金作者:绘你一世倾城链接:https://juejin.im/post/5d84e21f6fb9a06ac824814912306 抢票,极限并发带来的思考每到节...

文献查询网站_qq_39389646的博客-程序员秘密

1.Web of Science ( http://apps.webofknowledge.com )2. Glgoo学术搜索引擎(https://scholar.glgoo.org/ )3. X-Mol学术资讯数据库(https://scholar.glgoo.org/ )4. 谷粉学术搜索引擎(https://gfsoso.99lb.net/ )5. SCI-HUB搜索引擎(http:...

问题解决之——WARNING: Re-reading the partition table failed with error 16: 设备或资源忙._Snail_cz的博客-程序员秘密

linux扩展磁盘,格式化分区时候,如果报错:WARNING: Re-reading the partition table failed with error 16: 设备或资源忙.[[email protected] grid]# fdisk /dev/sda...WARNING: Re-reading the partition table failed with error 16: 设备或资源忙.The kernel still uses the old table. The new table will

HuggingFace 所有预训练模型下载地址_Reza.的博客-程序员秘密

继承自上一篇博客:BERT、RoBERTa下载地址map除了像上一篇博客一样,自己去huggingface transformer的源代码里面把各个模型的下载url找出来之外,最快的方法可以直接上huggingface model 官网:如下,搜索自己需要的模型:然后在Files and Versions里面把模型需要的参数bin、config和vocab都一一down下来:当然也可以wget:...

MacOS下载MySQLWorkbench8.0.23意外退出解决办法_李问号的博客-程序员秘密

MacOS下载MySQLWorkbench8.0.23意外退出解决办法MacOS11版本下载完MySQLWorkbench8.0.23,打开时显示意外退出,原因可能是目前apple还不支持MySQLWorkbench最新版。解决方法是先下载旧版本即MySQLWorkbench8.0.22版本。下载链接: https://dev.mysql.com/downloads/workbench/.下载过程非常简单,和下载其它软件没什么区别。成功下载并打开,配上实图:...

Linux 内核延时函数_与时俱进2014的博客-程序员秘密_linux内核延时

linux内核提供3个函数分别进行纳秒,微妙和毫秒延时:void ndelay(unsigned long nsecs);void udelay(unsigned long usecs);void mdelay(unsigned long msecs);这3个函数的延时原理是忙等待,也就是说在延时的过程中并没有放弃cpu,根据cpu的频率进行一定次数

随便推点

【Android】详解Android 网络操作_weixin_30667649的博客-程序员秘密

目录结构:contents structure [+]判断网络判断是否有网络连接判断WIFI网络是否可用判断MOBILE网络是否可用获取当前网络连接的类型信息监听网络获取网络信息需要在AndroidManifest.xml文件中加入相应的权限。&lt;uses-permission android:name="android.pe...

PTA:删除重复字符_Xerxes Lee的博客-程序员秘密

PTA:删除重复字符题目输入格式输出格式解答样例题目本题要求编写程序,将给定字符串去掉重复的字符后,按照字符ASCII码顺序从小到大排序后输出。输入格式输入是一个以回车结束的非空字符串(少于80个字符)。ad2f3adjfeainzzzv输出格式输出去重排序后的结果字符串。23adefijnvz解答样例#define _CRT_SECURE_NO_WARNINGS#inc...

Win32 api使用中调用GetOpenFileName打开文件对话框无响应的解决方法_0x03ff的博客-程序员秘密

好久没有写代码了,偶尔兴起写了一下居然还BUG不断,简直不能忍受。短短的几行代码就调了一下午,最后才发现原来是消息函数的错误。调用打开文件的代码void OpenFileDlg(HWND hDlg, TCHAR *pszPathName){ TCHAR szPathName[MAX_PATH]; // lStructSize // 指定这个结构的大小,以字节为单位。 // Wi

openlayers学习二:画多边形_锦岁的博客-程序员秘密_openlayers画多边形

&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;title&gt;画多边形&lt;/title&gt; &lt;script src="dist/ol.js"&gt;&lt;/script&gt; &lt;link rel="styl...

vant 上传附件后回显_vant上传文件到后端_weixin_39964819的博客-程序员秘密

最近在做手机版页面,采用的vant框架,这个上传控件和以前用iview、element有点不一样,iview、element都是直接提供后端接口文件会自动发送到后端,vant需要自己负责发送文件到后端,对于我这种面向百度编程人员还是有点难度。特意记一下,能帮到其他面向百度编程人员代码很简单,基本是使用文件构建FormData参数,如下:html代码:after-read="afterRead":b...

云客Drupal源码分析之缓存系统Cache_u011474028的博客-程序员秘密

在介绍drupal8的缓存系统前我们先了解一下缓存系统的本质及特性,缓存的存在依赖于两个目的:节省资源和提高速度,起不到这两作用则缓存没有存在的必要,当一个结果需要进行大量计算才能得到,而它又不会频繁更新那么缓存结果可以节省大量算力,缓存的是一个结果,这个结果可以存放在多台服务器上面实现负载均衡,从而进一步提高访问速度,在高访问网站中缓存非常重要,drupal8的缓存设计也是围绕这两个目的而设计。

推荐文章

热门文章

相关标签