How To Use Pyqt Signals And Slots

 
Slots

Recently, I was writing an integration test for a data visualization GUI. The GUI calls out to an external process to run a simulation, waits for the simulator to produce data, and reads the data back in to produce pretty graphs, export data to other formats, etc. I use a PySide signal to inform the GUI that the simulator has produced data. Unfortunately, I couldn’t find a clean way to have my integration test wait for that signal. Luckily, I have found the solution, and I’ll explain it here.

  1. All the predefined signals & slots provided by pyqt are implemented by QT's c code. Whenever you want to have a customized signal & slot in Python, it is a python signal & slot. Hence there are four cases to emits a signal to a slot: from a QT signal to a QT slot.
  2. To establish a signal and slot connection between two widgets in a dialog, you first need to switch to Qt Designer's Edit Signals/Slots mode. To do that, you can press the F4 key, select the EditEdit Signals/Slots option in the main menu, or click on the Edit Signals/Slots button on the toolbar.
  3. Signals and Slots. PySide and PyQt are Python bindings to the Qt GUI and application framework. One killer feature of Qt is the signal & slot system, which is a way for widgets and objects to communicate events to one another. An object in Qt can send a signal to other subscribed objects. Signals are used to inform other objects that an event.
  4. Events and signals, on the other hand, are central to PyQt. Signals and slots are used to connect one object to another. An example is the perennial pushbutton, whose clicked signal gets connected to the accept slot function of a dialog box. Signals are used to connect entities internal to the application.

Signals and Slots

PyQt4 has a unique signal and slot mechanism to deal with events. Signals and slots are used for communication between objects. A signal is emitted when a particular event occurs. A slot can be any Python callable. A slot is called when a signal connected to it is emitted. PyQt4.5 introduced a new style API for working with signals.

PySide and PyQt are Python bindings to the Qt GUI and application framework. One killer feature of Qt is the signal & slot system, which is a way for widgets and objects to communicate events to one another.

How To Use Pyqt Signals And Slots Free

An object in Qt can send a signal to other subscribed objects. Signals are used to inform other objects that an event has occurred. For example, buttons fire the QPushButton.pressed signal when they are clicked. When you want to handle a button click, you connect the signal to a function. You can create your own signals, and connect them to arbitrary python functions. In my GUI, I have something like the following:

In this code snippet, we create the Simulator and DataReader classes, and connect them together using a signal. The Simulator class is a QObject, so it can send signals. It runs a simulation in a separate process, and then emits the finished signal when it’s done. We also happen to use a button that starts the simulation when clicked. We connect the simulator’s finished signal to the DataReader so it knows when to begin reading data.

The Test

So, how do I test the above code? I want to run the simulator and make sure it has the correct output. I initially wrote a test like this:

What’s wrong here? The assertion line is executed too early; the simulator probably hasn’t even started in the nanoseconds between when I told it to start and when I check the data! How can we solve this issue? I tried a time.sleep() loop that polls if the simulation is complete. This blocks the main GUI thread, and is not a good solution. I also tried similar things using the threading module, but to no avail. It turns out, we can use a new Qt event loop to stop execution.

Solution

How to use pyqt signals and slots key

I figured out that you can call QEventLoop.exec_() to create a nested event loop. That is, our QApplication instance event loop is already running, but calling QEventLoop.exec_() will stop execution using a new event loop until QEventLoop.quit() is called. Here is our modified example test:

We create an event loop that will help us wait for our simulation to complete. When run_simulation() is called, we wait at the loop.exec_() line until finished is emitted. When the simulator finishes, the event loop will exit, and we will advance beyond the loop.exec_() line.

General Solution

There is one problem with this approach. What if simulator.finished is never emitted, like when an error occurs? We would never advance beyond loop.exec_(). If we had this test running on a continuous integration server, we would lock up a job forever until we realized the test never finished!

A solution is to use the QTimer.singleShot() function with a timeout. That means for every time we want to create an event loop, we have to set up the loop, hook up our signals, and create a timer with a suitable timeout in case the signals never fire. Here is a context manager that can handle this for us:

We can use it like this:

Isn’t that great? I think so.

pytest-qt

How To Use Pyqt Signals And Slots Games

I run my tests using the pytest library. There is a plugin for pytest called pytest-qt, which comes with a fixture called qtbot. A qtbot is used to handle all of the boilerplate that comes along with testing PySide/PyQt code, like:

  • Setting up a QApplication
  • Simulating mouse clicks and keyboard interaction
  • Closing windows after tests

Once I figured out how to stop test execution to wait for a signal, I created a pull request to add this functionality to pytest-qt. I created a more general solution that allows multiple signals, or none at all (just wait for a timeout). If you use PySide/PyQt, but you think testing your GUI code is a pain, check out pytest-qt! Take a look at the example in the docs to see how to use pytest-qt to block tests for signals.

Thanks for reading!

Please enable JavaScript to view the comments powered by Disqus.comments powered by DisqusI was to lazy to take a look at the new-styleUse signal and slot support which was introduced in PyQt 4.5 until yesterday. I did know that there were something called new-style signals and slots but that was the end of the story. Now I have taken the time and I think it's a cleaner solution than the old-style.
I'll just give you a short intro to whet your appetite, find all details here yourself.
This is the old way of connecting a signal to a slot. To use the new-style support just replace line 11 with following code
The new-style support introduces an attribute with the same name as the signal, in this case clicked

How To Use Pyqt Signals And Slots Using

.
If you need to define your own signal you'll do something like this (off the top of my head):
How to use pyqt signals and slots freeAnd the old way:
IMHO the new-style

Pyqt Signals And Slots

support is more pythonic and you don't have to specify your signals as strings when connecting. If you use pydev (eclipse) you'll also have completion support for signals.