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 1:
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.