Animating custom widgets with QPropertyAnimation not working with pyside2

Hello and thank you for all your work, this website is a great tool to learn pyside2.

I’me having an issue with " Animating custom widgets with QPropertyAnimation" example.

It doesn’t work for me with pyside2: properties are acting strangely.
I’ve been able to make the not animated toggle widget work by make these small changes.

  • In the __init__ i’ve setted the two properties to 0 (otherwise it breaks later when the code tries to access them):
       self.handle_position = 0
       self.pulse_radius = 0
  • In the handle position i’ve changed self._handle_position to self.handle_position :
xPos = contRect.x() + handleRadius + trailLength * self.handle_position

I can’t figure out why it didn’t work and I haven’t solved AnimatedToggle which seems to have other problems with QPropertyAnimation.

Hi @Barsotti welcome to the forum & thanks for the feedback!

I remember when we were writing this tutorial having some problems with earlier versions of PySide2 – can you check which version you currently have installed? I’ve got 5.15.2 currently and can’t see any problems when running the example code.

fyi by replacing self._handle_position with self.handle_position you’re overwriting the Qt property (defined using the @Property(float) decorator). This will disable all the property behavior including animations etc.

    @Property(float)
    def handle_position(self):
        return self._handle_position

I think we need to solve the other error first & then this shouldn’t be necessary to change.

Hi @martin i’m using pyside2 5.13.2.
Hi don’t understand why changing :

xPos = contRect.x() + handleRadius + trailLength * self._handle_position

to

xPos = contRect.x() + handleRadius + trailLength * self.handle_position

would override Qt property, i’m just accessing it.

Here is the minimum changes I had to do to make it work :

class Toggle(QCheckBox):

   _transparent_pen = QPen(Qt.transparent)
   _light_grey_pen = QPen(Qt.lightGray)

   def __init__(self,
       parent=None,
       bar_color=Qt.gray,
       checked_color="#00B0FF",
       handle_color=Qt.white,
       ):
       super().__init__(parent)
       self._handle_position = 0
       self._pulse_radius = 0

       # Save our properties on the object via self, so we can access them later
       # in the paintEvent.
       self._bar_brush = QBrush(bar_color)
       self._bar_checked_brush = QBrush(QColor(checked_color).lighter())

       self._handle_brush = QBrush(handle_color)
       self._handle_checked_brush = QBrush(QColor(checked_color))

       # Setup the rest of the widget.

       self.setContentsMargins(8, 0, 8, 0)

       self.stateChanged.connect(self.handle_state_change)

   @Property(int)
   def handle_position(self):
       return int(self._handle_position)

   @handle_position.setter
   def handle_position(self, pos):
       """change the property
       we need to trigger QWidget.update() method, either by:
           1- calling it here [ what we're doing ].
           2- connecting the QPropertyAnimation.valueChanged() signal to it.
       """
       self._handle_position = pos
       self.update()

   @Property(float)
   def pulse_radius(self):
       return self._pulse_radius

   @pulse_radius.setter
   def pulse_radius(self, pos):
       self._pulse_radius = pos
       self.update()

   def sizeHint(self):
       return QSize(48, 45)

   def hitButton(self, pos: QPoint):
       return self.contentsRect().contains(pos)

   def paintEvent(self, e: QPaintEvent):

       contRect = self.contentsRect()
       handleRadius = round(0.24 * contRect.height())

       p = QPainter(self)
       p.setRenderHint(QPainter.Antialiasing)

       p.setPen(self._transparent_pen)
       barRect = QRectF(0, 0, contRect.width() - handleRadius, 0.40 * contRect.height())
       barRect.moveCenter(contRect.center())
       rounding = barRect.height() / 2

       # the handle will move along this line
       trailLength = contRect.width() - 2 * handleRadius
       xPos = contRect.x() + handleRadius + trailLength * self.handle_position

       if self.isChecked():
           p.setBrush(self._bar_checked_brush)
           p.drawRoundedRect(barRect, rounding, rounding)
           p.setBrush(self._handle_checked_brush)

       else:
           p.setBrush(self._bar_brush)
           p.drawRoundedRect(barRect, rounding, rounding)
           p.setPen(self._light_grey_pen)
           p.setBrush(self._handle_brush)

       p.drawEllipse(
           QPointF(xPos, barRect.center().y()),
           handleRadius, handleRadius)

       p.end()

   @Slot(int)
   def handle_state_change(self, value):
       self.handle_position = 1 if value else 0

I was referring to this line in your first comment where the value was being set onto handle_position – this will override the property. I see that’s fixed in your second example, so no problem.

 self.handle_position = 0

You’re right, if you’re reading from it in your own code it will make no difference which you use.

i’m using pyside2 5.13.2.

I think that explains the problem – if I remember correctly the bug was fixed in 5.15.2. Are you able to update to that to test it out?

Hi @martin,

Unfortunately i’m using a conda env Python 3.6 and pyside2 5.13.2 is the last version available on anaconda. I’ve had many issues trying to install it with pip, so I will stick to this version for now.

Thank you for your help.