【问题标题】:Remain the zoom feature while remove the scroll (pan) feature PyQt保留缩放功能,同时删除滚动(平移)功能 PyQt
【发布时间】:2021-04-18 10:14:32
【问题描述】:

我正在做一个图像查看器,允许用户浏览他们的文件夹并显示文件夹中的第一张图像。通过滚动鼠标滚轮,用户可以放大和缩小图像。但是,由于鼠标滚轮的滚动功能仍然存在,因此图像也会上下平移。我尝试 scrollbaralwaysoff 但它也不起作用。我可以知道如何在保留缩放功能的同时禁用滚动功能吗?

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
import os
from tg_audit import Ui_MainWindow




class mainProgram (QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.browser()
 

    def browser(self):
        self.model = QFileSystemModel(self.centralwidget)
        self.model.setRootPath('')
        self.treeView.setModel(self.model)
        self.treeView.setAnimated(False)
        self.treeView.setIndentation(20)
        self.treeView.setSortingEnabled(True)
        self.treeView.clicked.connect(self.open_image)

    def open_image(self, index):
        #get path from browser
        path = self.sender().model().filePath(index)
        print(path)
        self.folder_path = r'{}'.format(path)
        self.list_of_images = os.listdir(self.folder_path)
        self.list_of_images = sorted(self.list_of_images)

        #path of the image
        input_img_raw_string = r'{}\\{}'.format(path,self.list_of_images[0])

        #load image path to graphic view
        self.scene = QGraphicsScene()
        self.scene.addItem(QGraphicsPixmapItem(QPixmap.fromImage(QImage(input_img_raw_string))))
        self.graphicsView.setScene(self.scene)
        self.graphicsView.fitInView(self.scene.sceneRect(),Qt.KeepAspectRatio)
        self.zoom = 1
        self.rotate = 0


    def wheelEvent(self, event):

        x = event.angleDelta().y() / 120
        if x > 0:
            self.zoom *= 1.05
            self.updateView()
        elif x < 0:
            self.zoom /= 1.05
            self.updateView()



    def updateView(self):

        self.graphicsView.setTransform(QTransform().scale(self.zoom, self.zoom).rotate(self.rotate))

if __name__ == '__main__':
    import sys
    from PySide2.QtWidgets import QApplication
    app = QApplication(sys.argv)
    imageViewer = mainProgram()
    imageViewer.show()
    sys.exit(app.exec_())

【问题讨论】:

    标签: python qt pyqt5


    【解决方案1】:

    在处理 UI 元素时,重要的是要记住 Qt 将事件中继到“当前”小部件(键盘事件的当前焦点元素,或鼠标下最上面的启用小部件),然后该小部件将决定是否可以处理该事件,在后一种情况下,该事件然后传播到其父级,继续爬上对象树,直到小部件实际可以处理该事件。请注意,在这种情况下,处理一个事件并不意味着该小部件实际上以某种方式对其作出反应:由于各种原因,一个小部件可能只是接受事件并且仍然不做任何事情(考虑一个禁用的按钮)。

    问题是您只为主窗口实现滚轮事件,这意味着两件事:

    • 即使在不相关的上下文上滚动也可能会收到该事件;例如,如果您在 QLineEdit 上滚动,您将触发缩放,这可能会让用户非常困惑;
    • 只有在图形视图传播时才会触发缩放:如果滚动条可见,它们将“吃掉”(接受)事件,直到它们到达它们的边界,并且仅在那个时候点事件会传播到主窗口,最后触发缩放;

    解决方案是在适当的小部件视图上跟踪滚轮事件;有两种可能的解决方案:子类化和事件过滤。

    子类化

    这是常见的 OOP 方法,它提供了更好的模块化并允许更整洁的重新实现。

    class View(QtWidgets.QGraphicsView):
        def __init__(self):
            super().__init__()
            self.zoom = 1
            self.rotate = 0
    
        def fitInView(self, *args, **kwargs):
            super().fitInView(*args, **kwargs)
            self.zoom = self.transform().m11()
    
        def wheelEvent(self, event):
            x = event.angleDelta().y() / 120
            if x > 0:
                self.zoom *= 1.05
                self.updateView()
            elif x < 0:
                self.zoom /= 1.05
                self.updateView()
    
        def updateView(self):
            self.setTransform(QtGui.QTransform().scale(self.zoom, self.zoom).rotate(self.rotate))
    

    请注意,我重新实现了fitInView,以便正确地将缩放设置为通过重新缩放设置的当前缩放级别。

    事件过滤

    Qt 允许在任何 QObject 上安装 事件过滤器(所有小部件都继承自 QWidget 并且 QWidget 继承自 QObject),这将捕获那些将要接收的 任何事件对象,并允许通过忽略该事件或执行您需要的任何操作来处理该事件。
    QGraphicsView 是 QAbstractScrollArea 的子类,因此接收滚轮事件的小部件实际上是它的 viewport。这意味着滚轮事件首先被发送到那个视口(实际上是场景),然后,如果还没有被接受,它会被发送到滚动条,最后是实际的 QGraphicsView 小部件。所以,为了避免触发滚动条,我们不能在视图上安装过滤器,而是在视口上。

    class mainProgram(QMainWindow, Ui_MainWindow):
        def __init__(self):
            super().__init__()
            # ...
            self.graphicsView.viewport().installEventFilter(self)
    
        def eventFilter(self, source, event):
            if source == self.graphicsView.viewport() and event.type() == event.Wheel:
                x = event.angleDelta().y() / 120
                if x > 0:
                    self.zoom *= 1.05
                    self.updateView()
                elif x < 0:
                    self.zoom /= 1.05
                    self.updateView()
                return True
            return super().eventFilter(source, event)
    

    关于您的代码的一些进一步考虑:

    • self.sender() 应始终谨慎使用,并且仅在没有其他选择时才使用;在你的情况下这是没有意义的,因为open_image 只连接到视图的clicked 信号,所以只需使用path = self.treeView.model().filePath(index);
    • self.list_of_images = sorted(self.list_of_images) 是多余的,self.list_of_images.sort() 通常是首选;
    • 整个文件路径和像素图加载不必要地复杂并且容易出错:原始前缀是不必要的,因为 Qt 已经返回转义路径; os.listdir 如果路径不是目录则引发异常;无需创建新场景,只需在__init__ 中创建一个场景,然后调用它的clear()
    • QPixmap 完全能够从路径加载图像,仅在特定情况下才需要使用 QImage。

    【讨论】:

    • 谢谢先生!它适用于事件过滤器。但是,对于子类方法,我应该如何在主窗口中调用它?因为图形视图是在 Qt 设计器生成代码的主窗口内实现的。
    • @crazybin98 你需要 promote 小部件才能使用子类。参见例如this answer(在这种情况下,它是一个自定义小部件,在您的只是子类 QGraphicsView 并在 Designer 中相应地提升 that 类)。
    • 我现在也可以使用子类方法了!再次感谢您的信息!
    • @crazybin98 不客气!请记住,如果某个答案解决了您的问题,您应该通过单击其左侧的灰色勾号将其标记为已接受。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多