I am currently working on a Gui, which counts until a given input number is reached. It should be possible to stop the while-loop during counting and restart it again using buttons without ending the main Gui thread. This already works, but only if the target counting number is fixed in the thread work function, e.g. n = 10 -> counts to 10 and can't be changed to 20 or any other number without closing the Gui and change the number in the main code. I want to use a line edit for number input and send the desired target number to the thread, so that it counts up to that given number at the time I start the counting. However, when starting the Gui (after implementing the signals usually needed to send and get the input value) typing the number in the line edit and pressing the start button, I get a TypeError, stating that the 'decorated slot has no signature compatible with started()' ('started' should connect thread and worker function). Does anyone by chance know where I am doing a mistake or have to change something and/or if there is a workaround for this error?
I am quite desperate (I've been trying to fix this for quite a while and couldn't find any hints in the net...) and any help is greatly appreciated! Best regards and thanks!
This is the complete code with comments:
import sys
import time
from PyQt5.QtWidgets import QPushButton, QMainWindow, QApplication, QLineEdit
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class Worker(QObject):
finished = pyqtSignal() # signal out to main thread to alert it that work is completed
def __init__(self):
super(Worker, self).__init__()
self.working = True # flag to control our loop
@pyqtSlot(int) # should take the sended integer n from signal...
def work(self, n):
s = 0
while self.working:
if s != n: # count until input integer is reached (doesn't work so far)
print(s)
s += 1
time.sleep(0.5)
self.finished.emit() # alert gui that the loop stopped
class Window(QMainWindow):
sendnumber = pyqtSignal(int) # send signal (this is how it is usually done...?)
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 200, 250)
self.setWindowTitle("Program")
self.inputnumber=QLineEdit(self, placeholderText="number")
self.inputnumber.resize(self.inputnumber.minimumSizeHint())
self.inputnumber.move(50, 50)
self.startbtn = QPushButton("Start", self)
self.startbtn.resize(self.startbtn.minimumSizeHint())
self.startbtn.move(50, 100)
self.stopbtn = QPushButton("Stop", self)
self.stopbtn.resize(self.stopbtn.minimumSizeHint())
self.stopbtn.move(50, 150)
self.thread = None
self.worker = None
self.startbtn.clicked.connect(self.start_loop)
def start_loop(self):
self.thread = QThread() # a new thread to run the background tasks in
self.worker = Worker() # a new worker to perform those tasks
self.worker.moveToThread(self.thread) # move the worker into the thread, do this first before connecting the signals
self.thread.started.connect(self.worker.work) # begin worker object loop when the thread starts running
self.sendnumber.connect(self.worker.work) # connect input number to worker in thread
# this doesn't work so far and gives a TypeError!
self.stopbtn.clicked.connect(self.stop_loop) # stop the loop on the stop button click
self.worker.finished.connect(self.loop_finished) # do something in the gui when the worker loop ends
self.worker.finished.connect(self.thread.quit) # tell the thread it's time to stop running
self.worker.finished.connect(self.worker.deleteLater) # have worker mark itself for deletion
self.thread.finished.connect(self.thread.deleteLater) # have thread mark itself for deletion
# make sure those last two are connected to themselves or you will get random crashes
self.thread.start()
def stop_loop(self):
self.worker.working = False
# when ready to stop the loop, set the working flag to false
@pyqtSlot() # as far as I know you need this Slot to send the input to Slot in thread?
def getnumber(self):
try:
n = int(self.inputnumber.text()) # input for the work function in thread
print('Trying number...')
except:
print('Use an integer!')
return
self.sendnumber.emit(n) # emit integer signal
print('Emitting signal to worker...')
def loop_finished(self):
# received a callback from the thread that it's completed
print('Loop finished.')
if __name__ == '__main__':
def run():
app = QApplication(sys.argv)
gui = Window()
gui.show()
app.exec_()
run()
And here is the console output showing the error:
Traceback (most recent call last):
File "C:/Users/***/WhileLoopInterruptTest2.py", line 63, in start_loop
self.thread.started.connect(self.worker.work)
TypeError: decorated slot has no signature compatible with started()
This is all the error says and the Gui has to be closed. I am using Python 3.6.1, Spyder 3.3.1, PyQt5.9.
Replace this line:
self.thread.started.connect(self.worker.work)
with this:
self.thread.started.connect(lambda: self.worker.work(10))
You can replace 10
with the value from the text box. Just remember to convert it to an int
first.
Checkout this article for a more detailed explanation.