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

PyQt5:翻转QGraphicsItem父项后子项位置异常求助

QGraphicsItem父子项翻转后平移错位问题解决

问题描述

尝试对包含子项(蓝色矩形、文本)和孙项(内部数字)的父QGraphicsItem执行水平翻转操作:

  1. 先对父项应用翻转变换:
parentItem.setTransformOriginPoint(parentItem.boundingRect().center())
parentItem.setTransform(QTransform.fromScale(-1, 1))
  1. 为恢复文本和数字的可读性,对其单独重新翻转:
# 子项文本
child.setTransformOriginPoint(child.boundingRect().center())
child.setTransform(QTransform.fromScale(-1, 1), True)

# 孙项数字
grandchild.setTransformOriginPoint(grandchild.boundingRect().center())
grandchild.setTransform(QTransform.fromScale(-1, 1), True)

但重新翻转后出现明显的平移错位问题,以下是可复现的完整代码:

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class ParentItem(QGraphicsRectItem):
    def __init__(self, pos, name, parent=None):
        w, h = 550, 220
        super().__init__(-w/2, -h/2, w, h)
        self.setPos(pos)

        self.name = ChildText(self.boundingRect().topLeft() - QPointF(0, 100), f">NAME{name}", self)
        self.value = ChildText(self.boundingRect().topLeft()- QPointF(0, 50), f">VALUE_{name}", self)

        self.ChildPad1 = ChildPad(QPointF(-150, 0), "1", self)
        self.ChildPad2 = ChildPad(QPointF(+150, 0), "2", self)

        self.color = QColor(192, 192, 192)
        self.setPen(QPen(self.color, 5))

        self.setFlag(self.ItemIsMovable, True)
    def flipParent(self):
        self.setTransformOriginPoint(self.boundingRect().center())
        self.setTransform(QTransform.fromScale(-1, 1))
    
    def reflipChilds(self):
        # Child Texts
        self.name.setTransformOriginPoint(self.name.boundingRect().center())
        self.name.setTransform(QTransform.fromScale(-1, 1))

        self.value.setTransformOriginPoint(self.value.boundingRect().center())
        self.value.setTransform(QTransform.fromScale(-1, 1))

        # GrandChild Numbers
        for child in self.childItems():
            if isinstance(child, ChildPad):
                child.Number.setTransformOriginPoint(child.Number.boundingRect().center())
                child.Number.setTransform(QTransform.fromScale(-1, 1))

class ChildText(QGraphicsTextItem):
    def __init__(self, pos, text=">Text", parent=None): 
        super().__init__(parent) 
        self.setPos(pos)

        self.parent = parent
        self.text = text
        self.color = QColor(255, 0, 0)
        
        self.setDefaultTextColor(self.color)      
        self.setFlag(self.ItemIsMovable, True)
        
        f = QFont()
        f.setPointSizeF(min(self.parent.boundingRect().width()/8, self.parent.boundingRect().height()/8))
        self.setFont(f)
        self.setHtml(f"<p><center>{self.text}</center></p>")

class ChildPad(QGraphicsRectItem):
    def __init__(self, pos, pinNumber, parent=None):
        w, h = 200, 100 
        super().__init__(-w/2, -h/2, w, h, parent)  
        self.setPos(pos)

        self.parent = parent
        self.color = QColor(255, 0, 0)

        self.setPen(QPen(self.color, Qt.MiterJoin, 1))
        self.setBrush(QBrush(self.color))

        self.Number = GrandChildNumber(pinNumber, self)

class GrandChildNumber(QGraphicsTextItem):
    def __init__(self, pinNumber, parent=None):
        super().__init__(parent)
        self.parent = parent

        self.color = QColor(32, 32, 32)

        self.setHtml(f"{pinNumber}")
        self.moveToParentCenter()
    
    def moveToParentCenter(self):
            f = QFont()
            f.setPointSizeF(min(self.parent.boundingRect().width()/4, self.parent.boundingRect().height()/4))    
            self.setFont(f)
            rect = self.boundingRect()
            rect.moveCenter(self.parent.boundingRect().center())
            self.setPos(rect.topLeft())
            self.adjustSize()

