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

Posted

技术标签:

【中文标题】如何在我的 Python Qt5 UI 后端执行代码时显示加载动画 gif?【英文标题】:How to display a loading animated gif while a code is executing in backend of my Python Qt5 UI? 【发布时间】:2020-08-13 10:26:31 【问题描述】:

环境:

Python 3.7

Qt5

Windows 10

问题:

当我执行我的代码时,它会立即显示 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

【问题讨论】:

您忘记发布模块MyAppUI.Ui 感谢您的帮助。你什么意思?我在最底部有这一行:window = Ui() 将您发布的内容复制到新目录并尝试运行该应用程序。您的示例不可重现。 对不起,我不明白。当我运行此代码时,它会加载 UI 并运行准备脚本。问题是它不显示加载 gif,并且在准备脚本运行时 UI 被冻结。请您说“您发布的内容”更准确吗?你是说所有的代码吗?您是说 MyAppUI 吗? MyAppUI.Ui 是用 Qt Designer 完成的。 【参考方案1】:

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

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

【讨论】:

以上是关于如何在我的 Python Qt5 UI 后端执行代码时显示加载动画 gif?的主要内容,如果未能解决你的问题,请参考以下文章

Qt5 / PyQt5 : 带有 QML 前端和 Python 后端的自定义 QML 组件

未找到 Matplotlib Qt5Agg 后端

如何在我的代码中使用 createWindow [Qt5.8]

Qt5 - Windows:Windows 找不到可执行文件

ui_* 文件不是 QT5.5 生成的

如何在我的代码中查明触发 QObject::connect 的调用的位置:无法在 Qt5 中对类型的参数进行排队?