PYQT5实现图片显示、通过滚轮缩放图片、通过鼠标拖动图片移动,搞懂所有细节

本文在这篇文章的基础上进行优化:

pyqt5实现图片显示、图片放大/缩小(通过滚轮)、图片移动(鼠标拖动)_小郁同学的博客-CSDN博客_pyqt5 放大图片

 首先要说明2个重要细节:
一般触发paintEvent事件的有如下几种原因:

窗口显隐导致重画
窗口大小(重新调整)改变,或者重新排布(布局)导致重画
调用update 或者 repaint重画
当窗口第一次显示时,系统会自动产生绘图事件
当窗口部件被其他部件遮挡时,然后又再次显示出来,会对隐藏区域进行重绘事件
————————————————
原文链接:https://blog.csdn.net/liunanya/article/details/93844597

在案例中,如果窗口已打开图片,再次打开图片时,会弹出图片选择框,图片选择框会遮挡图片显示控件,这个操作将触发paintEvent事件。如果设置图片左上角的点QPoint为(0,0)倒没什么问题,一旦不是(0,0)将会出错,因为在其它分支会重新计算QPoint。

很多意想不到的地方会触发paintEvent事件

第2个细节:

painter.drawPixmap(self.point, self.scaled_img)

中,self.point不是图片显示的左上角真实值,需要除以scale才是,形如self.point / self.scale,参考文献中似乎也未留意到这一点。

我的案例优化点:

1.初始图片居中显示

2.缩放:如鼠标点在图上,则根据这一点顶点缩放,否则按图此前的左上角点进行缩放

3.图片拖动时,左键按住的点不会漂移

代码如下:

# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QImageReader
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
from PyQt5.Qt import QPixmap, QPoint, Qt, QPainter, QIcon
from PyQt5.QtCore import QSize


class ImageBox(QWidget):
    def __init__(self):
        super(ImageBox, self).__init__()
        self.img = None
        self.scaled_img = None
        self.start_pos = None
        self.end_pos = None
        self.left_click = False
        self.wheel_flag = False

        self.scale = 1
        self.old_scale = 1
        self.point = QPoint(0, 0)
        self.x = -1
        self.y = -1
        self.new_height = -1
        self.new_width = -1

    def init_ui(self):
        self.setWindowTitle("ImageBox")

    def set_image(self, img_path):
        self.img = QPixmap(img_path)
        width, height = self.img.width(), self.img.height()
        if height / width > 990 / 660:
            new_height = 990
            new_width = width * 990 / height
        else:
            new_height = height * 660 / width
            new_width = 660
        self.point = QPoint(int((660 - new_width) * 0.5), int((990 - new_height) * 0.5))
        self.img = self.img.scaled(new_width, new_height, Qt.KeepAspectRatio)
        self.scaled_img = self.img

        self.new_height = new_height
        self.new_width = new_width
        self.scale = 1

    def paintEvent(self, e):
        if self.scaled_img:
            painter = QPainter()
            painter.begin(self)
            painter.scale(self.scale, self.scale)
            if self.wheel_flag:        # 定点缩放
                self.wheel_flag = False
                # 判断当前鼠标pos在不在图上
                this_left_x = self.point.x() * self.old_scale
                this_left_y = self.point.y() * self.old_scale
                this_scale_width = self.new_width * self.old_scale
                this_scale_height = self.new_height * self.old_scale

                # 鼠标点在图上,以鼠标点为中心动作
                gap_x = self.x - this_left_x
                gap_y = self.y - this_left_y
                if 0 < gap_x < this_scale_width and 0 < gap_y < this_scale_height:
                    new_left_x = int(self.x / self.scale - gap_x / self.old_scale)
                    new_left_y = int(self.y / self.scale - gap_y / self.old_scale)
                    self.point = QPoint(new_left_x, new_left_y)
                # 鼠标点不在图上,固定左上角进行缩放
                else:
                    true_left_x = int(self.point.x() * self.old_scale / self.scale)
                    true_left_y = int(self.point.y() * self.old_scale / self.scale)
                    self.point = QPoint(true_left_x, true_left_y)
            painter.drawPixmap(self.point, self.scaled_img)  # 此函数中还会用scale对point进行处理
            painter.end()

    def wheelEvent(self, event):
        angle = event.angleDelta() / 8  # 返回QPoint对象,为滚轮转过的数值,单位为1/8度
        angleY = angle.y()
        self.old_scale = self.scale
        self.x, self.y = event.x(), event.y()
        self.wheel_flag = True
        # 获取当前鼠标相对于view的位置
        if angleY > 0:
            self.scale *= 1.08
        else:  # 滚轮下滚
            self.scale *= 0.92
        if self.scale < 0.3:
            self.scale = 0.3
        self.adjustSize()
        self.update()

    def mouseMoveEvent(self, e):
        if self.left_click:
            self.end_pos = e.pos() - self.start_pos                    # 当前位置-起始位置=差值
            self.point = self.point + self.end_pos / self.scale        # 左上角的距离变化
            self.start_pos = e.pos()
            self.repaint()

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.left_click = True
            self.start_pos = e.pos()

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.left_click = False


