1

How can i fix the QLabel to not clip the text when resizing? This is a widget that will be placed inside a QDialog eventually. So the resizing of the Dialog will happen if a user resizes the main dialog.

enter image description here

enter image description here

'''
Main Navigation bar
'''
################################################################################
# imports
################################################################################
import os
import sys
import inspect
from PySide2 import QtWidgets, QtCore, QtGui


################################################################################
# widgets
################################################################################
class Context(QtWidgets.QWidget):

    def __init__(self):
        super(Context, self).__init__()

        # controls
        self.uiThumbnail = QtWidgets.QLabel()
        self.uiThumbnail.setMinimumSize(QtCore.QSize(100, 75))
        self.uiThumbnail.setMaximumSize(QtCore.QSize(100, 75))
        self.uiThumbnail.setScaledContents(True)
        self.uiThumbnail.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.uiThumbnail.setObjectName('thumbnail')

        self.uiDetailsText = QtWidgets.QLabel()
        self.uiDetailsText.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.uiDetailsText.setWordWrap(True)
        self.uiDetailsText.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
        self.uiDetailsText.setOpenExternalLinks(True)

        self.uiMenuButton = QtWidgets.QPushButton()
        self.uiMenuButton.setFixedSize(QtCore.QSize(24, 24))
        self.uiMenuButton.setFocusPolicy(QtCore.Qt.NoFocus)

        # header layout
        self.headerLayout = QtWidgets.QHBoxLayout()
        self.headerLayout.setSpacing(6)
        self.headerLayout.setContentsMargins(6, 6, 6, 6)
        self.headerLayout.setAlignment(QtCore.Qt.AlignTop)
        self.headerLayout.addWidget(self.uiThumbnail)
        self.headerLayout.addWidget(self.uiDetailsText)
        self.headerLayout.addWidget(self.uiMenuButton)
        self.headerLayout.setAlignment(self.uiThumbnail, QtCore.Qt.AlignTop)
        self.headerLayout.setAlignment(self.uiMenuButton, QtCore.Qt.AlignTop)

        # frames
        self.headerFrame = QtWidgets.QFrame()
        self.headerFrame.setObjectName('panel')
        self.headerFrame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.headerFrame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.headerFrame.setLayout(self.headerLayout)
        
        # layout
        self.mainLayout = QtWidgets.QHBoxLayout()
        self.mainLayout.setSpacing(6)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.addWidget(self.headerFrame)

        self.setLayout(self.mainLayout)

        self.setStyleSheet('''
            #thumbnail {
                background-color: rgb(70,70,70);
            }
            #panel { 
                background-color: rgb(120,120,120); 
                border-radius:3px;
            }
        ''')
        self.updateContext()


    # methods
    def updateContext(self):
        self.uiDetailsText.setText('''
            <span style="font-size:14px;">
                <b>A title goes here which can wrap</b>
            </span>
            <br>
            <span style="font-size:11px;">
                <b>Status:</b> Additional details go here
                <br>
                <b>User:</b>  
                User information goes here
                <br>
                <b>About:</b> Some more information
                <br>
                <b>Date:</b> 2021-07-03
                <br>
            </span>
        ''')



################################################################################
# main
################################################################################
if __name__ == '__main__':
    pass
    app = QtWidgets.QApplication(sys.argv)
    ex = Context()
    ex.resize(500,70)
    ex.show()
    sys.exit(app.exec_())

I tried adding this and it didn't help at all...

    # methods
    def resizeEvent(self, event):
        newHeight = self.uiDetailsText.heightForWidth(self.uiDetailsText.width())
        self.uiDetailsText.setMaximumHeight(newHeight)
        event.accept()
