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

时间:2019-12-30 15:09:06

标签: python pyqt5

下面给出的代码是从SO上的another question派生的。

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

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


main.py

using System;
using UnityEditor;
using UnityEditor.PackageManager.Requests;
using UnityEditor.PackageManager;
using UnityEngine;

namespace Unity.Editor.Example {
   static class ListPackageExample
   {
       static ListRequest Request;

       [MenuItem("Window/List Package Example")]
       static void List()
       {
           Request = Client.List();    // List packages installed for the Project
           EditorApplication.update += Progress;
       }

       static void Progress()
       {
           if (Request.IsCompleted)
           {
               if (Request.Status == StatusCode.Success)
                   foreach (var package in Request.Result)
                       Debug.Log("Package name: " + package.name);
               else if (Request.Status >= StatusCode.Failure)
                   Debug.Log(Request.Error.message);

               EditorApplication.update -= Progress;
           }
       }
   }
}

mainwindow.ui

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

1 个答案:

答案 0 :(得分:1)

对象收到事件后,其event()函数将对其进行处理。
如果对象以某种方式支持该事件,则它可以返回False,这意味着该事件将由其祖先(如果有的话)处理(对于小部件,表示其直接父代),它将继续直到顶级对象,直到它们中的任何一个都返回True。
考虑到某些事件(例如QWheelEvent)可以由小部件处理,并且仍然可以“返回”给其父母(通过返回False),这与将事件设置为接受或忽略并不一定有关。

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

此外,您使用的是QGraphicsView(内部创建QGraphicsWheelEvent并根据场景内容和rect表现不同),它是一个滚动区域,一个复杂的窗口小部件,至少具有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)