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
.triggered that 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.
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)
- Multithreading PyQt applications with QThreadPool
- QColorButton: A color-selector tool for PyQt
- cx_Freeze and PySide on Mac
- Create Simple GUI Applications with Python and Qt
Get my latest Python projects, tips & tutorials direct to your Inbox.