Threading with QRunnable - Proper manner of sending bi-directional callbacks - python

From a tutorial, I've seen that signals and slots can be used to make callbacks from a worker thread into the main GUI thread, but I'm uncertain how to establish bi-directional communication using signals and slots. The following is what I am working with:
class RespondedToWorkerSignals(QObject):
callback_from_worker = pyqtSignal()
class RespondedToWorker(QRunnable):
def __init__(self, func, *args, **kwargs):
super(RespondedToWorker, self).__init__()
self._func = func
self.args = args
self.kwargs = kwargs
self.signals = RespondedToWorkerSignals()
self.kwargs['signal'] = self.signals.callback_from_worker
print("Created a responded-to worker")
#pyqtSlot()
def run(self):
self._func(*self.args, **self.kwargs)
#pyqtSlot()
def acknowledge_callback_in_worker(self):
print("Acknowledged Callback in Worker")
class MainWindow(QMainWindow):
# Signal meant to connect to a slot present within a worker
mainthread_callback_to_worker = pyqtSignal()
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Quick UI setup
w, lay = QWidget(), QVBoxLayout()
w.setLayout(lay)
self.setCentralWidget(w)
self.timer_label = QLabel("Timer Label")
lay.addWidget(self.timer_label)
self.btn_thread_example = QPushButton("Push Me")
self.btn_thread_example.pressed.connect(self.thread_example)
lay.addWidget(self.btn_thread_example)
self.threadpool = QThreadPool()
self.show()
# Set up QTimer to continue in the background to help demonstrate threading advantage
self.counter = 0
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.recurring_timer)
self.timer.start()
#pyqtSlot()
def do_something(self, signal):
# signal argument will be the callback_from_worker and it will emit to acknowledge_callback_in_mainthread
print("do_something is sleeping briefly. Try to see if you get a locked widget...")
time.sleep(7)
signal.emit()
#pyqtSlot()
def acknowledge_callback_in_mainthread_and_respond(self):
# this function should respond to callback_from_worker and emit a response
print("Acknowledged Callback in Main")
self.mainthread_callback_to_worker.emit()
def thread_example(self):
print("Beginning thread example")
worker = RespondedToWorker(self.do_something)
worker.signals.callback_from_worker.connect(self.acknowledge_callback_in_mainthread_and_respond)
# self.mainthread_callback_to_worker.connect(worker.acknowledge_callback_in_worker) # <-- causes crash
def recurring_timer(self):
self.counter += 1
self.timer_label.setText(f"Counter: {self.counter}")
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
app.setStyle("Fusion")
win.show()
sys.exit(app.exec())
At the current moment, the script can make the second thread and send a signal to the main GUI. I'd like the GUI to send a response signal back to the worker thread. I'm also uncertain why connecting the main/GUI's signal mainthread_callback_to_worker causes a crash (see the commented-out line).
I understand that one workaround would be for do_something to return some value and then use it inside the worker as "acknowledgement". But I'd like to know the solution using signals & slots, if possible.

