You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5:QGraphicsView适配父视图宽度,初始化PDF显示异常修复

问题描述

我实现了一个GraphicsView类,用于在QGraphicsView中展示PDF的顶部区域并让PDF填满视图宽度(无需保持PDF与视图的比例),同时在顶部添加了可移动的文本框。该类后续会在MainView、Application中逐层实例化。

当前遇到的问题:窗口首次打开加载PDF时,PDF无法填满视图宽度,只有调整窗口尺寸后才能正常显示(已将场景背景设为红色调试,推测是PDF未适配场景宽度)。试过在load_pdf函数里调用_update_view或者直接缩放视图都没用,求解决办法。

代码示例

import fitz
import sys
from PyQt5 import QtWidgets, QtGui, QtCore

class GraphicsView(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(GraphicsView, self).__init__(parent)
        self._init_view()
        self._init_scene()
        self._init_pixmap_item()
        self._init_text_box()
        self._init_layout()

    def _init_view(self):
        self.scale_factor = 6
        self.view = QtWidgets.QGraphicsView()
        self._turn_off_scrollbars()
        self._enable_anti_aliasing()
        self._set_transform_anchor()
        self._install_event_filter()

    def _turn_off_scrollbars(self):
        self.view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

    def _enable_anti_aliasing(self):
        self.view.setRenderHint(QtGui.QPainter.Antialiasing)
        self.view.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)

    def _set_transform_anchor(self):
        self.view.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        
    def _install_event_filter(self):
        self.view.viewport().installEventFilter(self)

    def _init_scene(self):
        self.scene = QtWidgets.QGraphicsScene(self.view)
        self.view.setScene(self.scene)

        #red background for debugging
        self.scene.setBackgroundBrush(QtGui.QBrush(QtGui.QColor("red")))

    def _init_pixmap_item(self):
        self.pixmap_item = QtWidgets.QGraphicsPixmapItem()
        self.scene.addItem(self.pixmap_item)                      

    def _init_text_box(self):
        self.font_size = 12
        self.font_name = QtGui.QFont("Helvetica", self.font_size)
        self.font_color = QtGui.QColor("blue")
        self.v_pos = 10
        self.h_pos = 10

        self.text_box = QtWidgets.QGraphicsTextItem("Hello World")
        self.text_box.setDefaultTextColor(self.font_color)
        self.text_box.setFont(self.font_name)
        self.text_box.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
        self.text_box.setTransform(QtGui.QTransform.fromScale(2.25, 2.25), True)
        self.scene.addItem(self.text_box)
  
    def _init_layout(self):
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.view)
        self.setLayout(layout)
        self.setGeometry(100, 100, 1400, 400)
        self.setMinimumSize(800, 400)

    def load_pdf(self, path):
        pdf_doc = fitz.open(path)
        page = pdf_doc[0]
        self._transform_pdf_page(page)
        self._position_text_box()
        self._update_view()

    def _transform_pdf_page(self, page):
        matrix = fitz.Matrix(self.scale_factor, self.scale_factor)
        pixmap = page.get_pixmap(matrix=matrix, alpha=False)
        image = QtGui.QImage(
            pixmap.samples, pixmap.width, pixmap.height, QtGui.QImage.Format_RGB888)
        self._fit_image(image)
        
    def _fit_image(self, image):
        self.pixmap_item.setPixmap(QtGui.QPixmap.fromImage(image))
        self.scene.setSceneRect(self.pixmap_item.boundingRect())
        self.view.fitInView(self.scene.sceneRect(), QtCore.Qt.KeepAspectRatio)               
    
    def _position_text_box(self):
        # To Do: Test on multiple pdfs
        # Maybe add an offset, so that the scale factor is the same everywhere
        self.text_box.setPos(
            self.view.sceneRect().width() - self.text_box.boundingRect().width() * 2.3 - self.h_pos * 2.3,
            self.text_box.boundingRect().height() * 1.7 - self.h_pos * 2.3)            
        
    def resizeEvent(self, event):
        self._update_view()
        return super().resizeEvent(event)

    def _update_view(self):
        self.view.resetTransform()
        aspect_ratio = self.scene.sceneRect().height() / self.scene.sceneRect().width()
        view_width = self.view.viewport().width()
        view_height = aspect_ratio * view_width
        self.view.setTransform(
            QtGui.QTransform().scale(
                view_width / self.view.sceneRect().width(),
                view_height / self.view.sceneRect().height()))
        self.view.ensureVisible(0, 0, 0, 0)
        self.view.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = GraphicsView()
    window.load_pdf(
        "C:/test.pdf"
    )
    window.show()
    sys.exit(app.exec_())
解决方案

问题根源是窗口首次显示前,viewport的尺寸还未完全确定,此时调用_update_view获取的self.view.viewport().width()不是最终显示的宽度,导致缩放计算错误。

以下是两种可行的解决方式:

方法一:延迟执行视图更新

修改load_pdf函数,使用QTimer.singleShot延迟执行_update_view,确保窗口已经完成布局:

def load_pdf(self, path):
    pdf_doc = fitz.open(path)
    page = pdf_doc[0]
    self._transform_pdf_page(page)
    self._position_text_box()
    # 延迟10ms执行,确保窗口布局完成
    QtCore.QTimer.singleShot(10, self._update_view)

方法二:在showEvent中触发更新

重写showEvent方法,在窗口第一次显示时强制更新视图:

def showEvent(self, event):
    super().showEvent(event)
    # 仅在首次显示时更新
    if not event.spontaneous():
        self._update_view()

额外优化建议

  1. 移除_fit_image中的self.view.fitInView调用,因为你不需要保持比例,这行代码会干扰_update_view的缩放逻辑。
  2. _position_text_box中硬编码的缩放系数(2.3、1.7)可以换成基于当前视图缩放比例的动态计算,避免不同尺寸下文本框位置偏移:
def _position_text_box(self):
    # 获取当前视图的水平缩放比例
    scale = self.view.transform().m11()
    text_rect = self.text_box.boundingRect()
    scene_width = self.scene.sceneRect().width()
    # 动态计算右上角位置,间距按视图实际像素计算
    self.text_box.setPos(
        scene_width - text_rect.width() * scale - self.h_pos,
        self.v_pos
    )

内容的提问来源于stack exchange,提问作者schmiddl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 18:45:20