Search code examples
pythonanimationpyqtpyqt4

PyQt4: Use QVariantAnimation to animate between two values


I have a custom progress bar that I would like to animate as it changes from one value to another. When I get a value, I repaint the rectangle which represents the progress bar - so I think this should be as simple as animating the value itself and redrawing on each change. I've been looking through the Animation Framework documentation and I"m pretty sure subclassing a QVariantAnimation will do what I need - but there are little to no Python examples that I can find, and I'm a bit lost.

Here's where I've got so far (please excuse me if this is way off):

import sys
from PyQt4 import QtGui, QtCore

class AnimateBetweenNums(QtCore.QVariantAnimation):
    def __init__(self):
        QtCore.QVariantAnimation.__init__(self)

    def updateCurrentValue(self, value):
        print value.toString()


class MyProgressbar(QtGui.QWidget):

    def __init__(self):
        super(MyProgressbar, self).__init__()
        self.initUI()

    def initUI(self):
        self.setMinimumSize(2, 2)
        self.value = 50

    def setValue(self, value):
        oldValue = 10
        newValue = 70

        anim = AnimateBetweenNums()
        anim.setStartValue(oldValue)
        anim.setEndValue(newValue)
        anim.setDuration(1000)
        anim.start()
        anim.valueChanged.connect(self.updateValue)

    def updateValue(self, value):
        self.value = value
        self.repaint()

    def paintEvent(self, e):
        qp = QtGui.QPainter()
        qp.begin(self)
        self.drawWidget(qp)
        qp.end()

    def drawWidget(self, qp):
        size = self.size()
        w = size.width()
        h = size.height()

        till = int(((w / 100.0) * self.value))

        #the bar
        qp.setPen(QtGui.QColor(255, 255, 255))
        qp.setBrush(QtGui.QColor(0, 228, 47))
        qp.drawRect(0, 0, till, h)

        #the box
        pen = QtGui.QPen(QtGui.QColor(75,80,100), 1, QtCore.Qt.SolidLine)
        qp.setPen(pen)
        qp.setBrush(QtCore.Qt.NoBrush)
        qp.drawRect(0, 0, w - 1, h - 1)


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

        self.initUI()

    def initUI(self):
        hbox = QtGui.QVBoxLayout()
        self.button10 = QtGui.QPushButton("10")
        hbox.addWidget(self.button10)
        self.button70 = QtGui.QPushButton("70")
        hbox.addWidget(self.button70)

        self.progress = MyProgressbar()
        hbox.addWidget(self.progress)

        self.setLayout(hbox)

        self.setGeometry(300, 300, 390, 210)
        self.show()

        self.button10.clicked.connect(self.changeValue10)
        self.button70.clicked.connect(self.changeValue70)

    def changeValue10(self, value):
        self.progress.setValue(10)
        self.progress.repaint()

    def changeValue70(self, value):
        self.progress.setValue(70)
        self.progress.repaint()


def main():
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

Solution

  • Below is a re-written version of your script that hopefully does what you intended. The animation object should only be created once, and because you are using Python 2 with PyQt4, you need to make sure any QVariant values are converted correctly. I also changed the setValue() method so that it restarts from the previous value.

    import sys
    from PyQt4 import QtGui, QtCore
    
    class AnimateBetweenNums(QtCore.QVariantAnimation):
        def __init__(self):
            QtCore.QVariantAnimation.__init__(self)
    
        def updateCurrentValue(self, value):
            print value.toString()
    
    class MyProgressbar(QtGui.QWidget):
        def __init__(self):
            super(MyProgressbar, self).__init__()
            self.initUI()
    
        def initUI(self):
            self.setMinimumSize(2, 2)
            self.anim = AnimateBetweenNums()
            self.anim.setDuration(1000)
            self.anim.valueChanged.connect(self.updateValue)
            self.value = 50
    
        def setValue(self, value):
            self.anim.setStartValue(self.value)
            self.anim.setEndValue(value)
            self.anim.start()
    
        def updateValue(self, value):
            self.value = QtCore.QVariant(value).toInt()[0]
            self.repaint()
    
        def paintEvent(self, e):
            qp = QtGui.QPainter()
            qp.begin(self)
            self.drawWidget(qp)
            qp.end()
    
        def drawWidget(self, qp):
            size = self.size()
            w = size.width()
            h = size.height()
            till = int(((w / 100.0) * self.value))
    
            #the bar
            qp.setPen(QtGui.QColor(255, 255, 255))
            qp.setBrush(QtGui.QColor(0, 228, 47))
            qp.drawRect(0, 0, till, h)
    
            #the box
            pen = QtGui.QPen(QtGui.QColor(75,80,100), 1, QtCore.Qt.SolidLine)
            qp.setPen(pen)
            qp.setBrush(QtCore.Qt.NoBrush)
            qp.drawRect(0, 0, w - 1, h - 1)
    
    
    class Example(QtGui.QWidget):
        def __init__(self):
            super(Example, self).__init__()
            self.initUI()
    
        def initUI(self):
            hbox = QtGui.QVBoxLayout()
            self.button10 = QtGui.QPushButton("10")
            hbox.addWidget(self.button10)
            self.button70 = QtGui.QPushButton("70")
            hbox.addWidget(self.button70)
            self.progress = MyProgressbar()
            hbox.addWidget(self.progress)
            self.setLayout(hbox)
            self.setGeometry(300, 300, 390, 210)
            self.show()
            self.button10.clicked.connect(self.changeValue10)
            self.button70.clicked.connect(self.changeValue70)
    
        def changeValue10(self, value):
            self.progress.setValue(10)
    
        def changeValue70(self, value):
            self.progress.setValue(70)
    
    def main():
        app = QtGui.QApplication(sys.argv)
        ex = Example()
        sys.exit(app.exec_())
    
    if __name__ == '__main__':
        main()