PyQt Thread Completes and outputs same message twice - pyqt

I'm trying to create a play/pause/resume/ button with a progress bar. User presses 'Start' and the progress bar initializes and a pause/resume button populates. Once the progress completes, a message is printed and the pause/resume buttons are hidden while the 'Start' button is shown again. However, when I try to run 'Start' again, it prints the same completion message an additional time.
If I run it twice, it prints out two completion messages. Run it three times, prints out three completion messages and etc.
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)
finished = pyqtSignal()
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.001)
while self.is_paused:
time.sleep(0)
if self.is_killed:
break
self.signals.finished.emit()
def finished(self):
print('Finished')
def pause(self):
self.is_paused = True
def resume(self):
self.is_paused = False
# def kill(self):
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Some buttons
self.w = QWidget()
self.l = QHBoxLayout()
self.w.setLayout(self.l)
self.btn_start = QPushButton("Start")
self.l.addWidget(self.btn_start)
self.setCentralWidget(self.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)
self.btn_start.pressed.connect(self.start_runner)
# btn_pause.pressed.connect(self.runner.pause)
# btn_resume.pressed.connect(self.runner.resume)
# self.startTestSignal.connect(self.update_progress)
self.show()
def update_progress(self, n):
self.progress.setValue(n)
def start_runner(self):
# Create ThreadPool
self.threadpool = QThreadPool()
self.threadpool.clear()
# Create a runner
self.runner = JobRunner()
self.runner.signals.progress.connect(self.update_progress)
self.threadpool.start(self.runner)
self.progress.setValue(0)
# Change Start Button to Pause
self.btn_start.hide()
# self.btn_start.setEnabled(False)
self.btn_pause = QPushButton("Pause")
self.btn_resume = QPushButton("Resume")
self.l.addWidget(self.btn_pause)
self.l.addWidget(self.btn_resume)
self.btn_pause.pressed.connect(self.runner.pause)
self.btn_resume.pressed.connect(self.runner.resume)
self.runner.signals.finished.connect(self.check)
def check(self):
print('Thread Done')
self.btn_start.show()
self.progress.setValue(0)
# self.runner.terminate()
self.btn_pause.hide()
self.btn_resume.hide()
app = QApplication([])
w = MainWindow()
app.exec_()

You are creating a WorkerSignals as a class attribute, making it a persistent for the class; so when you do this:
self.runner.signals.finished.connect(self.check)
you are actually connecting again to signals.finished, and when you emit the signal the function self.check is called each time the signal has been connected.
The solution is to create the WorkerSignals as an instance attribute:
class JobRunner(QRunnable):
def __init__(self):
super().__init__()
self.signals = WorkerSignals()
self.is_paused = False
self.is_killed = False

Related

How to communicate QThread with my Widgets from main class

