QWheelEvent 使用分析一例

本文详细解析了在Qt环境下,当鼠标悬停于spinbox内部时滚轮事件生效,离开后失效的情况,并通过深入理解Qt事件系统,揭示了问题本质及解决方案。通过在spinbox的wheelEvent()函数中正确处理事件转发与忽略机制,实现了滚轮事件在spinbox外部的有效响应。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

缘起

一网友问:

在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()不就行了??

答案是:不行!!

事件转发?

我们在 Qt事件系统 一文中提到了事件转发,它描述的是这样一种情况:

+------------+
|     C      |
| +-------+  |
| |   B   |  |
| | +--+  |  |
| | |  |  |  |
| | | A|  |  |
| | +--+  |  |
| +-------+  |
+------------+

假定有父子关系的3个Widget:A、B、C(C是顶级窗口)。我们现在在 A 上点击鼠标:

  • 如果A没有接受该事件(ignore),那么事件将转发到B
  • 如果B依然没有接受,将转发到C
  • 即使C不接受,但由于已经是顶级窗口,事件也将停止传递。

所以,我们应该看清楚了:Manual中这个句子描述的是事件转发时的处理。

但前一节的那个句子不是事件转发。二者没有联系!!

WheelEvent

Qt源码学习(从Win32到Qt) 一文中,我们举得就是Wheel Event的例子。它不属于我们前面提到的事件转发行为。

我们重新看看这段代码(它在窗口的回调函数中被调用,将系统的Wheel事件转换成Qt的Wheel事件,而后派发):

bool QETWidget::translateWheelEvent(const MSG &msg) { ... // if there is a widget under the mouse and it is not shadowed // by modality, we send the event to it first int ret = 0; QWidget* w = QApplication::widgetAt(globalPos); if (!w || !qt_try_modal(w, (MSG*)&msg, ret)) { //synaptics touchpad shows its own widget at this position //so widgetAt() will fail with that HWND, try child of this widget w = this->childAt(this->mapFromGlobal(globalPos)); if (!w) w = this; } // send the event to the widget or its ancestors { QWidget* popup = QApplication::activePopupWidget(); if (popup && w->window() != popup) popup->close(); QWheelEvent e(w->mapFromGlobal(globalPos), globalPos, delta, Qt::MouseButtons(state & Qt::MouseButtonMask), Qt::KeyboardModifier(state & Qt::KeyboardModifierMask), orient); if (QApplication::sendSpontaneousEvent(w, &e)) return true; } // send the event to the widget that has the focus or its ancestors, if different if (w != QApplication::focusWidget() && (w = QApplication::focusWidget())) { QWidget* popup = QApplication::activePopupWidget(); if (popup && w->window() != popup) popup->close(); QWheelEvent e(w->mapFromGlobal(globalPos), globalPos, delta, Qt::MouseButtons(state & Qt::MouseButtonMask), Qt::KeyboardModifier(state & Qt::KeyboardModifierMask), orient); if (QApplication::sendSpontaneousEvent(w, &e)) return true; } return false;

  • 首先是定位光标下的widget,将事件发送到该widget中(注意popup widget的处理)
  • 如果该widget不接受(sendSpontaneousEvent返回false)
    • 则查找当前拥有焦点的widget,让事件发送到该焦点widget

看到差别了吧:

要求

事件转发

返回true,但event被ignore

此处

返回false

注意:QWidget::event()的Manual中有些关于事件转发的介绍,请注意查看(此处略)。

如何解决?

答案至此已经很明确了:

  • 在wheelEvent()中,我们只能调用event的accept或ignore,这个只会影响到事件转发
  • 要影响返回值,我们必须override基类的 event() 函数让其返回false才行

一个完整的例子如下:

# -*- coding: UTF-8 -*- ''' Copyright (C) 2011 dbzhang800#gmail.com ''' try: from PySide import QtCore, QtGui except ImportError: from PyQt4 import QtCore, QtGui 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_())

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值