I’m fairly new to PyQt and have been struggling with this problem for some time.
(In reality, I have a much more complex routine where after pressing the “start” button, the measurement routine is executed using python’s threading).
I would like to have a button to stop/cancel a running loop – QThread. Let’s say one made a mistake and wants to restart the routine, so the app should not be closed.
I found this example very useful
https://forum.pythonguis.com/t/pause-a-running-worker-thread/147/4?u=martin
from PyQt5.QtWidgets import (QWidget, QApplication, QProgressBar, QMainWindow,QHBoxLayout,QPushButton)
from PyQt5.QtCore import (Qt, QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool)
import time
class WorkerSignals(QObject):
progress = pyqtSignal(int)
class JobRunner(QRunnable):
signals = WorkerSignals()
def __init__(self):
super().__init__()
self.is_paused = False
self.is_killed = False
@pyqtSlot()
def run(self):
for n in range(100):
self.signals.progress.emit(n + 1)
time.sleep(0.1)
while self.is_paused:
time.sleep(0)
if self.is_killed:
break
def pause(self):
self.is_paused = True
def resume(self):
self.is_paused = False
def kill(self):
self.is_killed = True
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Some buttons
w = QWidget()
l = QHBoxLayout()
w.setLayout(l)
btn_stop = QPushButton("Stop")
btn_pause = QPushButton("Pause")
btn_resume = QPushButton("Resume")
l.addWidget(btn_stop)
l.addWidget(btn_pause)
l.addWidget(btn_resume)
self.setCentralWidget(w)
# Create a statusbar.
self.status = self.statusBar()
self.progress = QProgressBar()
self.status.addPermanentWidget(self.progress)
# Thread runner
self.threadpool = QThreadPool()
# Create a runner
self.runner = JobRunner()
self.runner.signals.progress.connect(self.update_progress)
self.threadpool.start(self.runner)
btn_stop.pressed.connect(self.runner.kill)
btn_pause.pressed.connect(self.runner.pause)
btn_resume.pressed.connect(self.runner.resume)
self.show()
def update_progress(self, n):
self.progress.setValue(n)
app = QApplication([])
w = MainWindow()
app.exec_()
However, the “run/pause/stop” functions are now inside of the “MainWindow”. But I want to create my app in a Model–View–Presenter pattern, which means I want to have a separate file/class where I define a thread.
Here is how I imagine it could be implemented, but of course it’s wrong and I don’t understand how should I do it correctly.
from PyQt5.QtWidgets import (QWidget, QApplication, QProgressBar, QMainWindow,QHBoxLayout,QPushButton)
from PyQt5.QtCore import (Qt, QObject, pyqtSignal, pyqtSlot, QRunnable, QThreadPool)
import time
class WorkerSignals(QObject):
progress = pyqtSignal(int)
class JobRunner(QRunnable):
signals = WorkerSignals()
def __init__(self):
super().__init__()
self.is_paused = False
self.is_killed = False
@pyqtSlot()
def runn(self):
# Thread runner
self.threadpool = QThreadPool()
# Create a runner
self.runner = self.run()
self.threadpool.start(self.runner)
def run(self):
for n in range(100):
self.signals.progress.emit(n + 1)
time.sleep(0.1)
while self.is_paused:
time.sleep(0)
if self.is_killed:
break
def pause(self):
self.is_paused = True
def resume(self):
self.is_paused = False
def kill(self):
self.is_killed = True
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Some buttons
w = QWidget()
l = QHBoxLayout()
w.setLayout(l)
btn_stop = QPushButton("Stop")
btn_pause = QPushButton("Pause")
btn_resume = QPushButton("Resume")
btn_start = QPushButton("Start")
l.addWidget(btn_start)
l.addWidget(btn_stop)
l.addWidget(btn_pause)
l.addWidget(btn_resume)
l.addWidget(output_rd)
self.setCentralWidget(w)
self.runner = JobRunner()
btn_start.pressed.connect(self.runner.runn)
btn_stop.pressed.connect(self.runner.kill)
btn_pause.pressed.connect(self.runner.pause)
btn_resume.pressed.connect(self.runner.resume)
self.show()
def update_progress(self, n):
self.progress.setValue(n)
app = QApplication([])
w = MainWindow()
app.exec_()
I would appreciate any tips. Thanks!