如何使用事件过滤器处理 QComboBox 滚轮事件?

问题描述 投票:0回答:1

在 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)

但我的问题是,这个事件过滤器似乎没有按照我的预期过滤事件。我希望它仅过滤掉 watched

 对象的事件 
,同时还允许将事件传播到父窗口小部件来处理,但这并没有发生。

是否可以使用

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()
    
python events mousewheel pyside6 qcombobox
1个回答
0
投票
我现在找到了一个适用于 Qt5 和 Qt6 的解决方案。

在 Qt6 中,似乎有必要在事件过滤器中返回 true 之前显式忽略滚轮事件,而在 Qt5 中则不需要。但请注意,我仅在 arch-linux 上使用 Qt-5.15.11 和 Qt-6.6.0 / Qt-6.2.2 进行了测试,因此我不能保证这适用于所有可能的 Qt 版本和/或平台。 [这里还值得指出的是,QComboBox(和其他类似小部件)当前的焦点策略行为有些问题,因为即使将焦点策略设置为

NoFocus

,小部件 
still 也接受wheel-滚动事件 - 请参阅QTBUG-19730。实际上,重新实现轮子事件处理是一种有点粗糙的解决方法,没有必要]。

下面是基于问题中的代码的简化工作演示。我假设所需的行为是在

unfocused 组合框上滚轮滚动仍应滚动父滚动区域,但 focused 组合框应正常滚动其项目:

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_()
    
© www.soinside.com 2019 - 2024. All rights reserved.