Signals and Slots PyQt clarification - python

I have noticed that there are a lot of users, myself included, who don't quite grasp the concept of signals and slots in Qt. I was hoping to get some clarification on the following:
#I have a function that runs as soon as the GUI is built, this takes the information from
#a list and puts it into a string which is then uploaded to a texbox. At the bottom of this
#loop, I want it to call a function in the parent thread via signals and slots, as
#recommended by other users.
class MainWindow(QtGui.QMainWindow):
#all the code needed to build the GUI
thread_mythread = threading.Thread(target = self.updateText, args = ())
thread_mythread.start()
def clearText(self):
self.TextEdit.clear()
def updateText(self):
self.trigger.connect(self.clearText)
while True:
self.trigger.emit()
NewString = list.pop(0)
#I think I may have to use append, as setText() is not safe outside of the parent thread
self.TextEdit.append(NewString)
Although probably terribly incorrect, I attempt to use signals. Is this the proper way to do it? I also get an error that says that the Main Window object has no attribute "trigger",why is this?
thank you.

The reason you get that error is exactly the reason described by the error message - the signal trigger has not been defined anywhere in your class. You need to define it before you can emit it.
Signals and slots are used to communicate between different objects. In your example you are trying to do everything from within your MainWindow class and there is no interaction with other objects. You also only need to make the call to connect() once. You would typically call this either in the class constructor or from your main function after instantiating the objects you want to connect together.
Take a look at http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html for some examples of how to use signals and slots properly in PyQt.
For threading, use QThread rather than threading.Thread as it is better integrated with the Qt framework. This post shows some simple examples of how to use QThread in PyQt. The second method (using moveToThread()) is considered to be the most correct way to create new threads.
The basic idea for your kind of problem is:
handle GUI operations from the main thread
handle blocking operations (in your case the while loop) in a separate thread
emit signals from the worker thread to call functions (slots) in the main thread and vice versa
Also note that:
You cannot call any methods of QWidget its descendents from a secondary thread
Signals can also send data if you need to pass it between threads

To add to #user3419537 good answer. A very quick threading example:
from PyQt4.QtCore import QObject, pyqtSlot, pyqtSignal, QThread, \
Q_ARG, Qt, QMetaObject
class MyWorker(QObject):
# define signal
clear = pyqtSignal()
update_text_signal = pyqtSignal(str) # passes a string back
finished = pyqtSignal()
def __init__(self, parent=None):
super(MyWorker, self).__init__(parent)
# Add functions etc.
#pyqtSlot(list)
def update_text(self, string_list):
#Intensive operation
self.clear.emit() # moved outside of while
while(True):
#This is infinite loop so thread runs forever
new_string = self.string_list.pop(0)
self.update_text_signal.emit(new_string) # Fixed this line
#Finished
self.finished.emit()
Then in your MainWindow class
self.my_thread = QThread()
self.handler = MyWorker()
self.handler.moveToThread(self.my_thread)
self.handler.clear.connect(self.clearText)
self.handler.update_text_signal.connect(self.update_line_edit)
self.handler.finished.connect(self.my_thread.quit)
# Start Thread
self.my_thread.start()
#pyqtSlot(str)
def update_line_edit(self, text):
self.TextEdit.append(text)
QMetaObject.invokeMethod(self.handler, 'update_text',
Qt.QueuedConnection,
Q_ARG(list, string_list))
You will need to call self.my_thread.quit() before your application closes to stop the thread and avoid the error: QThread: Destroyed while thread is still running
Please read docs for QMetaObject.invokeMethod.

Related

different ways of calling class method with button clicks

