Qt中事件处理的方法和实例

一、Qt中事件处理的方式

 

1、事件处理模式一

        首先是事件源产生事件,最后是事件处理器对这些事件进行处理。然而也许大家会问,

Qt中有这么多类的事件,我们怎么样比较简便的处理每个事件呢?设想,如果是每个事件都对应同一个事件处理器,在该事件处理器中对不同的事件进行分类处理,这样的弊端有两点:第一,导致该事件处理器过于臃肿复杂;第二,这样不便于扩展,当系统新增加事件类型或者是我们需要使用到自定义事件时,就不得不修改Qt的源码来达到目的。所以Qt设计者的做法是针对不同类型的事件提供不同的事件处理器与之对应。这里又有一个问题了,Qt中是怎么让不同类型事件与之对应的事件处理器相关联的呢?我们不难猜想在事件和事件处理器中间必定有一个桥梁。这个桥梁就是QObject::event()函数,该函数是虚函数,QObject的子类例如QWidget都实现了该函数。该函数的主要功能是进行事件的分发,也就是将不同类型的事件与之相对应的事件处理器相关联,该函数并不对事件进行处理,真正的事件处理是在事件处理器中进行的。

        例如:当QWidget产生QPaintEvent事件后,QWidget的event函数会将该事件分发给QWidget::paintEvent()事件处理器,这样该事件就得到处理了。

以上内容用一个图形表示就是:

2、事件处理模式二

        很多时候,我们只对某些特定的事件比较关心,例如:鼠标单击或者键盘按下等事件。其它的事件我们并不关心它是否发生,也无需对它们进行处理,这个时候最直接的想法就是将这些事件过滤掉,这样做既可以免去对它们进行处理,也可以避免它们对程序其它部分产生影响。因此,处理模式二中我们引入了事件过滤器这个概念。

        如果对象安装了事件过滤器,则事件在到达目标对象之前先被事件过滤器截获,进行一些处理之后再交给目标对象,该模式总结为一个图如下:


注意:这里需要区别对待,如果你是使用installEventFilter()函数给目标对象注册事件过滤器,那么该事件过滤器只对该目标对象有效,只有该对象的事件需要先传递给eventFilter()函数进行过滤,然后调用相应的事件处理器进行处理,非目标对象则不受影响。如果你是给程序中唯一的QApplication对象注册事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给eventFilter()函数,然后再使用事件处理器进行处理。

3、事件处理模式三

        Qt调用QApplicaton来发送一个事件。所以我们可以重新实现QApplication中的notify()函数来获取事件并进行处理,而且使用该函数获取事件的时间要早于事件过滤器获取事件的时间。但是使用事件过滤器较为简便,因为我们可以有多个事件过滤器,但是只能有一个notify()函数。
用一个图来总结该模式就是:


二、Qt中事件处理的方法

        从以上三个处理模式我们可以看出,这是一个不断完善的过程,从3个模式的讨论中我们不难找到可以进行事件处理的地方,而这几个地方就是我们在编写程序的时候可以控制事件处理的地方。
1、event()函数
首先是控制事件分发的event()函数,我们可以改写该函数,改变事件的分发方式,这样就可以改变事件处理的结果。
2、notify()函数
实现该函数可以截获事件,并对事件加以处理,但是该方法很少用,这里不做介绍。
3、事件过滤器
实现自己的事件过滤器就可以改变事件处理的方法和结果,这个方法比较常用。
4、事件处理器
事件处理的最后一步,也是最重要的一步就是事件处理器,因为它才是真正进行事件处理的地方,我们可以改写以有的事件处理器,以此改变已有事件的处理方法和处理结果,我们也可以定义自己的事件类型和相应的事件处理器。

注意:以上四种方法中最常用的是后两者:事件过滤器和事件处理器。


补充内容:

1、事件的传递

        包括鼠标和键盘事件在内的很多事件都可以被传递。如果事件在到达目标对象之前没有被截获处理,或者已经传递给了它的目标对象但目标对象并没有进行处理,那么此时,目标对象的父对象将变成新的目标对象,整个事件处理的过程将重复进行,直到该事件被处理或者到达最顶层对象为止。