class Ui_Form(QWidget):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(900, 1080)
        self.scrollArea = QtWidgets.QScrollArea(Form)
        self.scrollArea.setGeometry(QtCore.QRect(150, 10, 680, 990))
        self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        # self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")

        self.scrollAreaWidgetContents = QtWidgets.QWidget()
        self.box = ImageBox()

        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 680, 990))
        self.scrollAreaWidgetContents.setMinimumSize(QtCore.QSize(100, 100))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.gridLayout = QtWidgets.QGridLayout(self.scrollAreaWidgetContents)
        self.gridLayout.setObjectName("gridLayout")

        self.gridLayout.addWidget(self.box, 0, 0, 1, 1)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)

        self.open_file = QtWidgets.QPushButton(Form)
        self.open_file.setGeometry(QtCore.QRect(30, 100, 81, 41))
        font = QtGui.QFont()
        font.setFamily("Aharoni")
        font.setPointSize(10)
        font.setBold(True)
        font.setWeight(75)
        self.open_file.setFont(font)
        self.open_file.setObjectName("open_file")
        self.open_file.clicked.connect(self.open_image)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):
        _translate = QtCore.QCoreApplication.translate
        Form.setWindowTitle(_translate("Form", "Form"))
        self.open_file.setText(_translate("Form", "选择文件"))

    def open_image(self):
        img_name, _ = QFileDialog.getOpenFileName(None, "Open Image File","","All Files(*);;*.jpg;;*.png;;*.jpeg")
        self.box.set_image(img_name)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_Form()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

 

PyQt 是一个用于创建图形用户界面(GUI)应用程序的工具集,它是 Python 编程语言的一个绑定,用来调用 Qt 库。在 PyQt 中,布局管理器用于管理控件的位置和大小,以适应它们的父窗口变化。垂直布局(QVBoxLayout)是一种常用的布局方式,它按照垂直方向排列子控件。 要让垂直布局跟随窗口缩放,你需要确保几个方面: 1. 布局本身应该被设置到一个可以自动调整大小的控件上,比如 QMainWindow 或者 QWidget。 2. 主窗口(通常是 QWidget)的大小策略(QSizePolicy)应该设置为可以根据内容调整大小,这可以通过调用 `setSizePolicy` 方法并传递 `QSizePolicy.Preferred` 作为参数来实现。 3. 使用布局管理器(如 QVBoxLayout)时,确保它被添加到设置了大小策略的控件上。 以下是一个简单的例子,演示如何创建一个垂直布局,并使它跟随主窗口的缩放变化: ```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton class MainWindow(QMainWindow): def __init__(self): super().__init__() # 创建一个中心控件 central_widget = QWidget(self) self.setCentralWidget(central_widget) # 创建一个垂直布局 layout = QVBoxLayout() # 添加几个按钮到布局中 for i in range(5): button = QPushButton(f"Button {i+1}") layout.addWidget(button) # 将布局应用到中心控件上 central_widget.setLayout(layout) # 设置中心控件的大小策略,以便它可以随主窗口缩放 central_widget.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) # 设置主窗口的初始大小和最小大小 self.setGeometry(300, 300, 400, 300) self.setMinimumSize(400, 300) self.show() if __name__ == "__main__": app = QApplication(sys.argv) mainWin = MainWindow() sys.exit(app.exec_()) ``` 在这个例子中,当主窗口被缩小或放大时,垂直布局中的按钮也会随之调整大小和位置,以填满可用空间。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值