I am stuck. It should be easy and I have done it many times using the C++ API of Qt however for some reason several of my signals/slots are not working when I'm doing this in PyQt (I've recently started with the concept of a worker QObject
in PyQt). I believe it has to do something with the separate thread I'm emitting my signals to/from.
from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot, QTimer
from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
class Slave(QObject):
countSignal = pyqtSignal(int)
def __init__(self, parent = None):
super(Slave, self).__init__()
self.toggleFlag = False
self.counter = 0
@pyqtSlot()
def work(self):
if not self.toggleFlag: return
if self.counter > 10: self.counter = 0
self.counter += self.counter
self.countSignal.emit(self.counter)
@pyqtSlot()
def toggle(self):
self.toggleFlag = not self.toggleFlag
class Master(QWidget):
toggleSignal = pyqtSignal()
def __init__(self, parent = None):
super(Master, self).__init__()
self.initUi()
self.setupConn()
def __del__(self):
self.thread.quit()
while not self.thread.isFinished(): pass
def initUi(self):
layout = QVBoxLayout()
self.buttonToggleSlave = QPushButton('Start')
self.labelCounterSlave = QLabel('0')
layout.addWidget(self.buttonToggleSlave)
layout.addWidget(self.labelCounterSlave)
self.setLayout(layout)
self.show()
def setupConn(self):
self.thread = QThread()
slave = Slave()
timer = QTimer()
timer.setInterval(100)
# Make sure that both objects are removed properly once the thread is terminated
self.thread.finished.connect(timer.deleteLater)
self.thread.finished.connect(slave.deleteLater)
# Connect the button to the toggle slot of this widget
self.buttonToggleSlave.clicked.connect(self.toggle)
# Connect widget's toggle signal (emitted from inside widget's toggle slot) to slave's toggle slot
self.toggleSignal.connect(slave.toggle)
# Connect timer's timeout signal to slave's work slot
timer.timeout.connect(slave.work)
timer.timeout.connect(self.timeout)
# Connect slave's countSignal signal to widget's viewCounter slot
slave.countSignal.connect(self.viewCounter)
# Start timer
timer.start()
# Move timer and slave to thread
timer.moveToThread(self.thread)
slave.moveToThread(self.thread)
# Start thread
self.thread.start()
@pyqtSlot(int)
def viewCounter(self, value):
print(value)
self.labelCounterSlave.setText(str(value))
@pyqtSlot()
def toggle(self):
print("Toggle called")
self.buttonToggleSlave.setText("Halt" if (self.buttonToggleSlave.text() == "Start") else "Start")
self.toggleSignal.emit()
@pyqtSlot()
def timeout(self):
print("Tick")
if __name__ == "__main__":
app = QApplication([])
w = Master()
w.setStyleSheet('cleanlooks')
app.exec_()
Following things are not triggered/emitted:
timeout()
slot of my widget - I added this to see why the timer is not triggering my worker's slot but all I found out is that it doesn't work here either...work()
and toggle()
slots inside my Slave
worker classcountSignal
- it is never emitted since my widget's viewCounter()
slot is never triggeredI have no idea what I'm doing wrong. I have connected the signals and slots, started my timer, moved it along with the worker to my separate thread and started the thread.
Am I missing something here?
There are several issues with the code that are preventing it from working correctly.
As per the documentation, you must start (and stop) a timer from the thread it resides in. You cannot start it from another thread. If you want to have the timer reside in the thread, you should relocate the instantiation code to the Slave
object and call timer.start()
in a slot connected to the threads started
signal. You do need to be careful here though as the Slave.__init__
method is going to still run in the main thread. Alternatively, you could just leave the timer in the main thread.
slave
and timer
are being garbage collected when setupConn()
has finished. Store them as self.slave
and self.timer
. (Alternatively you should be able to specify a parent for them, but this seems to result in app crashes on exit so it's probably best to stick to storing them as instance attributes).
I assume the line self.counter += self.counter
should really be self.counter += 1
? Otherwise the counter is never incremented :)