Reputation: 709
I know how to send signals from worker threads back to the main GUI thread, but how can I send signals from the main thread to the worker thread?
Here's some sample code which includes a signal and slot. Here I'm sending signals back to the main thread, but how can I go in the opposite direction?
The goal here being to send a signal to change the value of self.do to 0 when I want the thread to stop.
Here's the main file and I'll put the UI file below
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
from progressUI import Ui_Form
import sys
import time
class ProgressBar(QObject):
progress = pyqtSignal(int)
kill = pyqtSignal()
def __init__(self, timer, parent=None):
super(ProgressBar, self).__init__(parent)
self.time = timer
self.do = 1
def work(self):
while self.do == 1:
y = 0
for _ in range(100):
time.sleep(.1)
y += 1
self.progress.emit(y)
break
self.kill.emit()
@pyqtSlot(str)
def worker_slot(self, sentence):
print(sentence)
class Go(QMainWindow, Ui_Form, QObject):
custom_signal = pyqtSignal(str)
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.progressBar.setValue(0)
self.startThread()
@pyqtSlot(int)
def updateProgress(self, val):
self.progressBar.setValue(val)
self.custom_signal.emit('hi from main thread')
def startThread(self):
self.progressThread = ProgressBar(60)
self.thread = QThread()
self.progressThread.moveToThread(self.thread)
self.progressThread.progress.connect(self.updateProgress)
self.progressThread.kill.connect(self.thread.quit)
self.custom_signal.connect(self.progressThread.worker_slot)
self.thread.started.connect(self.progressThread.work)
self.thread.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
MainApp = Go()
MainApp.show()
sys.exit(app.exec_())
Here's the UI file.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(658, 118)
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(30, 40, 601, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(45, 75, 581, 26))
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "TextLabel"))
Upvotes: 7
Views: 10444
Reputation: 3096
Hi encountered the same proble on another type of project, see PyQt5 unable to stop/kill QThread,
found very informative solution/explanation here : Stopping an infinite loop in a worker thread in PyQt5 the simplest way and tried to use the second proposed solution : Solution 2: Passing a mutable as a control variable, choose this one because my project doesnt have an infinite loop, but a for loop running until a directory is emptied of all is subolders/files.
To try to understand how it works see my result, applied to code above:
UI file progressUI_2.py
:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(658, 218)
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(30, 40, 581, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.label = QtWidgets.QLabel(Form)
self.label.setGeometry(QtCore.QRect(45, 75, 581, 26))
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.button = QtWidgets.QPushButton('press here', Form)
self.button.setGeometry(QtCore.QRect(30, 115, 581, 26))
self.button.setObjectName("button")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.label.setText(_translate("Form", "TextLabel"))
Main script file main.py
:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
from progressUI_2 import Ui_Form
import sys
import time
class ProgressBar(QObject):
progress = pyqtSignal(int)
kill = pyqtSignal()
def __init__(self, timer, ctrl, parent=None):
super(ProgressBar, self).__init__(parent)
self.time = timer
self.do = 1
self.flag = 'off'
self.ctrl = ctrl # dict with your control var
def work(self):
print('Entered run in worker thread')
print('id of ctrl in worker:', id(self.ctrl))
self.ctrl['break'] = False
while self.do == 1:
y = 0
for _ in range(100):
time.sleep(.1)
y += 1
self.progress.emit(y)
if self.flag == 'on':
break
# print('flag : ', self.flag, 'self.do : ', self.do)
if self.ctrl['break'] == True :
break
break
# self.kill.emit()
@pyqtSlot(str)
def worker_slot2(self, sentence2):
self.flag = 'on'
self.do = 0
print('from worker !!!!', sentence2, 'self.flag : ', self.flag,'self.do : ', self.do)
@pyqtSlot(str)
def worker_slot(self, sentence):
print(sentence)
class Go(QMainWindow, Ui_Form, QObject):
custom_signal = pyqtSignal(str)
button_signal = pyqtSignal(str)
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.progressBar.setValue(0)
self.button.clicked.connect(self.clicked)
self.ctrl = {'break': False} # dict with your control variable
print('id of ctrl in main:', id(self.ctrl))
self.startThread()
def clicked(self):
print('clicked')
self.button_signal.emit('emitted from go ')
self.flag = 'on'
self.do = 0
# self.ctrl['break'] = True
@pyqtSlot(int)
def updateProgress(self, val):
self.progressBar.setValue(val)
self.custom_signal.emit('hi from main thread')
def startThread(self):
self.progressThread = ProgressBar(60, self.ctrl)
self.thread = QThread()
self.progressThread.moveToThread(self.thread)
self.progressThread.progress.connect(self.updateProgress)
self.progressThread.kill.connect(self.thread.quit)
self.custom_signal.connect(self.progressThread.worker_slot)
self.thread.started.connect(self.progressThread.work)
self.button_signal.connect(self.progressThread.worker_slot2)
self.thread.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
MainApp = Go()
MainApp.show()
sys.exit(app.exec_())
try to run the main.py
commenting out and uncommenting self.ctrl['break'] = True
in :
def clicked(self):
print('clicked')
self.button_signal.emit('emitted from go ')
self.flag = 'on'
self.do = 0
# self.ctrl['break'] = True
and see if you are able to stop the progress bar pushing the QBButton,
notice how trying to change self.do
and self.flag
in various part of the code prove to be unsuccessful, not sure if it just because I should have passed them to the worker
Upvotes: 1
Reputation: 709
How do you send a signal to a worker thread? Exactly the same way as sending a signal from a worker thread to the GUI. I expected it to be more different.
@three-pineapples linked to an excellent example of bi-directional communication between the main thread and a worker thread.
If you want to create a custom signal in the main GUI thread, you need to make sure you inherit QObject and then you'll be able to create custom signals.
I updated my code in the original post to include the UI file so you can run it, and I included an example of a custom signal in the GUI thread that sends a signal to the worker.
However you will not see the output of the print statement until the for loop has finished as it blocks the worker from processing signals, as @three-pineapples also stated.
So, although it's not the best example, hopefully if someone is having the same trouble understanding the concept, maybe this will help.
Upvotes: 1