如何在我的 Python Qt5 UI 后端执行代码时显示加载动画 gif?

How to display a loading animated gif while a code is executing in backend of my Python Qt5 UI?

环境:

Python 3.7

Qt5

Windows10

问题:

当我执行我的代码时,它立即显示 UI,然后它应该在这些初始化任务 运行 时进行一些其他准备工作并显示加载 gif。但它确实有效。 UI 没有显示 gif,而是被阻塞(冻结),等待我的准备脚本完成它的工作。

我的脚本有一个 运行 我的主脚本“StartMyApp”的按钮,并在 MyApp 运行 时显示动画 gif 而不会冻结我的 UI。为此,我使用多线程。它工作得很好。我使用了本教程:https://www.learnpyqt.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/

所以我想通过克隆相同的逻辑,我可以在 UI 的 init 显示另一个加载 gif,但它没有用。我错过了什么。我不明白,因为“运行”按钮通过显示 gif 和 运行 主代码而不冻结 UI 来完美工作,而我的“准备”代码没有显示 gif 和冻结我的 UI 直到完成。

有人知道这个问题的根源吗?

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_()

    

编辑:

我添加了使用 Qt Designer 制作的 MyAppUI.ui 的 xml 源代码以重现我的示例:

https://drive.google.com/file/d/1U9x0NmZ7GP6plzvRb6YgwIqaFHCz1PMc/view?usp=sharing

一切都适合你。 请注意我转了

self.gif_loading.stop()             # <---- +++
self.label_loading.setHidden(True)  # <---- +++
    

thread_complete方法中添加QtCore.QThread.msleep (5000) 进入run方法观察self.gif_loading

的过程
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
from PyQt5.Qt import *


class WorkerSignals(QObject):
    finished = pyqtSignal ()
    error = pyqtSignal (tuple)
    result = pyqtSignal (object)
    progress = pyqtSignal (int)


class Worker (QRunnable):
    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)
            
            QtCore.QThread.msleep(5000)                        #   +++ !!!!!!!!!!!!!!!!!!!!!!
            
        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)                                # !!!
        uic.loadUi('my_app_ui.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  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
        pass                                                             # +++

    def StartMyApp(self, progress_callback):
        # do some stuff
        pass                                                             # +++

    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("D:/_Qt/__Qt/wait.gif")          # ('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): 
        print("!!! <=== This method work !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
        # === Play animated gif ================
        self.label_loading.setHidden (False)
        self.gif_loading = QMovie("D:/_Qt/__Qt/wait.gif")        # ('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)                     # ---
        
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv    
    def print_output(self, obj):
        print(f'def print_output(self, obj): {obj}')    
        
    def thread_complete(self, val='finished'):
        print(f'def thread_complete(self, obj): {val}') 
        self.gif_loading.stop ()                                  # <---- +++
        self.label_loading.setHidden (True)                       # <---- +++        
        
    def progress_fn(self, val):
        print(f'def progress_fn(self, obj): {val}')  
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        

        
if __name__ == '__main__':    
    app = QtWidgets.QApplication(sys.argv)
    window = Ui()
    app.exec_()