Search code examples
pythonpython-2.7pyqtqpushbuttonqpropertyanimation

"Blinking" buttons in PyQT5


Here's the deal: I'm trying to make button A "blink" when button B is pushed, and the blinking should then stop when the user pushes button A. Furthermore, button A should then go back to its former state.

After long reading through the documentation and other questions at SO, I have now following piece of code:

Python 2.7

class Widget(QWidget):

    def __init__(self):
        super(Widget, self).__init__()

        self.resize(300,200)
        layout = QVBoxLayout(self)


        self.button_stop = QPushButton("Stop")
        layout.addWidget(self.button_stop)

        self.button_start = QPushButton("Start", self)
        layout.addWidget(self.button_start)

        self.animation = QPropertyAnimation(self, "color", self)
        self.animation.setDuration(1000)
        self.animation.setLoopCount(100)
        self.animation.setStartValue(self.color)
        self.animation.setEndValue(self.color)
        self.animation.setKeyValueAt(0.1, QColor(0,255,0))

        self.button_start.clicked.connect(self.animation.start)
        self.button_stop.clicked.connect(lambda: self.stop())

    def stop(self):
        self.animation.stop()
        self.button_stop.setStyleSheet("")

    def getColor(self):
        return self.button_stop.palette().base()

    def setColor(self, color):
        palette = self.button_stop.palette()
        palette.setColor(self.button_stop.backgroundRole(), color)
        self.button_stop.setAutoFillBackground(True)
        self.button_stop.setPalette(palette)


    color = pyqtProperty(QColor, getColor, setColor)


if __name__ == "__main__":
    app = QApplication([])
    w = Widget()
    w.show()
    app.exec_()

The code itself I got mostly from @Alexander Lutsenko in this question, with a few modifications here and there to test my needs. The main part ist straightforward: I create a window with two QPushButton, one to start the QPropertyAnimation and the other one to stop it. The QPropertyAnimation itself is applied to one of the buttons. It should in theory change the background color of the button, but due to the limitations explained in this other question it only provides a colorful border to the QPushButton. And I'm fine with that, it doesn´t look that intrusive.

The problem

After starting the animation, if I push the Stop button, the button doesn't go back to its original state (with no colorful border), but stays with the color of the animation at the time the Stop button was pressed.

Furthermore, I get following warning:

TypeError: unable to convert a Python 'QBrush' object to a C++ 'QColor' instance

But the script keeps running and the animation going, so that doesn't break anything.

The question

How do I "reset" the styleSheet of the button properly? Is there another (maybe better and pythonic) way to do the blinking button stuff? Thanks a lot!


Solution

  • The property does not save the initial state so even if you pair the animation it will not be restored to the initial state. So the solution in this case is to save that state in a variable and create a reset_color() method that restores the color again..

    On the other hand the error message: TypeError: unable to convert to Python 'QBrush' object to a C ++ 'QColor' instance indicates that the code self.button_stop.palette().base() returns a QBrush, but it is expected a QColor, and there is no conversion implied, so that implementation must be changed.

    As a matter of order I will create a new class that inherits from QPushButton and implement those properties.

    from PyQt5.QtWidgets import *
    from PyQt5.QtGui import *
    from PyQt5.QtCore import *
    
    class BlinkButton(QPushButton):
        def __init__(self, *args, **kwargs):
            QPushButton.__init__(self, *args, **kwargs)
            self.default_color = self.getColor()
    
        def getColor(self):
            return self.palette().color(QPalette.Button)
    
        def setColor(self, value):
            if value == self.getColor():
                return
            palette = self.palette()
            palette.setColor(self.backgroundRole(), value)
            self.setAutoFillBackground(True)
            self.setPalette(palette)
    
        def reset_color(self):
            self.setColor(self.default_color)
    
        color = pyqtProperty(QColor, getColor, setColor)
    
    
    class Widget(QWidget):
    
        def __init__(self):
            super(Widget, self).__init__()
    
            self.resize(300,200)
            layout = QVBoxLayout(self)
    
            self.button_stop = BlinkButton("Stop")
            layout.addWidget(self.button_stop)
    
            self.button_start = QPushButton("Start", self)
            layout.addWidget(self.button_start)
    
            self.animation = QPropertyAnimation(self.button_stop, "color", self)
            self.animation.setDuration(1000)
            self.animation.setLoopCount(100)
            self.animation.setStartValue(self.button_stop.default_color)
            self.animation.setEndValue(self.button_stop.default_color)
            self.animation.setKeyValueAt(0.1, QColor(0,255,0))
    
            self.button_start.clicked.connect(self.animation.start)
            self.button_stop.clicked.connect(self.stop)
    
        def stop(self):
            self.animation.stop()
            self.button_stop.reset_color()
    
    if __name__ == "__main__":
        app = QApplication([])
        w = Widget()
        w.show()
        app.exec_()