How to display a loading animated gif while a code is executing in backend of my Python Qt5 UI?
-
Environment:
Python 3.7
Qt5
Windows 10
Problem:
When I execute my code, it shows immediately the UI, then it supposes to make some other preparing stuff and display a loading gif while these initialization tasks are running. But it does work. Instead of showing the gif, the UI is blocked(froze) waiting for my preparing script to finish its job.
My script has a button to Run my main script "StartMyApp" and show an animated gif while MyApp is running without freezing my UI. I use multithread for this purpose. It works perfectly. I used this tutorial : https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/
So I thought by cloning the same logic, I could display another loading gif at the init of my UI but it didn't work. I missed something. I don't understand because the "Run" button works perfectly by showing the gif and running the main code without freezing the UI whereas my "preparing" code is not showing the gif and freezing my UI until it finishes.
Does anyone understand the source of this issue?
from PyQt5 import QtWidgets, uic, QtGui from PyQt5.QtCore import * from PyQt5.QtGui import QMovie import traceback, sys class WorkerSignals(QObject): ''' Defines the signals available from a running worker thread. Supported signals are: finished No data error `tuple` (exctype, value, traceback.format_exc() ) result `object` data returned from processing, anything progress `int` indicating % progress ''' finished = pyqtSignal () error = pyqtSignal (tuple) result = pyqtSignal (object) progress = pyqtSignal (int) class Worker (QRunnable): ''' Worker thread Inherits from QRunnable to handler worker thread setup, signals and wrap-up. :param callback: The function callback to run on this worker thread. Supplied args and kwargs will be passed through to the runner. :type callback: function :param args: Arguments to pass to the callback function :param kwargs: Keywords to pass to the callback function ''' def __init__(self, fn, *args, **kwargs): super (Worker, self).__init__ () # Store constructor arguments (re-used for processing) self.fn = fn self.args = args self.kwargs = kwargs self.signals = WorkerSignals () # Add the callback to our kwargs self.kwargs['progress_callback'] = self.signals.progress @pyqtSlot () def run(self): ''' Initialise the runner function with passed args, kwargs. ''' # Retrieve args/kwargs here; and fire processing using them try: result = self.fn (*self.args, **self.kwargs) except: traceback.print_exc () exctype, value = sys.exc_info ()[:2] self.signals.error.emit((exctype, value, traceback.format_exc ())) else: self.signals.result.emit (result) # Return the result of the processing finally: self.signals.finished.emit () # Done class Ui(QtWidgets.QMainWindow): def __init__(self): super(Ui, self).__init__() uic.loadUi('Ui/MyAppUI.Ui', self) # === We display the UI ========== self.show() # === THis will handle the MULTITHREAD PART =================== self.threadpool = QThreadPool() print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) self.StartPreparingMyApp() #<======== This method doesn't work!!!! # === Associate methods to the buttons of the UI ============== self.button_Report.clicked.connect (self.ButtonStartMyAppReport) self.button_Run.clicked.connect (self.ButtonStartMyApp) def StartMyAppReport(self, progress_callback): #do some stuff def StartMyApp(self, progress_callback): # do some stuff def ButtonStartMyApp(self): #<=== This method works perfectly by showing the loading gif. # Pass the function to execute # === We need to block the Button Run and change its color self.button_Run.setEnabled (False) self.button_Run.setText ('Running...') self.button_Run.setStyleSheet ("background-color: #ffcc00;") self.label_logo.setHidden (True) self.label_running.setHidden (False) # === Play animated gif ================ self.gif = QMovie ('ui/animated_gif_logo_UI_.gif') self.label_running.setMovie (self.gif) self.gif.start () self.EditTextFieldUi (self.label_HeaderMsg1, '#ff8a00', "MyApp is running the tasks... You can press the button 'Report' to see what MyApp has done.") self.EditTextFieldUi (self.label_HeaderMsg2, '#ff8a00', "Press 'button 'Quit' to stop and turn off MyApp.") worker = Worker (self.StartMyApp) # Any other args, kwargs are passed to the run function worker.signals.result.connect (self.print_output) worker.signals.finished.connect (self.thread_complete) worker.signals.progress.connect (self.progress_fn) # Execute self.threadpool.start (worker) def PreparingMyApp(self, progress_callback): #do some stuff return "Done" def ButtonStartMyAppReport(self): # Pass the function to execute worker = Worker (self.StartMyAppReport) # Any other args, kwargs are passed to the run function worker.signals.result.connect (self.print_output) worker.signals.finished.connect (self.thread_complete) worker.signals.progress.connect (self.progress_fn) # Execute self.threadpool.start(worker) def StartPreparingMyApp(self): #<=== This method doesn't work !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # === Play animated gif ================ self.label_loading.setHidden (False) self.gif_loading = QMovie ('ui/loading.gif') self.label_loading.setMovie (self.gif_loading) self.gif_loading.start () # Pass the function to execute worker = Worker (self.PreparingMyApp) # Any other args, kwargs are passed to the run function worker.signals.result.connect (self.print_output) worker.signals.finished.connect (self.thread_complete) worker.signals.progress.connect (self.progress_fn) # Execute self.threadpool.start (worker) self.gif_loading.stop () self.label_loading.setHidden (True) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) window = Ui() app.exec_()
Edit:
I added the xml source of MyAppUI.ui made with Qt Designer in order to reproduce my example:
https://drive.google.com/file/d/1U9x0NmZ7GP6plzvRb6YgwIqaFHCz1PMc/view?usp=sharing
-
Hi and welcome to devnet,
@CentreFF said in How to display a loading animated gif while a code is executing in backend of my Python Qt5 UI?:
fn)
# Execute self.threadpool.start (worker) self.gif_loading.stop () self.label_loading.setHidden (True)
You are stopping and hiding the gif directly before then end of the constructor.
-
@SGaist Thank you so much for trying to help me.
I move the location of these 2 lines:self.gif_loading.stop () self.label_loading.setHidden (True)
It didn't fix the issue.
I even removed them. It didn't fix the issue.
And I still have the problem of my UI froze while my preparing script is running. I don't understand because it runs like a new thread. And this technic worked for my "Run" button.
-
I do not know how fast your method is, but you usually do not start doing animation within the constructor of a widget. Until the constructor is done and the show event is called, there's no physical presence of the widget.
AFAIK it is best to construct all your GUI pieces and once done trigger your long operation that should also show an animation. If you really want to do that from the constructor, use a single shot timer with a 0 delay that will then trigger as soon as possible once the event loop is running to call your method.
-
This post is deleted!