python-3.xloopspyqt5qstatusbar

PyQt5 - repeatedly update status-bar from a list of strings without blocking the gui


I can use QStatusBar to display a message by feeding it a single string, e.g.:

self.statusBar().showMessage("My message here, also show it for 1 sec", 1000)

In my event-loop, the above message will be shown on the status-bar for exactly 1000 ms. But say that instead of a string "My message here", I have a list of strings I want to display one at the time. I can do that via a loop by giving it a delay via time.sleep(1) - but that would unfortunately block the gui until the loop is over, and I don't want that. Can I separate the processes of main event loop and status bar updating, and if so, how?

The example code below has a button, which when pressed, generates three different messages stored in a list. They are then shown in the status-bar, one at a time, and the button cannot be pressed until the loop ends. The behaviour that I want is that the button is clickable (gui isn't blocked), and if it's clicked while the previous messages are showing, the showing is interrupted and new messages are shown.

---Example code below---

import sys
import time
from PyQt5 import QtWidgets

class Window(QtWidgets.QMainWindow):
    """Main Window."""
    MSG_ID = 1
    def __init__(self, parent=None):
        """Initializer."""
        super().__init__(parent)
        #stuff
        self.messages = []
        self.setWindowTitle('Main Window')
        self.setStatusBar(QtWidgets.QStatusBar())
        self.statusBar().showMessage("My message first time")
        self.button = QtWidgets.QPushButton("Test",self)
        self.button.clicked.connect(self.handleButton)
        
    def handleButton(self):
        self.messages = [f"message_num {msg_id}" for msg_id in range(Window.MSG_ID,Window.MSG_ID+3)]
        Window.MSG_ID+=3
        self.updateStatus()
        print(self.messages)
        
    def updateStatus(self):
        self.statusBar().clearMessage()
        for item in self.messages:
            self.statusBar().showMessage(item,1000)
            time.sleep(1.2)
           
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())

The above code creates a list of messages


Solution

  • There is no need to use timers or sleep for this, because the status-bar sends a messageChanged signal every time a temporary message changes (including when it's removed). This can be used to create a simple call-back loop that does what you want:

    class Window(QtWidgets.QMainWindow):
        ...
        def __init__(self, parent=None):
            ...
            self.statusBar().messageChanged.connect(self.updateStatus)
    
        def handleButton(self):
            self.messages = [f"message_num {msg_id}" for msg_id in range(Window.MSG_ID,Window.MSG_ID+3)]
            Window.MSG_ID+=3
            self.updateStatus()
    
        def updateStatus(self, message=None):
            print('update-status:', (message, self.messages))
            if not message and self.messages:
                self.statusBar().showMessage(self.messages.pop(0), 1000)
    

    This does not block the gui and allows the message sequence to be re-started at any time by clicking the button.