def main():
    import sys
    app = QtWidgets.QApplication(sys.argv)
    scene = QGraphicsScene()

    # No Transformation applied
    originalItem = ParentItem(QPointF(300, 100), "ORIGINAL", scene)
    scene.addItem(originalItem)

    # Flipped the whole parent item
    flipParentItem = ParentItem(QPointF(300, 500), "FLIPP_PARENT", scene)
    flipParentItem.flipParent()
    scene.addItem(flipParentItem)

    # Flipped the whole parent item, then reflip the Text and Number
    reflipChildItem = ParentItem(QPointF(300, 900), "REFLIP_CHILDS", scene)
    flipParentItem.flipParent()
    reflipChildItem.reflipChilds()
    scene.addItem(reflipChildItem) 

    view = QtWidgets.QGraphicsView(scene)
    view.setRenderHints(QtGui.QPainter.Antialiasing)
    view.show()
    sys.exit(app.exec_())
    
if __name__ == '__main__':
    main()

解决方案

问题根源在于变换原点的坐标系混淆、变换叠加方式错误,以及代码中的逻辑bug,以下是分步修正:

1. 修正main函数的逻辑错误

原代码中reflipChildItem未正确执行父项翻转,反而重复翻转了flipParentItem,修正为:

# Flipped the whole parent item, then reflip the Text and Number
reflipChildItem = ParentItem(QPointF(300, 900), "REFLIP_CHILDS", scene)
reflipChildItem.flipParent()  # 调用自身的flipParent方法
reflipChildItem.reflipChilds()
scene.addItem(reflipChildItem) 

2. 修正子项的变换原点与叠加方式

父项翻转后,子项的局部坐标系已被父项变换影响,直接使用自身boundingRect中心作为变换原点会导致错位。需要将变换原点转换到父项坐标系下,同时使用叠加变换(第二个参数设为True):

def reflipChilds(self):
    # Child Texts:转换坐标系获取正确的变换原点
    name_global_center = self.name.mapToParent(self.name.boundingRect().center())
    self.name.setTransformOriginPoint(self.name.mapFromParent(name_global_center))
    self.name.setTransform(QTransform.fromScale(-1, 1), True)

    value_global_center = self.value.mapToParent(self.value.boundingRect().center())
    self.value.setTransformOriginPoint(self.value.mapFromParent(value_global_center))
    self.value.setTransform(QTransform.fromScale(-1, 1), True)

    # GrandChild Numbers
    for child in self.childItems():
        if isinstance(child, ChildPad):
            num_global_center = child.Number.mapToParent(child.Number.boundingRect().center())
            child.Number.setTransformOriginPoint(child.Number.mapFromParent(num_global_center))
            child.Number.setTransform(QTransform.fromScale(-1, 1), True)

3. 优化孙项的居中逻辑

GrandChildNumber的moveToParentCenter方法中,adjustSize()会改变文本的boundingRect,需先调整尺寸再计算位置:

def moveToParentCenter(self):
    f = QFont()
    f.setPointSizeF(min(self.parent.boundingRect().width()/4, self.parent.boundingRect().height()/4))    
    self.setFont(f)
    self.adjustSize()  # 先调整尺寸再计算居中位置
    rect = self.boundingRect()
    rect.moveCenter(self.parent.boundingRect().center())
    self.setPos(rect.topLeft())

修正原理

  • 坐标系转换:通过mapToParent和mapFromParent将子项的中心点转换到父项坐标系下,确保变换原点在父项翻转后仍处于正确位置。
  • 叠加变换:setTransform(..., True)让子项的变换叠加在父项的变换之上,避免替换局部变换导致的位置偏移。
  • 尺寸调整顺序:先调用adjustSize()确定文本的实际尺寸,再基于此计算居中位置,避免旧尺寸导致的错位。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 06:39:17