Transmit extra data with signals in PyQt
Signals are a neat feature of Qt that allow message-passing between different areas of your program. To use a signal you attach a function to be called in the event of the signal firing, usually accepting a small item of data about the signal state.
However, there is a limitation: the signal can only emit the data it was
designed to do. So for example, a
QAction has a
fires when that particular action has been activated. Unfortunately the
receiving connected function only receives one thing:
False. In other words, the receiving function has no way of knowing
which action triggered it.
This is usually fine. You can tie a particular action to a particular function. However, sometimes you want to trigger multiple actions off the same type of action, and treat them differently. Here’s a neat trick to do just that.
Instead of binding the target function to the signal, you can instead bind a wrapper function that accepts the original signal, attaches some more data, then passes it on. The code to do this (using a lambda) would be:
lambda checked: self.onTriggered(checked, <object>)
Here we take the
checked signal, add the object it’s come from, then
pass it onto the handler. All we need to do is set the object correctly
when building the connect:
action = QAction() action.triggered.connect( lambda checked: self.onTriggered(checked, action) )
onTriggered handler can receive the calling action along with
the check state when it’s triggered.
Ready to build your own apps?
Create Simple GUI Applications with Python and Qt
The complete beginners guide to building cross-platform GUI applications with Python. Step by step from displaying your first window, to fully functional and useable software.
Join 300+ readers already using this book to build awesome things.
77 page ebook, 2.5 hours of video tutorials
Unfortunately things aren’t always that simple. If you try and build multiple actions like this by looping over a set of objects you’ll get bitten. Here we’re creating a series of actions, and trying to pass the sequence number with the signal.
for a in range(0, 10): action = QAction() action.triggered.connect( lambda checked: self.onTriggered(checked, a) )
However, when the lambda is evaluated the value of
a will be set to the value it had
at the end of the loop, so clicking any of them will result in the same value being sent (here 9).
The solution is to pass the value in as a named parameter. By doing this the value is bound at the time the lamdba is created, and will hold the correct value whenever it is called.
lambda checked, a=a: self.onTriggered(checked, a) )
Putting this into a loop, it would look like this:
for a in range(0, 10): action = QAction() action.triggered.connect( lambda checked, a=a: self.onTriggered(checked, a) )
Here’s an example of me doing exactly that to handle outputting a list of QAction labels into a QMenu for the visual editor in Pathomx
for wid in range( self.app.views.count() ): if self.app.views.widget(wid).is_floatable_view: ve = QAction(self.app.views.tabText(wid), o) ve.triggered.connect( lambda n, wid=wid: self.onAddView(n, wid) ) vmenu.addAction(ve)
Get regular GUI tips & tutorials direct to your Inbox.