前面的章节中我们曾经提到event()
函数。事件对象创建完毕后,Qt 将这个事件对象传递给QObject
的event()
函数。event()
函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。
如上所述,event()
函数主要用于事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个event()
函数了。例如,我们希望在一个QWidget
组件中监听 tab 键的按下,那么就可以继承QWidget
,并重写它的event()
函数,来达到这个目的:
bool CustomWidget::event(QEvent *e) { if (e->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e); if (keyEvent->key() == Qt::Key_Tab) { qDebug() << "You press tab."; return true; } } return QWidget::event(e); }
CustomWidget
是一个普通的QWidget
子类。我们重写了它的event()
函数,这个函数有一个QEvent
对象作为参数,也就是需要转发的事件对象。函数返回值是 bool 类型。如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,并且,该事件对象设置了accept()
,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。注意,在event()
函数中,调用事件对象的accept()
和ignore()
函数是没有作用的,不会影响到事件的传播。
我们可以通过使用QEvent::type()
函数可以检查事件的实际类型,其返回值是QEvent::Type
类型的枚举。我们处理过自己感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()
函数继续转发,否则这个组件就只能处理我们定义的事件了。为了测试这一种情况,我们可以尝试下面的代码:
bool CustomTextEdit::event(QEvent *e) { if (e->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e); if (keyEvent->key() == Qt::Key_Tab) { qDebug() << "You press tab."; return true; } } return false; }
CustomTextEdit
是QTextEdit
的一个子类。我们重写了其event()
函数,却没有调用父类的同名函数。这样,我们的组件就只能处理 Tab 键,再也无法输入任何文本,也不能响应其它事件,比如鼠标点击之后也不会有光标出现。这是因为我们只处理的KeyPress
类型的事件,并且如果不是KeyPress
事件,则直接返回 false,鼠标事件根本不会被转发,也就没有了鼠标事件。
通过查看QObject::event()
的实现,我们可以理解,event()
函数同前面的章节中我们所说的事件处理器有什么联系:
//!!! Qt5 bool QObject::event(QEvent *e) { switch (e->type()) { case QEvent::Timer: timerEvent((QTimerEvent*)e); break; case QEvent::ChildAdded: case QEvent::ChildPolished: case QEvent::ChildRemoved: childEvent((QChildEvent*)e); break; // ... default: if (e->type() >= QEvent::User) { customEvent(e); break; } return false; } return true; }
这是 Qt 5 中QObject::event()
函数的源代码(Qt 4 的版本也是类似的)。我们可以看到,同前面我们所说的一样,Qt 也是使用QEvent::type()
判断事件类型,然后调用了特定的事件处理器。比如,如果event->type()
返回值是QEvent::Timer
,则调用timerEvent()
函数。可以想象,QWidget::event()
中一定会有如下的代码:
switch (event->type()) { case QEvent::MouseMove: mouseMoveEvent((QMouseEvent*)event); break; // ... }
事实也的确如此。timerEvent()
和mouseMoveEvent()
这样的函数,就是我们前面章节所说的事件处理器 event handler。也就是说,event()
函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()
函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 Qt 调用我们自己实现的版本。
由此可以见,event()
是一个集中处理不同类型的事件的地方。如果你不想重写一大堆事件处理器,就可以重写这个event()
函数,通过QEvent::type()
判断不同的事件。鉴于重写event()
函数需要十分小心注意父类的同名函数的调用,一不留神就可能出现问题,所以一般还是建议只重写事件处理器(当然,也必须记得是不是应该调用父类的同名处理器)。这其实暗示了event()
函数的另外一个作用:屏蔽掉某些不需要的事件处理器。正如我们前面的CustomTextEdit
例子看到的那样,我们创建了一个只能响应 tab 键的组件。这种作用是重写事件处理器所不能实现的。
52 评论
又问一个无关的问题……最近想在自己的手机上移植android cyanogenmod,因为手机是中国电信合约机,根本没有外国hacker组织帮忙做包,国内的包也很少(电信合约机的缘故)而且比较坑……豆哥熟悉android移植么,编译环境啊什么源码获取啊都已经全部搞定了,感觉原材料和工具都已经备齐,但是实在是不知道怎么着手,难道我应该去一字一行看cyanogenmod和htc官方的两套源代码吗,我觉得这工作量实在是太巨了……下载就下了我三天三夜……,网上那些蜻蜓点水的教程根本就是水贴,只会教怎么链接到代码库等最基本的工作(其实大陆还有g`f`w障碍那些教程也基本不说可见应该不是认真做的),还教怎么在虚拟机里装一个ubuntu...(我现在是单硬盘主引导的opensuse反正环境是没有什么障碍),而从头为一台机子做抑制更是中外都没有,连cmwiki都没有,唉……想来豆哥既然对qt这样的框架感兴趣,似乎也应该会了解android的样子(这是武断随便想的,因为总觉得有点共鸣,像是我平时比较在意的就有android qt 和 html5等等)。觉得如果能有linux c方面的资深咨询一下,或许可以不至于想现在这样手足无措,不过只是问一问而已算不上是什么请求,谢先。
Android 移植的确没有做过,google 上面一下 android port 没准儿会有一些信息,比如这里 http://www.freeyourandroid.com/guide/porting-android。因为对移植一直没有研究,所以不便多做解释。抱歉的哦。
啊……多谢回复,在xda之类的地方问了很久,回答基本就是google it,说实话我已经google好久了才发问了(否则好像会被骂),可是终究还是遇到很大阻塞,只能慢慢来了吧。
先去把linux嵌入式驱动开发学会,一切就都不是问题了~
event()函数返回值true或false有什么用
event() 返回值供系统使用,一般自己不会在意这个返回值的。如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。
如果事件处理了却返回false会产生错误码?会影响事件向父组件的传递吗?
这个我的确没有测试过,你可以试一下看看 。像这种回调函数,系统只能假设开发人员会按照文档说明实现并且不会犯错,因为系统本身没办法判断究竟需要怎样的逻辑,只能相信开发人员。
谢谢!
true就不会传递event
false会继续向父组建传递event
我看没明白这个文章
是不是我点 tab键不会有反应???
求解
点tab键会被这个自定义的custom组件接收处理,处理完成后变返回了,
如果不是点tab的其他所有事件依然交付给上一级的父对象处理。
一个事件发生之后,Qt怎么知道该发给哪个QObject的event()函数呢?我看了一下文档里关于QApplication::sendEvent()函数的说明,一个sendEvent()函数只能连接一对event-receiver。所以Qt是会自动把事件发送给所有的QObject(包括自定义的)吗?
个人认为会进行广播,也就是发送给所有 QObject,只不过有的对象会进行处理,有的对象直接忽略。至于 sendEvent() 函数,你当然可以通过两次调用来发送给两个对象的
thx,感谢豆子君的回复~btw,说得更细点可能是QCoreApplication类先发送给每个Object树的顶层?(因为子组件ignore后会自动发送给父组件)
感觉应该是可以这样理解的
keypress event应该是发给当前焦点的object,如果不处理为false,再由object向父object传递,处理就为true 停止传递
豆子豆子,你的第一段代码和第二段代码是不是贴反了?
代码没贴反,是我看错了。
第一段代码, 如果处理了 tab 键后, 直接返回 true, 那是不是意味着 tab 键消息不会再被分发?
同样的, 第二段代码, 如果只是想拦截所有的消息, 那返回 false 和 返回 true 不都是一样的吗?
如果返回值是 true,并且,该事件对象设置了accept(),那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。
这里, 返回 false 又会怎么样? 我感觉不管是返回 true 还是 false, 因为都没有进行分发, 所以消息都算是被拦截了, 返回 true QT 会继续处理下一个事件, 返回 false 难道是不处理了吗?
编译提示CustomWidget不是域名或命名空间,怎么办?
有可能是没有 include 头文件之类,或者哪里写错了
那需要include什么头文件?是QWidget吗?我加了但是没用...
如果是 CustomWidget 找不到,应该 include CustomWidget 头文件
Qt::Popup属性在windows平台与linux平台下表现得不一致是怎么回事?
需求:
A,B同属于一个主窗口
第一步:窗口A,通过点AA按键 后popup出来,点此窗口以外的主窗口区域,这个窗口A会自动隐藏起来。
第二步:窗口B,通过点BB按键 后popup出来,
当点AA按键时效果是把B窗口隐藏再把A窗口显示出来。
windows平台的表现是这样的,只要设置了Qt::Popup属性
linux平台的表现是:在第二步按AA键时只是把B窗口隐藏了,A窗口并没有显示出来
看了这两节,想问下组件事件和信号处理的区别是什么呢或者说两者适用的场合,他们处理的信息有什么区别呢?比如说,同样的按钮点击,信号和事件应该都能进行处理吧?上节说的是使用组件关心的是槽,自定义组件,关心的是事件,这里自定义组件应该也是从组件类继承来的吧,还是说自己完全制作的(就是说自己封装的)?到底应该怎样理解他们之间的关系呢?谢谢
这个没有绝对的答案,不过个人认为,如果信号槽和事件都可以处理的,优先选择信号,因为信号的解耦更好,使用也更简单,不容易出错。当没有类似信号时,只能选择事件。当你继承了某个组件,就可以选择使用事件,因为事件处理函数都是 protected 的,很明显只能继承使用;并且,由于信号槽比一般的函数调用慢大约一个数量级,因此在已经继承了情况下,选择事件可能更合理一些。
好的,多谢豆子哥的耐心解答,对于您说的 没有类似信号时,只能选择事件。是在什么情况下呢?信号和事件都是可以进行自定义的,没有类似信号可以通过自定义信号来处理?不知道这种理解对不对?对于继承,感觉绝大多数自定义组件都是继承来的吧,不知道理解对不对?
没有类似信号,比如你需要分开处理鼠标左键、右键等点击时,就只能使用事件,信号是没有类似区分的。如果你是自定义组件供外部使用(不仅仅是给别的开发者,也可能是被系统其它组件调用),就可以在事件中发出自己的信号,方便调用。自定义组件大部分会通过继承,也有可能是组合,比如几个按钮一组,类似 QDialogButtonBox(当时都会是继承了 QWidget)。
需要分开处理鼠标左键、右键等点击时,可以通过自定义鼠标左键、右键等点击信号以及相应的槽来实现吧?
是的,但是系统没有类似的信号,所以就是不得不使用事件的情况
请问大神,按钮用鼠标点击了一下之后,再按下空格键会自动调用该按钮,是怎么回事,
鼠标点击之后,按钮会获得焦点。在操作系统的实现上,按下空格会触发窗口中具有焦点的组件的动作
重写事件处理器是不是要在原来event()函数中重新调用这个事件处理器,那不是相当于要改写event()函数?
不需要,因为
event()
函数是虚函数,会在运行时调用我们实现的版本,也就是动态绑定。那是不是通过子类进行重写事件处理器,运行期间会调用子类的实现版本?
是的,因为这个函数是 virtual 的,会在运行时决定执行哪个实现
豆哥,返回false是指继续传播事件给父类吗?如果是那么上面说,鼠标事件根本不会被转发又是什么意思呢?
您好。我重写CustomWidget中重写event()函数后编译运行,按下Tab键,不会触发CustomWidget::event(),而是焦点在上一章的两个按钮(CustonButton和CustomButtonEx)之间来回切换。请问这说明我没有重载成功吗?谢谢!
默认行为应该是切换,所以注意下函数签名是不是重写?
你好。
对于前面的一个有关sendpost函数的问题,你的猜测是广播形式,我这边跟踪源码后,发现并不是这样的,而是在发送信号的时候,先使用reciever和事件形成QPostEvent存入list,然后在事件循环执行到sendPost的时候,取出这个list的元素,往reciever分发,这是我对目前5.9源码的跟踪,请作者有时间再次跟踪确认。
我之前也看过作者有关事件的文章,但是前几次看完都是晕晕呼呼的,可能是我基础不好,看了就忘记了。
最近,我自己开始跟踪qt源码,收获很大,对于qt的事件机制有一些了解,但也模糊。
作者的qtcreator源码分析,我也看过一些,还是非常不错的,有时间继续读,这些作者的贡献。
码字略多,请耐心读完,谢谢。
既然用c++分格的类型转换 dynamic_cast 转换不是比用static_cast更加安全,对于基类到派生类的转换
是的,这里用 dynamic_cast 要比 static_cast 好
豆子 怎么调用父类同名函数呀 那个只处理tab不处理其他事件这个问题,最后是怎么解决了的呢?没看太明白这里
子类如果实现了父类的同名函数,由于多态机制,使用父类指针调用时会直接调用子类的实现,这样父类函数的实现逻辑就没有了。如果仍然需要父类函数的实现逻辑,就需要使用 QTextEdit::event() 这样的调用。
处理Tab 按键事件,使用`KeyPressEvent()`好呢,还是`event()` 好呢?
两种都可以实现,看怎么方便怎么来
如果自己需要处理多种类型的事件时,使用event,反则使用 对应的事件处理器
这里例子中关于Tab键盘事件非常值得注意的是,QWidget::event帮助里说了,如果一个组件有子组件,则Tab和Shift+Tab事件不会被event(也就不会被keyPressEvent)所处理,而是直接在子组件之间切换焦点。通过测试的确是这样的,这里例子成立的条件是没有可切换焦点的子组件,那么Tab和Shift+Tab则会被event接受到。
感谢作者的教程,受益匪浅。
上面说的问题在QWidget::event里找到了代码
```
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
keyPressEvent(k);
```
但是如果我在一个外层MainWindow里面放CustomWidget,里面再放Button的话,最后按下Tab的时候外层Mainwindow、里层CustomWidget还是Button都收不到Tab,所以我很好奇这个切换焦点操作是在什么位置执行的。看来还需要进一步了解。