1

This issue is specific to PyQt5, but C++ Qt5 answers are fine too.

Within a QScrollArea with fixed width and variable height, I have a QVBoxLayout that contains QLabels. These QLabels have setWordWrap(True) and contain text which may be longer than the fixed width of the QScrollArea. When the text inside a QLabel wraps to ~4 lines, everything works fine, but when the QLabel requires more than that, it fails to continue increasing the height of the QLabel, and cuts off some of the text on the top and bottom.

This answer tried to fix essentially the same issue, which involves setting the QLabel's vertical sizePolicy() to MinimumExpanding, and this technically worked, but it will force the QLabels to try and fill the entire QScrollArea viewport if the viewport hasn't been filled with QLabels yet.

Here's how it currently looks without the sizePolicy set to MinimumExpanding (notice the 1st QLabel):

sizePolicy not set

Here's how it looks when MinimumExpanding is set as the vertical size policy of the QLabels (Looks great...):

sizePolicy set

But it results in this behavior when there is only a few QLabels in the scroll area, which is unacceptable behavior, as this is going to be a "comments" service, where people can post their questions in plain text:

unacceptable behavior

Does anyone have a workaround for this issue, or experience anything similar to it?

For reference, here is some of my code:

class NewsList(QtWidgets.QScrollArea):
    def __init__(self, parent=None):
        super(NewsList, self).__init__(parent)
        self.setMaximumWidth(200)
        self.setWidgetResizable(True)

        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(5, 5, 5, 5)
        layout.setSpacing(5)
        layout.setAlignment(QtCore.Qt.AlignTop)
        self.news_widget = QtWidgets.QFrame()
        self.news_widget.setStyleSheet("""
            QFrame {
                background-color: #ffffff;
            }
        """)
        self.news_widget.setLayout(layout)
        self.setWidget(self.news_widget)
        self.fetch_news()

    def fetch_news(self):
        self.append_message('DSADAISH dshadbsasdsadh sd ashd sah dsha dhsa dsa d')
        self.append_message('DSADAISH dshadbsasdsadh sd ashd sah dsha dhsa dsa d')
        self.append_message('DSADAISH dshadbsasdsadh sd ashd sah dsha dhsa dsa d DSADAISH dshadbsasdsadh sd ashd sah dsha dhsa dsa d')
        self.append_message('DSADAISH ')
        self.append_message('DSADAISH ')
        self.append_message('DSADAISH dshadbsasdsadh sd ashd sah dsha dhsa dsa d')
        self.append_message('DSADAISH dshadbsasdsadh sd ashd sah dsha dhsa dsa d')

    def append_message(self, text):
        new_item = QtWidgets.QLabel(text)
        new_item.setWordWrap(True)
        new_item.setStyleSheet("""
            QLabel {
                padding: 4px;
                border: 1px solid black;
                background-color: #ffffff;
            }
        """)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
bhlee
  • 104
  • 14
  • Could you explain what is wrong with the second image ?, I do not understand when you say: *But it is in this behavior when there is only a few QLabels in the scroll area, which is unacceptable behavior, as this is going to be a "comments" service, where can people post their questions in plain text:* – eyllanesc Apr 15 '18 at 05:51
  • The two `QLabels` that I showed in the example image should take up a minimum amount of height such that it fits only the text inside. However, with `MinimumExpanding` set on the label, this expands the `QLabel`'s height to the height of the viewport. The container holding the Labels is a `ScrollArea`. – bhlee Apr 15 '18 at 07:45
  • Apologies, got confused by "second image", thought you meant "third image." In the second image, it looks great as is, but having `QLabels` NOT take up the ENTIRE viewport results in the third image, which is explained in the above comment. – bhlee Apr 15 '18 at 07:59
  • I do not understand you, explain me about the second image, what is the problem ?, I see that the labels occupy a minimum height that only shows the necessary text. Or do you mean that the scrollbar does not appear ?, In addition I just tried your code and a QScrollArea appears empty. – eyllanesc Apr 15 '18 at 08:19
  • You could show an image of how you want it to be a second image. – eyllanesc Apr 15 '18 at 08:20
  • The second image is the result of the `MinimumExpanding` flag being set on the `QLabels` and it presents correct behavior when the number of widgets "fills" the `QScrollArea`, however, when QLabels are a small number and only fill the current viewport partially, they expand to fill the given viewport, seen in image 3. This is not what I want, I want the `QLabel`s to shrink and take up the minimum height required to fit the text similar to image 2. Also, I edited the code so it should work now. – bhlee Apr 15 '18 at 12:19
  • Now I understand, you could correct your code because when I execute it, there is only an empty QScrollArea. – eyllanesc Apr 15 '18 at 12:21

1 Answers1

2

This can be solved quite simply by using the addStretch method of the layout that contains the news items:

class NewsList(QtWidgets.QScrollArea):
    def __init__(self, parent=None):
        ...    
        layout = QtWidgets.QVBoxLayout()
        layout.setContentsMargins(5, 5, 5, 5)
        layout.setSpacing(5)
        layout.setAlignment(QtCore.Qt.AlignTop)
        # add a stretchable space to the bottom of the layout
        layout.addStretch(1)

    def append_message(self, text):
        ...
        # set the size policy of the label
        new_item.setSizePolicy(
            QtWidgets.QSizePolicy.Preferred,
            QtWidgets.QSizePolicy.MinimumExpanding)
        # insert the label before the spacer        
        layout = self.news_widget.layout()
        layout.insertWidget(layout.count() - 1, new_item)

The spacer pushes the labels upwards, which stops them streching to take up the available space. Using the stretch-factor argument of addStretch ensures that the spacer always takes precedence over the other items in the layout.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336