超过定义的QRECT时,QgraphicStextItem的文本

发布于 2025-01-20 18:52:33 字数 4348 浏览 0 评论 0原文

我有一个 QGraphicsTextItem ,它是绘制一个盒子的 QGraphicsPathItem 的子项。我希望 QGraphicsTextItem 只显示适合该框的文本,如果它溢出,我希望该文本被忽略。 输入图片此处的描述

我已经能够使其正常工作,但使用硬编码值,这并不理想。 这是我的基本代码:

class Node(QtWidgets.QGraphicsPathItem):
    def __init__(self, scene, parent=None):
        super(Node, self).__init__(parent)

        scene.addItem(self)

        # Variables
        self.main_background_colour = QtGui.QColor("#575b5e")
        self.dialogue_background_colour = QtGui.QColor("#2B2B2B")
        self.dialogue_text_colour = QtGui.QColor("white")
        self.brush = QtGui.QBrush(self.main_background_colour)
        self.pen = QtGui.QPen(self.dialogue_text_colour, 2)

        self.dialogue_font = QtGui.QFont("Calibri", 12)
        self.dialogue_font.setBold(True)
        self.dialogue_font_metrics = QtGui.QFontMetrics(self.dialogue_font)

        self.dialogue_text = "To find out how fast you type, just start typing in the blank textbox on the right of the test prompt. You will see your progress, including errors on the left side as you type. You can fix errors as you go, or correct them at the end with the help of the spell checker. If you need to restart the test, delete the text in the text box. Interactive feedback shows you your current wpm and accuracy. Bring me all the biscuits, for I am hungry. They will be a fine meal for me and all the mice in town!"

        # Rects
        self.main_rect = QtCore.QRectF(0, -40, 600, 240)
        self.dialogue_rect = QtCore.QRectF(self.main_rect.x() + (self.main_rect.width() * 0.05), self.main_rect.top() + 10,
                          (self.main_rect.width() * 0.9), self.main_rect.height() - 20)

        self.dialogue_text_point = QtCore.QPointF(self.dialogue_rect.x() + (self.dialogue_rect.width() * 0.05), self.dialogue_rect.y() + 10)

        # Painter Paths
        self.main_path = QtGui.QPainterPath()
        self.main_path.addRoundedRect(self.main_rect, 4, 4)
        self.setPath(self.main_path)

        self.dialogue_path = QtGui.QPainterPath()
        self.dialogue_path.addRect(self.dialogue_rect)

        self.dialogue_text_item = QtWidgets.QGraphicsTextItem(self.dialogue_text, self)
        self.dialogue_text_item.setCacheMode(QtWidgets.QGraphicsPathItem.DeviceCoordinateCache)
        self.dialogue_text_item.setTextWidth(self.dialogue_rect.width() - 40)
        self.dialogue_text_item.setFont(self.dialogue_font)
        self.dialogue_text_item.setDefaultTextColor(self.dialogue_text_colour)
        self.dialogue_text_item.setPos(self.dialogue_text_point)

        # HARDCODED ELIDE
        elided = self.dialogue_font_metrics.elidedText(self.dialogue_text, QtCore.Qt.ElideRight, 3300)
        self.dialogue_text_item.setPlainText(self.dialogue_text) # elided

        # Flags
        self.setFlag(self.ItemIsMovable, True)
        self.setFlag(self.ItemSendsGeometryChanges, True)
        self.setFlag(self.ItemIsSelectable, True)
        self.setFlag(self.ItemIsFocusable, True)
        self.setCacheMode(QtWidgets.QGraphicsPathItem.DeviceCoordinateCache)

    def boundingRect(self):
        return self.main_rect

    def paint(self, painter, option, widget=None):
        # Background
        self.brush.setColor(self.main_background_colour)
        painter.setBrush(self.brush)

        painter.drawPath(self.path())

        # Dialogue
        self.brush.setColor(self.dialogue_background_colour)
        painter.setBrush(self.brush)
        self.pen.setColor(self.dialogue_background_colour.darker())
        painter.setPen(self.pen)

        painter.drawPath(self.dialogue_path)

