I am programming a desktop app for a small local hotel using python.
As is so often the case, I also have a heavy-duty process/method in the background. During that time a progressbar should pop up and fill itself. The progress bar should block any further input and serves as user information: "Data/tasks are being processed"
My issue:
I am unable to achieve both: running the heavy_load_method
in the background and showing&filling the progessbar at the same time.
I assume that I implement my progressbar incorrectly for multithreading/multiprocessing.
Since my failure is possibly related to my current implementation of the MVP, some code snippets:
# main.py
# importing all modules (presenter, model and all views/formulars (including the progressbar view/form)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainwindow = Mainwindow()
progressbar = ProgressBarPopup()
model = Model()
...
presenter = Presenter(model, mainwindow, progressbar, ...)
sys.exit(app.exec_())
# presenter.py
# imports modules required for data manipulation
class Presenter:
def __init__(self, model, mainwindow, progressbar, ...)
self.model = model
self.mainwindow = mainwindow
self.progressbar = progressbar
...
def heavy_load_method(self):
# does operations like:
# - getting (minor) amounts of data from form/view (related view/form method calls)
# - processes already cached `self.data` (via Presenter method calls)
# - calling and calculating data from the model/database
# - calling functions from imported moduls like "myfilemanagement.py"
# - ...
# MyProgressbar.py
from PyQt5.QtWidgets import QProgressBar, QDialog, QVBoxLayout, QLabel
from PyQt5.QtCore import Qt, QTimer
class ProgressBarPopup(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Progressbar")
self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
self.setFixedSize(300, 100)
self.setWindowModality(Qt.ApplicationModal)
layout = QVBoxLayout()
self.label = QLabel("Processing...", self)
self.label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.label)
self.progressbar = QProgressBar(self)
self.progressbar.setRange(0, 99)
layout.addWidget(self.progressbar)
self.setLayout(layout)
# Progressbar visual update timer
self.step = 0
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_progress)
# Progressbar exit/close timer
self.close_timer = QTimer(self)
self.close_timer.timeout.connect(self.close_progressbar)
def close_progressbar(self):
self.close_timer.stop()
self.close()
def update_progress(self):
self.step += 1
if self.step <= 98:
self.progressbar.setValue(self.step)
if self.step > 98:
self.timer.stop()
self.label.setText("This takes longer then expected.")
def setup_progressbar_and_show(self):
self.step = 0
self.progressbar.setValue(self.step)
self.timer.start(70)
self.close_timer.start(10000)
self.label.setText("Processing...")
self.show()
I have tried to multithread
or multiprocess
the method heavy_load_method
. My previous attempts failed because I could not multithread
/multiprocess
the progressbar.
I'm a bit embarrassed that it took me so long but:
Even if you intuitively believe that you have to QThread the progressbar (as this is a pyqt5 object), it is actually the other way round. heavy_duty_method
must be detached from the presenter and placed in a separate QThread sub-class. An object is created from this during runtime, which can be integrated and run as below.
# presenter.py
# imports modules required for data manipulation and 'Create_Heavy_Duty_Thread' class
class Presenter:
def __init__(self, model, mainwindow, progressbar, ...)
self.model = model
self.mainwindow = mainwindow
self.progressbar = progressbar
...
def prepare_for_load(self)
if conditions_are_met:
self.worker = Create_Heavy_Duty_Thread(self.model, invoice_dict, bookings, isClassic)
self.worker.started.connect(self.progressbar.setup_progressbar_and_show)
self.worker.finished.connect(self.progressbar.close_progressbar)
self.worker.start()
class Create_Heavy_Duty_Thread(QThread):
def __init__(self, model, invoice_dict, bookings, isClassic):
super().__init__()
self.model = model
self.invoice_dict = invoice_dict
self.bookings = bookings
self.isClassic = isClassic
... # all methods needed to perform heavy_duty_method
def heavy_duty_method(self):
# heavy duty is done here
...
def run(self):
self.heavy_duty_method()