I am exploring how to call class methods in PyQt applications. As a test, I created a widget that initializes a worker, puts it on a separate thread, and starts the thread. I also created two buttons:
The left button connects to the run function of the worker directly
The right button connects to a widget method that calls the worker run function
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
from time import sleep
class Worker(QObject):
worker_finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot()
def run(self):
print("Sleeping")
for ii in range(5):
print(ii)
sleep(1)
print("Finished sleeping")
class MainApp(QWidget):
def __init__(self):
super().__init__()
self._threads = []
#Create the worker
self.worker = Worker()
layout = QHBoxLayout(self)
#Move the worker on the thread and start
self.put_on_thread_and_start(self.worker)
#This button calls worker.run() directly
button = QPushButton("Call \"\"run\"\" method directly")
button.clicked.connect(self.worker.run)
layout.addWidget(button)
#This button calls a widget method, which calls worker.run()
button = QPushButton("Call \"\"run\"\" method through this widget class method")
button.clicked.connect(self.make_the_worker_run)
layout.addWidget(button)
def put_on_thread_and_start(self, worker_class):
myThread = QThread()
self._threads.append(myThread)
worker_class.moveToThread(myThread)
print("Starting thread...")
myThread.start()
def make_the_worker_run(self):
self.worker.run()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
When I click the left button, the worker executes in the background, as expected. However, when I click the right button the widget freezes until the worker has finished running. What's the difference between the two approaches?
When a signal is emitted to a function (slot) and that signal is emitted, Qt detects if the signal emission and the receiving objects are in the same thread. If they are not, the slot is executed in the receiver's thread and control is immediately returned in the sender instead. This is what allows to use threads on Qt without blocking the "main Qt thread" (which is responsible of keeping the UI responsive).
A very important thing to understand is that Qt is able to detect what thread emitted the signal and on what thread the slot is, then it decides if the slot can be directly executed or not.
In your first case, the button (which is going to emit the signal) and the self.worker.run are on different threads and Qt knows that when it tries to call run; the result is that the function will be executed in the other thread.
In the second case, Qt only knows about make_the_worker_run, which from its perspective is in the same thread of the button: Qt doesn't know anything about what you actually do in that function. The fact that run is in a method of an object that has been moved to another thread doesn't mean anything, and the result is that the function will be executed in the main thread, hence the blocking.
Read more about this topic on the Qt docs about Threads and QObjects.
There is an incorrect conception of how threads, QThread, signals, etc. work.
When a QObject is moved to a QThread it only tells the Qt eventloop that if it invokes a slot of that QObject it must be executed in the thread that manages the QThread (if the Qt::QueuedConnection or Qt::AutoConnection flag is used in the connection). And how does the Qt eventloop invoke the functions? Well, for this use the signals, timers, QMetaObject::invokedMethod, QEvents, etc.
The above explains why the first method works: By using the clicked signal to invoke "run" it notifies the eventloop that it should invoke the method, and using the previous rule it will invoke the thread that manages the QThread.
In the second method instead you are invoking it directly in the main thread which blocks the eventloop of the main thread freezing the GUI.

Real-Time-Plotting using pyqtgraph and threading

this is a bit longer, the first part is just a description of the problem, the second one the question if my "fix" is correct.
I started with python programming. I created a program that communicates with an Arduino that reads the temperature of a furnace of our melting lab. The temperature is then used in a PID algorithm and an output is set to the Arduino. The communication is done via pyserial. So far, everthing works, including live plotting of the temperature signals, PID-variables and so on. The script includes a the main loop and 3 threads (serial communication, a datashifter that reads from serialport, the set temperature from the QWidget and the output of the PID algorithm. This values are used to create an array for displaying within pyqtgraph. Finally, the third thread shifts the data from the datashifter to the QWidget.
When using my Linux-Notebook, everything works fine, and the GUI never stops updating. In contrast, when using any Windows-Host, i have the problem that some pyqtgraphs stop to refresh. The behavior is strange, because i set all data at more or less the same time, with the same numpy array (just different columns) - some plots refresh longer (hours), some stop earlier (minutes). After searching more or less the hole internet ;-) I think that I found the problem: Its the passing of data from from a thread to the GUI. Some dummy code to explain what's going on:
DataUpdaterToGUI(QThread):
#sets the QWidget from main loop
def setGUI(self, gui):
self.gui = gui
def run()
while True:
with lock(): # RLock() Instance
copyArray = self.dataArray[:] # copy the array from the shifter
self.gui.plot1.copyArray(dataArray[:, 0], copyArray[:, 1])
self.gui.plot2.copyArray(dataArray[:, 0], copyArray[:, 2])
# self.gui.update()
# QApplication.instance().processEvents()
Neither calling self.gui.update() nor processEvents() has any influence on the outcome: The plots stop redrawing after a while (on windows).
Now i have a very simple example, and just want to make sure if I'm using the threading-stuff correctly. It works fine, but I have some questions:
Does the signal-slot approach copy the passed data?
Why is it not necessary to call the update() method of the QWidget?
Do I have to use any kind of locks when using signals?
class Main(QWidget):
def __init__(self):
super().__init__()
self.layout = QGridLayout(self)
self.graph = pg.PlotWidget()
self.graph.setYRange(0,1000)
self.plot = self.graph.plot()
self.layout.addWidget(self.graph,0,0)
self.show()
def make_connection(self, data_object):
data_object.signal.connect(self.grab_data)
#pyqtSlot(object)
def grab_data(self, data):
print(data)
self.plot.setData(data)
class Worker(QThread):
signal = pyqtSignal(object)
def __init__(self):
super().__init__()
def run(self):
self.data = [0, 1]
i = 2
while True:
self.data[1] = i
self.signal.emit(self.data)
time.sleep(0.01)
i += 1
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Main()
worker = Worker()
widget.make_connection(worker)
worker.start()
sys.exit(app.exec_())
Does the signal-slot approach copy the passed data? The signals are thread-safe and when transferring data they make a copy so the thread that precedes the data and the thread that consumes it (GUI Thread) will not have conflicts
Why is it not necessary to call the update() method of the QWidget? Actually pyqtgraph calls the update method, plot is a PlotDataItem, so if we check the source code of setData() method, it calls the updateItems() method, in that method the setData() method of the curve or scatter attribute is called (according to the type of graphics), in the case of curve its setData() method calls updateData(), and the updateData() method calls update, and in the case of the scatter its setData() method calls addpoint(), and addPoints() calls invalidate(), and this invalidate() method calls update().
Do I have to use any kind of locks when using signals? No, as the signals are thread-safe so Qt already has the guards set to avoid the collision.