My app is freezing because i need to create a new QThread, but am a little confused how to create it when i call widgets that exists on the main class.
here is my code so far ...
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.uic import loadUi
import time
##Main Class
class HWindow(QtWidgets.QWidget):
def __init__(self):
super(HWindow, self).__init__()
loadUi("Hw.ui",self) ##load ui
#Create a QThread object
self.thread = QtCore.QThread()
#Create a QThread object
self.workerMain = WorkerMain()
#Move worker to the thread
self.workerMain.moveToThread(self.thread)
#Start the thread
self.thread.start()
self.runningMain = False
def activateMain(self):
if self.chkb_main.isChecked():
self.lbl_disena_main.setText("Activated")
self.lbl_disena_main.setStyleSheet('color: green;')
#Change variable and call the function
runningMain = True
self.myFunction()
else:
self.lbl_disena_main.setText("Disabled")
self.lbl_disena_main.setStyleSheet('color: red;')
#Change variable and call the function
runningMain = False
def myFunction(self):
while self.runningMain == True:
if self.gpb_main.isChecked() and self.chkb_main.isChecked():
print("running ...")
time.sleep(3)
##Worker Class
class WorkerMain(QtCore.QObject):
threadRunning = QtCore.pyqtSignal()
def __init__(self):
super(WorkerMain, self).__init__()
def run(self):
print("Thread Running ...")
#i cant call my widgets from my main class from here.
'''
while self.runningMain == True:
if self.gpb_main.isChecked() and self.chkb_main.isChecked():
print("running ...")
time.sleep(3)
'''
def stop(self):
print("Thread Stopped ...")
self.terminate()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
hWindow = QtWidgets.QWidget()
hWindow = HWindow()
hWindow.show()
sys.exit(app.exec_())
Based on pyqt5 documentation and examples, you create a loop on the run method, but it is complicated when i have to create that loop based on what the user select (GUI widgets).
i just figure it out, here is my code ...
class HWindow(QtWidgets.QWidget):
def __init__(self):
super(HWindow, self).__init__()
loadUi("Hw.ui",self) ##load ui
#Create a QThread object
self.thread = QtCore.QThread()
#Create a QThread object
self.workerMain = WorkerMain(self)
#Move worker to the thread
self.workerMain.moveToThread(self.thread)
#Connect the start signal to the run slot
self.workerMain.threadRunning.connect(self.workerMain.run)
#Connect the activateMain signal to the start slot
self.chkb_main.toggled.connect(self.activateMain)
#Start the thread
self.thread.start()
def activateMain(self):
if self.chkb_main.isChecked():
self.lbl_disena_main.setText("Activated")
self.lbl_disena_main.setStyleSheet('color: green;')
self.workerMain.runningMain = True
self.workerMain.threadRunning.emit()
if not self.thread.isRunning():
self.thread.start()
else:
self.lbl_disena_main.setText("Disabled")
self.lbl_disena_main.setStyleSheet('color: red;')
self.workerMain.runningMain = False
self.workerMain.stop()
##Worker Class
class WorkerMain(QtCore.QObject):
threadRunning = QtCore.pyqtSignal()
def __init__(self, parent):
super(WorkerMain, self).__init__()
self.parent = parent
self.runningMain = False
def run(self):
print("Thread Running ...")
while self.runningMain:
if self.parent.gpb_main.isChecked() and self.parent.chkb_main.isChecked():
print("running ...")
time.sleep(3)
def stop(self):
print("Thread Stopped ...")
if self.parent.thread.isRunning():
self.parent.thread.quit()
self.parent.thread.wait()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
hWindow = QtWidgets.QWidget()
hWindow = HWindow()
hWindow.show()
sys.exit(app.exec_())

PyQt Progress Bar Update using Thread

I've been writing a program that used a MyWindow(QTableWidget) with a thread. A progress bar is displayed above the sub-window(self.win) displayed as a pop-up.
I want a green bar on the status bar to be displayed consecutively, however after resetting the Spyder-kernel, the green bar does not output continuously. And I want to run the 'stop'/'continue' alternately every time I click the push button. This hasn't been resolved for almost three days.
import sys, time
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import QThread, pyqtSignal, pyqtSlot, QRect
from PyQt5.QtWidgets import *
progVal = 0
class thread(QThread):
signalTh = pyqtSignal(int)
def __init__(self, *args):
super().__init__()
self.flag = True
def run(self):
global progVal
if self.flag:
self.signalTh.emit(progVal)
time.sleep(0.1)
def stop(self):
self.flag = False
self.quit()
self.wait(2)
class MyWindow(QTableWidget):
def __init__(self):
global progVal
super().__init__()
self.setupUi()
self.show()
self.test = thread(None)
self.test.signalTh.connect(self.signal_function)
self.test.run()
self.saveData()
def saveData(self):
global progVal
counts = range(1, 51)
for row in counts:
progVal = int(row/len(counts)*100)
self.test.signalTh.emit(progVal)
time.sleep(0.1)
def click1_function(self):
if self.test.flag:
self.test.stop()
self.pb_start.setText('Start!')
else:
self.test.flag = True
self.test.run()
self.pb_start.setText('Stop!')
#pyqtSlot(int)
def signal_function(self, val):
self.progress.setValue(val)
self.progress.update()
self.win.update()
self.update()
def setupUi(self):
self.resize(500, 400)
self.pb_start = QPushButton(self)
self.pb_start.setGeometry(QRect(80, 20, 100, 50))
self.pb_start.setText("Start")
self.pb_start.clicked.connect(self.click1_function)
self.win = QDialog(self)
self.win.resize(330, 100)
self.progress = QProgressBar(self.win)
self.progress.setGeometry(10, 10, 300, 30)
self.progress.setMaximum(100)
self.win.show()
def closeEvent(self, event):
quit_msg = "Are you sure you want to exit the program?"
reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No)
if reply == QMessageBox.Yes:
self.test.stop()
event.accept()
else:
event.ignore()
if __name__ == "__main__":
app = QApplication(sys.argv)
myApp = MyWindow()
myApp.show()
app.exec_()

I wonder why pyqtSignal() is used. (pyqt, QThread, signal, slot)

