【转】QT事件传递与事件过滤器

  1、事件类型

  Qt程序是事件驱动的,程序的每个动作都是由幕后某个事件所触发。 Qt事件的类型很多,常见的qt的事件如下:
    
        键盘事件: 按键按下和松开。
        鼠标事件: 鼠标移动,鼠标按键的按下和松开。
        拖放事件: 用鼠标进行拖放。
        滚轮事件: 鼠标滚轮滚动。
        绘屏事件: 重绘屏幕的某些部分。
        定时事件: 定时器到时。
        焦点事件: 键盘焦点移动。
        进入和离开事件: 鼠标移入widget之内,或是移出。
        移动事件: widget的位置改变。
        大小改变事件: widget的大小改变。
        显示和隐藏事件: widget显示和隐藏。
        窗口事件: 窗口是否为当前窗口。

还有一些非常见的qt事件,比如socket事件剪贴板事件字体改变布局改变等等。

Qt 的event和Qt中的signal不一样。 后者通常用来”使用”widget,而前者用来”实现” widget。 比如一个按钮,我们使用这个按钮的时候,我们只关心他clicked()的signal,至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的。 但是如果我们要重载一个按钮的时候,我们就要面对event了。 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候。

2、 事件产生和处理流程

2.1 事件的产生

事件的两种来源:

一种是系统产生的;通常是window system把从系统得到的消息,比如鼠标按键,键盘按键等,放入系统的消息队列中。 Qt事件循环的时候读取这些事件,转化为QEvent,再依次处理。

一种是由Qt应用程序程序自身产生的。程序产生事件有两种方式,一种是调用QApplication::postEvent()。 例如QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数,new出来一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理。 另一种方式是调用sendEvent()函数。 这时候事件不会放入队列,而是直接被派发和处理,QWidget::repaint()函数用的就是这种方式。

// 自定义事件的时候讲述: 需要注意的时,这两个函数的使用方法不大一样,一个是new,一个是…。

2.2 事件的调度

两种调度方式,一种是同步的,一种是异步。

Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环。 该循环可以简化的描述为如下的代码:

1         while ( !app_exit_loop )
2         {
3                 while( !postedEvents ) { processPostedEvents() }
4                 while( !qwsEvnts ){ qwsProcessEvents();   }
5                 while( !postedEvents ) { processPostedEvents() }
6         }

先处理Qt事件队列中的事件,直至为空。 再处理系统消息队列中的消息,直至为空,在处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理。

调用QApplication::sendEvent的时候,消息会立即被处理,是同步的。 实际上QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节。

2.3 事件的派发和处理

首先说明Qt中事件过滤器的概念。 事件过滤器是Qt中一个独特的事件处理机制,功能强大而且使用起来灵活方便。 通过它,可以让一个对象侦听拦截另外一个对象的事件。 事件过滤器是这样实现的: 在所有Qt对象的基类: QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObject (qobjA)给另一个QObject (qobjB)安装了事件过滤器之后,qobjB会把qobjA的指针保存在eventFilters中。 在qobjB处理事件之前,会先去检查eventFilters列表,如果非空,就先调用列表中对象的eventFilter()函数。

一个对象可以给多个对象安装过滤器。 同样,一个对象能同时被安装多个过滤器,在事件到达之后,这些过滤器以安装次序的反序被调用。 事件过滤器函数( eventFilter() ) 返回值是bool型,如果返回true,则表示该事件已经被处理完毕,Qt将直接返回,进行下一事件的处理; 如果返回false,事件将接着被送往剩下的事件过滤器或是目标对象进行处理。

Qt中,事件的派发是从 QApplication::notify() 开始的,因为QAppliction也是继承自QObject,所以先检查QAppliation对象,如果有事件过滤器安装在qApp上,先调用这些事件过滤器。 接下来QApplication::notify() 会过滤或合并一些事件(比如失效widget的鼠标事件会被过滤掉,而同一区域重复的绘图事件会被合并)。 之后,事件被送到reciver::event() 处理。

