QGraphicsItemGroup网格吸附移动异常:仅可向右、向下移动问题求助
问题:QGraphicsItemGroup子类移动时仅能向右/向下,网格吸附功能异常
问题描述
在带有网格的QGraphicsScene中,我实现了QGraphicsItemGroup的子类,重写itemChange函数以实现移动时的网格吸附。但添加组到场景后,只能向右或向下移动组,无法向上或向左。注释掉itemChange函数后移动恢复正常,但失去网格吸附功能。该网格吸附逻辑对自定义QGraphicsItems子类工作正常。
场景参数:
- 场景尺寸:8192×8192像素
- 网格大小:32×32像素
snap_to_grid函数:将输入坐标吸附到最近的网格点- 已重写
boundingRect()返回childrenBoundingRect(),但问题未解决 - 尝试调整组初始位置后,移动时组会被立即推向右下方向
原始代码
from os import environ environ["QT_ENABLE_HIGHDPI_SCALING"] = "0" from PyQt6.QtWidgets import * from PyQt6.QtCore import * app = QApplication([]) class GridSnappingQGIG(QGraphicsItemGroup): def __init__(self, **kwargs): super().__init__(**kwargs) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable, True) self.setFlag(QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges, True) def boundingRect(self): return self.childrenBoundingRect() def itemChange(self, change, value): scene = self.scene() if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange and scene: scene = self.scene() x, y = scene.snap_to_grid(value.x(), value.y(), scene.grid_size) bounding_rect = self.boundingRect() x, y = min(8192 - bounding_rect.width(), max(0, x)), min(8192 - bounding_rect.height(), max(0, y)) return QPointF(x, y) return super().itemChange(change, value) class Scene(QGraphicsScene): def __init__(self, *args): super().__init__(*args) self.grid_size = 32 def snap_to_grid(self, x, y, grid_size): return [grid_size * round(x / grid_size), grid_size * round(y / grid_size)] scene = Scene(0, 0, 8192, 8192) rect1 = QGraphicsRectItem(0, 0, 32, 32) scene.addItem(rect1) rect1.setPos(4000,4000) rect2 = QGraphicsRectItem(0, 0, 128, 256) scene.addItem(rect2) rect2.setPos(3680,3680) group = GridSnappingQGIG() scene.addItem(group) group.addToGroup(rect1) group.addToGroup(rect2) view = QGraphicsView(scene) view.showMaximized() app.exec()
解决方案
问题根源在于QGraphicsItemGroup的坐标系逻辑:组的pos是自身坐标系原点在场景中的位置,而子项的pos是相对于组坐标系的。原始代码直接限制组的pos为非负数,导致无法向左/向上移动(因为组的初始pos为(0,0),子项的场景位置是子项自身pos加组pos,此时组的pos不能小于0)。
修改itemChange函数,改为基于组的场景边界计算目标位置,而不是直接限制组的pos:
def itemChange(self, change, value): scene = self.scene() if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange and scene: # 获取组当前在场景中的边界 current_scene_rect = self.mapRectToScene(self.boundingRect()) # 计算拖动带来的偏移量 offset = value - self.pos() # 计算目标边界 target_scene_rect = current_scene_rect.translated(offset) # 对目标边界的左上角进行网格吸附 snapped_top_left = QPointF( scene.snap_to_grid(target_scene_rect.left(), target_scene_rect.top(), scene.grid_size)[0], scene.snap_to_grid(target_scene_rect.left(), target_scene_rect.top(), scene.grid_size)[1] ) # 计算组的目标pos:确保整个组边界不超出场景 target_pos = snapped_top_left - self.boundingRect().topLeft() # 限制边界在场景内 target_pos.setX(max(0, min(target_pos.x(), scene.width() - current_scene_rect.width()))) target_pos.setY(max(0, min(target_pos.y(), scene.height() - current_scene_rect.height()))) return target_pos return super().itemChange(change, value)
修改说明
- 先获取组当前在场景中的实际边界,而不是仅依赖自身坐标系的
boundingRect - 通过偏移量计算目标边界,对边界左上角进行网格吸附,确保组整体对齐网格
- 基于吸附后的边界左上角,反推组的目标
pos,同时确保整个组不会超出场景范围 - 不再直接限制组的
pos为非负数,而是基于组的实际尺寸计算可移动范围
系统信息
OS Name: Microsoft Windows 10 Home OS Version: 10.0.19045 N/A Build 19045 Python 3.10.2 PyQt6 6.4.2 pyqt6-plugins 6.4.2.2.3 PyQt6-Qt6 6.4.3 PyQt6-sip 13.5.2 pyqt6-tools 6.4.2.3.3 qt6-applications 6.4.3.2.3 qt6-tools 6.4.3.1.3
内容的提问来源于stack exchange,提问作者Ethan Alwaise
相关产品推荐
相关产品推荐

