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

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


  • Lifetime Qt Champion

    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.


  • Lifetime Qt Champion

    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.


Log in to reply