当Qt应用程序在单独的线程中运行时,无法为GIF设置动画

时间:2019-12-31 06:30:50

标签: python multithreading architecture pyqt pyqt5

我想使用PyQt显示选定的静态图像或animated gif using QLabel小部件。对于我的特定用例,我仅使用Qt显示图像,而我的Python代码的其余部分不需要Qt。我想要我的Qt app to run in the background (separate thread)。这是因为在运行app.exec_()之后,Qt应用程序进入了连续事件循环,从而阻塞了Python代码中的其余逻辑。

使用这两个类,我能够实现一个可行的解决方案(下面的代码)。使用此方法,我可以随时更新静态或动画图像,并且代码可以按预期工作。但是,我不想合并两个类,而是想合并为一个类(下面的代码)。一种方法的问题是显示的gif没有动画。每当我更新静态图像时,代码就会按预期工作,但是,当使用gif图像时,显示的gif不会被动画化。为什么一类方法不能为显示的gif设置动画?我为什么做错了?

工作:两班制

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
import time
import threading
from PyQt5.QtCore import QObject, pyqtSignal
import os


def main():

    print('Step 1')
    print('     Some logic here without QT')

    print('Step 2')
    print('     Launch QT app to run in background')

    myapp = myImageDisplayApp()

    print('Step 3')
    print('     Continue some logic while QT running in background')
    time.sleep(2)

    print('Step 4')
    print('     Update the displayed image in the QT app running in background')

    myapp.emit_image_update('qt_test_static.png')
    time.sleep(1)
    myapp.emit_image_update('qt_test_movie.gif')
    time.sleep(1)   

    print('Step 5')
    print('     Continue logic while QT running in background')
    time.sleep(3)

class myImageDisplayApp (QObject):

    # Define the custom signal
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#the-pyqtslot-decorator
    signal_update_image = pyqtSignal(str)

    def __init__ (self):

        super().__init__()

        self.app = QApplication(sys.argv)

        # Setup the seperate thread 
        # https://stackoverflow.com/a/37694109/4988010
        self.thread = threading.Thread(target=self.run_app_widget_in_background) 
        self.thread.daemon = True
        self.thread.start()

    def run_app_widget_in_background(self):

        self.app = QApplication(sys.argv)
        self.my_bg_qt_app = qtAppWidget(qt_patterndisplay_threader=self)
        self.app.exec_()


    def emit_image_update(self, pattern_file=None):
        print('signal_image_update')
        self.pattern_file = pattern_file
        self.signal_update_image.emit(self.pattern_file)


