缘起
一网友问:
在VS里用Qt新建一个含mainwindow的项目,然后里面添加一个spinbox。除此之外啥都不做。 运行后,鼠标在spinbox内的时候,滚轮有效,移到spinbox外,无效。 在这种情况下,如何使滚轮在spinbox外对其有效?
答案其实很简单,QWheelEvent 的 Manual中如此描述(可是,你真的理解这句话了么?):
Wheel events are sent to the widget under the mouse cursor, but if that widget does not handle the event they are sent to the focus widget.
一点弯路
说实话,这句话一开始我没有理解。相信大家也是一样,特别是,和它紧挨着还有这样一句话:
A wheel event contains a special accept flag that indicates whether the receiver wants the event. You should call ignore() if you do not handle the wheel event; this ensures that it will be sent to the parent widget.
看起来答案应该很明确,对吧?我们在派生类中override基类的wheelEvent(),然后对事件调用ignore()不就行了??
答案是:不行!!
事件转发?
我们在
一文中提到了事件转发,它描述的是这样一种情况:
+------------+ | C | | +-------+ | | | B | | | | +--+ | | | | | | | | | | | A| | | | | +--+ | | | +-------+ | +------------+
假定有父子关系的3个Widget:A、B、C(C是顶级窗口)。我们现在在 A 上点击鼠标:
- 如果A没有接受该事件(ignore),那么事件将转发到B
- 如果B依然没有接受,将转发到C
- 即使C不接受,但由于已经是顶级窗口,事件也将停止传递。
所以,我们应该看清楚了:Manual中这个句子描述的是事件转发时的处理。
但前一节的那个句子不是事件转发。二者没有联系!!
WheelEvent
在
一文中,我们举得就是Wheel Event的例子。它不属于我们前面提到的事件转发行为。
我们重新看看这段代码(它在窗口的回调函数中被调用,将系统的Wheel事件转换成Qt的Wheel事件,而后派发):
- 首先是定位光标下的widget,将事件发送到该widget中(注意popup widget的处理)
- 如果该widget不接受(sendSpontaneousEvent返回false)
- 则查找当前拥有焦点的widget,让事件发送到该焦点widget
看到差别了吧:
要求 |
|
事件转发 |
返回true,但event被ignore |
此处 |
返回false |
注意:QWidget::event()的Manual中有些关于事件转发的介绍,请注意查看(此处略)。
如何解决?
答案至此已经很明确了:
- 在wheelEvent()中,我们只能调用event的accept或ignore,这个只会影响到事件转发
- 要影响返回值,我们必须override基类的 event() 函数让其返回false才行
一个完整的例子如下:
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.spinbox = QtGui.QSpinBox();
vbox = QtGui.QVBoxLayout(self)
vbox.addWidget(self.spinbox)
vbox.addStretch(1)
def event(self, evt):
if evt.type()==QtCore.QEvent.Wheel:
return False
return super(Widget, self).event(evt)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())