Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Automatically resizing label text in Qt - strange behaviour

In Qt I have a composite widget that consists of several QLabels arranged inside QBoxLayouts. When the widget is resized I want the label text to scale to fill the label area and I have implemented resizing of the text in the resizeEvent.

This works but there seems to be some sort of feedback loop happening. The composite widget is placed in a main window inside a QBoxLayout along with some other widgets. When the main window is made smaller, the composite widget initially maintains its size and then resizes toward the correct size in several steps (about 10-15). If the the text height is set to more than about 0.8 times the label height then on resizing the text and containing widget grow larger with each step until eventually the app crashes.

Is this the correct approach to achieve this effect? If so, what might the problem be with the resizing?

Below is the resizeEvent code.

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)

(using PyQt4 4.8, Qt 4.7.4, Win 7 & OSX 10.6)

like image 578
glennr Avatar asked Jan 09 '12 22:01

glennr


2 Answers

I think problem with resizing caused by SizePolicy. Try to set label's size policy to Ignored it should help.

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

Is this the correct approach to achieve this effect?

Probably yes, quick search in documentation gave no better solutions. But I would create subclass of QLabel, and do policy setup and resizing there. Example:

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)

In case you need to fit text not only by height, but by width too, some aditional code required.

like image 50
reclosedev Avatar answered Nov 16 '22 00:11

reclosedev


reclosedev's answer gave me the key clue of using the Ignored size policy, but there were still a few details to iron out. Here's an example that calculates the font size that will fit in the label's current size.

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)

Unfortunately, there are a few properties required to make this work when you use the scaled label. Either be sure to always set them, or override __init__() to make the defaults useful. Here's a working example that sets them:

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()
like image 25
Don Kirkby Avatar answered Nov 16 '22 02:11

Don Kirkby