Smart sizing of QScrollArea
-
I am currently facing a problem regarding the height of my QScrollArea.
I want to have a layout like this:
It is important to note that window height and number of buttons in the QScrollArea can vary. I do not care about the width, because it behaves as expected.
My requirements are the following:-
The upper and lower group of buttons should be distributed equally across the height of the parent
-
The QScrollArea should always try to show all of it's contents, when there is extra space available
-
The QScrollArea should not contain any extra space at any time
-
When there is not enough space to show it's contents, the QScrollArea should shrink (preferably up to showing only one button), and have the ability to scroll
-
Inside the QScrollArea the buttons should always be grouped, keeping only the spacing of the layout between each other.
I have written a minimal example (which can be seen in the screenshot above) fulfilling three (1st, 3rd and 5th) of the requirements:
import sys from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QScrollArea, QSizePolicy, QVBoxLayout, QWidget, ) class MainWindow(QMainWindow): def __init__(self): super().__init__() widget = QWidget() vbox = QVBoxLayout(widget) vbox.addWidget(self._init_static()) vbox.addWidget(self._init_scroll()) self.setCentralWidget(widget) def _init_static(self): add = QPushButton("Add Button") add.clicked.connect(self.add_button) return add def _init_scroll(self): content_widget = QWidget() content_widget.setSizePolicy( QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Maximum ) self.inner_vbox = QVBoxLayout(content_widget) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setSizePolicy( QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Maximum ) scroll_area.setWidget(content_widget) return scroll_area def add_button(self): btn = QPushButton("Button") btn.setMinimumHeight(30) self.inner_vbox.addWidget(btn) self.last_widget = btn app = QApplication(sys.argv) window = MainWindow() window.setGeometry(100, 100, 600, 800) window.show() sys.exit(app.exec_())
Now if you change the vertical SizePolicy of the QScrollArea to "Preferred", it fulfills the 2nd and 5th requirement, but none of the others.
If you try the example yourself, just click the "Add Button" button to add buttons to the QScrollArea and see the results.
There seem to be only two Behaviors of the QScrollArea: Either expanding and consuming all extra space, or having a fixed height.
Does anyone know what I am missing here, or do my requirements clash anywhere and this is not possible?
-
-
In the mean time I have read some other Threads, apparently also having this problem, but I couldn't find a working solution yet.
I have discovered the QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents property, but it doesn't seem to change anything.
Also, I found that setting content_widget SizePolicy to Fixed will prevent the buttons to shrink.
Anyway, here is an updated example, also with the option to remove the buttons:import sys from PyQt5.QtWidgets import ( QAbstractScrollArea, QApplication, QMainWindow, QPushButton, QScrollArea, QSizePolicy, QVBoxLayout, QWidget, ) class MainWindow(QMainWindow): def __init__(self): super().__init__() widget = QWidget() vbox = QVBoxLayout(widget) vbox.addLayout(self._init_static()) self.scrollarea = self._init_scroll() vbox.addWidget(self.scrollarea) self.setCentralWidget(widget) def _init_static(self): vbox = QVBoxLayout() add = QPushButton("Add Button") add.clicked.connect(self.add_button) clear = QPushButton("Clear Buttons") clear.clicked.connect(self.clear_buttons) vbox.addWidget(add) vbox.addWidget(clear) return vbox def _init_scroll(self): content_widget = QWidget() content_widget.setSizePolicy( QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed ) self.inner_vbox = QVBoxLayout(content_widget) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setSizePolicy( QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Maximum ) scroll_area.setSizeAdjustPolicy( QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents ) scroll_area.setWidget(content_widget) return scroll_area def add_button(self): btn = QPushButton("Remove Button") btn.setMinimumHeight(30) self.inner_vbox.addWidget(btn) btn.clicked.connect(btn.deleteLater) def clear_buttons(self): for child in self.scrollarea.widget().children(): if isinstance(child, QPushButton): child.deleteLater() app = QApplication(sys.argv) window = MainWindow() window.setGeometry(100, 100, 600, 800) window.show() sys.exit(app.exec_())