title: QScrollArea with FlowLayout widgets not resizing properly
body: <p>I want to create a widget similar to the KDE (or Gnome or MacOS) system settings (e.g., like this picture)</p>
  
  <p><img src="https://i.imgur.com/UuMe0l8.png" alt=""></p>
  
  <p>I already implemented a FlowLayout from the <a href="https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html" rel="nofollow noreferrer">Qt docs exampe</a>.</p>
  
  <p>If I put some FlowLayout widgets (wrapped in a container widget with a QVBoxLayout) into a QScrollArea and resize the QSrollArea, everything flows and re-layouts as it shoulds.</p>
  
  <p><em>However</em>, if I increase the scroll area’s width so that it needs less height, the scroll area’s still thinks that its widgets require
  the orginal height for their minimumWidth: </p>
  
  <p><img src="https://media.giphy.com/media/3o7aDeJwuklZBaTdQI/giphy.gif" alt="0_1507630989642_flow_layout_in_scrollarea.gif"> </p>
  
  <p>How can I can I update the scroll area with the actual height of its child so that the vertical scroll bar disappears when it’s no longer needed?</p>
  
  <p>Below, you’ll find the (Python) implementation of the FlowLayout and in the <code>__main__</code> block the actual example.</p>
  
  <p>Cheers, 
  Stefan</p>
  
  <pre><code>"""
  PyQt5 port of the `layouts/flowlayout
  &lt;https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html&gt;`_ example
  from Qt5.
  
  Usage:
  
      python3 -m pip install pyqt5
      python3 flow_layout.py
  
  """
  from PyQt5.QtCore import pyqtSignal, QPoint, QRect, QSize, Qt
  from PyQt5.QtWidgets import QLayout, QSizePolicy, QSpacerItem
  
  
  class FlowLayout(QLayout):
      """A ``QLayout`` that aranges its child widgets horizontally and
      vertically.
  
      If enough horizontal space is available, it looks like an ``HBoxLayout``,
      but if enough space is lacking, it automatically wraps its children into
      multiple rows.
  
      """
      heightChanged = pyqtSignal(int)
  
      def __init__(self, parent=None, margin=0, spacing=-1):
          super().__init__(parent)
          if parent is not None:
              self.setContentsMargins(margin, margin, margin, margin)
          self.setSpacing(spacing)
  
          self._item_list = []
  
      def __del__(self):
          while self.count():
              self.takeAt(0)
  
      def addItem(self, item):  # pylint: disable=invalid-name
          self._item_list.append(item)
  
      def addSpacing(self, size):  # pylint: disable=invalid-name
          self.addItem(QSpacerItem(size, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))
  
      def count(self):
          return len(self._item_list)
  
      def itemAt(self, index):  # pylint: disable=invalid-name
          if 0 &lt;= index &lt; len(self._item_list):
              return self._item_list[index]
          return None
  
      def takeAt(self, index):  # pylint: disable=invalid-name
          if 0 &lt;= index &lt; len(self._item_list):
              return self._item_list.pop(index)
          return None
  
      def expandingDirections(self):  # pylint: disable=invalid-name,no-self-use
          return Qt.Orientations(Qt.Orientation(0))
  
      def hasHeightForWidth(self):  # pylint: disable=invalid-name,no-self-use
          return True
  
      def heightForWidth(self, width):  # pylint: disable=invalid-name
          height = self._do_layout(QRect(0, 0, width, 0), True)
          return height
  
      def setGeometry(self, rect):  # pylint: disable=invalid-name
          super().setGeometry(rect)
          self._do_layout(rect, False)
  
      def sizeHint(self):  # pylint: disable=invalid-name
          return self.minimumSize()
  
      def minimumSize(self):  # pylint: disable=invalid-name
          size = QSize()
  
          for item in self._item_list:
              minsize = item.minimumSize()
              extent = item.geometry().bottomRight()
              size = size.expandedTo(QSize(minsize.width(), extent.y()))
  
          margin = self.contentsMargins().left()
          size += QSize(2 * margin, 2 * margin)
          return size
  
      def _do_layout(self, rect, test_only=False):
          m = self.contentsMargins()
          effective_rect = rect.adjusted(+m.left(), +m.top(), -m.right(), -m.bottom())
          x = effective_rect.x()
          y = effective_rect.y()
          line_height = 0
  
          for item in self._item_list:
              wid = item.widget()
  
              space_x = self.spacing()
              space_y = self.spacing()
              if wid is not None:
                  space_x += wid.style().layoutSpacing(
                      QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal)
                  space_y += wid.style().layoutSpacing(
                      QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical)
  
              next_x = x + item.sizeHint().width() + space_x
              if next_x - space_x &gt; effective_rect.right() and line_height &gt; 0:
                  x = effective_rect.x()
                  y = y + line_height + space_y
                  next_x = x + item.sizeHint().width() + space_x
                  line_height = 0
  
              if not test_only:
                  item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
  
              x = next_x
              line_height = max(line_height, item.sizeHint().height())
  
          new_height = y + line_height - rect.y()
          self.heightChanged.emit(new_height)
          return new_height
  
  
  if __name__ == '__main__':
      import sys
      from PyQt5.QtWidgets import QApplication, QPushButton, QScrollArea, QVBoxLayout, QWidget
  
  
      class Container(QWidget):
          def __init__(self):
              super().__init__()
              self.setLayout(QVBoxLayout())
              self._widgets = []
  
          def sizeHint(self):
              w = self.size().width()
              h = 0
              for widget in self._widgets:
                  h += widget.layout().heightForWidth(w)
  
              sh = super().sizeHint()
              print(sh)
              print(w, h)
              return sh
  
          def add_widget(self, widget):
              self._widgets.append(widget)
              self.layout().addWidget(widget)
  
          def add_stretch(self):
              self.layout().addStretch()
  
  
      app = QApplication(sys.argv)  # pylint: disable=invalid-name
      container = Container()
      for i in range(2):
          w = QWidget()
          w.setWindowTitle('Flow Layout')
          l = FlowLayout(w, 10)
          w.setLayout(l)
          l.addWidget(QPushButton('Short'))
          l.addWidget(QPushButton('Longer'))
          l.addWidget(QPushButton('Different text'))
          l.addWidget(QPushButton('More text'))
          l.addWidget(QPushButton('Even longer button text'))
          container.add_widget(w)
      container.add_stretch()
  
      sa = QScrollArea()
      sa.setWidgetResizable(True)
      sa.setWidget(container)
      sa.show()
  
      sys.exit(app.exec_())
  </code></pre>