同样,在reciver::event()中,先检查有无事件过滤器安装在reciever上。 若有,则调用之。 接下来,根据QEvent的类型,调用相应的特定事件处理函数。 一些常见的事件都有特定事件处理函数,比如:mousePressEvent(),focusOutEvent(),resizeEvent(),paintEvent(),resizeEvent()等等。 在实际应用中,经常需要重载这些特定事件处理函数在处理事件。 但对于那些不常见的事件,是没有相对应的特定事件处理函数的。 如果要处理这些事件,就需要使用别的办法,比如重载event() 函数,或是安装事件过滤器。

事件派发和处理的流程图如下:

2.4 事件的转发

对于某些类别的事件,如果在整个事件的派发过程结束后还没有被处理,那么这个事件将会向上转发给它的父widget,直到最顶层窗口。 如图所示,事件最先发送给QCheckBox,如果QCheckBox没有处理,那么由QGroupBox接着处理,如果QGroupBox没有处理,再送到QDialog,因为QDialog已经是最顶层widget,所以如果QDialog不处理,QEvent将停止转发。

如何判断一个事件是否被处理了呢? Qt中和事件相关的函数通过两种方式相互通信。 QApplication::notify(),QObject::eventFilter(),QObject::event() 通过返回bool值来表示是否已处理。 “真”表示已经处理,“假”表示事件需要继续传递。 另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识。 这种方式只用于event() 函数和特定事件处理函数之间的沟通。 而且只有用在某些类别事件上是有意义的,这些事件就是上面提到的那些会被转发的事件,包括: 鼠标,滚轮,按键等事件。

3、实际运用

根据对Qt事件机制的分析,我们可以得到5种级别的事件过滤,处理办法。 以功能从弱到强,排列如下:

3.1 重载特定事件处理函数
    
    最常见的事件处理办法就是重载象mousePressEvent(),keyPressEvent(),paintEvent() 这样的特定事件处理函数。 以按键事件为例,一个典型的处理函数如下:

 1         void imageView::keyPressEvent(QKeyEvent * event)
 2         {
 3             switch (event->key()) {
 4                 case Key_Plus:
 5                     zoomIn();
 6                     break;
 7                 case Key_Minus:
 8                     zoomOut();
 9                     break;
10                 case Key_Left:
11                     // …
12                 default:
13                     QWidget::keyPressEvent(event);
14             }
15         }

3.2 重载event()函数
    
    通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它。 比如,当我们想改变tab键的默认动作时,一般要重载这个函数。 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数。 当我们重载event()函数时,需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件。

下面这个例子演示了如何重载event()函数,改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上。 )

 1         bool CodeEditor::event(QEvent * event)
 2         {
 3             if (event->type() == QEvent::KeyPress)
 4             {
 5                 QKeyEvent *keyEvent = (QKeyEvent *) event;
 6                 if (keyEvent->key() == Key_Tab)
 7                 {
 8                     insertAtCurrentPosition(‘\t’);
 9                     return true;
10                 }
11             }
12             return QWidget::event(event);
13         }

3.3 在Qt对象上安装事件过滤器

安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)
    
    首先调用B的installEventFilter( const QOject *obj ),以A的指针作为参数。 这样所有发往B的事件都将先由A的eventFilter()处理。
    
    然后,A要重载QObject::eventFilter()函数,在eventFilter() 中书写对事件进行处理的代码。
    
    用这种方法改写上面的例子: (假设我们将CodeEditor 放在MainWidget中)

 1         MainWidget::MainWidget()
 2         {
 3             CodeEditor * ce = new CodeEditor( this, “code editor”);
 4             ce->installEventFilter( this );
 5         }
 6
 7         bool MainWidget::eventFilter( QOject * target, QEvent * event )
 8         {
 9             if( target == ce )
10             {
11                 if( event->type() == QEvent::KeyPress )
12                 {
13                     QKeyEvent *ke = (QKeyEvent *) event;
14                     if( ke->key() == Key_Tab )
15                     {
16                         ce->insertAtCurrentPosition(‘\t’);
17                         return true;
18                     }
19                 }
20             }
21             return false;
22         }

3.4 给QAppliction对象安装事件过滤器

一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个 eventFilter()。 在debug的时候,这个办法就非常有用,也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()丢掉。 ( 在QApplication::notify() 中,是先调用qApp的过滤器,再对事件进行分析,以决定是否合并或丢弃)

3.5 继承QApplication类,并重载notify()函数

Qt是用QApplication::notify()函数来分发事件的。想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法。 通常来说事件过滤器更好用一些,因为不需要去继承QApplication类。 而且可以给QApplication对象安装任意个数的事件过滤器,相比之下,notify()函数只有一个。

