PyQt5:翻转QGraphicsItem父项后子项位置异常求助
QGraphicsItem父子项翻转后平移错位问题解决
问题描述
尝试对包含子项(蓝色矩形、文本)和孙项(内部数字)的父QGraphicsItem执行水平翻转操作:
- 先对父项应用翻转变换:
parentItem.setTransformOriginPoint(parentItem.boundingRect().center()) parentItem.setTransform(QTransform.fromScale(-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
相关产品推荐
相关产品推荐

