IARI
IARI

Reputation: 1377

Blinking Widget with PyQt

I simply want some elements inside a QDialog to be blinking (altering background color).

Now preferably I'd like to be able to use something that already exists and encapsulates the blinking state, i.e. blinking with css3 or maybe it is possible with QPropertyAnimation?

Since I didn't find any nice info on that option I tried the less optimal solution:

excerpt from the Dialogs __init__:

self.timer = QTimer()
self.timer.timeout.connect(self.update_blinking)
self.timer.start(250)
self.last_blinked = None

and

def update_blinking(self):
    self.frame.setStyleSheet(
        self.STYLE_BLINK_ON if self.blink else self.STYLE_BLINK_OFF)
    self.blink = not self.blink

where STYLE_BLINK_ON and STYLE_BLINK_OFF are some css specifying the background colors. That works but

  1. I find it super ugly, it feels like code from the 90s
  2. It isn't usable as the frequent style-update interrupts button-clicks.

Explanation for 2.: Assume the widget that should be blinking is a frame. When a button inside that frame is clicked, the clicked signal isn't emitted if a style-update of the frame occurs before the mouse-button is released.

A completely different solution that encapsulates things and doesn't require me to manually start a timer would of course be preferred. But I would be grateful if someone at least came up with a solution which solves point 2.

Upvotes: 3

Views: 7348

Answers (1)

Alexander Lutsenko
Alexander Lutsenko

Reputation: 2160

The one way is to use QPropertyAnimation. QPropertyAnimation interpolates over Qt properties - this fact causes difficulties:

1) Change appearance via style sheet -- animation cannot work with strings, because they're not interpolable.

2) Manipulate background directly -- background color is stored deep inside QWidget.palette, it's not a QProperty. The possible solution is to transform background color into a widget's property:

class AnimatedWidget(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)

        color1 = QtGui.QColor(255, 0, 0)
        color2 = QtGui.QColor(0, 255, 0)

        self.color_anim = QtCore.QPropertyAnimation(self, 'backColor')
        self.color_anim.setStartValue(color1)
        self.color_anim.setKeyValueAt(0.5, color2)
        self.color_anim.setEndValue(color1)
        self.color_anim.setDuration(1000)
        self.color_anim.setLoopCount(-1)
        self.color_anim.start()

    def getBackColor(self):
        return self.palette().color(QtGui.QPalette.Background)

    def setBackColor(self, color):
        pal = self.palette()
        pal.setColor(QtGui.QPalette.Background, color)
        self.setPalette(pal)

    backColor = QtCore.pyqtProperty(QtGui.QColor, getBackColor, setBackColor)

The other approach is dealing with QStateMachines. They're able to manipulate any properties, not only interpolable ones:

class StateWidget(QtGui.QWidget):
    def __init__(self):
        QtGui.QWidget.__init__(self)

        style1 = "background-color: yellow"
        style2 = "background-color: black"

        # animation doesn't work for strings but provides an appropriate delay
        animation = QtCore.QPropertyAnimation(self, 'styleSheet')
        animation.setDuration(150)

        state1 = QtCore.QState()
        state2 = QtCore.QState()
        state1.assignProperty(self, 'styleSheet', style1)
        state2.assignProperty(self, 'styleSheet', style2)
        #              change a state after an animation has played
        #                               v
        state1.addTransition(state1.propertiesAssigned, state2)
        state2.addTransition(state2.propertiesAssigned, state1)

        self.machine = QtCore.QStateMachine()
        self.machine.addDefaultAnimation(animation)
        self.machine.addState(state1)
        self.machine.addState(state2)
        self.machine.setInitialState(state1)
        self.machine.start()

Upvotes: 7

Related Questions