1

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()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
user1281071
  • 875
  • 2
  • 13
  • 23
  • Please post a minimal example to demonstrate the problem. – S. Nick Feb 19 '19 at 08:16
  • I replaced last image with gif to show that when upper groupBox is collapsed the lower groupBox stays at the same position. I would like lower groupBox to move up towards collapsing upper groupBox so there is minimal space between them. – user1281071 Feb 19 '19 at 08:48
  • 1
    Please post a minimal example to demonstrate the problem [mcve] – S. Nick Feb 19 '19 at 08:59
  • I have rearranged text so the question begins with motivation followed by current implementation, problem statement and question itself. Below that is visual representation of problem for easier understanding.Newly created section 'Additional information' should serve thous who are willing to answer my question and need more information. I hope this format is more readable. – user1281071 Feb 19 '19 at 10:14
  • Try putting the GroupBox objects inside a [QVBoxLayout](https://doc.qt.io/qt-5/qvboxlayout.html). That should handle automatically spacing and positioning them. You might need to pick up resize events or some such, but I think it should just work. The doc on [layout management](https://doc.qt.io/qt-5/layout.html) is pretty good. – Simon Hibbs Feb 19 '19 at 13:21

1 Answers1

1

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_())
eyllanesc
  • 235,170
  • 19
  • 170
  • 241