Issue
This problem is an example to improve the understanding of layout and size policy.
In this example code I do set the width of a QDialog
to a minimum and maximum. This works. I don't touch the height of the dialog.
The problem is that the text in a QLabel
is to much to fit into the dialog but the dialog do not increase its height to fit the whole label.
Which component need to change its behavior: The dialog, the layout, the label? Do I need to change the size policy or something else?
#!/usr/bin/env python3
import sys
import this
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class MyDialog(QDialog):
def __init__(self):
super().__init__()
self.setMinimumWidth(200)
self.setMaximumWidth(400)
wdg = QLabel(self)
wdg.setWordWrap(True)
# Zen of Python in Rot13
wdg.setText(this.s.replace('\n', ' ') + " THE END")
layout = QVBoxLayout(self)
layout.addWidget(wdg)
if __name__ == "__main__":
app = QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
sys.exit(app.exec())
Solution
tl;dr
- long text should never be fully shown;
- QLabel is a peculiar widget, and is not a great one to understand the basics of layout management;
If a program needs to display some text that goes over 3/4 lines, a scrollable area is always preferable, especially if the text is part of a more complex layout.
Where is the problem?
This is caused by a known issue with QLabel and layout managers:
The use of rich text in a label widget can introduce some problems to the layout of its parent widget. Problems occur due to the way rich text is handled by Qt's layout managers when the label is word wrapped.
Note that using setWordWrap(True)
is equivalent to setting rich text contents, even when using plain text: even when using new line characters, non-wrapped text can be easily laid out, because the contents of each line are fixed and known; when wrapping is enabled, the text layout becomes dynamic and it requires the text layout engine to lay out characters depending on the current width.
Now, while some programs are able to use a specific aspect ratio when resized (under certain conditions and on specific OS capabilities), laying out of text is quite different and much more complex.
Resizing with a fixed aspect ratio is quite simple: when the user tries to resize the window, a simple formula can tell the final size based on the current attempt, the result is always consistent and deterministic, no matter if the reference is considered on the changed width (if the user resizes horizontally), height (vertical resizing) or both.
Text layouts
When a "text area" is resized, things get weird, and that's because wrapping is normally based on two aspects: the size of characters (including different sizes for each characters if the font is not mono spaced), the wrapping rules (only break on spaces, break on special characters, etc.).
The resulting ratio is never the same, depending on what dimension is resized:
- if you have very long words, vertical resizing might change the width, and by a lot;
- horizontal resizing could be completely indifferent (especially with long words), or could result in changing the line count by one or more;
Most importantly, when the user resizes the window from the corner, then altering both the width and height of the text area, what dimension should be used as reference? Remembering the points above, that would be tricky, and also extremely complex if not unreliable.
This is part of the reasons for which that issue has never been (nor will ever be) fixed.
Another one is that, even if a proper implementation would have been possible, it would also be very demanding for the computation of the widget layout. Doing that would be impractical: such computations would greatly affect the performance of the UI when doing a simple resizing, and for the wrong reasons, because long text should never be fully shown, but, instead, put in a scrollable context. Consider users with visual impairments that normally use big font sizes.
Since one of the main priority of a graphical program is to try to be as responsive as possible (without blocking user interaction), QLabel has some limits in its implementations: it does its best to show its contents without thinking about it too much. Under certain conditions, it just assumes aspects related to its possible size hints.
That's why you can only see part of the text.
A common objection is that web browsers are capable of dealing with these issues. That objection is invalid for at least two reasons:
- a web browser window is a scroll area, it doesn't resize itself in order to show its contents;
- text displaying and layout is a fundamental aspect of web browsers, and their engines are specifically optimized with that priority in mind;
Still, is it possible?
Yes, it is, but with some limits.
Based on your question, the most important problem is that the text is not fully shown when the program starts.
This can still be fixed by using some Qt features and functions.
QWidgets (and layouts) provide two important functions: hasHeightForWidth()
and the related heightForWidth()
. QLabel implements them, and properly returns them to the layout manager. Unfortunately, due to the aspects explained above, it doesn't always work out of the box.
QLayout also provides an important function: QLayout.closestAcceptableSize()
.
In your case, a simple solution is to add the following in the __init__()
, after the whole layout has been completed:
self.resize(QLayout.closestAcceptableSize(self,
QSize(self.width(), 1)))
Note that this can only work for top level widgets, and it works properly because you explicitly set a maximum size that is smaller than the default (top level widgets have a default size of 640x480
).
Now, the important question is: can we do it while resizing the window?
Well, theoretically, yes. But it's ugly and discouraged.
The trick is to check if the current width has changed or the event is spontaneous
(aka: set by the OS, not from Qt): the latter is important to check, because window resizing is primarily managed by the OS, which always has the "last word", including when the mouse button used to start the resizing action is released. Also, some systems and configurations don't use opaque resizing, so resize events are only sent when the resizing has been completed (aka, the mouse button has been released).
It's ugly because it causes a lot of flickering, and it's not guaranteed to work consistently among different OSs and configurations.
class MyDialog(QDialog):
def __init__(self):
...
self.fixSize()
def fixSize(self):
bestSize = QLayout.closestAcceptableSize(self,
QSize(self.width(), 1))
if self.height() < bestSize.height():
self.resize(bestSize)
def resizeEvent(self, event):
super().resizeEvent(event)
if event.oldSize().width() != event.size().width():
QTimer.singleShot(0, self.fixSize)
elif event.spontaneous():
self.fixSize()
Conclusion
There are lots of aspects that need to be considered when dealing with layout management. Also, one of the drawbacks of having a cross-platform toolkit, is that some level of compromise has to be accepted. Window size management is quite different between various OSs, and providing a completely consistent behavior for "grained" size control among all them is practically impossible.
The initial suggestion is always valid, though: long text should always be put within a scrollable area.
So, consider using:
- QPlainText for simple text;
- QTextEdit with
setReadOnly(True)
for basic rich text (see the limited available HTML subset); - as an alternative to QTextEdit, a QScrollArea with a QLabel set as its widget, with the following considerations:
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.