With Qt in PySide6 I want to center a QGraphicsSimpleTextItem
inside the boundingRect
of its parent item. Obviously I want the text to always have the same scaling ratio independent of the transformation I apply on the view. If necessary, it would also be possible to keep the same scaling and just hide it if its across the rect of the parent.
I set the views tansformation with fitInView(visible_rect, Qt.IgnoreAspectRatio)
. But I can not figure out how to do it properly.
I tried setting the ItemIgnoresTransformations
flag on the text item, but then scaling and centering just don't work.
I tried to override the paintEvent
method and calculate the correct position and scaling there (which is properly the way to go), but I also failed at that. In the moment the paintEvent
looks like that:
def paint(self, painter, option, widget=...):
painter.setFont(self._font)
painter.setPen(QColor("purple"))
x_cen = self.parentItem().boundingRect().center().x()
y_cen = self.parentItem().boundingRect().center().y()
center = painter.worldTransform().map(x_cen, y_cen)
painter.setTransform(QTransform())
painter.drawText(center[0], center[1], self.text)
Centering without the transformation is no problem. Does somebody had a similar problem or just knows how to apply the correct transfomation on the QGraphicsSimpleTextItem
?
Overriding the paint()
of a standard graphics item should normally be considered as last resource, and using the predefined behavior is preferable since it already considers all relative transformations applied to the whole view/scene/object tree.
The ItemIgnoresTransformations
is correct, but we must remember that this will ignore all transformations, which are also considered for the position of the item relative to the parent (including the scene).
If you try to position the text item based on its bounding rect, that won't be valid whenever the transformation is ignored, because the translation would be in "absolute" coordinates, while what you need is the position relative to the parent which, instead, does use transformations (scaling).
A possible solution is to use a QGraphicsItem subclass as a "proxy" intermediate, and add the text item as its child. Then we can easily translate the position of the text item to the origin point of the "proxy", and then all we need is to position that in the center of the parent rect:
class CenterText(QGraphicsItem):
def __init__(self, text='', parent=None):
super().__init__(parent)
self.setFlag(self.ItemIgnoresTransformations)
self.textItem = QGraphicsSimpleTextItem(text, self)
self.textItem.setPos(-self.textItem.boundingRect().center())
def setText(self, text):
self.textItem.setText(text)
self.textItem.setPos(-self.textItem.boundingRect().center())
def boundingRect(self):
return self.childrenBoundingRect()
def paint(self, *args):
pass
class View(QGraphicsView):
def __init__(self):
scene = QGraphicsScene()
super().__init__(scene)
self.setRenderHint(QPainter.Antialiasing)
scene.setSceneRect(0, 0, 640, 480)
r = scene.addRect(200, 200, 80, 20)
r.setFlag(r.ItemIsMovable)
text = CenterText('Hello world!', r)
text.setPos(r.rect().center())
def resizeEvent(self, event):
super().resizeEvent(event)
self.fitInView(self.sceneRect())
Then, the matter of drawing the text whether it can be fully shown is a bit delicate.
The simplest solution would be to not prevent drawing, but clipping it. QGraphicsItem provides the ItemClipsChildrenToShape
flag which ensures that the children of an item are never drawn outside the parent's bounding rect.
Just add r.setFlag(r.ItemClipsChildrenToShape)
to the code above, and the text will be enclosed within the parent rectangle.
Alternatively, since we know for sure that the text item does not use transformations, we can override paint()
and check if the painter is clipping and its clip rect does not include the bounding rect (actually, the shape) of the text item.
class HideableText(QGraphicsSimpleTextItem):
def paint(self, qp, opt, widget=None):
if qp.clipBoundingRect().contains(self.boundingRect()):
super().paint(qp, opt, widget)
class CenterText(QGraphicsItem):
def __init__(self, text='', parent=None):
super().__init__(parent)
self.setFlag(self.ItemIgnoresTransformations)
self.textItem = HideableText(text, self)
self.textItem.setPos(-self.textItem.boundingRect().center())
... as above