我回来了,前不久去日本大阪旅游了。这次出行最大的感受是累人,其次是日本有很多优秀值得我学习的地方。虽然不喜欢旅游,但是旅游真的能开阔见识,可以让人更客观的了解这个世界。很多东西是无法通过教科书、网络文章、别人口头转述的方式传达给你的,甚至这些传达到你脑袋里的东西其实都是别人刻意安排出来的。文字描述出来的东西天生就是片面的,不要太相信文字,多出去走走,特别是出国走走。现在出国游没那么贵了,或许年收入的 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。 ---------------------------------------------------------------------------------------------------------------------- |