2、event实例解析

       下面的代码是QWidget::event()函数的代码,从代码中可以看出event()函数确实只进行事件的分发而不负责事件的处理。由于函数代码过多,且都是一类型的用switch语句进行处理的,这里只贴出一部分代码:

  1. bool QWidget::event(QEvent *event)  
  2. {  
  3.     Q_D(QWidget);  
  4.   
  5.     // ignore mouse events when disabled  
  6.     if (!isEnabled()) {  
  7.         switch(event->type()) {  
  8.         case QEvent::TabletPress:  
  9.         case QEvent::TabletRelease:  
  10.         case QEvent::TabletMove:  
  11.         case QEvent::MouseButtonPress:  
  12.         case QEvent::MouseButtonRelease:  
  13.         case QEvent::MouseButtonDblClick:  
  14.         case QEvent::MouseMove:  
  15.         case QEvent::TouchBegin:  
  16.         case QEvent::TouchUpdate:  
  17.         case QEvent::TouchEnd:  
  18.         case QEvent::ContextMenu:  
  19. #ifndef QT_NO_WHEELEVENT  
  20.         case QEvent::Wheel:  
  21. #endif  
  22.             return false;  
  23.         default:  
  24.             break;  
  25.         }  
  26.     }  
  27.     switch (event->type()) {  
  28.     case QEvent::MouseMove:  
  29.         mouseMoveEvent((QMouseEvent*)event);  
  30.         break;  
  31.   
  32.     case QEvent::MouseButtonPress:  
  33.         // Don't reset input context here. Whether reset or not is  
  34.         // a responsibility of input method. reset() will be  
  35.         // called by mouseHandler() of input method if necessary  
  36.         // via mousePressEvent() of text widgets.  
  37. #if 0  
  38.         resetInputContext();  
  39. #endif  
  40.         mousePressEvent((QMouseEvent*)event);  
  41.         break;  
  42.   
  43.     case QEvent::MouseButtonRelease:  
  44.         mouseReleaseEvent((QMouseEvent*)event);  
  45.         break;  
  46.   
  47.     case QEvent::MouseButtonDblClick:  
  48.         mouseDoubleClickEvent((QMouseEvent*)event);  
  49.         break;  
  50. #ifndef QT_NO_WHEELEVENT  
  51.     case QEvent::Wheel:  
  52.         wheelEvent((QWheelEvent*)event);  
  53.         break;  
  54. #endif  
  55. #ifndef QT_NO_TABLETEVENT  
  56.     case QEvent::TabletMove:  
  57.     case QEvent::TabletPress:  
  58.     case QEvent::TabletRelease:  
  59.         tabletEvent((QTabletEvent*)event);  
  60.         break;  

    在上一篇中我们了解了Qt中事件处理的方式,也提到了最常用的就是使用事件处理器和事件过滤器这两种方法。在这一篇,我们就来看看事件处理器和事件过滤器是怎么使用的。

一、事件处理器使用实例

       Qt中针对每一种常见的事件类型都提供了相应的事件处理器,我们如果想捕获某种类型的事件并进行自定义处理,那么只需要实现重写这些事件处理器就行,至于常见的时间类型和对应的事件处理器如下图:

        在我的程序中,我使用到了鼠标滚轮事件,主要实现的就效果就是大家比较熟悉的:用一个控件显示图片,当滚动鼠标滚轮的时候可以调整图片显示的大小。
        这里我要做的就是捕获该图片显示控件的鼠标滚轮事件,然后改写该控件的鼠标滚轮滚动事件处理器。
        首先我们来看看鼠标滚轮事件以及相应的事件处理器是啥,查看上面的图即可知道:

        在Qt帮助文档里面我们可以查看到如下语句“The event handler QWidget::wheelEvent() receives wheel events.”。也就是说最后事件处理器会调用wheelEvent()函数来处理该事件,因此我们需要的就是改写wheelEvent()这个函数。

第一步:在头文件中申明该函数:


第二步实现wheelEvent()函数(这里主要关注的是事件处理的框架,而不是具体的示例,因此不去过多讲解代码细节):

  1. <span style="font-size:16px;">void ImageWidget::wheelEvent(QWheelEvent *event)  
  2. {  
  3.     int numDegrees = event->delta();  
  4.     int num = numDegrees / 120;  
  5.     if(num < 0)  
  6.     {  
  7.         num = 0 - num;  
  8.     }  
  9.   
  10.     if (event->orientation() == Qt::Horizontal) {  
  11.   
  12.         //scrollHorizontally(numSteps);  
  13.         scale *= 1.25;  
  14.         resize(this->scale * this->size());  
  15.     } else {  
  16.         //scrollVertically(numSteps);  
  17.         if(numDegrees > 0)  
  18.         {  
  19.             //scale *= 0.75;  
  20.             resize(num * 0.75 * this->size());  
  21.         }  
  22.         else if(numDegrees < 0)  
  23.         {  
  24.             //scale *= 1.25;  
  25.             resize(num * 1.25 * this->size());  
  26.         }  
  27.     }  
  28.   
  29.     event->accept();  
  30. }</span>  

        到这里这个功能就实现了,大家看看上面的头文件就知道这里还实现了其它类型事件的处理,其实都是一样的思路,找到欲处理的事件类型,找到对应的事件处理器,重写事件处理器中处理事件的方法即可。

二、事件过滤器使用实例

        Qt事件模型中一项非常强大的功能就是一个QObject实例可以监视另一个QObject实例中的事件,实现方法是在目标对象中安装事件过滤器。这里我们接着上面的实例进行,为上面的图片浏览器增加一个功能:在我的程序中设置了一个自动播放的按键,点击按键后,在图片显示部件中就会显示一系列图片,就像动画播放许多幅图片一样,当在上面点击鼠标左键时,就停止动画播放而是停留在当前播放的图片;当我再点击鼠标右键的时候,又恢复为动画播放模式。
        首先说一下主要的页面布局:我在一个QMainWindow中放置一个自定义的图片显示部件。对照上面的说明,我们很容易知道,实现该功能的方法就是在目标部件(自定义的图片显示部件)上注册事件过滤器,此时的事件过滤器就是我们所说的监视对象,完成这些步骤之后,当目标部件有事件产生后,首先会传递给监视对象(事件过滤器)进行处理而不是该事件对应的事件处理器。所以说我们可以截获事件进行处理。监视对象截获目标对象的事件后就会调用自己的eventFilter()函数处理这些事件。
总结起来就两个步骤:

第一:对目标对象调用installEventFilter()来注册监视对象(事件过滤器);
第二:重写监视对象的eventFilter()函数处理目标对象的事件。

我们就严格按照这两步走:
首先使用installEventFilter()函数给目标对象注册事件监听器:

  1. <span style="font-size:16px;">//给图片显示部件注册事件过滤器  
  2. imageWidget->installEventFilter(this);</span>  

然后就是重写监视对象eventFilter()函数:

  1. <span style="font-size:16px;">//图片显示部件时间过滤器处理  
  2. bool PMainWindow::eventFilter(QObject *target, QEvent *event)  
  3. {  
  4.     if(target == imageWidget)  
  5.     {  
  6.         qDebug("The imageWidget generate the event!");  
  7.         if(event->type() == QEvent::MouseButtonPress)  
  8.         {  
  9.             QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);  
  10.             if(mouseEvent->buttons() & Qt::LeftButton)  
  11.             {  
  12.                 qDebug("The Left Button Event!");  
  13.                 killTimer(timeId);  
  14.             }  
  15.             else if(mouseEvent->buttons() & Qt::RightButton)  
  16.             {  
  17.                 qDebug("The Right Button Event!");  
  18.   
  19.                 //clickNum++;  
  20.                 //again();  
  21.                 timeId = startTimer(3000);  
  22.             }  
  23.   
  24.             return true;  
  25.         }  
  26.     }  
  27.   
  28.     //其它部件产生的事件则交给基类处理  
  29.     return QMainWindow::eventFilter(target, event);  
  30. }</span>  


阅读更多
个人分类: QT
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