python - 从另一个线程更新QLabel的内容时,GIF不会进行动画处理

标签 python multithreading pyqt pyqt5

我创建了一个简单的应用程序,该应用程序使用PyQt5在QLabel中显示图像。图像可以是静态的(例如:png,jpeg,bmp),也可以是gif。
下面的示例代码的说明如下:

  • 类ImageDisplayer()负责创建一个QLabel,其中包含要显示的所需图像。 update_image()方法允许将QLabel中显示的图像更新为所需的新图像。 QLabel窗口显示在所需的屏幕上(使用多台显示器时)。
  • main()方法是PyQt5应用程序的简单演示,该应用程序使用ImageDisplayer类在QLabel上显示所需的图像。在现实世界的最终用例中,该主​​Qt应用将具有其他复杂的小部件/逻辑以与用户进行交互(例如:询问用户要显示哪个图像),并且从ImageDisplayer显示的QLabel将始终以全屏方式显示所需的图像。在辅助监视器上。但是,为简单起见,我没有在下面的示例代码中显示此内容。
  • test_image_sequence()方法是一个简单的函数,它循环遍历各种测试图像以调试/排除ImageDisplayer()类的开发问题。

  • 问题: ImageDisplayer类按预期工作,但是,当我尝试从单独的线程调用update_image()方法时,gif图像没有动画。例如,当我使用QThreadPool在单独的线程中运行test_image_sequence()方法时,静态图像将按预期显示,但gif不会显示动画。
    import os, sys, time, pathlib
    from PyQt5 import QtWidgets
    from PyQt5.QtCore import Qt, QRunnable, QThreadPool
    from PyQt5.QtGui import QColor, QPixmap, QMovie
    from PyQt5.QtWidgets import QApplication, QLabel, QWidget
    
    CURRENT_PATH = str(pathlib.Path(__file__).parent.absolute())
    
    def main():
    
        app = QtWidgets.QApplication(sys.argv) 
    
        my_image_window = ImageDisplayer(monitor_num=0,)
    
        # Method 1: When not using threads, the gif animates as expected 
        # my_image_window.update_image(CURRENT_PATH + r'\test_images\gif_image_2.gif')
    
        # Method 2: When using threads, gif does NOT animate 
        thread = QThreadPool.globalInstance()
        worker = Worker(test_image_sequence, my_image_window)
        thread.start(worker)
    
        app.exec_()
    
    
    def test_image_sequence(widget):
        print('Will start testing seq. of images')
        time.sleep(1)
    
        images = []
        images.append(CURRENT_PATH + r'\test_images\static_image_1.png')
        images.append(CURRENT_PATH + r'\test_images\static_image_2.png')
        images.append(CURRENT_PATH + r'\test_images\gif_image_1.gif')
        images.append(CURRENT_PATH + r'\test_images\gif_image_2.gif')
    
        for i in images:
            print('Updating image to:', i)
            widget.update_image(pattern_file=i)
            time.sleep(3)
    
    class ImageDisplayer():
        
        def __init__(self, monitor_num=0,):
    
            # Get instance of the current QApplication 
            self.app = QtWidgets.QApplication.instance() #https://stackoverflow.com/a/53387775/4988010 
    
            # Gather info on avaliable monitor and select the desired one 
            self.screen = self.app.screens()[monitor_num]
            self.screen_width = self.screen.size().width()
            self.screen_height = self.screen.size().height()
    
            # Init class attributes
            self.pattern_file = None                        # Set a default pattern if given during init 
            self.pixmap = None                              # Static image content
            self.pixmap_mv = None                           # Movie content
    
            self.scale_window = 2                           # For debugging: If not full screen the window will be scaled by half of screen size 
    
            # Define a constant color images when no image displayed 
            self.pixmap_blank = QPixmap(self.screen_width, self.screen_height)
            self.pixmap_blank.fill(QColor('green'))
            self.pixmap = self.pixmap_blank                                     # Default during init
    
            self.app_widget = None          # QLabel widget object
            self.setupGUI()                 # Setup and show the widget 
    
        def setupGUI(self):
    
            print('Setting up the QLabel')
    
            # Create QLabel object
            self.app_widget = QLabel()
            self.app_widget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
            self.app_widget.setStyleSheet('QLabel { background-color: green;}')
            self.app_widget.setCursor(Qt.BlankCursor)                       # A blank/invisible cursor, typically used when the cursor shape needs to be hidden.
    
            # Scale the widget to the size of the screen
            self.app_widget.setGeometry(0, 0, self.screen_width/self.scale_window , self.screen_height/self.scale_window)           # Set the size of Qlabel to size of the screen
    
            # Set a default pattern during init
            self.app_widget.setPixmap(self.pixmap)
            self.app_widget.show()
    
            # Move window to topleft corner of the selected screen 
            self.app_widget.windowHandle().setScreen(self.screen)
            self.app_widget.move(self.screen.geometry().topLeft())
    
        def update_image(self, pattern_file):
            self.pattern_file = pattern_file
    
            print('Pattern file: ', pattern_file)
            filename, file_extension = os.path.splitext(pattern_file)       # Get filename and extension https://stackoverflow.com/a/541394/4988010
            
            self.app_widget.clear()                     # Clear all existing content of the QLabel
            self.pixmap = QPixmap(self.pattern_file)
    
            if (file_extension == '.png') or (file_extension == '.jpg') or (file_extension == '.jpeg') or (file_extension == '.bmp'):
                # File is a static image
                # https://doc.qt.io/qt-5/qpixmap.html
                print('Image is a static')
                self.app_widget.setPixmap(self.pixmap)
            elif (file_extension == '.gif'):
                # File is a movie
                print('Image is movie')
                self.pixmap_mv = QMovie(self.pattern_file)
    
                # Connect the "finished() signal to movie_finished() slot"
                self.pixmap_mv.finished.connect(self.movie_finished)
    
                # Debugging text
                print('Movie is valid: ', self.pixmap_mv.isValid())
                print('loopCount: ', self.pixmap_mv.loopCount())
                print('frameCount: ', self.pixmap_mv.frameCount())
                print('Default speed: ', self.pixmap_mv.speed())
    
                self.app_widget.setMovie(self.pixmap_mv)
                self.pixmap_mv.start()
                
        def movie_finished(self):
    
            print('Movie finished')
            # After movie is finished, show blank screen            
            self.app_widget.setPixmap(self.pixmap_blank)
    
    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
    
        def run(self):
            self.fn(*self.args, **self.kwargs)
    
    if __name__ == "__main__":
        main()
    

    最佳答案

    基本的Qt规则:您不应直接从另一个线程修改GUI,因为它不是线程安全的,而应使用信号。例如,在这种情况下,您可以创建一个作为代理的类:

    # ...
    class Signaller(QObject):
        imageChanged = pyqtSignal(str)
    
        def update_image(self, pattern_file):
            self.imageChanged.emit(pattern_file)
    
    
    def main():
    
        app = QtWidgets.QApplication(sys.argv)
    
        my_image_window = ImageDisplayer(monitor_num=0,)
    
        signaller = Signaller()
        signaller.imageChanged.connect(my_image_window.update_image)
    
        thread = QThreadPool.globalInstance()
        worker = Worker(test_image_sequence, signaller)
        thread.start(worker)
    
        app.exec_()
    # ...
    

    关于python - 从另一个线程更新QLabel的内容时,GIF不会进行动画处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62976727/

    相关文章:

    python - 发送终止后Qthread不会停止

    python - 在 Python 中显示 SQLite 数据库中的表

    python - 在矩阵中找到最接近/相似的值(向量)

    python - 在 python3 和示例代码中使用 boto3 访问 Amazon EC2 信息时出错

    c# - C#-BlockingCollection : Can we have 2 threads Take() the same value?

    multithreading - 为什么 C++11acquire_release 栅栏不足以用于 Dekker 同步?

    python - 从存储为字符串的文件路径中获取文件名

    r - 具有 0 值的 R 矩阵单元格需要多少空间?以及如何处理大矩阵计算

    python - 如何让pyqt5开发的应用程序在ubuntu上支持中文输入

    python - 如何在 QLineedit 完成器中删除光标