转自:http://blog。csdn。net/xuxinshao/article/details/6734749

一个事件过滤器的安装需要下面2个步骤:
    1)调用installEventFilter()注册需要管理的对象。
    2)在eventFilter() 里处理需要管理的对象的事件。

一般,推荐在CustomerInfoDialog的构造函数中注册被管理的对象。像下面这样:

1         CustomerInfoDialog:: CustomerInfoDialog (QWidget *parent):  QDialog(parent) {。。。
2              firstNameEdit->installEventFilter(this);
3              lastNameEdit->installEventFilter(this);
4              cityEdit->installEventFilter(this);
5              phoneNumberEdit->installEventFilter(this);
6         } 

一旦,事件管理器被注册,发送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit的事件将首先发送到eventFilter()。

下面是一个 eventFilter()函数的实现:

 1         bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
 2         {
 3              if (target == firstNameEdit || target == lastNameEdit
 4                      || target == cityEdit || target == phoneNumberEdit) {
 5                  if (event->type() == QEvent::KeyPress) {
 6                      QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
 7                      if (keyEvent->key() == Qt::Key_Space) {
 8                          focusNextChild();
 9                          return true;
10                      }
11                  }
12              }
13              return QDialog::eventFilter(target, event);
14         }

在上面的函数中,我们首先检查目标部件是否是 firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit。接着,我们判断事件是否是按键事件。如果事件是按键事件,我们把事件转换为QKeyEvent。接着,我们判断是否按下了空格键,如果是,我们调用focusNextChild(),把焦点传递给下一个控件。然后,返回,true通知Qt,我们已经处理了该事件。

如果返回false的话,Qt继续将该事件发送给目标控件,结果是一个空格被插入到QLineEdit中。

如果目标控件不是 QLineEdit,或者按键不是空格键,我们将把事件传递给基类的eventFilter()函数。

总结,Qt提供5个级别的事件处理和过滤(功能由弱到强)
    
    1、重新实现事件函数。 比如: mousePressEvent(),keyPress-Event(),  paintEvent() 。
       这是最常规的事件处理方法。
       
    2、重新实现QObject::event()
       这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时。
       
    3、安装事件过滤器
    
    4、在 QApplication 上安装事件过滤器
       这之所以被单独列出来是因为: QApplication 上的事件过滤器将捕获应用程序的所有事件,而且第一个获得该事件。也就是说事件在发送给其它任何一个event filter之前发送给QApplication的event filter。
    
    5、重新实现QApplication 的 notify()方法
    Qt使用 notify()来分发事件。要想在任何事件处理器捕获事件之前捕获事件,唯一的方法就是重新实现QApplication 的 notify()方法。

从assistant上获取的qt event表述:
    
    1、qt的event通过QObject通知另一个qobject,event用一个QEvent类表示,它是所有event事件的基类,特殊event如鼠标event用QEvent的子类,如QMouseEvent类表示。

2、QWidget重载了event()函数,并在这里把所有的event转发给相应的event处理函数,如mousePressEvent(),mouseReleaseEvent()。

3、qt还可以设置一个QObject去监视另一个QObject的event,这个功能通过eventfilter()实现的(installEventFilter()函数)。

4、对于event的截取,我们可以1)重载特定的eventhandler,2)重载QObject的event(),3)安装eventfilter,4)在QApplication安装eventfilter

5、重载QApplication的notify(),此时event还没有转发给eventfilter,可以监视所有的event。

感谢:

【文字来源】http://blog.csdn.net/xuxinshao/article/details/6734749

【图片来源】http://blog.csdn.net/michealtx/article/details/6865891

时间: 2024-12-15 02:37:48

【转】QT事件传递与事件过滤器的相关文章

QT父子窗口事件传递与事件过滤器(讲了一些原理,比较清楚)

处理监控系统的时候遇到问题,在MainWidget中创建多个子Widget的时候,原意是想鼠标点击先让MainWidget截获处理后再分派给子Widget去处理,但调试后发现如果子Widget重新实现了事件方法,就直接处理掉事件了,没有进到MainWidget的处理方法中去,如果子Widget没有accept或ignore该事件,则该事件就会被传递给其父亲,在子Widget存在accept或ignore事件的时候,想要经过一下MainWidget的处理方法,就得用到事件处理器,因此网上找了一下,

