admin5940 | 2020-08-13 20:34:18 UTC | #1
Environment:
Python 3.7
Qt5
Windows 10
Problem:
When I execute my code, it shows immediately the UI, then it supposes to make some other preparing stuff and display a loading gif while these initialization tasks are running. But it does work. Instead of showing the gif, the UI is blocked(froze) waiting for my preparing script to finish its job.
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.
My script has a button to Run my main script "StartMyApp" and show an animated gif while MyApp is running without freezing my UI. I use multithread for this purpose. It works perfectly. I used this tutorial : https://www.pythonguis.com/courses/concurrent-execution/multithreading-pyqt-applications-qthreadpool/
So I thought by cloning the same logic, I could display another loading gif at the init of my UI but it didn't work. I missed something. I don't understand because the "Run" button works perfectly by showing the gif and running the main code without freezing the UI whereas my "preparing" code is not showing the gif and freezing my UI until it finishes.
Does anyone understand the source of this issue?
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().__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_()
Edit:
I added the xml source of MyAppUI.ui made with Qt Designer in order to reproduce my example:
https://drive.google.com/file/d/1U9x0NmZ7GP6plzvRb6YgwIqaFHCz1PMc/view?usp=sharing
Eolinwen | 2020-08-20 16:52:25 UTC | #2
Hi,
I have not tested your application but just looking your code, I note something who seems to be wrong (perhaps an typo error ?) for me.
class Ui(QtWidgets.QMainWindow):
def __init__(self):
super(Ui, self).__init__()
uic.loadUi('Ui/MyAppUI.Ui', self)
# === We display the UI ==========
self.show()
it would not be ('Ui/MyAppUI.ui', self) by chance ?
Eolinwen | 2020-08-20 17:02:13 UTC | #3
You should take a look at this tutorial (in French) about throbbers. http://python.jpvweb.com/python/mesrecettespython/doku.php?id=pyqt5_throbber
Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!