事件

在Qt中,事件是作为对象处理的,所有事件对象继承自抽象类QEvent。此类用来表示程序内部发生或者来自于外部但应用程序应该知道的动作。事件能够能过被 QObject 的子类接受或者处理,但是通常用在与组件有关的应用中。本文主要阐述了在一个典型应用中的事件接收与处理。

事件的传递发送

当一个事件产生时,Qt 通过实例化一个 QEvent 的合适的子类来表示它,然后通过调用 event() 函数发送给 QObject 的实例(或者它的子类)。 
event() 函数本身并不会处理事件,根据事件类型,它将调用相应的事件处理函数,并且返回事件被接受还是被忽略。 
一些事件,比如 QMouseEvent 和 QKeyEvent,来自窗口系统;有的,比如 QTimerEvent,来自于其他事件源;另外一些则来自应用程序本身。

事件的类型

大部分事件类型有专门的类,比如 QResizeEvent, QPaintEvent, QMouseEvent, QKeyEvent 和 QCloseEvent。它们都是 QEvent 的子类,并且添加了自己特定的事件处理函数。比如 QResizeEvent 事件添加了 size()和 oldSize() 函数,使组件获知自身大小的改变。

有些事件支持不止一个事件类型。比如 QMouseEvent 鼠标事件,可以表示鼠标的按下,双击,移动,以及其它的一些操作。

每一个事件都有其相关联的类型,由 QEvent::Type 定义。我们能够很方便地在运行时用这些类型来判断该事件是哪一个子类。

因为程序响应方式的多样性和复杂性,Qt 的事件传递机制是富有弹性很灵活的。QCoreApplication::notify() 的相关文档阐述大部分内容;Qt Quarterly 中的文章 Another Look at Events 也进行了简要描述。在这里我们的阐述对于 95% 的程序而言来说已经足够了。

事件的处理

通常事件的处理需要调用一个虚函数。比如,QPaintEvent 事件的处理需要调用 QWidget::paintEvent() 函数。这个虚函数负责做出适当的响应,通常是用来重绘组件。如果你在自己的函数中并不打算实现所有的处理,你可以调用基类的实现。 
例如,下面的代码用来处理鼠标左键点击一个自定义的选择框的操作,而其他的点击事件则被传递给基类 QCheckBox 处理。

  1. void MyCheckBox::mousePressEvent(QMouseEvent *event)
  2. {
  3. if (event->button() == Qt::LeftButton) {
  4. // handle left mouse button here
  5. } else {
  6. // pass on other buttons to base class
  7. QCheckBox::mousePressEvent(event);
  8. }
  9. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果你想代替基类的处理,你必须自己实现所有的功能。但是,如果你只想扩展子基类的功能,你只需要实现你自己需要的那部分,剩下的让基类来替你处理。 
少数情况下,Qt 可能没有指定专门的处理函数,或者指定的处理函数不能满足要求。通常对 Tab 键的处理就会发生这种情况。一般地,Tab 键用来移动焦点,但是一些控件需要 Tab 键作其它的事情。 
这些对象可以通过重新实现 QObject::event() 来满足需要,它们可以在通用处理调用之前或之后来加入自己的处理,或者完全将事件处理替换为自己的事件处理函数。一个非常罕见的控件或许既要处理 Tab 键,又要调用程序特定的事件类型。那么,我们就可以使用以下代码实现。

  1. bool MyWidget::event(QEvent *event)
  2. {
  3. if (event->type() == QEvent::KeyPress) {
  4. QKeyEvent *ke = static_cast<QKeyEvent *>(event);
  5. if (ke->key() == Qt::Key_Tab) {
  6. // special tab handling here
  7. return true;
  8. }
  9. } else if (event->type() == MyCustomEventType) {
  10. MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);
  11. // custom event handling here
  12. return true;
  13. }
  14. return QWidget::event(event);
  15. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

注意,QWidget::event() 在那些没有被处理的事件仍然要被调用,并且通过返回值表示事件是否被处理,返回 true 表示事件被阻止发送到其他的对象。

事件过滤器

有时,并不存在一个特定事件函数,或者特定事件功能不足。最普通的例如按下tab键。正常情况下,被QWidget看成是去移动 键盘焦点,但少数窗口部件需要自行解释。 
让我们试着设想已经有了一个CustomerInfoDialog的小部件。CustomerInfoDialog 包含一系列QLineEdit. 现在,我们想用空格键来代替Tab,使焦点在这些QLineEdit间切换。 
一个解决的方法是子类化QLineEdit,重新实现keyPressEvent(),并在keyPressEvent()里调用focusNextChild()。像下面这样:

  1. void MyLineEdit::keyPressEvent(QKeyEvent *event)
  2. {
  3. if (event->key() == Qt::Key_Space)
  4. {
  5. focusNextChild();
  6. }
  7. else
  8. {
  9. QLineEdit::keyPressEvent(event);
  10. }
  11. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

但这有一个缺点。如果CustomerInfoDialog里有很多不同的控件(比如QComboBox,QEdit,QSpinBox),我们就必须子类化这么多控件。这是一个烦琐的任务。 
一个更好的解决办法是: 让CustomerInfoDialog去管理他的子部件的按键事件,实现要求的行为。我们可以使用事件过滤器。

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

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

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

一旦,事件管理器被注册,发送到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. {
  6. if (event->type() == QEvent::KeyPress)
  7. {
  8. QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  9. if (keyEvent->key() == Qt::Key_Space)
  10. {
  11. focusNextChild();
  12. return true;
  13. }
  14. }
  15. }
  16. return QDialog::eventFilter(target, event);
  17. }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在上面的函数中,我们首先检查目标部件是否是 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()方法。


Qt创建了QEvent事件对象之后,会调用QObject的event()函数做事件的分发。有时候,你可能需要在调用event()函数之前做一些另外的操作,比如,对话框上某些组件可能并不需要响应回车按下的事件,此时,你就需要重新定义组件的event()函数。如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用event()函数。

QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:

virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )

