提问人:Brendan Abel 提问时间:10/24/2023 最后编辑:ekhumoroBrendan Abel 更新时间:10/24/2023 访问量:64
如何使用事件过滤器处理 QComboBox wheel 事件?
How to handle QComboBox wheel-events with an event-filter?
问:
在 Qt6 中,许多小部件(QComboBox、QSpinBox)会窃取应该由其父小部件(如 QScrollArea)处理的鼠标滚轮事件,即使它们没有焦点,即使 已设置为 .focusPolicy
StrongFocus
我可以对这些小部件类进行子类化,并重新实现处理程序以忽略这些事件以完成我想要的功能,但这样做感觉不优雅,因为我需要对表现出这种行为的每个小部件类进行子类化。wheelEvent
class MyComboBox(QtWidgets.QComboBox):
def wheelEvent(self, event: QtGui.QWheelEvent) -> None:
if not self.hasFocus():
event.ignore()
else:
super().wheelEvent(event)
Qt提供了另一种忽略事件的方法,这感觉更具可扩展性和优雅性,因为我可以创建一个事件过滤器并将其应用于任意数量的不同小部件。installEventFilter
class WheelEventFilter(QtCore.QObject):
"""Ignores wheel events when a widget does not already have focus."""
def eventFilter(self, watched: QtCore.QObject, event: QtCore.QEvent) -> bool:
if (
isinstance(watched, QtWidgets.QWidget)
and not watched.hasFocus()
and event.type() == QtCore.QEvent.Type.Wheel
):
# This filters the event, but it also stops the event
# from propagating up to parent widget.
return True
# This doesn't actually ignore the event for the given widget.
event.ignore()
return False
else:
return super().eventFilter(watched, event)
但是,我的问题是,此事件过滤器似乎没有像我预期的那样过滤事件。我希望它只过滤掉被监视
对象的事件,同时还允许将事件传播到父小部件进行处理,但这并没有发生。
是否可以使用上面定义的处理程序实现与上述处理程序相同的效果?wheelEvent
eventFilter
下面是一个显示此行为的独立可重现示例。如果您尝试将鼠标滚动到其中一个组合框上滚动滚动区域,则该组合框将窃取焦点和滚轮事件。
import sys
from PySide6 import QtWidgets, QtCore
class MyWidget(QtWidgets.QWidget):
def __init__(self) -> None:
super().__init__()
# # layout
self._layout = QtWidgets.QVBoxLayout()
self.setLayout(self._layout)
# layout for widget
self._mainwidget = QtWidgets.QWidget()
self._mainlayout = QtWidgets.QVBoxLayout()
self._mainwidget.setLayout(self._mainlayout)
# widgets for widget
self._widgets = {}
num_widgets = 20
for i in range(num_widgets):
combo = QtWidgets.QComboBox()
combo.addItems([str(x) for x in range(1, 11)])
combo.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
self._mainlayout.addWidget(combo)
self._widgets[i] = combo
# scroll area
self._scrollarea = QtWidgets.QScrollArea(self)
self._scrollarea.setWidgetResizable(True)
self._scrollarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self._scrollarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self._layout.addWidget(self._scrollarea)
# widget for scroll area
self._scrollarea.setWidget(self._mainwidget)
def main() -> None:
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
app.exec()
if __name__ == "__main__":
main()
答:
我现在找到了一个适用于 Qt5 和 Qt6 的解决方案。
似乎在 Qt6 中,有必要在事件过滤器中返回 true 之前显式忽略 wheel-event,而在 Qt5 中则不是。但请注意,我只在 arch-linux 上使用 Qt-5.15.11 和 Qt-6.6.0 / Qt-6.2.2 对此进行了测试,因此我不能保证这适用于所有可能的 Qt 版本和/或平台。[这里还值得指出的是,QComboBox(和其他类似的小部件)当前的焦点策略行为有些值得怀疑,因为即使焦点策略设置为 ,小部件仍然接受滚轮滚动事件 - 参见 QTBUG-19730。实际上,重新实现轮子事件处理是一种有点粗略的解决方法,不应该是必要的]。NoFocus
下面是一个基于问题中代码的简化工作演示。我假设期望的行为是,在未聚焦的组合框上滚动的滚轮仍应滚动父滚动区域,但聚焦的组合框应正常滚动浏览其项目:
import sys
from PySide6 import QtWidgets, QtCore
# from PySide2 import QtWidgets, QtCore
# from PyQt6 import QtWidgets, QtCore
# from PyQt5 import QtWidgets, QtCore
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self._layout = QtWidgets.QVBoxLayout()
self.setLayout(self._layout)
self._mainwidget = QtWidgets.QWidget()
self._mainlayout = QtWidgets.QVBoxLayout()
self._mainwidget.setLayout(self._mainlayout)
for i in range(20):
combo = QtWidgets.QComboBox()
combo.addItems(list('ABCDEF'))
combo.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
combo.installEventFilter(self)
self._mainlayout.addWidget(combo)
self._scrollarea = QtWidgets.QScrollArea(self)
self._scrollarea.setWidgetResizable(True)
self._layout.addWidget(self._scrollarea)
self._scrollarea.setWidget(self._mainwidget)
def eventFilter(self, watched, event):
if (event.type() == QtCore.QEvent.Type.Wheel and
not watched.hasFocus()):
event.ignore()
return True
else:
return super().eventFilter(watched, event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
print(f'{QtCore.__package__} (Qt-{QtCore.qVersion()})')
if hasattr(app, 'exec'):
app.exec()
else:
app.exec_()
评论
False
True