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:
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()
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_())