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

两个同步QGraphicsView图层缩放后场景矩形错位,平移后恢复对齐

问题解决:双QGraphicsView缩放时场景错位问题

问题描述

自定义两个QGraphicsView类:

  • GraphicsView:支持缩放与中键平移,用于显示线条
  • PolygonGraphicsView:接收前者信号同步场景,用于显示多边形

平移操作下两者场景对齐正常,但缩放至滚动条激活后,场景矩形错位,多边形与线条相对位置偏移,仅平移操作可恢复对齐,需实现缩放、平移时两者场景始终对齐。

问题原因

  1. 平移通过修改sceneRect实现,缩放通过修改transform实现,两种变换组合在滚动条激活时,QGraphicsView内部滚动机制会导致场景显示区域计算不一致
  2. 手动构建缩放变换矩阵的方式,未与QGraphicsView内置缩放逻辑完全匹配,加剧了错位
  3. PolygonGraphicsView同步时同时设置sceneRect和transform,但关闭滚动条后,sceneRect的修改无法正确映射到视口显示区域

解决方案

  1. 统一变换方式:平移和缩放均通过修改transform实现,不再修改sceneRect,让sceneRect保持初始完整场景范围
  2. 简化缩放逻辑:使用QGraphicsView内置scale()方法,依赖已设置的AnchorUnderMouse自动处理鼠标中心缩放,避免手动构建变换矩阵的误差
  3. 同步逻辑优化:仅同步transform即可,无需同步sceneRect,确保两个View变换完全一致

修改后的完整代码

from PyQt5 import QtWidgets, QtGui, QtCore
import sys


class PolygonGraphicsView(QtWidgets.QGraphicsView):

    def __init__(self, scene, parent=None):
        super(PolygonGraphicsView, self).__init__(scene, parent)
        # 设置锚点,与主View保持一致
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

        # 禁用滚动条
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

    @QtCore.pyqtSlot(QtGui.QTransform)
    def setTransformSync(self, transform_matrix):
        # 仅同步变换矩阵
        self.setTransform(transform_matrix)


class GraphicsView(QtWidgets.QGraphicsView):
    # 仅同步变换矩阵的信号
    transform_changed = QtCore.pyqtSignal(QtGui.QTransform)

    def __init__(self, scene, parent=None):
        super(GraphicsView, self).__init__(scene, parent)

        self.pos_init_class = None
        self.scale_factor = 1.5

        # 设置锚点
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

    def send_transform_update(self):
        # 发送当前变换矩阵
        self.transform_changed.emit(self.transform())

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton:
            # 记录鼠标的视图坐标,而非场景坐标
            self.pos_init_class = event.pos()
            self.send_transform_update()
        else:
            super(GraphicsView, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if self.pos_init_class and event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = None
            self.send_transform_update()
        else:
            super(GraphicsView, self).mouseReleaseEvent(event)

    def mouseMoveEvent(self, event):
        if self.pos_init_class:
            # 计算视图坐标的偏移量
            delta = event.pos() - self.pos_init_class
            # 通过translate实现平移,变换会加入到transform中
            self.translate(delta.x(), delta.y())
            self.pos_init_class = event.pos()
            self.send_transform_update()

        super(GraphicsView, self).mouseMoveEvent(event)

    def wheelEvent(self, event):
        # 使用内置scale方法,自动处理锚点逻辑
        zoom_factor = self.scale_factor if event.angleDelta().y() > 0 else 1 / self.scale_factor
        self.scale(zoom_factor, zoom_factor)
        self.send_transform_update()


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        # 透明样式
        self.this_style_sheet = """
                   background-color: transparent;
                   selection-background-color:transparent;
               """

        self.transparent_viewport_style_sheet = """
                   background-color: transparent;
               """

        # 窗口几何设置
        self.setGeometry(0, 0, 800, 500)
        # 初始完整场景范围,覆盖所有图形元素
        self.scene_rect_value = QtCore.QRectF(721200, -9680100, 400, 400)

        # 中心部件与布局
        central_widget = QtWidgets.QWidget()
        layout = QtWidgets.QGridLayout()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

        # 多边形视图(底层)
        self.polygon_scene = QtWidgets.QGraphicsScene()
        self.polygon_graphicsView = PolygonGraphicsView(scene=self.polygon_scene, parent=self)
        self.polygon_graphicsView.setSceneRect(self.scene_rect_value)
        layout.addWidget(self.polygon_graphicsView, 0, 0)

        # 线条视图(顶层)
        self.scene = QtWidgets.QGraphicsScene()
        self.graphicsView = GraphicsView(scene=self.scene, parent=self)
        self.graphicsView.setSceneRect(self.scene_rect_value)
        self.graphicsView.setStyleSheet(self.this_style_sheet)
        self.graphicsView.viewport().setStyleSheet(self.transparent_viewport_style_sheet)
        layout.addWidget(self.graphicsView, 0, 0)

        # 连接变换同步信号
        self.graphicsView.transform_changed.connect(self.polygon_graphicsView.setTransformSync)

        # 添加测试图形
        self.dummy_lines_polygons()

    def dummy_lines_polygons(self):
        # 添加多边形
        polygon_list = [[[721278.105, 9679978.402], [721378.918, 9679958.288], [721395.355, 9680058.781], [721294.491, 9680079.538]],
                        [[721398.840, 9679959.473], [721486.867, 9679930.441], [721502.381, 9680029.483], [721416.757, 9680055.152]]]
        for pol in polygon_list:
            polygon = QtGui.QPolygonF()
            for item in pol:
                x = item[0]
                y = -item[1]
                polygon.append(QtCore.QPointF(x, y))
            polygon_item = QtWidgets.QGraphicsPolygonItem(polygon)
            brush = QtGui.QBrush(QtGui.QColor(0, 0, 255))
            polygon_item.setBrush(brush)
            self.polygon_scene.addItem(polygon_item)

        # 添加线条
        line_list = [[[721385.198, 9679948.971], [721404.402, 9680064.235] ], [[721404.402, 9680064.235], [721512.651, 9680035.611]]]
        pen = QtGui.QPen(QtGui.QColor(255, 0, 0))
        for line in line_list:
            x1 = line[0][0]
            y1 = -line[0][1]
            x2 = line[1][0]
            y2 = -line[1][1]
            line = QtWidgets.QGraphicsLineItem(x1, y1, x2, y2)
            line.setPen(pen)
            self.scene.addItem(line)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

关键修改点说明

  • 平移逻辑:将修改sceneRect的方式改为调用translate(),平移变换直接集成到transform中,避免与滚动条机制冲突
  • 缩放逻辑:移除手动构建变换矩阵的代码,改用scale()方法,利用QGraphicsView内置锚点逻辑实现准确的鼠标中心缩放
  • 同步机制:仅同步transform矩阵,不再同步sceneRect,两个View保持相同的初始sceneRect,确保显示区域完全一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 12:57:02