This is the test code about QThread and Signal.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import time
import sys
class Thread1(QThread):
set_signal = pyqtSignal(int) # (1) ##
def __init__(self, parent):
super().__init__(parent)
def run(self):
for i in range(10):
time.sleep(1)
self.set_signal.emit(i) # (3) ##
class MainWidget(QWidget):
def __init__(self):
super().__init__()
thread_start = QPushButton("시 작!")
thread_start.clicked.connect(self.increaseNumber)
vbox = QVBoxLayout()
vbox.addWidget(thread_start)
self.resize(200,200)
self.setLayout(vbox)
def increaseNumber(self):
x = Thread1(self)
x.set_signal.connect(self.print) # (2) ##
x.start()
def print(self, number):
print(number)
if __name__ == '__main__':
app = QApplication(sys.argv)
widget = MainWidget()
widget.show()
sys.exit(app.exec_())
In the example of QThread I searched for, a pyqtSignal()(step 1) object was created, the desired slot function was connected(step 2) by connect, and then called by emit()(step 3).
I don't know the difference from calling the desired method immediately without connecting the connect().
So, the goal of codes is triggering a desired function every second. You are right about it.
But if you create multiple objects, you can connect it to different desired functions. Or connect multiple function to one signal.
x = Thread1(self)
x.set_signal.connect(self.print) # (2) ##
x.start()
y = Thread1(self)
y.set_signal.connect(self.print2)
time.sleep(0.5)
y.start()

Is there a way to cancel a background worker task in PyQt5 using a pushButton?

I'm trying to make the background worker start with the start button and stop with the stop button. When I push start the program starts but I can't stop it and it also exits the gui when completed. Sorry, I am a novice just trying to learn. I found this example here and I am just trying to expand on it.
Background thread with QThread in PyQt
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QPushButton
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0") # start out at 0.
##### I added the stuff below 6/8/2020 ####################################
k = 2
self.button1 = QPushButton("Push to start") # create start button
self.button2 = QPushButton("Push to Stop") # create a stop button.
self.button1.clicked.connect(self.start) # connect button to function
self.button2.clicked.connect(self.stop) # connect button to function
########################################################################
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent! #### Calls to worker.py
self.thread = QThread() # no parent! #### Creates an empty thread
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady) # connects the definition below
# 3 - Move the Worker object to the Thread object
self.obj.moveToThread(self.thread)
# 4 - Connect Worker Signals to the Thread slots
self.obj.finished.connect(self.thread.quit)
# 5 - Connect Thread started signal to Worker operational slot method
#self.thread.started.connect(self.obj.procCounter(k)) #### starts the counter in worker.py
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def start(self):
k=2
self.obj.procCounter(k)
def stop(self):
self.thread.quit()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,1)
grid.addWidget(self.button1, 0, 0)
grid.addWidget(self.button2, 0, 2)
self.move(300, 150)
self.setWindowTitle('thread test')
self.show()
def onIntReady(self, i):
self.label.setText("{}".format(i))
print(i)
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject): # this is called by Worker.worker() in main.py
finished = pyqtSignal() # sets up to emit a signal back to main.py
intReady = pyqtSignal(int) # emits an integer to main.py def onIntReady
#pyqtSlot()
def procCounter(self,k): # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i*k) # emits the signal to onIntReady
#def procCounter(self): # A slot takes no params
# for i in range(1, 100):
# time.sleep(1)
# self.intReady.emit(i)
self.finished.emit()

How to write a multi-threaded Pyside application

What is the simplest way to multi-thread a Pyside application, so the GUI can be operational and the thread will still run?
Thread class:
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
Complete .pyw
import sys,time
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtWebKit import *
def thread():
global threade
threade = MyLongThread()
threade.run()
def thread_terminate():
global threade
threade.terminate()
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
app = QApplication(sys.argv)
wid = QWidget()
wid.resize(250, 400)
wid.setWindowTitle('Threaded Program')
#wid.setWindowIcon(QIcon('web.png'))
#### BUTTONS
btn = QPushButton('Stop', wid)
btn.setToolTip('Stop the thread.') ## Stop the thread
btn.resize(btn.sizeHint())
btn.move(147, 50)
btn.clicked.connect(thread_terminate)
qbtn = QPushButton('Start', wid)
qbtn.setToolTip('Start the thread.') ## End the Thread
qbtn.resize(btn.sizeHint())
qbtn.move(27, 50)
qbtn.clicked.connect(thread)
####
#### LABEL
label = QLabel('Start The Thread :)',wid)
label.resize(label.sizeHint())
label.move(28, 15)
####
wid.show()
sys.exit(app.exec_())
When I run the code and press the start button, it freezes the gui but prints run.
Don't directly call thread.run() as this executes the method in the main thread. Instead, call thread.start() which will launch a thread and start executing your run() method in the thread.

Resources