To understand the cause of the error you must run the code in the terminal and you will get the following error message:
QObject::connect: Cannot connect MainWindow::mainthread_callback_to_worker() to (nullptr)::acknowledge_callback_in_worker()
Traceback (most recent call last):
File "main.py", line 72, in thread_example
self.mainthread_callback_to_worker.connect(worker.acknowledge_callback_in_worker) # <-- causes crash
TypeError: connect() failed between MainWindow.mainthread_callback_to_worker[] and acknowledge_callback_in_worker()
Aborted (core dumped)
And the cause of the error is the abuse of the pyqtSlot decorator since it should only be used in the QObject methods but QRunnable is not causing that exception, in addition that in a non-QObject it does not take any advantage so that decorator in the run() method doesn't make sense.
On the other hand, a QRunnable is only an interface that lives in the main thread and only the run method is executed in another thread, so a QRunnable cannot be a worker since that type of objective must execute its methods in a secondary thread.
So with the above QRunnable is not the appropriate option, so for your purpose I recommend using a QObject that lives in a secondary thread and invoking the methods.
import sys
import time
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer, QThread
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QLabel,
QPushButton,
)
class Worker(QObject):
callback_from_worker = pyqtSignal()
def __init__(self, func, *args, **kwargs):
super(Worker, self).__init__()
self._func = func
self.args = args
self.kwargs = kwargs
self.kwargs["signal"] = self.callback_from_worker
def start_task(self):
QTimer.singleShot(0, self.task)
#pyqtSlot()
def task(self):
self._func(*self.args, **self.kwargs)
#pyqtSlot()
def acknowledge_callback_in_worker(self):
print("Acknowledged Callback in Worker")
print(threading.current_thread())
class MainWindow(QMainWindow):
mainthread_callback_to_worker = pyqtSignal()
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
w, lay = QWidget(), QVBoxLayout()
w.setLayout(lay)
self.setCentralWidget(w)
self.timer_label = QLabel("Timer Label")
lay.addWidget(self.timer_label)
self.btn_thread_example = QPushButton("Push Me")
self.btn_thread_example.pressed.connect(self.thread_example)
lay.addWidget(self.btn_thread_example)
self.counter = 0
self.timer = QTimer(interval=1000, timeout=self.recurring_timer)
self.timer.start()
self._worker = Worker(self.do_something)
self._worker.callback_from_worker.connect(
self.acknowledge_callback_in_mainthread_and_respond
)
self.worker_thread = QThread(self)
self.worker_thread.start()
self._worker.moveToThread(self.worker_thread)
#pyqtSlot()
def do_something(self, signal):
print(
"do_something is sleeping briefly. Try to see if you get a locked widget..."
)
time.sleep(7)
signal.emit()
#pyqtSlot()
def acknowledge_callback_in_mainthread_and_respond(self):
print("Acknowledged Callback in Main")
self.mainthread_callback_to_worker.emit()
def thread_example(self):
print("Beginning thread example")
self._worker.start_task()
def recurring_timer(self):
self.counter += 1
self.timer_label.setText(f"Counter: {self.counter}")
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
app.setStyle("Fusion")
win.show()
ret = app.exec_()
win.worker_thread.quit()
win.worker_thread.wait()
sys.exit(ret)

Related

Starting a QInputDialog from inside another thread and storing the inputted value into a variable

I have a function that is ran using the Worker method. Inside this function, there can be a condition that then requires the user to input a string and that string will be used in the function. I am able to get the QInputDialog to show up, but it freezes once I start typing something and the program crashes. I have an example of what I am doing right now below:
from PySide2.QtWidgets import QApplication, QInputDialog, QMainWindow, QPushButton
from PySide2.QtCore import *
import sys, os
class Worker_Class:
class WorkerSignals():
finished = Signal()
newRecord = Signal(str)
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker_Class.Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = Worker_Class.WorkerSignals()
#Slot()
def run(self):
try:
self.fn(*self.args, **self.kwargs)
except:
pass
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
self.setFixedSize(QSize(400, 300))
self.setCentralWidget(button)
self.threadpool = QThreadPool()
def start_worker(self):
worker = Worker_Class.Worker(get_dialog_value)
self.threadpool.start(worker)
def get_dialog_value():
text, ok = QInputDialog().getText(window, "Missing Module Partnumber", "Input Module Partnumber: ")
if ok and text:
return text
return ''
QCoreApplication.setLibraryPaths([os.getcwd() + "\\virtualenv\\Lib\\site-packages\\PyQt5\\Qt5\\plugins"])
app = QApplication(sys.argv)
window = MainWindow()
window.show()
window.start_worker()
app.exec_()
This is likely not the best solution, but here is an example of how you could open a QDialog and use it's return value in the same function from a thread. There is likely a better alternative to solving your problem, however given the limited context you have provided, it is difficult to know what to suggest.
The key is to use Signals trigger the launching of the dialog, so that it is opened from the main thread. Then the issue of getting the return value back to the same thread function can be accomplished by simply stalling the function until the thread can see and access the value.
class Worker_Class(QThread):
opendialog = Signal()
def __init__(self, window):
super().__init__()
self.window = window
self.value = None
def setvalue(self, value):
self.value = value
def run(self):
self.opendialog.emit()
while self.value is None:
pass
print(self.value)
# do something with value
# ...
# ...
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press Me!")
self.setFixedSize(QSize(400, 300))
self.setCentralWidget(button)
def start_worker(self):
self.thread = Worker_Class(self)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.opendialog.connect(self.get_dialog_value)
self.thread.start()
def get_dialog_value(self):
text, _ = QInputDialog().getText(window, "Missing Module Partnumber", "Input Module Partnumber: ")
self.thread.setvalue(text)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
window.start_worker()
app.exec_()
If you run the above code you will see that the dialog return value is successfully printed in the threads run function once the dialog closes.

