两个同步QGraphicsView图层缩放后场景矩形错位,平移后恢复对齐
问题解决:双QGraphicsView缩放时场景错位问题
问题描述
自定义两个QGraphicsView类:
GraphicsView:支持缩放与中键平移,用于显示线条PolygonGraphicsView:接收前者信号同步场景,用于显示多边形
平移操作下两者场景对齐正常,但缩放至滚动条激活后,场景矩形错位,多边形与线条相对位置偏移,仅平移操作可恢复对齐,需实现缩放、平移时两者场景始终对齐。
问题原因
- 平移通过修改
sceneRect实现,缩放通过修改transform实现,两种变换组合在滚动条激活时,QGraphicsView内部滚动机制会导致场景显示区域计算不一致 - 手动构建缩放变换矩阵的方式,未与QGraphicsView内置缩放逻辑完全匹配,加剧了错位
PolygonGraphicsView同步时同时设置sceneRect和transform,但关闭滚动条后,sceneRect的修改无法正确映射到视口显示区域
解决方案
- 统一变换方式:平移和缩放均通过修改
transform实现,不再修改sceneRect,让sceneRect保持初始完整场景范围 - 简化缩放逻辑:使用QGraphicsView内置
scale()方法,依赖已设置的AnchorUnderMouse自动处理鼠标中心缩放,避免手动构建变换矩阵的误差 - 同步逻辑优化:仅同步
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
相关产品推荐
相关产品推荐