JokerMartini
  • 5,674
  • 9
  • 83
  • 193
  • Does this answer your question? [Setting text on a QLabel in a layout, doesn't resize](https://stackoverflow.com/questions/19293507/setting-text-on-a-qlabel-in-a-layout-doesnt-resize) – Passerby Nov 20 '21 at 12:39
  • What should happen to the text when there's not enough horizontal space available? If you want it to grow into the vertical space, then add `self.uiDetailsText.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)`. However, it might be better to use a QTextBrowser instead of a QLabel so you get scrollbars when there's not enough vertical space either. Otherwise, you'll have to elide the contents and perhaps provide a tooltip with the full text whenever the available space is insufficient to show everything. – ekhumoro Nov 20 '21 at 14:39

1 Answers1

0

The size policy of a QLabel is always Preferred (in both directions), and if word wrapping or rich text is used, the text layout engine will try to find an optimal width based on the contents. This unfortunately creates some issues in layout managers, as explained in the layout documentation:

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.

Also consider the following line:

self.headerLayout.setAlignment(QtCore.Qt.AlignTop)

it will align the layout item (headerLayout) to the top of the headerFrame, which creates problems with the size hint of the label, since the size policy is Preferred. It's normally unnecessary (and often discouraged) to set the alignment of a layout if it's the top level layout of a widget.

Unfortunately, there's no easy solution for these situations, because:

  • to allow "free" resizing, the label cannot have any size constraints;
  • as soon as a widget is mapped, the size hint is just that: a hint; if the user resizes a window that contains wrapped text, the size choosen by the user is respected, even if that results in partially hiding the text;
  • the size hint considers the heightForWidth of children only when the layout is activated, after that the top level window will ignore any hint and will only honor the user choice, limited by the minimum size (or minimum size hint) of widgets;
  • heightForWidth() is called only for widgets that do not have a layout, otherwise the layout item's heightForWidth() will be called;

There are workarounds, but they are not always reliable.

A possible solution is to resize the top level window whenever the height doesn't respect the widget's heightForWidth(). Note that this is not completely reliable, and it's not 100% safe, as it requires to call a resize inside a resize event. If any of the parent has some functions that acts on delayed calls related to layouts (including changing the geometry), it might cause recursion problems.

class Context(QtWidgets.QWidget):
    resizing = False
    # ...
    def resizeEvent(self, event):
        super().resizeEvent(event)
        if self.resizing:
            return
        diff = self.heightForWidth(event.size().width()) - self.height()
        if diff > 0:
            self.resizing = True
            target = self
            while not target.windowFlags() & (QtCore.Qt.Window | QtCore.Qt.SubWindow):
                target = target.parent()
            target.resize(target.width(), target.height() + diff)
            self.resizing = False

Note that for this to work you still have to set the Expanding vertical size policy or remove the line for the layout alignment.

Most importantly, this can only be used only for a single widget in a window, so you should consider implementing it in the top level widget.

musicamante
  • 41,230
  • 6
  • 33
  • 58
  • This causes a lot of flickering when resizing. Is it intentional that the dialog can't be resized more freely? To me, just setting an expanding vertical size policy seems to work okay - but maybe I've misunderstood the OP's requirements. – ekhumoro Nov 20 '21 at 19:49
  • @ekhumoro damn, that was what I was afraid of. I did some testing, including using an MDI area and QGraphicsProxyWidget, but my default system doesn't use opaque resizing, so the recursion problem still arises in other cases for which the system geometry takes precedence in the loop. The only solution I could find was to use a pre-existing QTimer connected to a function that would call a lambda (set in the `resizeEvent`), it reduces the flickering, but it's still terrible. I know for a fact that, at least on Linux, it's possible to put restraints on the resizing (mpv can do that when trying -> – musicamante Nov 21 '21 at 21:29
  • @ekhumoro -> to respect aspect ratio), but I still don't know how to do that from python. Besides that, as far as I understood, the OP wants to ensure that the label is always properly shown when trying to resize the window (whether `Context` is the top level window or not). It's yet not clear if the problem has to be solved cross-platform or not, but I had contacts with him in the past and I believe that's the case. – musicamante Nov 21 '21 at 21:32