4

I have an existing application that I am polishing off and I want to add some animation to a few of the widgets. Animating widgets with QPropertyAnimation outside of layouts is easy and fun, however when they are in a layout I am having various difficulties. The current one giving me a headache is that when I animate the size of a widget, the layout does not adjust to it's new size.

So lets say I have a QVBoxLayout with three widgets: a label which should expand to all available space, a treeview, and a button. When I click the button I want the tree to collapse and the label to take over it's space. Below is this example in code, and as you can see while the tree animates it's size nothing happens, and then when I hide it at the end of the animation the label pops to fill the now vacant space. So it seems that during the animation the layout does not "know" the tree is resizing. What I would like to happen is that AS the tree shrinks, the label expands to fill it.

Could this could be done not by absolute sizing of the label, but by calling a resize on the layout or something like that? I ask because I want to animate several widgets across my application and I want to find the best way to do this without having to make too many widgets interdependent upon each other.

Example code:

import sys
from PyQt4 import QtGui, QtCore


class AnimatedWidgets(QtGui.QWidget):
    def __init__(self):
        super(AnimatedWidgets, self).__init__()

        layout1 = QtGui.QVBoxLayout()
        self.setLayout(layout1)

        expanding_label = QtGui.QLabel("Expanding label!")
        expanding_label.setStyleSheet("border: 1px solid red")
        layout1.addWidget(expanding_label)

        self.file_model = QtGui.QFileSystemModel(self)
        sefl.file_model.setRootPath("C:/")
        self.browse_tree = QtGui.QTreeView()
        self.browse_tree.setModel(self.file_model)
        layout1.addWidget(self.browse_tree)

        shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
        shrink_tree_btn.clicked.connect(self.shrink_tree)
        layout1.addWidget(shrink_tree_btn)

        #--

        self.tree_size_anim = QtCore.QPropertyAnimation(self.browse_tree, "size")
        self.tree_size_anim.setDuration(1000)
        self.tree_size_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
        self.tree_pos_anim = QtCore.QPropertyAnimation(self.browse_tree, "pos")
        self.tree_pos_anim.setDuration(1000)
        self.tree_pos_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
        self.tree_anim_out = QtCore.QParallelAnimationGroup()
        self.tree_anim_out.addAnimation(self.tree_size_anim)
        self.tree_anim_out.addAnimation(self.tree_pos_anim)

    def shrink_tree(self):
        self.tree_size_anim.setStartValue(self.browse_tree.size())
        self.tree_size_anim.setEndValue(QtCore.QSize(self.browse_tree.width(), 0))

        tree_rect = self.browse_tree.geometry()
        self.tree_pos_anim.setStartValue(tree_rect.topLeft())
        self.tree_pos_anim.setEndValue(QtCore.QPoint(tree_rect.left(), tree_rect.bottom()))

        self.tree_anim_out.start()
        self.tree_anim_out.finished.connect(self.browse_tree.hide)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = AnimatedWidgets()

    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Spencer
  • 1,931
  • 1
  • 21
  • 44

1 Answers1

1

The layouts handle the geometry() of the widgets so that when wanting to change the pos property these are interfacing with their handles so it is very common that you get that type of behavior, a better option is to use a QVariantAnimation to establish a fixed height:

import sys
from PyQt4 import QtGui, QtCore


class AnimatedWidgets(QtGui.QWidget):
    def __init__(self):
        super(AnimatedWidgets, self).__init__()

        layout1 = QtGui.QVBoxLayout(self)

        expanding_label = QtGui.QLabel("Expanding label!")
        expanding_label.setStyleSheet("border: 1px solid red")
        layout1.addWidget(expanding_label)

        self.file_model = QtGui.QFileSystemModel(self)
        self.file_model.setRootPath(QtCore.QDir.rootPath())
        self.browse_tree = QtGui.QTreeView()
        self.browse_tree.setModel(self.file_model)
        layout1.addWidget(self.browse_tree)

        shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
        shrink_tree_btn.clicked.connect(self.shrink_tree)
        layout1.addWidget(shrink_tree_btn)
        #--
        self.tree_anim = QtCore.QVariantAnimation(self)
        self.tree_anim.setDuration(1000)
        self.tree_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)

    def shrink_tree(self):
        self.tree_anim.setStartValue(self.browse_tree.height())
        self.tree_anim.setEndValue(0)
        self.tree_anim.valueChanged.connect(self.on_valueChanged)
        self.tree_anim.start()

    def on_valueChanged(self, val):
        h, isValid = val.toInt()
        if isValid:
            self.browse_tree.setFixedHeight(h)

def main():
    app = QtGui.QApplication(sys.argv)
    ex = AnimatedWidgets()

    ex.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    main()
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • Tahnks eyllanesc, but QVaraintAnimation is abstract and this code throws an error. So I should make it into a class first right? – Spencer May 30 '18 at 23:11
  • @Spencer Now update the code, it seems that you are using python2. try it again please. – eyllanesc May 30 '18 at 23:22
  • Yes you're right I'm on 2 (sorry for not specifying) however I'm still getting this error: `TypeError: PyQt4.QtCore.QVariantAnimation represents a C++ abstract class and cannot be instantiated` – Spencer May 30 '18 at 23:29
  • @Spencer What version of PyQt4 are you using? – eyllanesc May 30 '18 at 23:30
  • @Spencer I am using PyQt4 4.12.1, QVariantAnimation is not an abstract class, it is probably a bug of an old version of PyQt4, I recommend you update it. – eyllanesc May 30 '18 at 23:31
  • I'm on 4.11.4. Per this doc it definitely is abstract... http://doc.qt.io/archives/qt-4.8/qvariantanimation.html#details Am I missing something? – Spencer May 30 '18 at 23:32
  • @Spencer Try to update your version of PyQt4, in my case it does not generate that problem. :) – eyllanesc May 30 '18 at 23:35
  • Ok I'll have to give it a go a little later, can't install at the moment! – Spencer May 30 '18 at 23:38
  • Well anyways without updating I just classed the QVariantAnimation and all works well with your updated code! – Spencer May 30 '18 at 23:47
  • Only slightly off-topic: is there an easy way to un-set the fixed height when I animate the tree back in? I'd like to have the tree come back on screen and the resizing to be handled by the layout. Setting sizePolicy did not work as I thought it should... – Spencer May 30 '18 at 23:58
  • 1
    @Spencer see this: https://stackoverflow.com/questions/20526319/qt-how-to-disable-setfixedsizeform-size-once-its-set – eyllanesc May 31 '18 at 00:02