在 PyQt5 中按下 'ctrl' 时如何防止滚动?

How to prevent scrolling while 'ctrl' is pressed in PyQt5?

我正在使用 PyQt5 在 QGraphicsView 中查看图像。我希望能够在按住 ctrl 并使用鼠标滚轮的同时缩放 in/out。我有这个工作,但是如果图像太大,并且有滚动条,它会忽略缩放功能,直到您滚动到顶部或底部。

如何解决这个问题,使其在按下 ctrl 时不滚动,同时允许它缩放 in/out。

from PyQt5.QtWidgets import QFileDialog, QLineEdit, QWidget, QPushButton, QApplication, QVBoxLayout, QLabel, QGraphicsView, QGraphicsPixmapItem, QGraphicsScene
from PyQt5.QtCore import pyqtSignal, Qt
from pdf2image import convert_from_path
from PIL import ImageQt
import sys

class step1(QWidget):
    changeViewSignal = pyqtSignal()
    
    def __init__(self, parent=None):
        super(step1, self).__init__(parent)
        self.name = QLineEdit(self)
        self.fileBtn = QPushButton("Select file", self)
        self.nextBtn = QPushButton("Next", self)
        self.graphicsView = QGraphicsView()
        # self.graphicsView.setFrameShadow(QFrame.Raised)
        # self.graphicsView.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContentsOnFirstShow)
        self.graphicsView.setHorizontalScrollBarPolicy()

        self.layout = QVBoxLayout(self)
        self.layout.addWidget(self.name)
        self.layout.addWidget(self.fileBtn)
        self.layout.addWidget(self.nextBtn)
        self.layout.addWidget(self.graphicsView)

        self.fileBtn.clicked.connect(self.convert_file)

    def wheelEvent(self, event):
        modifiers = QApplication.keyboardModifiers()
        if modifiers == Qt.ControlModifier:
            self.graphicsView.scrollContentsBy(0,0)
            x = event.angleDelta().y() / 120
            if x > 0:
                self.graphicsView.scale(1.05, 1.05)
            elif x < 0:
                self.graphicsView.scale(.95, .95)

    def convert_file(self):
        fname = QFileDialog.getOpenFileName(self, 'Open File', 'c:\', "PDF Files (*.pdf)")
        if len(fname[0]) > 0:
            pages = convert_from_path(fname[0])
            images = []
            qimage = ImageQt.toqpixmap(pages[0])
            item = QGraphicsPixmapItem(qimage)
            scene = QGraphicsScene(self)
            scene.addItem(item)
            self.graphicsView.setScene(scene)
        

       

if __name__ == '__main__':
   app = QApplication(sys.argv)
   ex = step1()
   ex.show()
   sys.exit(app.exec_())

滚动首先由 QGraphicsView 处理,然后再传播到您重新实现 wheelEvent 的父窗口小部件。这就是为什么当它有 space 滚动时根据正常的 QGraphicsView 行为发生滚动。

一个解决方案是继承 QGraphicsView 并在那里重新实现 wheelEvent

class GraphicsView(QGraphicsView):

    def wheelEvent(self, event):
        if event.modifiers() & Qt.ControlModifier:
            x = event.angleDelta().y() / 120
            if x > 0:
                self.scale(1.05, 1.05)
            elif x < 0:
                self.scale(.95, .95)
        else:
            super().wheelEvent(event)

然后在这里使用子类名:

self.graphicsView = GraphicsView()

除了 提出的正确解决方案之外,还有使用事件过滤器的选项,这对于在 Designer 中创建的 UI 非常有用,无需使用升级的小部件。

要牢记的重要方面是事件过滤器 必须 安装在视图 viewport() 上(场景内容实际位于其中的小部件)呈现并可能滚动),因为 that 是将接收滚轮事件的小部件:输入事件始终发送到 下的小部件鼠标(或具有键盘焦点)[1],如果事件未被处理,可能会传播到它们的父级[2].

class step1(QWidget):
    def __init__(self, parent=None):
        # ...
        self.graphicsView.viewport().installEventFilter(self)

    def eventFilter(self, source, event):
        if event.type() == event.Wheel and event.modifiers() & Qt.ControlModifier:
            x = event.angleDelta().y() / 120
            if x > 0:
                self.graphicsView.scale(1.05, 1.05)
            elif x < 0:
                self.graphicsView.scale(.95, .95)
            return True
        return super().eventFilter(source, event)

返回True意味着视口[=52​​=]已经处理了事件,它应该传播到父级; QGraphicsView 基于 QAbstractScrollArea,如果视口不处理滚轮事件,它将调用视口父级(图形视图)的基础 wheelEvent 实现,默认情况下将 post 事件到滚动条。如果过滤器 returns True,它将避免传播,从而防止滚动。

请注意,除非您真的知道自己在做什么,否则您不应使用scrollContentsBy;作为文档 explains:«调用此函数以编程方式滚动是错误的,请改用滚动条»。

[1] 鼠标事件总是发送到鼠标下方的 topmost 小部件,除非 modal 子部件window 处于活动状态,或者有一个 mouse grabber,它是一个已经收到鼠标按钮按下事件但还没有收到鼠标按钮释放事件的小部件,或者已明确调用 grabMouse() was explicitly called. Keyboard events are always sent to the widget of the active window that has current focus, or the widget on which grabKeyboard() 的小部件。
[2]“处理的事件”可能是一个令人困惑的概念:它并不意味着小部件实际上对事件“做了”某事,也不意味着它没有,无论事件是否变成接受忽略。小部件可以“忽略”事件并仍然以某种方式对其做出反应:例如,您可能需要通知用户事件已 收到 ,但让父级管理它无论如何。