I'm writing a set of small python applications, that are aimed to be run via CLI. Some of the functions should be bundled together in a PyQT5 GUI to be easier usable. Now, I have one function inside my package, that tends to run quite long, so I would like to display a progress bar. However, the function itself needs to be able to be run without QT5 present. I'm looking for a way to have the progress from my long running imported function to be shown in the QT GUI without making QT a dependency of my package.
Simple example:
Somewhere inside my package:
import time
percent = 0
def long_running_function(percent):
while percent < 100:
percent+=1
#do something here to update percentage in QT
time.sleep(1) #just to indicate, that the function might be a blocking call
My simple GUI:
from my_package import long_running_function
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in running my external function and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
long_running_function(0)
self.progress.setValue(value) #probably somewhere
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
I know, that I could solve this, by emitting a pyqtsignal
in each iteration of the loop inside long_running_function
, but that would make QT a dependency of my package, which I would like to circumvent.
One possible solution is to create a QObject by implementing the __add__ and __lt__ operators to be the percent of the function:
from functools import partial
from PyQt5.QtCore import QObject, QThread, QTimer, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QDialog, QProgressBar, QPushButton
from my_package import long_running_function
class PercentageWorker(QObject):
started = pyqtSignal()
finished = pyqtSignal()
percentageChanged = pyqtSignal(int)
def __init__(self, parent=None):
super().__init__(parent)
self._percentage = 0
def __add__(self, other):
if isinstance(other, int):
self._percentage += other
self.percentageChanged.emit(self._percentage)
return self
return super().__add__(other)
def __lt__(self, other):
if isinstance(other, int):
return self._percentage < other
return super().__lt__(other)
def start_task(self, callback, initial_percentage):
self._percentage = initial_percentage
wrapper = partial(callback, self)
QTimer.singleShot(0, wrapper)
@pyqtSlot(object)
def launch_task(self, wrapper):
self.started()
wrapper()
self.finished()
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in running my external function and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("Progress Bar")
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton("Start", self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
thread = QThread(self)
thread.start()
self.percentage_worker = PercentageWorker()
self.percentage_worker.moveToThread(thread)
self.percentage_worker.percentageChanged.connect(self.progress.setValue)
self.percentage_worker.started.connect(self.onStarted)
self.percentage_worker.finished.connect(self.onFinished)
@pyqtSlot()
def onStarted(self):
self.button.setDisabled(True)
@pyqtSlot()
def onFinished(self):
self.button.setDisabled(False)
@pyqtSlot()
def onButtonClick(self):
self.percentage_worker.start_task(long_running_function, 0)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())