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()
额外优化建议
- 移除
_fit_image中的self.view.fitInView调用,因为你不需要保持比例,这行代码会干扰_update_view的缩放逻辑。 _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
相关产品推荐
相关产品推荐

