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)
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.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With