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

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)

修改说明

  1. 先获取组当前在场景中的实际边界,而不是仅依赖自身坐标系的boundingRect
  2. 通过偏移量计算目标边界,对边界左上角进行网格吸附,确保组整体对齐网格
  3. 基于吸附后的边界左上角,反推组的目标pos,同时确保整个组不会超出场景范围
  4. 不再直接限制组的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 21:14:59