简体   繁体   English

如何获取给定矩形的 QLabel 字体信息?

[英]How to get QLabel font information for a given rectangle?

I am trying to get font size of QLabel that the text fill entire QLabel's rectangle.I try to use QFontMetrics to get, but QFontMetrics can't get font size by gived rectangle?我正在尝试获取文本填充整个 QLabel 矩形的QLabel的字体大小。我尝试使用QFontMetrics来获取,但是QFontMetrics无法通过给定的矩形获取字体大小?

The example:这个例子:
The GUI may stuck while resize the window.调整 window 的大小时,GUI 可能会卡住。

class Label(QLabel):
    def __init__(self):
        super().__init__()
        self.resize(400, 300)
        font = self.calculate_font()
        self.setFont(font)
        self.setText('PASS')

    def calculate_font(self):
        for i in range(400):
            fm = QFontMetrics( QFont('Helvetica', i) )
            fmSize = fm.boundingRect(self.geometry(), Qt.AlignCenter, 'PASS').size()
            print(fm.boundingRect(self.geometry(), Qt.AlignCenter, 'PASS'), self.size())
            #need font height equal label height
            if fmSize.height() > self.size().height():
                return QFont('Helvetica', i)

    def resizeEvent(self, event):
        font = self.calculate_font()
        self.setFont(font)

app = QApplication([])
demo = Label()
demo.show()
app.exec()

Don't do it不要这样做

There are dozens of reasons for which what you want to achieve is simply wrong.您想要实现的目标完全错误的原因有很多。
The most important one is that dealing with text drawing and its size is not an easy task;最重要的是处理文本绘制及其大小不是一件容易的事; also, Qt uses the label contents to tell the window layout about the size it could have, the size it wants to have and, most importantly, the minimum size it should have;此外,Qt 使用 label 内容来告诉 window 布局它可以具有的大小,它想要的大小,最重要的是,它应该具有的最小大小; all this is very important to the GUI, as it will be taken into account in order to correctly resize all other elements of your interface.所有这些对 GUI 来说都非常重要,因为它会被考虑在内,以便正确调整界面的所有其他元素的大小。
Finally, all those factors are based on the text of the label and its formatting, which might depend on the text contents and the fact that the text might be "rich text" (including multiple font size and weight).最后,所有这些因素都基于 label 的文本及其格式,这可能取决于文本内容以及文本可能是“富文本”这一事实(包括多种字体大小和粗细)。

If you really are conscious about the concepts explained above, here are 4 possible implementations.如果您真的了解上面解释的概念,这里有 4 种可能的实现。 All of them partially support rich text (font weight, text color, etc, but not real text alignment).它们都部分支持富文本(字体粗细、文本颜色等,但不支持真正的文本对齐)。

标签测试示例

Resizing font label调整字体大小 label

While subclassing a QLabel might seem the simplest approach, it actually isn't, as it is a widget much more complex than it seems (as I wrote before, dealing with text widgets is not an easy task)虽然子类化 QLabel 似乎是最简单的方法,但实际上并非如此,因为它是一个比看起来复杂得多的小部件(正如我之前写的,处理文本小部件并非易事)

The most important downside of this method is that it is very slow, since it has to carefully compute the font size at each resize.这种方法最重要的缺点是它非常慢,因为它必须在每次调整大小时仔细计算字体大小。 While some improvement might be achieved with a better implementation, I wouldn't suggest this method anyway.虽然通过更好的实现可能会实现一些改进,但无论如何我都不建议使用这种方法。