How to add questions into a pyqt thread

EDIT: I have attempted to add a slot to use a signal so the question box is handled by the main thread and not the worker, but it does not open the question box it just finishes the thread. I am also unsure of how to make the worker wait for the result of the question and how to return to the worker once I know what the user wants to do.
The code I have is used to test some boards and based on what happens the thread should either continue on to the next test if there was a pass or ask the user a question if there was a failure. The problem is when there is a failure the question box appears but the thread in the background continues, I don't want that to happen as the result of subsequent tests rely on the answers from the tests before.
Below is a example of what I do not want to happen.
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget, QMessageBox
from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QRunnable, pyqtSlot
class WorkerSignals(QObject):
finished = pyqtSignal()
question = pyqtSignal()
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
#pyqtSlot()
def run(self):
try:
result = self.fn(*self.args, **self.kwargs)
except:
pass
else:
self.signals.progress.emit(result)
finally:
self.signals.finished.emit()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.btnStartTest = QPushButton("Start thread")
self.labelOne = QLabel("Test one")
self.labelTwo = QLabel("Test two")
layout.addWidget(self.btnStartTest)
layout.addWidget(self.labelOne)
layout.addWidget(self.labelTwo)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.threadpool = QThreadPool()
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
self.btnStartTest.clicked.connect(self.runTestsThread)
self.show()
def runTestsThread(self):
# Pass the function to execute
worker = Worker(self.execute_this_fn)
worker.signals.question.connect(self.question_asked)
worker.signals.finished.connect(self.thread_complete)
# Execute
self.threadpool.start(worker)
def execute_this_fn(self):
resultOne = "FAIL"
resultTwo = "PASS"
if resultOne == "PASS":
self.labelOne.setStyleSheet("background-color: LightGreen")
else:
self.labelOne.setStyleSheet("background-color: Red")
self.question.emit()
if resultTwo == "PASS":
self.labelTwo.setStyleSheet("background-color: LightGreen")
else:
self.labelTwo.setStyleSheet("background-color: Red")
self.question.emit()
def thread_complete(self):
print("thread complete")
def question_asked(self):
QMessageBox.question(self, "Message Box!!", "Do you want to continue or quit")
if __name__ == "__main__":
app = QApplication(sys.argv)
Main = MainWindow()
Main.show()
sys.exit(app.exec_())
I have tried using a signal to finish the thread if there is a failure, then ask the question but this did not work. I was unsure of how to then start another thread based on the answer to the question.
Any help on how to structure this would be helpful, thanks.

QTimer in worker thread blocking GUI

I am trying to create a worker thread whose job is to monitor the status bit of a positioning platform.
To do this I connect a QTimer timeout signal to a function that queries the platform.
class expSignals(QtCore.QObject):
pause=QtCore.pyqtSignal()
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
self.timer = QtCore.QTimer()
self.timer.start(100)
self.timer.timeout.connect(self.do_it)
self.lock=QtCore.QMutex()
self.running=True
self.stat=0
def do_it(self):
with QtCore.QMutexLocker(self.lock):
#self.stat = self.motors.get_status()
print(self.stat)
time.sleep(5)
#QtCore.pyqtSlot()
def stop1(self):
self.timer.stop()
print('stop heard')
The GUI stuff looks like this:
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread=QtCore.QThread(self)
#worker
self.mot=motorpositioner()
# =============================================================================
# Putting buttons and GUI stuff in place
# =============================================================================
self.button=QtWidgets.QPushButton('Derp',self)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
self.setGeometry( 300, 300, 350, 300 )
# =============================================================================
# Connecting signals
# =============================================================================
self.sig=expSignals()
self.sig2=expSignals()
self.button.clicked.connect(self.stop)
self.sig.pause.connect(self.mot.stop1)
self.sig2.pause.connect(self.thread.quit)
self.mot.moveToThread(self.thread)
self.thread.start()
def stop(self):
self.sig.pause.emit()
def closeEvent(self,event):
self.sig2.pause.emit()
event.accept()
However the way it is written now the GUI is unresponsive. However if I comment out self.timer.timeout.connect(self.do_it) and put do_it in a while(True) loop, the GUI isn't being blocked.
Why is the main thread being blocked when using QTimer?
I do not know what is expSignals() and I think it is not relevant, and neither is the button.
Your code has the following errors:
You are starting the timer before the thread starts, so the task will run on the GUI thread.
QTimer is not a child of motorpositioner so if motorpositioner moves to the new thread QTimer will not. For him to move he must be a son so you must pass him as a parent to self.
I do not know if it is a real error, but you are firing the QTimer every 100 ms but the task takes 5 seconds, although the QMutex helps to have no problems because it is blocked.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import time
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
self.timer = QtCore.QTimer(self)
self.lock = QtCore.QMutex()
self.running = True
self.stat = 0
def start_process(self):
self.timer.timeout.connect(self.do_it)
self.timer.start(100)
def do_it(self):
with QtCore.QMutexLocker(self.lock):
#self.stat = self.motors.get_status()
print(self.stat)
time.sleep(5)
#QtCore.pyqtSlot()
def stop1(self):
self.timer.stop()
print('stop heard')
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.thread = QtCore.QThread(self)
self.mot = motorpositioner()
self.mot.moveToThread(self.thread)
self.thread.started.connect(self.mot.start_process)
self.thread.start()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = MyApp()
ex.show()
sys.exit(app.exec_())

emitting signal when python thread finishes to close the Qt Gu that initiated the thread

I am trying to emit a signal when a python thread finish running so when it is done, the emtited signal close the PYQT window that started the thread.
in the code see comments.
import sys
import threading
from PyQt4 import QtGui, QtCore
class MyThread(threading.Thread):
""" docstring for MyThread
"""
def __init__(self, job_done, settings):
super(MyThread, self).__init__()
self._Settings = settings
def run(self):
while not job_done.is_set():
if not processSetting(self._Settings):
raise Exception("Could not process settings")
else:
## Emit Signal
pass
class MyWindow(QtGui.QWidget):
"""docstring for MyWindow"""
def __init__(self, settings, parent=None):
super(MyWindow, self).__init__(parent)
self._Settings = settings
self._job_done = threading.Event()
self._myThread = MyThread(self._job_done, self._Settings)
## catch the signal to close this window.
def closeEvent(self, event):
if self._myThread.isAlive():
reply=QtGui.QMessageBox.question(self, "Are you sure to quit?","Settings are getting applied !!!",QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
if reply==QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def processSettings():
print "processSettings() Called"
return True
def main():
app = QtGui.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
in the above code i want to signal when processSttingts returns True
and then MyWindow should close.
EDIT here is what I tried to do.
So what I am heading towards is to emit a signal if the processSettings returns True and close the QMainWindow MyWindow.
import sys
import threading
from PyQt4 import QtGui, QtCore
def processSettings():
print "processSettings() Called"
return True
class MyThread(threading.Thread):
""" docstring for MyThread
"""
def __init__(self, settings):
super(MyThread, self).__init__()
self._Settings = settings
self.signal = QtCore.SIGNAL("signal")
self._job_done = threading.Event()
def run(self):
# while not job_done.is_set():
print "in thread"
if not processSettings():
raise Exception("Could not process settings")
else:
QtCore.emit(self.signal, "hi from thread")
class MyWindow(QtGui.QMainWindow):
"""docstring for MyWindow"""
def __init__(self, settings, parent=None):
super(MyWindow, self).__init__(parent)
self._Settings = settings
self._myThread = MyThread(self._Settings)
self._myThread.daemon = False
self._myThread.start()
self.connect(self._myThread, self._myThread.signal, self.testfunc)
def closeEvent(self, event):
if self._myThread.isAlive():
reply=QtGui.QMessageBox.question(self, "Are you sure to quit?","Settings are getting applied !!!",QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
if reply==QtGui.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def testfunc(self, sigstr):
""" Purpose of this function is to close this window"""
print sigstr
self.close()
def main():
app = QtGui.QApplication(sys.argv)
settings = {'test': True}
wind = MyWindow(settings)
wind.show()
sys.exit(app.exec_())
if __name__ == '__main__':
sys.exit(main())
You need to ensure that the solution is thread-safe, and directly connecting signals emitted from a python thread to a slot in QT thread is not thread safe. The SafeConnector class proposed by mguijarr in his answer to "Emit signal in standard python thread" may be useful. I successfully use a modified version of his solution that uses a pipe rather than a socket:
class SafeConnector(object):
"""Share between Python thread and a Qt thread.
Qt thread calls :meth:`connect` and the python thread calls :meth:`emit`.
The slot corresponding to the emitted signal will be called in Qt's
thread.
"""
def __init__(self):
self._fh_read, self._fh_write = os.pipe()
self._queue = Queue.Queue()
self._qt_object = QtCore.QObject()
self._notifier = QtCore.QSocketNotifier(self._fh_read,
QtCore.QSocketNotifier.Read)
self._notifier.activated.connect(self._recv)
def close(self):
del self._qt_object
self._qt_object = None
os.close(self._fh_read)
os.close(self._fh_write)
self._fh_read, self._fh_write = None, None
def connect(self, signal, receiver):
"""Connect the signal to the specified receiver slot.
:param signal: The signal to connected.
:param receiver: The receiver slot for the signal.
"""
QtCore.QObject.connect(self._qt_object, signal, receiver)
def emit(self, signal, *args):
"""Emit a Qt signal from a python thread.
All remaning args are passed to the signal.
:param signal: The Qt signal to emit.
"""
self._queue.put((signal, args))
os.write(self._fh_write, '!')
def _recv(self):
"""Receive the signal from the Queue in Qt's main thread."""
os.read(self._fh_read, 1)
signal, args = self._queue.get()
self._qt_object.emit(signal, *args)
The example from mguijarr's post is valid using either implementation.
Here is an example of the solution I proposed in the comments:
from PyQt4 import QtGui, QtCore
import threading
import time
class MyThread(threading.Thread):
def __init__(self, *args):
threading.Thread.__init__(self, *args)
self.job_done = threading.Event()
self.qt_object = QtCore.QObject()
def end_job(self):
self.job_done.set()
def run(self):
while not self.job_done.is_set():
time.sleep(1)
QtCore.QObject.emit(self.qt_object, QtCore.SIGNAL("job_done"))
th = MyThread()
th.start()
app = QtGui.QApplication([])
w = QtGui.QWidget()
btn = QtGui.QPushButton('click me to exit', w)
QtGui.QVBoxLayout(w)
w.layout().addWidget(btn)
def btn_clicked():
th.end_job()
QtCore.QObject.connect(btn, QtCore.SIGNAL("clicked()"), btn_clicked)
QtCore.QObject.connect(th.qt_object, QtCore.SIGNAL("job_done"), w.close)
w.show()
app.exec_()

Pyqt: Change icon from button

I'm just started with pyqt and i want to change the icon from a button.
But i'm dowing it from another class and pyqt don't like that.
The error is: QPixmap: It is not safe to use pixmaps outside the GUI thread
I know i must using singal en emits.
But i don't know how i must use it for change the icon from a button.
This my code now:
import sys
import time
from PyQt4 import QtGui, QtCore
from pymodbus.exceptions import ConnectionException
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from data.database import Tags, Query_Tags
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.db_engine = create_engine('mysql://modbususer:modbususer#localhost/modbus')
self.db_session = sessionmaker(bind=self.db_engine)
self.db_session = self.db_session()
self.status = 0
self.initUI()
def initUI(self):
self.button = QtGui.QPushButton('Aan', self)
self.button.clicked.connect(self.run)
self.button.move(50,50)
Uit = QtGui.QPushButton('Uit', self)
Uit.clicked.connect(self.off)
Uit.move(150,50)
self.setGeometry(300, 300, 500, 150)
self.setWindowTitle('Quit button')
self.show()
self.worker = WorkThread(self)
self.worker.start()
def run(self):
add_tag = Query_Tags('18', '1')
self.db_session.add(add_tag)
self.db_session.commit()
def off(self):
add_tag = Query_Tags('18', '0')
self.db_session.add(add_tag)
self.db_session.commit()
self.status = 0
print self.store
def change(self):
print "test"
#self.button.setIcon(QtGui.QIcon("/home/stijnb/test/icon.png"))
#self.button.setIconSize(QtCore.QSize(16,16))
def database(self, store):
self.store = store
"""
Thread
"""
class WorkThread(QtCore.QThread):
def __init__(self, layout):
QtCore.QThread.__init__(self)
self.layout = layout
def __del__(self):
self.wait()
def run(self):
self.database = {}
while True:
self.db_engine = create_engine('mysql://modbususer:modbususer#localhost/modbus')
self.db_session = sessionmaker(bind=self.db_engine)
self.db_session = self.db_session()
for result in self.db_session.query(Tags):
self.database[int(result.id)] = {'naam': result.name, 'value': result.value}
self.change_icons()
time.sleep(1)
return
self.terminate()
def change_icons(self):
print self.database[21]['value']
if self.database[21]['value'] == '1':
self.layout.button.setIcon(QtGui.QIcon("/home/stijnb/test/aan.png"))
self.layout.button.setIconSize(QtCore.QSize(16,16))
else:
self.layout.button.setIcon(QtGui.QIcon("/home/stijnb/test/uit.png"))
self.layout.button.setIconSize(QtCore.QSize(16,16))
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You cannot touch GUI elements from any other thread other than the main GUI Thread. That's why Qt introduced messaging (Signals and Slots). You can connect to a signal from your worker thread that will be caught in the main thread, and then in the main thread you can change any element you want.
Here is a very simple example where I demonstrate the concept.
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
# Create a button and set it as a central widget
self.button = QtGui.QPushButton('My Button', self)
self.setCentralWidget(self.button)
# Start the thread
self.worker = WorkThread(self)
self.worker.start()
# Connect the thread's signal "change_text" to
# "self.change_thread" function
self.worker.change_text.connect(self.change_text)
def change_text(self):
# Slots are always executed in the GUI thread so it's safe to change
# anything you want in a slot function. Here we just flip the button
# text from foo to bar and back. But you can change the image or do
# whatever you want.
# Slots can also receive parameters so you can emit from your thread
# which text should be set
self.button.setText('bar' if self.button.text() == 'foo' else 'foo')
class WorkThread(QtCore.QThread):
# Define the signal
change_text = QtCore.pyqtSignal()
def __init__(self, layout):
QtCore.QThread.__init__(self)
def run(self):
while True:
# emit the signal every 3 seconds
self.sleep(3)
self.change_text.emit()
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())

Categories