QT开发(六十三)——QT事件机制分析

QT开发(六十三)--QT事件机制分析 一.事件机制 事件是由系统或者QT平台本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等:另一些事件则是由系统自动发出,如计时器事件. 事件的出现,使得程序代码不会按照原始的线性顺序执行.线性顺序的程序设计风格不适合处理复杂的用户交互,如用户交互过程中,用户点击"打开文件"将开始执行打开文件的操作,用户点击"保存文件"将开始执

Qt事件机制概览

Qt事件机制概览 Qt事件机制概览 消息循环 Qt事件循环 简介 QEventLoop 跨线程的信号和槽与事件循环 模态窗口 Native widget or Alien widget 创建Native widget 创建QApplication的message-only窗口 派发事件的公共基础方法 source code QApplication的创建过程 QWidget native QWidget 的创建过程 普通native widget回调过程 QApplication的message

QT事件

qtevents多线程工作object存储 Another Look at Events(再谈Events) 最近在学习Qt事件处理的时候发现一篇很不错的文章,是2004年季刊的一篇文章,网上有这篇文章的翻译版,但是感觉部分地方翻译的比较粗糙,不是很明确.索性重新翻译了一遍,并引用了原翻译版的一段译注.以下都是用自己能理解的方式来翻译的,由于水平有限,有很多不足的地方,希望大家指正. Another Look at Events (再谈Events) by Jasmin Blanchette 什

Qt事件机制浅析

Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事件: 按键按下和松开. 鼠标事件: 鼠标移动,鼠标按键的按下和松开. 拖放事件: 用鼠标进行拖放. 滚轮事件: 鼠标滚轮滚动. 绘屏事件: 重绘屏幕的某些部分. 定时事件: 定时器到时. 焦点事件: 键盘焦点移动. 进入和离开事件: 鼠标移入widget之内,或是移出. 移动事件: widget的位

Qt事件机制(是动作发生后,一种通知对象的消息,是被动与主动的总和。先处理自己队列中的消息,然后再处理系统消息队列中的消息)

Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事件: 按键按下和松开. 鼠标事件: 鼠标移动,鼠标按键的按下和松开. 拖放事件: 用鼠标进行拖放. 滚轮事件: 鼠标滚轮滚动. 绘屏事件: 重绘屏幕的某些部分. 定时事件: 定时器到时. 焦点事件: 键盘焦点移动. 进入和离开事件: 鼠标移入widget之内,或是移出. 移动事件: widget的位

Qt事件机制---信号通过事件实现,事件可以过滤,事件更底层,事件是基础,信号是扩展。

Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事件: 按键按下和松开. 鼠标事件: 鼠标移动,鼠标按键的按下和松开. 拖放事件: 用鼠标进行拖放. 滚轮事件: 鼠标滚轮滚动. 绘屏事件: 重绘屏幕的某些部分. 定时事件: 定时器到时. 焦点事件: 键盘焦点移动. 进入和离开事件: 鼠标移入widget之内,或是移出. 移动事件: widget的位

[GUI] QT事件与X11的关系

做了一段时间linux下与QT事件相关的工作,经常会遇到X11,总是苦于无法完全理解其与linux以及QT事件之间的关系,所以用两篇文章来简单总结下linux中的图形管理和QT事件与X11的关系. <1> linux中的图形管理 <2> QT事件与X11的关系 1. Qt中的事件 参考自<C++ GUI Qt 4编程>中第7章给出的Qt事件定义及说明: Qt的事件(event)是由窗口系统或者Qt自身产生,用以响应所发生的各类事情.当用户按下或者松开键盘或者鼠标上的按键

Qt:事件和事件循环

最近想要了解一下Qt线程,但在对相关资料师都是从线程和事件循环开始将.对于事件循环是个相对很抽象的概念,研究了很久也很难在脑子里建立起一个具体的模型.今天在这里对这几天研究内容做总结,为以后做参考. 一.事件 网上很多资料都将的很清楚.在这里重点是明确一下一个事件发出后对于该事件的一个调用顺序.在这里我们在QApplication安装一个过滤器,在一个一个Qwidget安装一个过滤器 代码如下: class Label : public QWidget { public: Label() { i