如果watched对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如停止对这个事件的响应,需要返回true

  1. bool MainWindow::eventFilter(QObject *obj, QEvent *event)
  2.    {
  3.    if (obj == textEdit) {
  4.    if (event->type() == QEvent::KeyPress) {
  5.    QKeyEvent *keyEvent = static_cast(event);
  6.    qDebug() << "Ate key press" << keyEvent->key();
  7.    return true;
  8.     } else {
  9.     return false;
  10.    }
  11.    } else {
  12.     // pass the event on to the parent class
  13.     return QMainWindow::eventFilter(obj, event);
  14.     }
  15.    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

上面的例子中为MainWindow建立了一个事件过滤器。为了过滤某个组件上的事件,首先需要判断这个对象是哪个组件,然后判断这个事件的类型。例如,我不想让textEdit组件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回false。对于其他组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。

在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:

  1. void QObject::installEventFilter ( QObject * filterObj )
  • 1

这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。例如,textField.installEventFilter(obj),则如果有事件发送到textField组件是,会先调用obj->eventFilter()函数,然后才会调用textField.event()。

当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。

如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。

注意,如果你在事件过滤器中delete了某个接收组件,务必将返回值设为true。否则,Qt还是会将事件分发给这个接收组件,从而导致程序崩溃。

事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在install之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

事件的调用最终都会调用QCoreApplication的notify()函数,因此,最大的控制权实际上是重写QCoreApplication的notify()函数。由此可以看出,Qt的事件处理实际上是分层五个层次:重定义事件处理函数,重定义event()函数,为单个组件安装事件过滤器,为QApplication安装事件过滤器,重定义QCoreApplication的notify()函数。这几个层次的控制权是逐层增大的。

http://blog.csdn.net/u013007900/article/details/50074127

QT学习 之 事件与事件过滤器(分为五个层次)的更多相关文章

  1. Qt 学习之路 2(22):事件总结

    Qt 学习之路 2(22):事件总结 豆子 2012年10月16日 Qt 学习之路 2 47条评论 Qt 的事件是整个 Qt 框架的核心机制之一,也比较复杂.说它复杂,更多是因为它涉及到的函数众多,而 ...

  2. QT学习(五)----360界面制作(2终结)

    继续上一章的360新特性界面.源代码:http://download.csdn.net/detail/zhangyang1990828/5241242 上一章中实现了整个界面的纯UI设计,这次我们让它 ...

  3. Qt 学习之路 2(21):事件过滤器

    Qt 学习之路 2(21):事件过滤器 豆子 2012年10月15日 Qt 学习之路 2 37条评论 有时候,对象需要查看.甚至要拦截发送到另外对象的事件.例如,对话框可能想要拦截按键事件,不让别的组 ...

  4. Qt 学习之路 2(18):事件

    Home / Qt 学习之路 2 / Qt 学习之路 2(18):事件 Qt 学习之路 2(18):事件  豆子  2012年9月27日  Qt 学习之路 2  60条评论 事件(event)是由系统 ...

  5. qt学习笔记(五) QGraphicsPixmapItem与QGraphicsScene的编程实例 图标拖动渐变效果

    应大家的要求,还是把完整的project文件贴出来,大家省点事:http://www.kuaipan.cn/file/id_48923272389086450.htm 先看看执行效果,我用的群创7寸屏 ...

  6. Qt入门(8)——事件和事件过滤器

    在Qt里,一个事件是继承自QEvent的对象.事件通过调用QObject::event(),被发送到继承自 QObject 的对象.事件发送就是一个事件已经产生,由 QEvent正好去表达,且QObj ...

  7. Qt 学习之路 :事件

    事件(event)是由系统或者 Qt 本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等:另一些事 ...

  8. Qt 学习之路 2(72):线程和事件循环

    Qt 学习之路 2(72):线程和事件循环 <理解不清晰,不透彻>  --  有需求的话还需要进行专题学习  豆子  2013年11月24日  Qt 学习之路 2  34条评论 前面一章我 ...

  9. Qt 学习之路 2(23):自定义事件

    Qt 学习之路 2(23):自定义事件  豆子  2012年10月23日  Qt 学习之路 2  21条评论 尽管 Qt 已经提供了很多事件,但对于更加千变万化的需求来说,有限的事件都是不够的.例如, ...

随机推荐

  1. contextServlet

    一:读取配置文件中的参数信息 1.新建servlet文件ContextServlet1,代码为: import java.io.IOException; import java.util.Enumer ...

  2. CopyU!新插件 CopyPC2U正式发布!

    CopyPC2U双向拷贝插件今天正式上线!该插件需要CopyU!较新版本的支持,可以帮助将文件从电脑自动的复制到移动设备上. 下载地址:http://kuai.xunlei.com/d/BiIvAED ...

  3. j详细说明ava于clone办法

    原文地址:http://leihuang.org/2014/11/14/java-clone/ In java, it essentially means the ability to create ...

  4. 与html相关的知识点整理

    梳理html时发现的一些问题.总结一下,答案大都从网上找来. 一.html 与 htm 的区别 没有本质的区别..htm是在win32时代,系统只能识别3位扩展名时使用的.现在一般都使用.html. ...

  5. 关于TableViewCell高度自适应问题的整理

    TableViewCell高度自适应在网上有很多资料,我只想找出最最最简单的一种方法. 首先梳理一下思路.说到TableViewCell我们第一个想到的问题或许就是cell的复用问题. 1.  [se ...

  6. des 加密 iOS

    转载请注明原文出处 生成同名的Des.h文件和Des.m文件后,拷贝下方的代码可直接使用.注意在Des.m文件中将key值修改为项目所需的key.--->  #define DesKey 过程很 ...

  7. 干I​n​l​a​y​的​生​产​过​程​

    •天线的组成 天线的材料有0.38PET和0.16以及0.3的铝箔组成. 倒封装 •将IC倒装在天线焊盘位置. •方法:先点胶水, 然后把IC对准焊盘(IC一面有凸点),通过热压把IC 固定在焊盘上. ...

  8. cout输出各种进制

    cout使用: int main(){ int a=10;  cout<<"Dec:"<<a<<endl;  cout<<hex&l ...

  9. TCP/IP笔记 三.运输层(4)——TCP链接管理与TCP状态机

    1. 建立连接 三次握手 (1)A 的 TCP 向 B 发出连接请求报文段,其首部中的同步比特 SYN 应置为 1,并选择序号 x,表明传送数据时的第一个数据字节的序号是 x. (2)B 的 TCP ...

  10. Azure 网站的新增功能:可配置的环境变量

     编辑人员注释:本文章由 WindowsAzure 网站团队的项目经理Erez Benari撰写. Azure最常用的强大功能之一是 XML文档转换 (XDT),通过此功能,您可以在Windows ...