Qt中自动调整标签文本大小 - 奇怪行为

10 投票
2 回答
19627 浏览
提问于 2025-04-17 09:53

在Qt中,我有一个组合控件,这个控件里面包含了几个QLabel,都是通过QBoxLayouts排列的。当这个控件被调整大小时,我希望标签的文字能够自动缩放,以填满标签的区域。我在resizeEvent里实现了文字的缩放。

这个方法是有效的,但似乎出现了一种反馈循环的问题。这个组合控件放在一个主窗口里,和其他一些控件一起使用QBoxLayout。当主窗口变小的时候,组合控件最开始保持原来的大小,然后会分几步(大约10到15步)逐渐调整到正确的大小。如果我把文字的高度设置得超过标签高度的0.8倍,那么在调整大小时,文字和包含它的控件会在每一步中变得越来越大,最终导致应用程序崩溃。

这样做是实现这个效果的正确方法吗?如果是的话,调整大小时可能出现了什么问题呢?

下面是resizeEvent的代码。

def resizeEvent(self, evt):
        print("resizeEvent", evt.size().width(), evt.size().height())
        QFrame.resizeEvent(self, evt)

        dataLabels = self.dataPanels.values()

        for label in dataLabels:            
            font = label.font()
            h = label.height()
            h2 = h * 0.8
            font.setPixelSize(h2)
            label.setFont(font)

(使用的是PyQt4 4.8,Qt 4.7.4,Win 7和OSX 10.6)

2 个回答

1

reclosedev的回答给了我一个重要的线索,就是要使用Ignored大小策略,不过还有一些细节需要处理。下面是一个示例,它计算出适合当前标签大小的字体大小。

from PySide2.QtGui import QResizeEvent, QFontMetrics, Qt
from PySide2.QtWidgets import QLabel


class ScaledLabel(QLabel):
    def resizeEvent(self, event: QResizeEvent):
        # This flag is used for pixmaps, but I thought it might be useful to
        # disable font scaling. Remove the check if you don't like it.
        if not self.hasScaledContents():
            return

        target_rect = self.contentsRect()
        text = self.text()

        # Use binary search to efficiently find the biggest font that will fit.
        max_size = self.height()
        min_size = 1
        font = self.font()
        while 1 < max_size - min_size:
            new_size = (min_size + max_size) // 2
            font.setPointSize(new_size)
            metrics = QFontMetrics(font)

            # Be careful which overload of boundingRect() you call.
            rect = metrics.boundingRect(target_rect, Qt.AlignLeft, text)
            if (rect.width() > target_rect.width() or
                    rect.height() > target_rect.height()):
                max_size = new_size
            else:
                min_size = new_size

        font.setPointSize(min_size)
        self.setFont(font)

不幸的是,当你使用缩放标签时,有几个属性是必须的。你要么确保总是设置这些属性,要么重写__init__()方法,让默认值变得有用。下面是一个设置了这些属性的有效示例:

from PySide2.QtCore import Qt
from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QSizePolicy, QLabel

from scaled_label import ScaledLabel


def main():
    app = QApplication()

    widget = QWidget()
    label1 = ScaledLabel('Lorem ipsum')
    label2 = ScaledLabel('Lorem ipsum')

    # Any policy other than Ignored will fight you when you resize.
    size_policy = QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
    label1.setSizePolicy(size_policy)
    label2.setSizePolicy(size_policy)

    # If you check this flag, don't forget to set it.
    label1.setScaledContents(True)
    label2.setScaledContents(True)

    # "Ignored" policy means you have to define your own minimum size.
    label1.setMinimumSize(200, 40)
    label2.setMinimumSize(50, 10)

    # Standard label attributes still work.
    label1.setAlignment(Qt.AlignBottom)
    label2.setAlignment(Qt.AlignTop)

    # Tell the layout to scale the two fields at different sizes.
    layout = QVBoxLayout(widget)
    layout.addWidget(label1)
    layout.addWidget(label2)
    layout.setStretch(0, 4)
    layout.setStretch(1, 1)

    widget.show()
    exit(app.exec_())


main()
10

我觉得调整大小的问题是由 SizePolicy 引起的。试着把 label 的大小策略设置为 Ignored,这样应该会有所帮助。

label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)

这样做是实现这个效果的正确方法吗?

可能是的,快速查了一下文档没有找到更好的解决方案。不过我会创建一个 QLabel 的子类,在那里设置策略和调整大小。举个例子:

class StretchedLabel(QLabel):
    def __init__(self, *args, **kwargs):
        QLabel.__init__(self, *args, **kwargs)
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)

    def resizeEvent(self, evt):
        font = self.font()
        font.setPixelSize(self.height() * 0.8)
        self.setFont(font)

如果你需要根据高度和宽度来适应文本,还需要一些额外的代码。

撰写回答