相同的鼠标移动以不同的方式评估



下面给出的代码来自SO的另一个问题。

我更改了使用鼠标移动来计算进度条值的方式,并添加了一个重置按钮。我注意到,以大致相同的速度和角度计算鼠标滚轮旋转并不总是以相同的方式进行评估。有时,当鼠标滚轮快速移动时,进度条会"跳跃"到几乎 100%,而在其他情况下,在几乎相同的速度和角度下,它甚至没有达到 50%。

这种不同行为的原因是什么(以及如何解决它(?


main.py

import sys
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QPoint, QEvent
from PyQt5.QtGui import QCursor, QPainterPath, QPen
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem, QWidget
from PyQt5.uic import loadUi
app = None

class MouseListener(QObject):
posChanged = pyqtSignal(QPoint)
wheelChanged = pyqtSignal(QPoint)
def __init__(self, widget):
super().__init__(widget)
self._widget = widget
self._childrens = []
self._setup_widget(self._widget)
for w in self._widget.findChildren(QWidget):
self._setup_widget(w)
self._childrens.append(w)
def _setup_widget(self, w):
w.installEventFilter(self)
w.setMouseTracking(True)
def eventFilter(self, obj, event):
if obj in [self._widget] + self._childrens and event.type() == QEvent.MouseMove:
self.posChanged.emit(event.globalPos())
if event.type() == QEvent.Wheel:
self.wheelChanged.emit(event.angleDelta())
if event.type() == QEvent.ChildAdded:
obj = event.child()
if obj.isWidgetType():
self._setup_widget(obj)
self._childrens.append(obj)
if event.type() == QEvent.ChildRemoved:
c = event.child()
if c in self._childrens:
c.removeEventFilter(self)
self._childrens.remove(c)
return super().eventFilter(obj, event)

class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
loadUi("mainwindow.ui", self)
self.showMaximized()
self.global_pos = QCursor.pos()
self.reset_button.clicked.connect(self._reset_progress_bars)
for lay in (self.verticalLayout_top, self.verticalLayout_bottom):
view = GraphicsView()
lay.addWidget(view)
window_listener = MouseListener(self)
window_listener.posChanged.connect(self.on_pos_changed)
window_listener.wheelChanged.connect(self.on_wheel_changed)
@staticmethod
def _update_bar(progress_bar, first, second):
delta = abs(first - second)
current_value = progress_bar.value()
new_value = current_value + delta
progress_bar.setValue(new_value)
def _reset_progress_bars(self):
self.progressBar_x_plus.setValue(0)
self.progressBar_x_minus.setValue(0)
self.progressBar_y_plus.setValue(0)
self.progressBar_y_minus.setValue(0)
self.progressBar_w_plus.setValue(0)
self.progressBar_w_minus.setValue(0)
def on_pos_changed(self, pos):
new_x = pos.x()
new_y = pos.y()
old_x = self.global_pos.x()
old_y = self.global_pos.y()
if new_x > old_x:
self._update_bar(self.progressBar_x_plus, old_x, new_x)
if new_x < old_x:
self._update_bar(self.progressBar_x_minus, old_x, new_x)
if new_y > old_y:
self._update_bar(self.progressBar_y_plus, old_y, new_y)
if new_y < old_y:
self._update_bar(self.progressBar_y_minus, old_y, new_y)
self.global_pos = pos
def on_wheel_changed(self, pos):
new_w = pos.y()
if new_w > 0:
self._update_bar(self.progressBar_w_plus, 0, new_w)
print("W+", new_w)
if new_w < 0:
self._update_bar(self.progressBar_w_minus, 0, new_w)
print("W-", new_w)

class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.start = None
self.end = None
self.setScene(QGraphicsScene())
self.path = QPainterPath()
self.item = GraphicsPathItem()
self.scene().addItem(self.item)
self.contents_rect = self.contentsRect()
self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
self.horizontalScrollBar().blockSignals(True)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.verticalScrollBar().blockSignals(True)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def mousePressEvent(self, event):
if event.buttons() & Qt.LeftButton:
self.start = self.mapToScene(event.pos())
self.path.moveTo(self.start)
super().mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
self.end = self.mapToScene(event.pos())
self.path.lineTo(self.end)
self.start = self.end
self.item.setPath(self.path)
super().mouseMoveEvent(event)

class GraphicsPathItem(QGraphicsPathItem):
def __init__(self):
super().__init__()
pen = QPen()
pen.setColor(Qt.black)
pen.setWidth(5)
self.setPen(pen)

def main():
global app
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())

if __name__ == "__main__":
main()

主窗口.UI

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1003</width>
<height>703</height>
</rect>
</property>
<property name="windowTitle">
<string>Mouse Pointer</string>
</property>
<property name="locale">
<locale language="English" country="UnitedKingdom"/>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0">
<layout class="QVBoxLayout" name="verticalLayout_bottom"/>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_top"/>
</item>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="QProgressBar" name="progressBar_y_plus">
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QProgressBar" name="progressBar_x_minus">
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QProgressBar" name="progressBar_x_plus">
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_y_minus">
<property name="text">
<string>Y-</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_x_plus">
<property name="text">
<string>X+</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QProgressBar" name="progressBar_y_minus">
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_y_plus">
<property name="text">
<string>Y+</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_x_minus">
<property name="text">
<string>X-</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QProgressBar" name="progressBar_w_plus">
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QProgressBar" name="progressBar_w_minus">
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_w_plus">
<property name="text">
<string>W+</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_w_minus">
<property name="text">
<string>W-</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="reset_button">
<property name="text">
<string>Reset bars</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1003</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

当对象收到事件时,其event()函数会处理它。
如果对象以某种方式支持该事件,它可以返回False,这意味着该事件将由其祖先处理(如果有的话((对于小部件,它意味着它的直接父级(,它将上升到顶级对象,直到它们中的任何一个返回 True。
考虑到像QWheelEvent这样的一些事件可以由小部件处理,并且仍然被"发回"给它们的父级(通过返回False(,这不一定与将事件设置为接受或忽略有关。

因此,当您看到"加速"值时,您得到的实际上是您添加到_childrens列表中的所有对象正在处理的相同轮子事件。

此外,您正在使用 QGraphicsView(它在内部创建 QGraphicsWheelEvent,并根据场景内容和矩形的不同行为不同(,它是一个滚动区域,一个至少具有 4 个子小部件的复杂小部件:视口、滚动内容和 2 个滚动条。
即使您隐藏了滚动条并阻止了它们的信号,它们仍然存在,滚动区域与它们交互,Qt通过向它们发送事件也是如此。
尝试让滚动条保持活动状态和可见,您将看到滚动条达到其最大值(对于向下滚动(或最小值(对于向上(后,滚轮事件的数量会变大:那是因为它们不能超过其当前限制,因此事件将被"忽略"(其event()返回 True(并由其父级处理。
在这种情况下,将或多或少地按以下顺序接收和筛选轮事件:

  • 图形视图的滚动区域"小部件",会将其发送到...
  • 图形场景(未过滤(,会将其发送到...
  • 接受滚轮事件(未筛选(的最顶层项,否则返回到
  • 图形视图的视口
  • 图形视图的滚动条
  • 图形视图的父级...
  • 中央小部件
  • 主窗口

在您的实现中,很难区分是否已筛选的事件,并且您显然不能使用与鼠标移动相同的概念,因为没有对先前事件数据的引用。

我能想到的唯一解决方案是子类 QApplication 并覆盖其notify()方法:

class WheelNotifyApp(QApplication):
wheelChanged = pyqtSignal(QPoint)
def notify(self, obj, event):
if event.type() == QEvent.Wheel and isinstance(obj, QWindow):
self.wheelChanged.emit(event.angleDelta())
return super().notify(obj, event)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
# ...
QApplication.instance().wheelChanged.connect(self.on_wheel_changed)

最新更新