I want to use PyQt to display a selected static image or an animated gif using QLabel widget. For my specific use case, I am only using Qt to display the image while rest of my Python code does NOT require Qt. I want my Qt app to run in the background (separate thread). This is because after running app.exec_()
, the Qt app goes into a continuous event loop thus blocking the rest of the logic in my Python code.
I was able to achieve a working solution (code below) using the two classes. Using this method, I am able to update static or animated images whenever I would like and the code works as expected. However, instead of having two classes, I would like to consolidate into one class (code also below). The problem with one class approach is that the displayed gif is NOT animated. The code works as expected whenever I update static images, however, when using gif images, the displayed gif is NOT animated. Why is does the one class approach not animate the displayed gif? Why am I doing wrong?
Working: Two class approach
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()
NOT Working: One class approach
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()