找回密码
 立即注册
Qt开源社区 门户 查看内容

Qt入门_摸索事件处理

2019-9-30 10:49| 发布者: admin| 查看: 2011| 评论: 0

摘要: 我回来了,前不久去日本大阪旅游了。这次出行最大的感受是累人,其次是日本有很多优秀值得我学习的地方。虽然不喜欢旅游,但是旅游真的能开阔见识,可以让人更客观的了解这个世界。很多东西是无法通过教科书、网络文 ...
我回来了,前不久去日本大阪旅游了。这次出行最大的感受是累人,其次是日本有很多优秀值得我学习的地方。虽然不喜欢旅游,但是旅游真的能开阔见识,可以让人更客观的了解这个世界。很多东西是无法通过教科书、网络文章、别人口头转述的方式传达给你的,甚至这些传达到你脑袋里的东西其实都是别人刻意安排出来的。文字描述出来的东西天生就是片面的,不要太相信文字,多出去走走,特别是出国走走。现在出国游没那么贵了,或许年收入的 1/20 就可以跟团出国游了。这次出行物有所值,多对比一下,才知道自己想要怎么样的生活。

 

回到正题,Spreadsheet是一个简单的电子表格程序,相当于简化版的 Excel,日常用用还不错,用自己写的工具还挺过瘾的。由于我想给 SpreadSheet 加上拖放文件的功能,所以摸索了一下事件处理相关的知识点,这篇文章可能有点小枯燥。

 

 

一、基础知识


什么是事件处理?


- 事件是窗口系统或者Qt对不同情况的响应。绝大多数被产生的事件都是对户行为的响应(例如使用鼠标点击Qt界面上的 Button),但是也有一些,比如定时器事件,它们是被系统独立产生。

 

- Qt 程序的 main 函数最后会调用QApplicaition::exec() 来进入主事件处理流程,说白了就是进入一个死循环,并不断监听应用程序的事件,发生事件时会生成一个 QEvent 对象;

 

- 事件是一个被发送到事件处理函数的对象,QEvent 的派生类用于表示具体的事件,例如 QMouseEvent 代表鼠标事件,QKeyEvent 代表键盘事件,常见的QEvent子类:



- Qt的主事件循环(QApplication::exec())从事件队列取得本地窗口系统的事件,并将它们转变成QEvent,然后将转换好的事件发送给QObjects,然后进入事件处理流程。Qt中任何QObject 子类都可以接收和处理事件;

 

 

事件处理的流程?




简单的说,就是事件发生后,QApplication::exec()循环会接收到事件,Qt 会创建一个事件对象 QEvent,然后将 QEvent 传给事件接收者 (receiver) 的event( QEvent*e )函数,receiver::event() 能处理一部分类型的事件,对于无法处理的事件,会抛给其他对象来处理,最典型的情况就是先抛给QWidget::event(),如果 QWidget::event() 还处理不了,就抛给 QObject::event();

 

从源码上了解 Qt 是如何构造事件和分发事件的(Qt-5.9):



有了上述知识作为背景,对于我们使用 Qt 的事件处理功能会是有一些帮助的。但是对于初学者,这一部分知识不需要理解得很透彻,只要有个大概的印象即可。等后面积累了一些事件处理的代码后,再回过头来继续补充这些深层的原理性知识,这样整个 Qt 事件处理的知识体系就会自然而然地建立起来。

 

二、事件的传递


 

demo1 用于探索事件传递的过程


界面如下:



 

让Widget 继承 QWdiget,MyLineEdit 继承 QLineEdit。

QLineEdit 的实例lineEdit 的父对象是Widget的实例,

然后重新实现 Widget 和 MyLineEdit 的 keyPressEvent(),观察一下调用顺序:





  

运行效果:



函数调用栈:

在KeyPressEvent()末尾打断点:



- 当在编辑框里按下按键时,Qt 会构造一个QEvent,然后发给receiver(即当前的焦点 MyLineEdit 控件),所以最先调用MyLineEdit::event();



- MyLineEdit::event() 没有被重新实现,所以事件被传递到了 QLineEdit::event();



- QLineEdit::event() 里也没有处理 KeyPressEvent 事件,而是将事件传递给 QWidget::event();



- QWidget::event() 处理 KeyPressEvent 事件的方式就是将其传递给 MyLineEdit::keyPressEvent();



- MyLineEdit::keyPressEvent() 只是简单的打印一句话,没有像QLineEdit 那样将键值显示在编辑框中,所以无论输入什么按键编辑框里都是空空如也;



- MyLineEdit::keyPressEvent() 最后也没回馈给Qt 是accept/ignore 了事件就返回了,但是在回到QWidget::event() 后, QWidget::event() 里应该是将事件 accept 了,所以事件才没继续传递到其父对象 (Widget) 的keyPressEvent ()里。


 


如果在 MyLineEdit::keyPressEvent() 里 ignore 了事件会怎样?




 


运行效果:




这次一样会先执行MyLineEdit::event() 到 MyLineEdit::keyPressEvent(),但是执行完这一部分后,会继续执行下面的操作:




这说明了:


如果事件到达目标对象(MyLineEditlineEdit)之前没被处理,或者也没被目标对象处理,Qt 就会重复整个事件的处理过程,但是这一次会被目标对象的父对象(Widget w)当做新的目标对象。

 

重新实现事件处理函数时,一般会调用父类的相应事件处理函数来实现默认操作:



在子类中可以通过“父类::虚函数”的方式来调用父类的虚函数,这里就不再演示运行效果了,跟普通的 QLineEdit 效果一样,只是中间插入了我们自定义的事件处理步骤(打印信息)。

 


 

5个级别的事件处理和过滤方式


了解了事件的传递后,再来了解下面5个级别的事件处理和过滤方式就没那么抽象了。下面按照优先级从低到高的顺序列举事件处理的5种方式。

 

1.重新实现特定事件处理器(specific event handler)

- mousePressEvent(),keyPressEvent()...,上面的 demo 就是用的这个方法;

- 这种方法最常用、最简单、但是功能最弱;

 

2.重新实现 QObject::event()

- event() 主要负责事件的分发;

- 重写event() 用于在事件到达特定事件处理器之前处理它们;

- 也可用于处理没有过特定事件处理器的不常见事件(例如 QEvent::HoverEnter);

- 对于没有明确处理的情况,必须调用其基类的 event();

- 如果传入的事件已被识别并且处理,返回 true,否则返回 false。

 

3.事件过滤器

- 通过对目标对象调用 installEventFilter() 来注册监视对象(monitor);

- 在监视对象(monitor)的eventFilter() 中处理目标对象的事件;

- 对象一旦安装过滤器 installEventFilter(),用于目标对象的所有事件都会先发送给监视对象的 eventFilter () 函数;

-  如果安装多个事件处理器,则会按照后安装先处理的顺序激活事件过滤器;

 

4.在 QApplication 对象中安装事件过滤器

- 如果 qApp 注册了事件过滤器,则应用程序中所有事件都会先发送给 qApp的 eventFilter() 函数;

- 一般用于调试。

 

5.子类化 QApplication 并且重新实现 notify()

- Qt使用QApplication::notify() 来发送一个事件;

- 重新实现这个函数可以使事件在到达事件过滤器之前获取它们;

- 有完全的控制权;

- 初学者用不上;

 


简单测试事件处理的其他几种方法


测试方式2:重新实现 MyLineEdit::event()



 


运行效果:




在还没调用特定事件处理器KeyPressEvent() 之前,event() 就会被调用;


对于其他非 KeyPress 事件,必须调用父类的event() 跟进处理;


 

测试方式3:安装事件过滤器:



...



...

 


运行效果:




Widget 的实例对象就是监视对象(monitor),而lineEdit 则是目的对象;


lineEdit 的所有事件都会先发给监视对象的eventFilter() 函数;

 



 


五、补充知识

Qt中的事件大致可分为3类:


- Spontaneous events

从系统得到的消息:鼠标按键、键盘按键、定时器事件等。转化为QEvent后被Qt事件系统依次处理;

 

- Posted events

由Qt或应用程序直接生成,放入Qt消息队列

QCoreApplication::postEvent()

 

- Sent events

由Qt或应用程序产生,不放入队列直接被派发和处理

QCoreApplication::sendEvent()

 

 

对比信号和事件


- 信号

信号不是事件,信号本质上是回调函数,可看作是同步操作;

信号发出者是对象;

信号不会循环,接收者会立刻收到;

信号返回值无意义;

 

- 事件

事件一般是通过postEvents()进入到进程主循环的事件队列中,是异步操作;

事件可以通过sentEvents(),是同步操作;

事件的发出者一般来说是窗口系统,少数来自系统内部;

事件回调时都是从当前窗口开始,一级一级向上派发,直到有一个窗口返回 true,截断了事件的处理为止;

事件会根据返回值判断事件是否被处理;

到此,Qt 的事件处理应该算入门完毕了,相关参考:

《C++ GUI Qt4 编程》chapter-7

《Qt Creator快速入门》 chapter-5.3

《Qt 帮助手册》The EventSystem

《Qt 帮助手册》 notify

《C++QT5跨平台界面编程原理实战大全》第12章节 Qt 事件

你和我各有一个苹果,如果我们交换苹果的话,我们还是只有一个苹果。但当你和我各有一个想法,我们交换想法的话,我们就都有两个想法了。如果你也对嵌入式系统开发有兴趣,并且想和更多人互相交流学习的话,请关注我的公众号:ESexpert,一起来学习吧,欢迎各种收藏/转发/批评,小小的转发一下对我来说是极大的恩惠,十分感谢!


----------------------------------------------------------------------------------------------------------------------
我们尊重原创,也注重分享,文章来源于微信公众号:嵌入式系统砖家,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
----------------------------------------------------------------------------------------------------------------------

鲜花

握手

雷人

路过

鸡蛋

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了