Cannot make QGraphicsTextItem both in center of the scene and with supported word wrap
-
Hi all, I always seem to find more questions about some minor things... :)
(Also, it is really interesting why this is flagged as spam... I literally needed to add this text here just so that that automatic anti-spam lets me in. I know my questions are annoying but they are not really spam :D)
This time I cannot get
QGraphicsTextItem
to be both in the center of its correspondingQGraphicsScene
and wrappable.In MRE below, if I leave
self._text.document().adjustSize()
commented out, then theQGraphicsTextItem
reacts to the change of the window size (i.e. wraps properly) but it is not centered and seems to be located rather randomly on the scene. If I uncommentself._text.document().adjustSize()
, then the text is indeed in center but the wrap is fixed and is not adapting to the size of the scene: i.e. even if there is enough space for the text, it will be wrapper for now reason and if, on the contrary, there is not enough space, the wrap will not change.I have seen several similar questions but they pretty much advised to use
setPos
, which is what I am doing. So, what am I doing wrong? How to make both properties available at the same time? Should I use something else instead ofQGraphicsTextItem
?import sys from PySide6.QtCore import Qt, QRectF from PySide6.QtGui import QColor, QFont from PySide6.QtWidgets import ( QApplication, QGraphicsView, QGraphicsScene, QGraphicsTextItem, QGraphicsPixmapItem, QVBoxLayout, QWidget, ) class ImageViewer(QGraphicsView): def __init__(self, text="", parent=None): super().__init__(parent) self._scene = QGraphicsScene(self) self._photo = QGraphicsPixmapItem() self._photo.setShapeMode(QGraphicsPixmapItem.ShapeMode.BoundingRectShape) self._scene.addItem(self._photo) self._text = QGraphicsTextItem(text) self._text.setDefaultTextColor(QColor(255, 255, 255)) self._text.document().setTextWidth(self.viewport().width() - 20) font = QFont() font.setPointSize(20) self._text.setFont(font) self._scene.addItem(self._text) self.setScene(self._scene) self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setBackgroundBrush(QColor(0, 0, 0)) self.setFrameShape(QGraphicsView.Shape.NoFrame) self._center_text() def _center_text(self): max_width = self.viewport().width() - 20 self._text.document().setTextWidth(max_width) # self._text.document().adjustSize() text_rect = self._text.boundingRect() scene_rect = self._scene.sceneRect() center_x = scene_rect.center().x() - text_rect.width() / 2 center_y = scene_rect.center().y() - text_rect.height() / 2 self._text.setPos(center_x, center_y) def resizeEvent(self, event): super().resizeEvent(event) self._center_text() class MainWindow(QWidget): def __init__(self): super().__init__() layout = QVBoxLayout(self) self.viewer = ImageViewer( "This is a centered text. Resize to test the wrapping behavior." ) layout.addWidget(self.viewer) self.setLayout(layout) self.setWindowTitle("QGraphicsTextItem Example") self.resize(800, 600) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
-
Hi all, I always seem to find more questions about some minor things... :)
(Also, it is really interesting why this is flagged as spam... I literally needed to add this text here just so that that automatic anti-spam lets me in. I know my questions are annoying but they are not really spam :D)
This time I cannot get
QGraphicsTextItem
to be both in the center of its correspondingQGraphicsScene
and wrappable.In MRE below, if I leave
self._text.document().adjustSize()
commented out, then theQGraphicsTextItem
reacts to the change of the window size (i.e. wraps properly) but it is not centered and seems to be located rather randomly on the scene. If I uncommentself._text.document().adjustSize()
, then the text is indeed in center but the wrap is fixed and is not adapting to the size of the scene: i.e. even if there is enough space for the text, it will be wrapper for now reason and if, on the contrary, there is not enough space, the wrap will not change.I have seen several similar questions but they pretty much advised to use
setPos
, which is what I am doing. So, what am I doing wrong? How to make both properties available at the same time? Should I use something else instead ofQGraphicsTextItem
?import sys from PySide6.QtCore import Qt, QRectF from PySide6.QtGui import QColor, QFont from PySide6.QtWidgets import ( QApplication, QGraphicsView, QGraphicsScene, QGraphicsTextItem, QGraphicsPixmapItem, QVBoxLayout, QWidget, ) class ImageViewer(QGraphicsView): def __init__(self, text="", parent=None): super().__init__(parent) self._scene = QGraphicsScene(self) self._photo = QGraphicsPixmapItem() self._photo.setShapeMode(QGraphicsPixmapItem.ShapeMode.BoundingRectShape) self._scene.addItem(self._photo) self._text = QGraphicsTextItem(text) self._text.setDefaultTextColor(QColor(255, 255, 255)) self._text.document().setTextWidth(self.viewport().width() - 20) font = QFont() font.setPointSize(20) self._text.setFont(font) self._scene.addItem(self._text) self.setScene(self._scene) self.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setBackgroundBrush(QColor(0, 0, 0)) self.setFrameShape(QGraphicsView.Shape.NoFrame) self._center_text() def _center_text(self): max_width = self.viewport().width() - 20 self._text.document().setTextWidth(max_width) # self._text.document().adjustSize() text_rect = self._text.boundingRect() scene_rect = self._scene.sceneRect() center_x = scene_rect.center().x() - text_rect.width() / 2 center_y = scene_rect.center().y() - text_rect.height() / 2 self._text.setPos(center_x, center_y) def resizeEvent(self, event): super().resizeEvent(event) self._center_text() class MainWindow(QWidget): def __init__(self): super().__init__() layout = QVBoxLayout(self) self.viewer = ImageViewer( "This is a centered text. Resize to test the wrapping behavior." ) layout.addWidget(self.viewer) self.setLayout(layout) self.setWindowTitle("QGraphicsTextItem Example") self.resize(800, 600) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())
@sapvi
Only suggestions.
Presumably you want to get the text wrapping/centering/whatever correct before you worry about the item being centered on the scene/view. Unless they interfere/interact with each other, which I wouldn't know.
You callQTextDocument::adjustSize()
. Does it improve any if you callQGraphicsTextItem::adjustSize()
instead/as well? E.g. maybe that is required forQGraphicsTextItem::boundingRect()
? -
@sapvi
Only suggestions.
Presumably you want to get the text wrapping/centering/whatever correct before you worry about the item being centered on the scene/view. Unless they interfere/interact with each other, which I wouldn't know.
You callQTextDocument::adjustSize()
. Does it improve any if you callQGraphicsTextItem::adjustSize()
instead/as well? E.g. maybe that is required forQGraphicsTextItem::boundingRect()
?@JonB hello, thanks for the answer.
I've tried also calling
QGraphicsTextItem::adjustSize()
(i.e.self._text.adjustSize()
). I've tried bothQTextDocument::adjustSize()
andQGraphicsTextItem::adjustSize()
in different configurations: before setting max width / after setting max width, together or separately. It did not seem to influence result at all: if I useadjustSize
, I get text centered but too narrow, if I do not use it, I get good wrap but in wrong position. -
Hi,
Might be a silly question but what about QGraphicsView::centerOn ?
-
Hi,
Might be a silly question but what about QGraphicsView::centerOn ?
@SGaist hi, I managed to fix it but my solution looks very over-engineered to me... I've also tried your suggestion but by itself it does not really work as intended.
def _center_text(self): width = max(0, self.viewport().width() - 20) doc = self._text.document() doc.setTextWidth(width) self.centerOn(self._text)
This does not really work because the problem is that the text itself is not centered. So, the text item is occupying width of whole viewport but text is on the left anyway, though wrap works correctly.
def _center_text(self): width = max(0, self.viewport().width() - 20) doc = self._text.document() doc.setTextWidth(-1) natural_width = doc.idealWidth() text_width = min(natural_width, width) doc.setTextWidth(text_width) self.centerOn(self._text)
This works almost as intended, but the wrapped text is again on the left. So the text is in the center when it has only one line, the effect is broken when wrap is introduced.
def _center_text(self): width = max(0, self.viewport().width() - 20) doc = self._text.document() doc.setTextWidth(-1) natural_width = doc.idealWidth() text_width = min(natural_width, width) doc.setTextWidth(text_width) self.centerOn(self._text) cursor = QTextCursor(doc) block_format = QTextBlockFormat() block_format.setAlignment(Qt.AlignCenter) cursor.select(QTextCursor.Document) cursor.mergeBlockFormat(block_format)
These crazy additional lines which I barely found make the centered text even for the wrapped version.
However, I still had some weird effect in my real app when first position of the text was wrong, but after any resize it would normalize. This is most likely because the widget there is not visible by default so some sizes are not properly calculated. I am not sure how to solve this with
centerOn
. Tried several things but problem persisted or the location broke completely. So I returned tosetPos
and even had to addsetSceneRect
on top of that. With this solution I get correct behavior at all time: different sizes, hiding/showing widget, switching between tabs... Amount of code required just for centering the text is crazy though, so if someone has improvement ideas, they are welcome here.def _center_text(self): width = max(0, self.viewport().width() - 20) height = max(0, self.viewport().height() - 20) self._scene.setSceneRect(QRectF(0, 0, width, height)) doc = self._text.document() doc.setTextWidth(-1) natural_width = doc.idealWidth() text_width = min(natural_width, width) doc.setTextWidth(text_width) cursor = QTextCursor(doc) block_format = QTextBlockFormat() block_format.setAlignment(Qt.AlignCenter) cursor.select(QTextCursor.Document) cursor.mergeBlockFormat(block_format) text_rect = self._text.boundingRect() scene_rect = self._scene.sceneRect() center_x = scene_rect.center().x() - text_rect.width() / 2 center_y = scene_rect.center().y() - text_rect.height() / 2 self._text.setPos(center_x, center_y)