PyQT force update textEdit before calling other function

My question concerns PyQT5.
I try to have a dialog window with a button that when clicked
updates some text of a QTextEdit field
calls a function (which needs much time to terminate)
Something like this:
class StartDialog(QtWidgets.QWidget, start_dialog_ui.Ui_Dialog):
def __init__(self, parent):
super(self.__class__, self).__init__()
self.setupUi(self)
self.OKButton.clicked.connect(self.start)
def start(self):
self.startDialogTextEdit.append("simulation running ...")
run_lengthy_function(self)
However, when I run my GUI I notice that the text is updated only after the lengthy function has terminated, although the QTextEdit.append is called before the lengthy function. How can I enforce that the text is updated in advance?
What I tried so far (but didn't work) was to let Python wait some time before triggering the lengthy function call, i.e.
from time import sleep
class StartDialog(QtWidgets.QWidget, start_dialog_ui.Ui_Dialog):
def __init__(self, parent):
super(self.__class__, self).__init__()
self.setupUi(self)
self.OKButton.clicked.connect(self.start)
def start(self):
self.startDialogTextEdit.append("simulation running ...")
sleep(5)
run_lengthy_function(self)
The repaint is called in event loop so sleep the whole thread does not change anything.
You can call repaint manually by:
self.startDialogTextEdit.repaint()
or call static method:
QCoreApplication.processEvents()
which also call repaint internally
The solution for the case that the text is displayed in the QTextEdit is to call qApp.processEvents(), this force to the GUI update:
def start(self):
self.startDialogTextEdit.append("simulation running ...")
QtWidgets.qApp.processEvents()
[...]
On the other hand if the task is heavy it may be blocking the GUI, so maybe one solution is to run it on another thread, I can not give a proper recommendation since I do not know your function

How to move an object back and forth between QThreads in Pyqt

In my program (using Python 2.7), I create an object containing some important data and methods. Some of the methods are CPU hungry, so in certain cases I move the object to a new QThread for the duration of the CPU intensive methods, then have them come back to the main thread. At a later point, when a CPU intensive method is called, I would like to move the object to another QThread again, however this fails saying "Current thread is not the object's thread".
Here is a trivial example which reproduces the problem:
import sys
from PyQt4 import QtCore, QtGui
from time import sleep
class ClassA(QtGui.QDialog):
def __init__(self):
super(ClassA, self).__init__()
mainLayout=QtGui.QVBoxLayout()
self.lineEdit=QtGui.QLineEdit()
mainLayout.addWidget(self.lineEdit)
self.setLayout(mainLayout)
self.show()
self.obj=ClassC(self)
self.executeProgram()
def executeProgram(self):
self.lineEdit.setText("Starting new thread...")
self.thread=QtCore.QThread()
self.obj.moveToThread(self.thread)
self.thread.started.connect(self.obj.doWork)
self.obj.doingWork.connect(self.updateGui)
self.obj.finished.connect(self.killThread)
self.thread.start()
def updateGui(self,message):
self.lineEdit.setText(message)
def killThread(self):
self.thread.quit()
self.thread.wait()
self.obj.finished.disconnect()
self.executeProgram()
class ClassC(QtCore.QObject):
finished=QtCore.pyqtSignal()
doingWork=QtCore.pyqtSignal(str)
def __init__(self,parent=None):
super(ClassC, self).__init__()
def doWork(self):
for i in range(5):
self.doingWork.emit("doing work: iteration "+str(i))
sleep(1)
self.finished.emit()
if __name__=="__main__":
app=QtGui.QApplication(sys.argv)
obj=ClassA()
app.exec_()
Is it possible to move an object to a different QThread multiple times? If so, how would I fix my code to do this?
Note moveToThread must be called on the thread that the object currently belongs to, so you may need to move the object back to the main thread before moving it to yet another thread.
Add the line mainThread = QtCore.QThread.currentThread() somewhere at the top, and put self.moveToThread(mainThread) right before emitting the finished signal.

Wrap a Python class in an asynchronous PyQt class

I am developing my pet project. It is a small app which downloads my inbox folder from Facebook. I want it to be available in both CLI and GUI (in PyQt) mode. My idea was that I write a class for the communication, and then the front-ends. I know that the downloading process is blocking which is not problem in CLI mode but it is in GUI.
I know that there is QNetworkAccessManager but I would have to re-implement the class that I have already written and then maintain two classes the same time.
I was searching for a while and I came up with one solutions where I subclass QObject and my FB class, implement the signals, than create a QThread object and use moveToThread() method which would be OK, but I must take care of creating and stopping the tread.
Is it possible to wrap my Python class to something to make it behave like QNetworkAccessManager? So the methods would return immediately and the object will emit signals when the data is ready.
Update
Thank you the comments. I have 2 main classes. The first is called SimpleGraph which just hides urllib2.urlopen(). It prepares the query and returns the decoded json got from Facebook. The actual work is happening in FBMDown class:
class FBMDown(object):
def __init__(self, token):
self.graph = SimpleGraph(token)
self.last_msg_count = 0
def _message_count(self, thread_id):
#TODO: Sanitize thread_id
p = {'q': 'SELECT message_count FROM thread WHERE thread_id = {0} LIMIT 1'.format(thread_id)}
self.last_msg_count = int(self.graph.call(params=p, path='fql')['data'][0]['message_count'])
return self.last_msg_count
So here when _message_count is called it returns the number of messages of the given thread id. This method works great in CLI mode, blocking is not a problem there.
I want to wrap this class into a class (if it is possible) which works asynchronous like QNetworkAccessManager, so it would not block the GUI but it would emit a signal when the data is ready.
The only technique I know now is to subclass QObject. It looks like this:
class QFBMDown(QtCore.QObject):
msg_count_signal = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def get_msg_count(self):
#Here happens the IO
time.sleep(2)
self.msg_count_signal.emit(1)
And this is my Windows class:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.centralwidget = QtGui.QWidget(self)
self.button_getmsgcount = QtGui.QPushButton(self.centralwidget)
self.setCentralWidget(self.centralwidget)
self.i = 0
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.block_indicator)
self.timer.start(20)
#self.worker = QtCore.QThread()
self.fbobj = QFBMDown()
#self.fbobj.moveToThread(self.worker)
#self.worker.start()
self.button_getmsgcount.clicked.connect(self.fbobj.get_msg_count)
self.fbobj.msg_count_signal.connect(self.show_msg_count)
def block_indicator(self):
self.button_getmsgcount.setText(str(self.i))
self.i += 1
def show_msg_count(self, data):
print 'Got {0} msgs in the thread'.format(data)
Now when I press the button the GUI blocks. If I de-comment line 13, 15, 16 (where I create a thread, move fbobj into this thread then start it) it does not block, it works nearly as I want but I have to do everything by hand every time and I have to take care of shutting down the thread (now I implement QMainWindow's closeEvent method where I shut down the thread before quit but I'm sure that this is not the right way to do).
Is it even possible what I want to do? I would not want to implement a new class and then maintain two classes which are doing the same thing.
The reason I am so insisted to do it like this is to make my app work without Qt but give a nice gui for those who don't want to type command line arguments.

Categories

Resources