QScrollArea具有FlowLayout部件没有调整正确

问题描述:

我想创建一个类似于KDE(如Gnome或者MacOS)系统设置的窗口小部件(例如,喜欢这幅画)QScrollArea具有FlowLayout部件没有调整正确

我已经从实施的FlowLayout Qt docs exampe

如果我将一些FlowLayout小部件(包装在具有QVBoxLayout的容器小部件中)放入QScrollArea中,并调整QSrollArea的大小,则所有事情都会按照它的原样流动和重新布局。

然而,如果我增加滚动区域的宽度,以便它需要较少的高度,滚动区域的仍然认为它的部件需要 其最小宽度的原单高度:

0_1507630989642_flow_layout_in_scrollarea.gif

哪有这样,当它不再需要垂直滚动条消失,我可以我更新了孩子的实际高度滚动区域?

下面,你会发现(Python)的实施的FlowLayout实际的例子,并在__main__块。

干杯, 斯蒂芬

""" 
PyQt5 port of the `layouts/flowlayout 
<https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ 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 <= index < len(self._item_list): 
      return self._item_list[index] 
     return None 

    def takeAt(self, index): # pylint: disable=invalid-name 
     if 0 <= index < 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 > effective_rect.right() and line_height > 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_()) 
+0

的[图标视图(https://doc.qt.io/qt-5/qlistview.html#viewMode-prop)QListView'的'已经有这个功能。 kde设置程序看起来像在滚动区域中使用多个列表视图。 – ekhumoro

+0

非常感谢提示。我玩弄了它,它在理论上有效,但是imho看起来不如流式布局。此外,如果添加多个(图标)列表视图的SrollArea,每个列表视图都有自己的滚动条和家长scrollarea没有滚动条。 –

+0

你应该能够改变在列表视图的滚动条政策。我也看不出有任何理由滚动区域不能被配置为产生滚动条。您还可以删除列表视图上的帧,以获得相当无缝的组合小部件。 – ekhumoro

将溶液(令人惊讶的)简单:使用的FlowLayout的heightChanged信号来更新所述容器(所述ScrollArea的微件)的最小高度。

这里是一个工作示例:

""" 
PyQt5 port of the `layouts/flowlayout 
<https://doc.qt.io/qt-5/qtwidgets-layouts-flowlayout-example.html>`_ example 
from Qt5. 

""" 
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 <= index < len(self._item_list): 
      return self._item_list[index] 
     return None 

    def takeAt(self, index): # pylint: disable=invalid-name 
     if 0 <= index < 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 > effective_rect.right() and line_height > 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, QGroupBox 

    app = QApplication(sys.argv) 

    container = QWidget() 
    container_layout = QVBoxLayout() 
    for i in range(2): 
     g = QGroupBox(f'Group {i}') 
     l = FlowLayout(margin=10) 
     l.heightChanged.connect(container.setMinimumHeight) 
     g.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_layout.addWidget(g) 
    container_layout.addStretch() 
    container.setLayout(container_layout) 

    w = QScrollArea() 
    w.setWindowTitle('Flow Layout') 
    w.setWidgetResizable(True) 
    w.setWidget(container) 
    w.show() 

    sys.exit(app.exec_())