这是我尝试使用的,但我的数学不行。我认为我以错误的方式处理这个问题:

    # Dialogue
    text_length = self.dialogue_font_metrics.horizontalAdvance(self.dialogue_text)
    text_metric_rect = self.dialogue_font_metrics.boundingRect(QtCore.QRect(0, 0, self.dialogue_text_item.textWidth(), self.dialogue_font_metrics.capHeight()), QtCore.Qt.TextWordWrap, self.dialogue_text)
    
    elided_length = (text_length / text_metric_rect.height()) * (self.dialogue_rect.height() - 20)
    elided = self.dialogue_font_metrics.elidedText(self.dialogue_text, QtCore.Qt.ElideRight, 3300)

    self.dialogue_text_item.setPlainText(elided)

任何建议将不胜感激!

I have a QGraphicsTextItem that is a child of a QGraphicsPathItem which draws a box. I want the QGraphicsTextItem to only display text that fits within the box, if it overflows I want that text to be elided.
enter image description here

I've been able to get this working, but with hardcoded values, which isn't ideal.
Here is my basic code:

class Node(QtWidgets.QGraphicsPathItem):
    def __init__(self, scene, parent=None):
        super(Node, self).__init__(parent)

        scene.addItem(self)

        # Variables
        self.main_background_colour = QtGui.QColor("#575b5e")
        self.dialogue_background_colour = QtGui.QColor("#2B2B2B")
        self.dialogue_text_colour = QtGui.QColor("white")
        self.brush = QtGui.QBrush(self.main_background_colour)
        self.pen = QtGui.QPen(self.dialogue_text_colour, 2)

        self.dialogue_font = QtGui.QFont("Calibri", 12)
        self.dialogue_font.setBold(True)
        self.dialogue_font_metrics = QtGui.QFontMetrics(self.dialogue_font)

        self.dialogue_text = "To find out how fast you type, just start typing in the blank textbox on the right of the test prompt. You will see your progress, including errors on the left side as you type. You can fix errors as you go, or correct them at the end with the help of the spell checker. If you need to restart the test, delete the text in the text box. Interactive feedback shows you your current wpm and accuracy. Bring me all the biscuits, for I am hungry. They will be a fine meal for me and all the mice in town!"

        # Rects
        self.main_rect = QtCore.QRectF(0, -40, 600, 240)
        self.dialogue_rect = QtCore.QRectF(self.main_rect.x() + (self.main_rect.width() * 0.05), self.main_rect.top() + 10,
                          (self.main_rect.width() * 0.9), self.main_rect.height() - 20)

        self.dialogue_text_point = QtCore.QPointF(self.dialogue_rect.x() + (self.dialogue_rect.width() * 0.05), self.dialogue_rect.y() + 10)

        # Painter Paths
        self.main_path = QtGui.QPainterPath()
        self.main_path.addRoundedRect(self.main_rect, 4, 4)
        self.setPath(self.main_path)

        self.dialogue_path = QtGui.QPainterPath()
        self.dialogue_path.addRect(self.dialogue_rect)

        self.dialogue_text_item = QtWidgets.QGraphicsTextItem(self.dialogue_text, self)
        self.dialogue_text_item.setCacheMode(QtWidgets.QGraphicsPathItem.DeviceCoordinateCache)
        self.dialogue_text_item.setTextWidth(self.dialogue_rect.width() - 40)
        self.dialogue_text_item.setFont(self.dialogue_font)
        self.dialogue_text_item.setDefaultTextColor(self.dialogue_text_colour)
        self.dialogue_text_item.setPos(self.dialogue_text_point)

        # HARDCODED ELIDE
        elided = self.dialogue_font_metrics.elidedText(self.dialogue_text, QtCore.Qt.ElideRight, 3300)
        self.dialogue_text_item.setPlainText(self.dialogue_text) # elided

        # Flags
        self.setFlag(self.ItemIsMovable, True)
        self.setFlag(self.ItemSendsGeometryChanges, True)
        self.setFlag(self.ItemIsSelectable, True)
        self.setFlag(self.ItemIsFocusable, True)
        self.setCacheMode(QtWidgets.QGraphicsPathItem.DeviceCoordinateCache)

    def boundingRect(self):
        return self.main_rect

    def paint(self, painter, option, widget=None):
        # Background
        self.brush.setColor(self.main_background_colour)
        painter.setBrush(self.brush)

        painter.drawPath(self.path())

        # Dialogue
        self.brush.setColor(self.dialogue_background_colour)
        painter.setBrush(self.brush)
        self.pen.setColor(self.dialogue_background_colour.darker())
        painter.setPen(self.pen)

        painter.drawPath(self.dialogue_path)