class ResizeFontLabel(QLabel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.paintFont = self.font()

    def updateFont(self):
        doc = QTextDocument()
        if self.textFormat() == Qt.RichText or self.textFormat() == Qt.AutoText and Qt.mightBeRichText(self.text()):
            doc.setHtml(self.text())
        else:
            doc.setPlainText(self.text())
        frame = doc.rootFrame().frameFormat()
        frame.setMargin(0)
        doc.rootFrame().setFrameFormat(frame)
        doc.setDefaultFont(self.paintFont)

        width = self.width()
        height = self.height()

        if doc.size().width() > width or doc.size().height() > height:
            while doc.size().width() > width or doc.size().height() > height:
                self.paintFont.setPointSizeF(self.paintFont.pointSizeF() - .1)
                doc.setDefaultFont(self.paintFont)
        elif doc.size().width() < width and doc.size().height() < height:
            while doc.size().width() < width and doc.size().height() < height:
                self.paintFont.setPointSizeF(self.paintFont.pointSizeF() + .1)
                doc.setDefaultFont(self.paintFont)

    def resizeEvent(self, event):
        self.updateFont()

    def paintEvent(self, event):
        doc = QTextDocument()
        if self.textFormat() == Qt.RichText or self.textFormat() == Qt.AutoText and Qt.mightBeRichText(self.text()):
            doc.setHtml(self.text())
        else:
            doc.setPlainText(self.text())
        frame = doc.rootFrame().frameFormat()
        frame.setMargin(0)
        doc.rootFrame().setFrameFormat(frame)
        doc.setDefaultFont(self.paintFont)
        qp = QPainter(self)
        doc.drawContents(qp, QRectF(self.rect()))

QLabels internally use a QTextDocument for both painting and sizing. QLabel 在内部使用 QTextDocument 进行绘制和调整大小。 The reason of the setMargin is due to the fact that a QTextDocument has some margin by default, and that's not used in labels. setMargin的原因是因为 QTextDocument 默认有一些边距,而标签中没有使用。

Notes:笔记:

  • be careful about the different or/and in the updateFont() method.请注意updateFont()方法中的不同or/and Their logic is very important他们的逻辑很重要
  • this method is slow这种方法很
  • it doesn't support text alignment (at least with this basic implementation)它不支持文本 alignment(至少在这个基本实现中)

Painting based font label基于绘画的字体label

This method is a simplification of the one above.该方法是上述方法的简化。 It doesn't compute the font size, but just paints the contents scaled to the size.它不计算字体大小,而只是绘制缩放到大小的内容。

class PaintLabel(QLabel):
    def paintEvent(self, event):
        doc = QTextDocument()
        if self.textFormat() == Qt.RichText or self.textFormat() == Qt.AutoText and Qt.mightBeRichText(self.text()):
            doc.setHtml(self.text())
        else:
            doc.setPlainText(self.text())
        frame = doc.rootFrame().frameFormat()
        frame.setMargin(0)
        doc.rootFrame().setFrameFormat(frame)
        scale = min(self.width() / doc.size().width(), self.height() / doc.size().height())
        qp = QPainter(self)
        qp.scale(scale, scale)
        doc.drawContents(qp, QRectF(self.rect()))

Notes:笔记:

  • it's faster than the first method它比第一种方法快
  • it doesn't support alignment它不支持 alignment
  • since the scaling doesn't take into account the font size, the text will not be as big as it could be (that's due to the fact that a QTextDocument can have multiple "text blocks", and computing them at each paintEvent would make this really complex and slow由于缩放没有考虑字体大小,因此文本不会尽可能大(这是因为 QTextDocument 可以有多个“文本块”,并且在每个 paintEvent 中计算它们会使这个真的很复杂很慢

Graphics view label图形查看 label

This is a completely different approach, as it uses the Graphics View Framework .这是一种完全不同的方法,因为它使用Graphics View Framework The trick is to use a single QGraphicsTextItem in the scene, and let the view take care about the resizing/alignment.诀窍是在场景中使用单个QGraphicsTextItem ,并让视图负责调整大小/对齐。

class GraphicsLabel(QGraphicsView):
    def __init__(self, text=''):
        super().__init__()
        # graphics view usually have a background and a frame around them,
        # let's remove both of them
        self.setFrameShape(0)
        self.setStyleSheet('background: transparent;')
        # as most QAbstractScrollAreas, views have a default minimum size hint
        # that makes them "bigger" whenever they're shown; let's ignore that
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        scene = QGraphicsScene(self)
        self.setScene(scene)
        self.textItem = scene.addText('')
        self.setText(text)

    def minimumSizeHint(self):
        # this is related to the minimum size hint specified above
        font = self.font()
        font.setPointSize(1)
        return QFontMetrics(font).boundingRect(self.textItem.toPlainText()).size()

    def setText(self, text):
        font = self.font()
        font.setPointSize(1)
        self.setMinimumSize(QFontMetrics(font).boundingRect(text).size())
        if Qt.mightBeRichText(text):
            self.textItem.setHtml(text)
        else:
            self.textItem.setPlainText(text)
        doc = self.textItem.document()
        frame = self.textItem.document().rootFrame().frameFormat()
        if frame.margin():
            frame.setMargin(0)
            doc.rootFrame().setFrameFormat(frame)
            self.textItem.setDocument(doc)

    def text(self):
        # provide a basic interface similar to a QLabel
        return self.textItem.toPlainText()

    def resizeEvent(self, event):
        # the base class implementation is always required for QAbstractScrollArea
        # descendants; then we resize its contents to fit its size.
        super().resizeEvent(event)
        self.fitInView(self.textItem, Qt.KeepAspectRatio)

Notes:笔记:

  • it does support alignment确实支持 alignment
  • there's no "optimal" minimum size (the readability of the text is based on the font, and we can't do anything about that)没有“最佳”最小尺寸(文本的可读性取决于字体,我们对此无能为力)
  • there's no "optimal" size hint, so the widget will be resized without any "complaints" about its text contents: if the label has very long text, that text will just be very, very small.没有“最佳”大小提示,因此将调整小部件的大小而不会对其文本内容产生任何“抱怨”:如果 label 的文本很长,那么该文本将非常非常小。
  • basic QWidget styling (through QWidget.setStyleSheet ) is not supported不支持基本的 QWidget 样式(通过QWidget.setStyleSheet

Graphics view QLabel widget图形视图 QLabel 小部件

This method is very similar to the one above, but instead of creating a simple "text item", it adds an actual QLabel to the graphics scene.此方法与上述方法非常相似,但不是创建简单的“文本项”,而是在图形场景中添加了一个实际的 QLabel。

class GraphicsLabelWidget(QGraphicsView):
    def __init__(self, text=''):
        super().__init__()
        self.setFrameShape(0)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        self.setStyleSheet('background: transparent;')
        scene = QGraphicsScene(self)
        self.setScene(scene)
        self.label = QLabel(text)
        self.labelItem = scene.addWidget(self.label)
        self.label.setStyleSheet(self.styleSheet())
        self.setText(text)

    def minimumSizeHint(self):
        font = self.font()
        font.setPointSize(1)
        doc = QTextDocument()
        if Qt.mightBeRichText(self.label.text()):
            doc.setHtml(self.label.text())
        else:
            doc.setPlainText(self.label.text())
        return QFontMetrics(font).boundingRect(self.label.text()).size()

    def setText(self, text):
        font = self.font()
        font.setPointSize(1)
        self.setMinimumSize(QFontMetrics(font).boundingRect(text).size())
        self.label.setText(text)

    def text(self):
        return self.label.toPlainText()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.fitInView(self.labelItem, Qt.KeepAspectRatio)

Notes:笔记:

  • slightly better than the basic addText graphics implementation;比基本的addText图形实现略好; at the same time, it's also slightly slower同时,它也稍微慢了一点
  • better support for stylesheets (but they should be applied to the actual child label)更好地支持样式表(但它们应该应用于实际的子标签)

Test example:测试示例:

Provided just for commodity (add the classes above to see it in action).仅为商品提供(添加上面的类以查看它的实际效果)。

class Demo(QWidget):
    def __init__(self):
        super().__init__()
        layout = QVBoxLayout(self)

        testText = 'PASS <b>bold</b><br/><i>new italic line</i><br/>{}'

        resizeLabel = ResizeFontLabel(testText.format('resize mode'))
        layout.addWidget(resizeLabel)
        resizeLabel.setAlignment(Qt.AlignRight|Qt.AlignBottom)

        paintLabel = PaintLabel(testText.format('paint mode'))
        layout.addWidget(paintLabel)
        paintLabel.setAlignment(Qt.AlignRight|Qt.AlignBottom)

        graphicsLabel = GraphicsLabel(testText.format('graphicsview mode'))
        layout.addWidget(graphicsLabel)
        graphicsLabel.setAlignment(Qt.AlignRight|Qt.AlignBottom)

        graphicsLabelWidget = GraphicsLabelWidget(testText.format('graphicsview mode'))
        layout.addWidget(graphicsLabelWidget)
        graphicsLabelWidget.setAlignment(Qt.AlignRight|Qt.AlignBottom)
        graphicsLabelWidget.label.setStyleSheet('QLabel {background: green;}')


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    demo = Demo()
    demo.show()
    app.exec()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM