Search code examples
pythonpyqtpyqt5qscrollarea

PyQt Automatically resize widget in scroll area


I am trying to create a window with a scroll area with widgets. This works. I tried to add a simple filter function to this window. This also works. The only problem is that the widgets inside the scroll area don't keep their size, when some are hidden. Is there a way to make sure the widgets in the scroll area maintain their size?

from PyQt5 import QtWidgets, QtCore, QtGui
import sys

# ----------------------------------------------------------------------------


class test(QtWidgets.QDialog):

    def __init__(self, *args, **kwargs):
        super(test, self).__init__(*args, **kwargs)

        self.main_layout = QtWidgets.QVBoxLayout(self)

        self.label_widget = QtWidgets.QWidget()
        self.label_layout = QtWidgets.QHBoxLayout()
        self.label_widget.setLayout(self.label_layout)
        self.main_layout.addWidget(self.label_widget)

        self.filter_field = QtWidgets.QLineEdit()
        self.label_layout.addWidget(self.filter_field)

        self.refresh_pbutton = QtWidgets.QPushButton("Refresh")
        self.label_layout.addWidget(self.refresh_pbutton)

        self.scroll_area = QtWidgets.QScrollArea()
        self.main_layout.addWidget(self.scroll_area)

        self.refresh_pbutton.clicked.connect(self.refresh)
        self.filter_field.textChanged.connect(self.filter)

        self.populate()

# ----------------------------------------------------------------------------

    def populate(self, *args, **kwargs):
        self.widgets = []
        self.scroll_widget = QtWidgets.QWidget()
        self.scroll_widget.setAutoFillBackground(True)
        self.scroll_widget.setStyleSheet("background-color:red;")
        self.scroll_layout = QtWidgets.QVBoxLayout()
        self.scroll_widget.setLayout(self.scroll_layout)

        for i in range(1, 11):
            widget = smallWidget(str(i))
            self.widgets.append(widget)
            self.scroll_layout.addWidget(widget)

        self.scroll_area.setWidget(self.scroll_widget)
        self.filter_field.setText("")

    def refresh(self):
        self.populate()

    def filter(self):
        filter_text = str(self.filter_field.text())
        for widget in self.widgets:
            if filter_text in widget.name:
                widget.show()
            else:
                widget.hide()

class smallWidget(QtWidgets.QWidget):
    def __init__(self, name, *args, **kwargs):
        super(smallWidget, self).__init__(*args, **kwargs)
        self.name = name

        self.main_layout = QtWidgets.QVBoxLayout(self)
        self.name_label = QtWidgets.QLabel(self.name)
        self.main_layout.addWidget(self.name_label)


if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)

    a = test()
    a.show()

    sys.exit(app.exec_())

Solution

  • You should not add the widgets directly to scroll_layout but create another layout, and in that layout add the widgets and use addStretch() so that it does not occupy the height of the first layout but the necessary one.

    def populate(self):
        self.widgets = []
        self.scroll_widget = QtWidgets.QWidget()
        self.scroll_widget.setAutoFillBackground(True)
        self.scroll_widget.setStyleSheet("background-color:red;")
        self.scroll_layout = QtWidgets.QVBoxLayout()
        self.scroll_widget.setLayout(self.scroll_layout)
    
        lay = QtWidgets.QVBoxLayout() # <---
        self.scroll_layout.addLayout(lay) # <---
        self.scroll_layout.addStretch() # <---
    
        for i in range(1, 11):
            widget = smallWidget(str(i))
            self.widgets.append(widget)
            lay.addWidget(widget) # <---
    
        self.scroll_area.setWidget(self.scroll_widget)
    

    Update:

    If you want the size of scroll_widget to be adjusted you must call adjustSize() an instant later with a QTimer since the geometry changes are not applied instantly

    def filter(self, text):
        for widget in self.widgets:
            widget.setVisible(text in widget.name)
        QtCore.QTimer.singleShot(0, self.scroll_widget.adjustSize)