This is what I've tried to use, but my maths is off. I think I'm approaching this in the wrong way:

    # Dialogue
    text_length = self.dialogue_font_metrics.horizontalAdvance(self.dialogue_text)
    text_metric_rect = self.dialogue_font_metrics.boundingRect(QtCore.QRect(0, 0, self.dialogue_text_item.textWidth(), self.dialogue_font_metrics.capHeight()), QtCore.Qt.TextWordWrap, self.dialogue_text)
    
    elided_length = (text_length / text_metric_rect.height()) * (self.dialogue_rect.height() - 20)
    elided = self.dialogue_font_metrics.elidedText(self.dialogue_text, QtCore.Qt.ElideRight, 3300)

    self.dialogue_text_item.setPlainText(elided)

Any suggestions would be appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

明月夜 2025-01-27 18:52:33

qfontmetrics Elide函数仅适用于单一的文本,不能用于 buarded 文本,这是涉及单词包装或新行时发生的事情。
即使试图根据任意大小为ELIDE函数设置宽度,它也无效:每当包装一条线时,用作该行的参考的宽度是“重置”。

想象一下,您希望文本宽50像素,所以您假设 将以两行分开,总共有100个像素。然后,您在该文本中有三个单词,每个40像素宽,elidedeText()的结果,带有100个像素的结果,将是所有三个单词,最后一个单词都含有。<<<<<<<<<<<<<<< br>
然后,您将启用单词包装的文本设置为50像素的最大宽度:结果是您只会看到前两个单词,因为每行只能适合一个单词。

唯一可行的解​​决方案是使用 qtextlayout ,并通过所有文本行迭代它会创建,然后,如果 Next 行的高度超过最大高度,则仅针对该行调用ElidedText()

但是请注意,这假设格式(字体,字体尺寸和重量)始终在整个文本中相同。更高级的布局是可能的,但是它需要更高级使用qtextDocument功能,qtextlayout和qtextformat。

        textLayout = QtGui.QTextLayout(self.dialogue_text, dialogue_font)
        height = 0
        maxWidth = text_rect.width()
        maxHeight = text_rect.height()
        textLayout.beginLayout()
        text = ''
        while True:
            line = textLayout.createLine()
            if not line.isValid():
                break
            line.setLineWidth(maxWidth)
            text += self.dialogue_text[
                line.textStart():line.textStart() + line.textLength()]
            line.setPosition(QtCore.QPointF(0, height))
            height += line.height()
            if height + line.height() > maxHeight:
                line = textLayout.createLine()
                line.setLineWidth(maxWidth)
                line.setPosition(QtCore.QPointF(0, height))
                if line.isValid():
                    last = self.dialogue_text[line.textStart():]
                    fm = QtGui.QFontMetrics(dialogue_font)
                    text += fm.elidedText(last, QtCore.Qt.ElideRight, maxWidth)
                break

请注意,您的项目实现有点值得怀疑:首先,您实际上是不是使用QGraphicsPathitem的任何功能,因为您覆盖了两个paint()>和boundingRect()

如果您想做类似的事情,只需使用基本的QGraphicSitem,否则始终尝试使用QT提供的现有类和函数,这对于图形视图框架尤其重要,这依赖于C ++优化:覆盖> paint paint ()迫使图形通过python,这是巨大瓶颈,尤其是在涉及许多项目的情况下。

与其绘画所有内容,不如创建具有正确设置属性的子项目。

最后,项目不应将自己添加到场景中。

这是一个更好,更简单(更可读性的)实现,它考虑了以上所有内容:

class Node(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super(Node, self).__init__(parent)

        self.setBrush(QtGui.QColor("#575b5e"))
        
        main_rect = QtCore.QRectF(0, -40, 600, 140)
        path = QtGui.QPainterPath()
        path.addRoundedRect(main_rect, 4, 4)
        self.setPath(path)

        hMargin = main_rect.width() * .05
        vMargin = 10
        dialogue_rect = main_rect.adjusted(hMargin, vMargin, -hMargin, -vMargin)

        dialogue_item = QtWidgets.QGraphicsRectItem(dialogue_rect, self)
        dialogue_color = QtGui.QColor("#2B2B2B")
        dialogue_item.setPen(QtGui.QPen(dialogue_color.darker(), 2))
        dialogue_item.setBrush(dialogue_color)

        text_rect = dialogue_rect.adjusted(hMargin, vMargin, -hMargin, -vMargin)
        dialogue_font = QtGui.QFont("Calibri", 12)
        dialogue_font.setBold(True)

        self.dialogue_text = "To find out how fast you type, just start typing "\
            "in the blank textbox on the right of the test prompt. You will see "\
            "your progress, including errors on the left side as you type. You "\
            "can fix errors as you go, or correct them at the end with the help "\
            "of the spell checker. If you need to restart the test, delete the "\
            "text in the text box. Interactive feedback shows you your current "\
            "wpm and accuracy. Bring me all the biscuits, for I am hungry. They "\
            "will be a fine meal for me and all the mice in town!"

        textLayout = QtGui.QTextLayout(self.dialogue_text, dialogue_font)
        height = 0
        maxWidth = text_rect.width()
        maxHeight = text_rect.height()
        textLayout.beginLayout()
        text = ''
        while True:
            line = textLayout.createLine()
            if not line.isValid():
                break
            line.setLineWidth(maxWidth)
            text += self.dialogue_text[
                line.textStart():line.textStart() + line.textLength()]
            line.setPosition(QtCore.QPointF(0, height))
            height += line.height()
            if height + line.height() > maxHeight:
                line = textLayout.createLine()
                line.setLineWidth(maxWidth)
                line.setPosition(QtCore.QPointF(0, height))
                if line.isValid():
                    last = self.dialogue_text[line.textStart():]
                    fm = QtGui.QFontMetrics(dialogue_font)
                    text += fm.elidedText(last, QtCore.Qt.ElideRight, maxWidth)
                break

        doc = QtGui.QTextDocument(text)
        doc.setDocumentMargin(0)
        doc.setDefaultFont(dialogue_font)
        doc.setTextWidth(text_rect.width())

        self.dialogue_text_item = QtWidgets.QGraphicsTextItem(self)
        self.dialogue_text_item.setDocument(doc)
        self.dialogue_text_item.setCacheMode(self.DeviceCoordinateCache)
        self.dialogue_text_item.setDefaultTextColor(QtCore.Qt.white)
        self.dialogue_text_item.setPos(text_rect.topLeft())

        # Flags
        self.setFlag(self.ItemIsMovable, True)
        self.setFlag(self.ItemSendsGeometryChanges, True)
        self.setFlag(self.ItemIsSelectable, True)
        self.setFlag(self.ItemIsFocusable, True)
        self.setCacheMode(self.DeviceCoordinateCache)

The QFontMetrics elide function only works for a single line of text, and cannot be used for layed out text, which is what happens when word wrapping or new lines are involved.
Even trying to set the width for the elide function based on an arbitrary size, it wouldn't be valid: whenever a line is wrapped, the width used as reference for that line is "reset".

Imagine that you want the text to be 50 pixels wide, so you suppose that some text would be split in two lines, with a total of 100 pixels. Then you have three words in that text, each 40 pixels wide, for which the result of elidedText() with 100 pixels will be that you'll have all three words, with the last one elided.
Then you set that text with word wrapping enabled and a maximum width of 50 pixels: the result will be that you'll only see the first two words, because each line can only fit one word.

The only viable solution is to use QTextLayout, and iterate through all the text lines it creates, then, if the height of the next line exceeds the maximum height, you call elidedText() for that line only.

Be aware, though, that this assumes that the format (font, font size and weight) will always be the same along the whole text. More advanced layouts are possible, but it requires more advanced use of QTextDocument features, QTextLayout and QTextFormat.

        textLayout = QtGui.QTextLayout(self.dialogue_text, dialogue_font)
        height = 0
        maxWidth = text_rect.width()
        maxHeight = text_rect.height()
        textLayout.beginLayout()
        text = ''
        while True:
            line = textLayout.createLine()
            if not line.isValid():
                break
            line.setLineWidth(maxWidth)
            text += self.dialogue_text[
                line.textStart():line.textStart() + line.textLength()]
            line.setPosition(QtCore.QPointF(0, height))
            height += line.height()
            if height + line.height() > maxHeight:
                line = textLayout.createLine()
                line.setLineWidth(maxWidth)
                line.setPosition(QtCore.QPointF(0, height))
                if line.isValid():
                    last = self.dialogue_text[line.textStart():]
                    fm = QtGui.QFontMetrics(dialogue_font)
                    text += fm.elidedText(last, QtCore.Qt.ElideRight, maxWidth)
                break

Note that your item implementation is a bit questionable: first of all, you're practically not using any of the features of QGraphicsPathItem, since you're overriding both paint() and boundingRect().

If you want to do something like that, just use a basic QGraphicsItem, otherwise always try to use the existing classes and functions Qt provides, which is particularly important for the Graphics View framework, which relies on the C++ optimizations: overriding paint() forces the drawing to pass through python, which is a huge bottleneck, especially when many items are involved.

Instead of painting everything, create child items with properly set properties.

Finally, an item should not add itself to a scene.

Here's a better, simpler (and more readable) implementation that considers all the above:

class Node(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super(Node, self).__init__(parent)

        self.setBrush(QtGui.QColor("#575b5e"))
        
        main_rect = QtCore.QRectF(0, -40, 600, 140)
        path = QtGui.QPainterPath()
        path.addRoundedRect(main_rect, 4, 4)
        self.setPath(path)

        hMargin = main_rect.width() * .05
        vMargin = 10
        dialogue_rect = main_rect.adjusted(hMargin, vMargin, -hMargin, -vMargin)

        dialogue_item = QtWidgets.QGraphicsRectItem(dialogue_rect, self)
        dialogue_color = QtGui.QColor("#2B2B2B")
        dialogue_item.setPen(QtGui.QPen(dialogue_color.darker(), 2))
        dialogue_item.setBrush(dialogue_color)

        text_rect = dialogue_rect.adjusted(hMargin, vMargin, -hMargin, -vMargin)
        dialogue_font = QtGui.QFont("Calibri", 12)
        dialogue_font.setBold(True)

        self.dialogue_text = "To find out how fast you type, just start typing "\
            "in the blank textbox on the right of the test prompt. You will see "\
            "your progress, including errors on the left side as you type. You "\
            "can fix errors as you go, or correct them at the end with the help "\
            "of the spell checker. If you need to restart the test, delete the "\
            "text in the text box. Interactive feedback shows you your current "\
            "wpm and accuracy. Bring me all the biscuits, for I am hungry. They "\
            "will be a fine meal for me and all the mice in town!"

        textLayout = QtGui.QTextLayout(self.dialogue_text, dialogue_font)
        height = 0
        maxWidth = text_rect.width()
        maxHeight = text_rect.height()
        textLayout.beginLayout()
        text = ''
        while True:
            line = textLayout.createLine()
            if not line.isValid():
                break
            line.setLineWidth(maxWidth)
            text += self.dialogue_text[
                line.textStart():line.textStart() + line.textLength()]
            line.setPosition(QtCore.QPointF(0, height))
            height += line.height()
            if height + line.height() > maxHeight:
                line = textLayout.createLine()
                line.setLineWidth(maxWidth)
                line.setPosition(QtCore.QPointF(0, height))
                if line.isValid():
                    last = self.dialogue_text[line.textStart():]
                    fm = QtGui.QFontMetrics(dialogue_font)
                    text += fm.elidedText(last, QtCore.Qt.ElideRight, maxWidth)
                break

        doc = QtGui.QTextDocument(text)
        doc.setDocumentMargin(0)
        doc.setDefaultFont(dialogue_font)
        doc.setTextWidth(text_rect.width())

        self.dialogue_text_item = QtWidgets.QGraphicsTextItem(self)
        self.dialogue_text_item.setDocument(doc)
        self.dialogue_text_item.setCacheMode(self.DeviceCoordinateCache)
        self.dialogue_text_item.setDefaultTextColor(QtCore.Qt.white)
        self.dialogue_text_item.setPos(text_rect.topLeft())

        # Flags
        self.setFlag(self.ItemIsMovable, True)
        self.setFlag(self.ItemSendsGeometryChanges, True)
        self.setFlag(self.ItemIsSelectable, True)
        self.setFlag(self.ItemIsFocusable, True)
        self.setCacheMode(self.DeviceCoordinateCache)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文