pythonpyqtpyqt5qvboxlayout

Qt layout does not reflect groupbox size change


I am trying to make UI with animated collapsible groupBox, using PyQt5 and QT Creator.

If groupBox is unchecked its height shrinks to some small value, if groupBox is checked its height expands to sizeHint().height()

The problem is when another groupBox is present in layout. The anothergroupBox position does not reflect that collapsed groupBox size changed.

Is there way how to force bottom groupBox to move with collapsing groupBox?

Here how it looks like:

enter image description here


Additional information

UI layout: enter image description here

groupBox resizing implementation:

my_ui._ui.groupBox.toggled.connect(my_ui.group_box_size_change)

def group_box_size_change(self):
    duration = 1000
    self.animaiton_gb = QtCore.QPropertyAnimation(self._ui.groupBox, b"size")
    self.animaiton_gb.setDuration(duration)

    self.animaiton_gb.setStartValue(QtCore.QSize(self._ui.groupBox.width(),  self._ui.groupBox.height()))

    if self._ui.groupBox.isChecked():
        self.animaiton_gb.setEndValue(QtCore.QSize(self._ui.groupBox.width(), self._ui.groupBox.sizeHint().height()))
    else:
        self.animaiton_gb.setEndValue(QtCore.QSize(self._ui.groupBox.width(), 49))

    self.animaiton_gb.start()

Solution

  • Considering my old answer to a question where a similar widget was required, the following is the solution. In that case, the strategy is to use a QScrollArea as a container and use the minimumHeight and maximumHeight properties.

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class CollapsibleBox(QtWidgets.QGroupBox):
        def __init__(self, title="", parent=None):
            super(CollapsibleBox, self).__init__(title, parent)
            self.setCheckable(True)
            self.setChecked(False)
            self.toggled.connect(self.on_pressed)
            self.toggle_animation = QtCore.QParallelAnimationGroup(self)
    
            self.content_area = QtWidgets.QScrollArea(maximumHeight=0, minimumHeight=0)
            self.content_area.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
            self.content_area.setFrameShape(QtWidgets.QFrame.NoFrame)
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.setSpacing(0)
            lay.setContentsMargins(0, 0, 0, 0)
            lay.addWidget(self.content_area)
    
            self.toggle_animation.addAnimation(QtCore.QPropertyAnimation(self, b"minimumHeight"))
            self.toggle_animation.addAnimation(QtCore.QPropertyAnimation(self, b"maximumHeight"))
            self.toggle_animation.addAnimation(QtCore.QPropertyAnimation(self.content_area, b"maximumHeight"))
    
        @QtCore.pyqtSlot(bool)
        def on_pressed(self, checked):
            self.toggle_animation.setDirection(QtCore.QAbstractAnimation.Forward if checked else QtCore.QAbstractAnimation.Backward)
            self.toggle_animation.start()
    
        def setContentLayout(self, layout):
            lay = self.content_area.layout()
            del lay
            self.content_area.setLayout(layout)
            collapsed_height = self.sizeHint().height() - self.content_area.maximumHeight()
            content_height = layout.sizeHint().height()
            for i in range(self.toggle_animation.animationCount()):
                animation = self.toggle_animation.animationAt(i)
                animation.setDuration(500)
                animation.setStartValue(collapsed_height)
                animation.setEndValue(collapsed_height + content_height)
    
            content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1)
            content_animation.setDuration(500)
            content_animation.setStartValue(0)
            content_animation.setEndValue(content_height)
    
    if __name__ == '__main__':
        import sys
        import random
    
        app = QtWidgets.QApplication(sys.argv)
        w = QtWidgets.QMainWindow()
        scroll = QtWidgets.QScrollArea()
        content = QtWidgets.QWidget()
        scroll.setWidget(content)
        scroll.setWidgetResizable(True)
        vlay = QtWidgets.QVBoxLayout(content)
        counter = 0
        for i in range(10):
            box = CollapsibleBox("Collapsible Box Header-{}".format(i))
            vlay.addWidget(box)
            lay = QtWidgets.QVBoxLayout()
            for j in range(8):
                btn = QtWidgets.QPushButton("PushButton-{}".format(counter))       
                lay.addWidget(btn)
                counter += 1
            box.setContentLayout(lay)
        vlay.addStretch()
        w.setCentralWidget(scroll)
        w.resize(240, 480)
        w.show()
    
        sys.exit(app.exec_())