Search code examples
pythonpyqtpyqt5qtoolbutton

PyQt QToolButton not updating icon when in focus


I'm having a problem updating the icon of a button set with QToolButton. The idea is to use the button for a movie player. To play, one presses the button and the icon changes to pause. When pressed again, play is paused and the icon reverts to play. I have some working code, but the problem is that the icon is not updating consistently. If I keep the Qt window in focus, it takes one or two button presses to change the icon to the intended image, by which time the actual image is not the intended image (swapped play/pause).

Here is some minimal example code:

from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget, QStyle, QToolButton

class Widget(QWidget):
    def __init__(self, parent=None):
        super(Widget, self).__init__(parent=parent)
        self.play_button = QToolButton(clicked=self.update_button)
        self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.addWidget(self.play_button)

        self.button_pressed = False

    def update_button(self):
        if self.button_pressed:
            self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
            self.button_pressed = False
            print("Button should be set to PLAY. Press is", self.button_pressed)
        else:
            self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
            self.button_pressed = True
            print("Button should be set to PAUSE. Press is", self.button_pressed)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

In the above I start with a stop icon just to be sure to observe a change (any click should always change the icon). Keeping the window focused, I get the following output:

  • 1st click: "Button should be set to PAUSE. Press is True" (no change in icon)
  • 2nd click: "Button should be set to PLAY. Press is False" (icon changes to pause)
  • 3rd click: "Button should be set to PAUSE. Press is True" (icon changes to play) (and so on, continues swapping as intended)

I've also noticed that if after each click, I click outside the Qt window, or resize the Qt window, the button icon updates to the correct one. What am I doing wrong? How do I force the icon to update?

This behaviour happens mostly with QToolButton, but QPushButton also give issues (works when focused, but misbehaves/loses track of correct status if I resize the Qt window). Using PyQt 5.12.3 and qt 5.12.5 on macOS.


Solution

  • Seems like this issue is a bug in the Qt implementation for macOS. I tested and it happens with both PyQt5 and PySide2, so it must come from Qt. Forcing a redraw with a call to .repaint() after .setIcon() seems to make the problem go away:

    self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
    self.play_button.repaint()