Qt事件补充 - 针对书《C++ GUI Qt 4编程》

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事件。

猜你喜欢

转载自blog.csdn.net/sujingclg/article/details/81417314
今日推荐