Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Push buttons and pixmaps not updating/refreshing properly in PyQt only on MacOS



  • Re: Layout not "updating" when removing all widgets inside it

    Similar issue to the thread referenced above ^^. When doing certain GUI events (enabling/disabling buttons, clicking buttons, drawing pixamps, etc), they randomly are not being updated (i.e. the buttons are getting "stuck" in the pressed state). This happens for ALL push buttons on my GUI. So for example a user clicks a button, the program does things, but the button is still in the pressed state (the buttons are not set to toggle) but the user can clearly keep clicking the button and the program continues to function as normal.

    This issue only exists for macOS, I have never seen this problem in a PyQt build on PC. I'm also doing multithreading, so that when a user clicks a button on the GUI, a new thread is created that does non-gui calculations, then upon finishing notifies the GUI to enable/disable certain buttons based on the operation. Therefore, the main GUI thread is always freed up to do the things it needs to do.

    I've seen this on PyQt 5.12 using Python 3.5x and 3.7x.

    Right now, I'm resorting to having a function that gets called when I emit a signal that goes in and manually calls .setEnabled(true) followed by .setEnabled(false). I then call .repaint() on each button. Manually setting setEnabled is the only way to get the .repaint() function to do anything.

    Anybody else seeing similar issues? Can anyone verify they've seen this issue on PC too?


  • Banned

    Okay I would have to ask how you are implementing those Threads --- sub-classing them perhaps??

    Also keep in mind that due to the GIL threads are not multiprocessing and only 1 Thread can be worked on at a time and depending on how you created that Thread it may (1) not really be threading or (2) it can still dominate the event queue blocking other events from occurring such as gui updates



  • @Denni-0 Yes, they are sub-classed. I followed the implementation used here: https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/.

    The GUI is not getting hung up during computationally expensive processes so I'm assuming the 'multithreading' is working as intended. Also, this was happening before I even implemented multithreading for my app (having a more responsive GUI was the main reason for wanting to implement multithreading in the first place). So I don't believe it is related to threading.

    What's confusing to me is why does the GUI never eventually update? If the event queue is being dominated by non-gui stuff, how do subsequent GUI events get handled as they move up in the queue? Certain interactions with the GUI (i.e. minimizing windows, starting other processed in the GUI) will cause some components to update/refresh but not all of them get updated. Other times, everything works just fine!

    Also, calling .update() on the affected widget doesn't appear to do anything (the button still shows as being pressed even though I can still click it and have it do stuff). I've even tried adding a processEvents() call after updating the widgets to force the GUI to have control of the event loop and it still doesn't update things. This is super frustrating, any ideas?


  • Lifetime Qt Champion

    Hi anybody welcome to devnet,

    Would it be possible to take a look at your code ?

    By the way, what version of Python and PyQt 5 are you using ?
    How did you install them ?



  • @SGaist The problem is that my GUI is fairly complex. I could put together a small working example with a widget and a couple of push buttons if I need to. Although I'd be willing to bet anybody running a PyQt GUI on macOS Sierra and above is going to see this problem.

    This was tested on PyQt 5.12 using Python 3.5 and Python 3.7. Packages were installed using Anaconda/pip.


  • Lifetime Qt Champion

    It would be most useful if you can build such an example. That would help debug your issue.


  • Banned

    Okay @wunjo if you Sub-Classed QThread (per the documentation which btw is wrong) then you did not use QThread the way it was actually meant to be used nor the way it ought to be used. I found this out myself after creating a project using QThread via Sub-Classing having issues and then finding out that some of those issues were due to sub-classing QThread. It took me several days to back all that out and implement QThreads the way they are supposed to be -- which btw makes a whole lot more sense now.

    That being said and because you have such complicated project you can shoot me a message on Discord DenniO#8137 and we dig a bit deeper into the problem as well as help you with how QThreads are supposed to be used.



  • @Denni-0 Ok, that's good to know! I'm definitely interested in using QThread the proper way. I will hit you up soon to get your feedback.

    In the meantime, I'm going to put together a minimal working example that doesn't use any threading to demonstrate the original issue for people using PyQT on macOS. I'll post it here soon...



  • I'm here to report that I have this exact same issue on Qt 5.12.3 under OSX using C++ directly.
    I have this in two very different parts of my rather large GUI, both having to do with background threads.

    The clicked event of the pushbutton never arrives to my slot in some cases if there is ongoing background work.
    I do not have this issue under Windows, and never had.

    I agree with wunjo that no matter how badly we might have implemented the background thread, the clicked event of the pushbutton should arrive on the event queue of the main thread eventually, but never actually does. (I'm using QThreadPool and add tasks to it using _pool.start(QRunnable *);)

    I'll check if the clicked event somehow wrongly gets sent to the background thread and report back.



  • My minimal example to reproduce doesn't reproduce the problem.

    In my original software, funnily enough, I receive the pressed() event from the pushbutton, but not the released() event in the error-cases. This also explains, why the button visualization stays in "down" state, it seems not even Qt is getting the up event somehow.


  • Banned

    Okay again -- if you have sub-classed QThread per the old pre-Qt4 documentation then you are doing it wrong and there is no guarantee that it is even working properly or at all. The problem is while there is information out there on how to do it right it is not overly easy to find (or at least not as easy to find as the wrong way to do it) so folks keep perpetuating the wrong way. I have posted 2 examples on how to use QThreads properly so my students have something to reference but I am no longer allowed to invite folks to the more interactive classroom because it somehow degrades this forum even though what it actually does is enhances this forum -- their call their rules -- whatever So I will post one of my examples here

    Example 1 : An example of how to properly create a Threaded application -- this also implements an endless running loop that can receive commands:

    from sys  import exit  as sysExit
    from time import sleep as tmSleep
    
    from PyQt5.QtCore    import QCoreApplication, QObject, QThread, pyqtSignal, pyqtSlot
    from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QVBoxLayout
    from PyQt5.QtWidgets import QPushButton, QLineEdit
    
    class Processor(QObject):
        sigCount = pyqtSignal(int)
    
        def __init__(self):
            QObject.__init__(self)
            self.Connected = True
            self.StreamRdy = False
            self.Count = 0
            self.Delay = 0
    
        def ProcessRunner(self):
            while self.Connected:
                if self.StreamRdy:
                    self.Delay = 0
                    self.Count += 1
                    self.sigCount.emit(self.Count)
                    tmSleep(0.5)
                else:
                    self.Count = 0
                    self.Delay -= 1
                    self.sigCount.emit(self.Delay)
                    tmSleep(0.5)
                QCoreApplication.processEvents()
    
        @pyqtSlot()
        def StartProcess(self):
            self.StreamRdy = True
    
        @pyqtSlot()
        def PauseProcess(self):
            self.StreamRdy = False
    
    class MainWindow(QWidget):
        sigStrtUp = pyqtSignal()
        sigPauser = pyqtSignal()
    
        def __init__(self):
            QWidget.__init__(self)
    
            self.setWindowTitle('Main Window')    
            self.setGeometry(150, 150, 200, 200)
    
            self.btnActivate = QPushButton('Start')
            self.btnActivate.clicked.connect(self.Activated)
    
            self.btnDeActivate = QPushButton('Pause')
            self.btnDeActivate.clicked.connect(self.DeActivate)
    
            self.btnExitApp = QPushButton('Quit')
            self.btnExitApp.clicked.connect(self.ExitApp)
            
            self.lneOutput = QLineEdit()
            
            HBox = QHBoxLayout()
            HBox.addWidget(self.btnActivate)
            HBox.addWidget(self.btnDeActivate)
            HBox.addWidget(self.btnExitApp)
            HBox.addStretch(1)
            
            VBox = QVBoxLayout()
            VBox.addWidget(self.lneOutput)
            VBox.addLayout(HBox)
            
            self.setLayout(VBox)
            
            self.EstablishThread()
    
        def EstablishThread(self):
          # Create the Object from Class
            self.Prcssr = Processor()
          # Assign the Database Signals to Slots
            self.Prcssr.sigCount.connect(self.CountRecieve)
          # Assign Signals to the Database Slots
            self.sigStrtUp.connect(self.Prcssr.StartProcess)
            self.sigPauser.connect(self.Prcssr.PauseProcess)
    
          # Create the Thread
            self.ThredHolder = QThread()
          # Move the Listener to the Thread
            self.Prcssr.moveToThread(self.ThredHolder)
          # Assign the Listener Starting Function to the Thread Call
            self.ThredHolder.started.connect(self.Prcssr.ProcessRunner)
          # Start the Thread which launches Listener.Connect( )
            self.ThredHolder.start()
    
        @pyqtSlot(int)
        def CountRecieve(self, Count):
            self.lneOutput.setText('Count : ' + str(Count))
    
        def Activated(self):
            self.sigStrtUp.emit()
    
        def DeActivate(self):
            self.sigPauser.emit()
     
        def ExitApp(self):
            sysExit()
    
    if __name__ == '__main__':
        MainThred = QApplication([])
    
        MainGui = MainWindow()
        MainGui.show()
    
        sysExit(MainThred.exec_())
    
      # If anyone wants more extensive free help I run an online lab-like classroom-like 
      # message server feel free and drop by you will not be able to post until I clear 
      # you as a student as this prevents spammers so if interested here is the invite
      # https://discord.gg/3D8huKC
    

    Now a secondary problem that many are going to run into is dealing with the GIL because that still exists and means that if you create any endlessly looping process that once it gets activated by the Event Queue it is going to shutdown the Event Queue because it will continuously take up all the Event processing time and as such more Events will not get processed and things will appear to freeze up or become unresponsive. The above example takes this into account and periodically passes control back to the Event Handler so that any queued events can be processed prior to going back to doing more work. However they strongly suggest that you do not do this but they do not explain the why they recommend such because frankly other than firing of a second process via multi-processing which in turn would then have the issue there is no means to allow an Event Handler to not get blocked by a continuously running loop. Now again I am not sure what it does but I have experienced similar commands within other languages that use Event queues that are used to pause a process to allow the Event queue to handle new Events so unless it was implemented incorrectly this should work without any issues and I think the warning is just there for newbies not understanding how Event queues work -- aka starting a process invoking the Event Queue that starts the same process again without checking to see if it is already running -- annoying code issues like that which might get implemented by the inexperienced



  • In my software the issue is not occuring on Qt 5.12.5 and also not on 5.13.1. So the easiest solution to the problem would probably be to just upgrade Qt.


  • Banned

    Not necessarily just because it is working (or seems to) does not mean you do not have hidden issues -- the sub-classed QThread was basically deprecated in a Qt4 version however the documentation for it is still pre-Qt4 and it was done because there were issues with sub-classing the QThread. So you do have a choice do it the right way or do it the wrong way but if you do it the wrong way just know you may have issues that are going to be very difficult to debug when they do eventually crop up. And do not believe me -- go and do your own research on it that is how I found out about the issues.



  • @Denni-0 I know all of this, and I have never subclassed QThread in my software. As stated above I am using QThreadPool and QRunnable.
    Nevertheless, I had the exact same problem as the original poster on OSX (and only there), and it went away with newer Qt versions.

    Either way, thanks for your effort and your answers!


Log in to reply