class qtAppWidget (QLabel):

    def __init__ (self, qt_patterndisplay_threader):

        super().__init__()

        # Connect the singal to slot
        qt_patterndisplay_threader.signal_update_image.connect(self.updateImage)

        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen 
        self.screens_available = self.app.screens()
        self.screen = self.screens_available[0]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        self.setupGUI()

    def setupGUI(self):

        # Define a constant color images
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor('blue'))

        scale_window = 2

        # Create QLabel object
        self.app_widget = QLabel()
        self.app_widget.setGeometry(0, 0, self.screen_width/scale_window , self.screen_height/scale_window)         # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle('myImageDisplayApp')
        self.app_widget.setAlignment(Qt.AlignLeft | Qt.AlignTop) #https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum                         
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()


    def updateImage(self, pattern_file=None):
        print('\nPattern 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.pattern_file = pattern_file
        self.pixmap = QPixmap(self.pattern_file)

        if file_extension != '.gif':
            # File is a static image
            print('Image is a static')
            self.app_widget.setPixmap(self.pixmap)
        else:
            # File is a movie
            print('Image is movie')

            self.pixmap_mv = QMovie(self.pattern_file)
            print('Check to see if gif valid: ', self.pixmap_mv.isValid())
            self.app_widget.setMovie(self.pixmap_mv)
            self.pixmap_mv.start()


if __name__ == "__main__":

    main()

不起作用:一种课堂方法

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
import time
import threading
from PyQt5.QtCore import QObject, pyqtSignal
import os


def main():

    print('Step 1')
    print('     Some logic here without QT')

    print('Step 2')
    print('     Launch QT app to run in background')

    myapp = myImageDisplayApp()

    print('Step 3')
    print('     Continue some logic while QT running in background')
    time.sleep(2)

    print('Step 4')
    print('     Update the displayed image in the QT app running in background')

    myapp.emit_image_update('qt_test_static.png')
    time.sleep(1)
    myapp.emit_image_update('qt_test_movie.gif')
    time.sleep(1)   

    print('Step 5')
    print('     Continue logic while QT running in background')
    time.sleep(3)

class myImageDisplayApp (QObject):

    # Define the custom signal
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#the-pyqtslot-decorator
    signal_update_image = pyqtSignal(str)

    def __init__ (self):

        super().__init__()

        # Connect the singal to slot
        self.signal_update_image.connect(self.updateImage)

        self.app = QApplication(sys.argv)

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen 
        self.screens_available = self.app.screens()
        self.screen = self.screens_available[0]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Setup the seperate thread 
        # https://stackoverflow.com/a/37694109/4988010
        self.thread = threading.Thread(target=self.run_app_widget_in_background) 
        self.thread.daemon = True
        self.thread.start()

    def run_app_widget_in_background(self):

        self.app = QApplication(sys.argv)
        # self.my_bg_qt_app = qtAppWidget(qt_patterndisplay_threader=self)
        self.setupGUI()
        self.app.exec_()

    def setupGUI(self):

        # Define a constant color images
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor('red'))

        scale_window = 2

        # Create QLabel object
        self.app_widget = QLabel()
        self.app_widget.setGeometry(0, 0, self.screen_width/scale_window , self.screen_height/scale_window)         # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle('myImageDisplayApp')
        self.app_widget.setAlignment(Qt.AlignLeft | Qt.AlignTop) #https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum                         
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

    def emit_image_update(self, pattern_file=None):
        print('signal_image_update')
        self.pattern_file = pattern_file
        self.signal_update_image.emit(self.pattern_file)

    def updateImage(self, pattern_file=None):
        print('\nPattern 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.pattern_file = pattern_file
        self.pixmap = QPixmap(self.pattern_file)

        if file_extension != '.gif':
            # File is a static image
            print('Image is a static')
            self.app_widget.setPixmap(self.pixmap)
        else:
            # File is a movie
            print('Image is movie')

            self.pixmap_mv = QMovie(self.pattern_file)
            print('Check to see if gif valid: ', self.pixmap_mv.isValid())
            self.app_widget.setMovie(self.pixmap_mv)
            self.pixmap_mv.start()


if __name__ == "__main__":

    main()

1 个答案:

答案 0 :(得分:0)

my previous answer相似,您必须在另一个线程中执行繁重的任务,并使用信号通知更改:

import sys
import time


from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QThread, QTimer
from PyQt5.QtGui import QColor, QPixmap, QMovie
from PyQt5.QtWidgets import QApplication, QLabel


class TaskManager(QObject):
    task3Finished = pyqtSignal()
    task4Finished = pyqtSignal()
    task5Finished = pyqtSignal()

    @pyqtSlot()
    def task(self):
        print("Step 3")
        time.sleep(2)
        self.task3Finished.emit()
        print("Step 4")
        time.sleep(1)
        self.task4Finished.emit()
        print("Step 5")
        time.sleep(1)
        self.task5Finished.emit()
        time.sleep(3)
        QApplication.quit()


class qtAppWidget(QLabel):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.app = QApplication.instance()
        self.screens_available = self.app.screens()
        self.screen = self.screens_available[0]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        self.setupGUI()

    def setupGUI(self):
        scale_window = 2

        self.setGeometry(
            0, 0, self.screen_width / scale_window, self.screen_height / scale_window
        )
        self.setWindowTitle("myImageDisplayApp")
        self.setAlignment(Qt.AlignLeft | Qt.AlignTop)
        self.show()

    @pyqtSlot()
    def on_task3_finished(self):
        pixmap = QPixmap("qt_test_static.png")
        self.setPixmap(pixmap)

    @pyqtSlot()
    def on_task4_finished(self):
        movie = QMovie("qt_test_movie.gif", parent=self)
        self.setMovie(movie)
        movie.start()


def main(args):
    print("Step 1")
    print("     Some logic here without QT")

    print("Step 2")
    print("     Launch QT app to run")
    app = QApplication(args)
    myapp = qtAppWidget()

    thread = QThread()
    thread.start()

    manager = TaskManager()
    # move the QObject to the other thread
    manager.moveToThread(thread)

    manager.task3Finished.connect(myapp.on_task3_finished)
    manager.task4Finished.connect(myapp.on_task4_finished)

    # start task
    QTimer.singleShot(0, manager.task)

    ret = app.exec_()

    thread.quit()
    thread.wait()

    del thread, app

    return ret


if __name__ == "__main__":

    sys.exit(main(sys.argv))