PyKDE4: new style signals and slots
Those who use PyQt and PyKDE4 are certainly familiar with the syntax used to connect signals and slots:
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyKDE4 import kdeui
class MyGUI(QtGui.QWidget):
def __init__(self, parent=None):
super(MyGUI, self).__init__(parent)
self.pushbutton = kdeui.KPushButton()
self.pushbutton.setText("Push me!")
QObject.connect(self.pushbutton, QtCore.SIGNAL("clicked()"),
self.button_pushed)
def button_pushed(self):
print "Button clicked"
The main advantage of this syntax is that it’s very close to the C++ equivalent, and so you can translate easily from C++ to Python. Unfortunately the advantages of this syntax end here. The disadvantages, at least from a Python coding perspective, outweigh the advantages:
- It’s extremely error-prone: make a typo, and not only your signal won’t be connected, but you won’t even get a warning, your program will simply do nothing;
- In case you have overloaded signals, you have to type the exact signature, going back to the first problem;
- It’s not Pythonic at all.
So, in recent PyQt versions (and thus also in PyKDE4) a new style approach was introduced (although the old style is always present should it be the need to). Using the new style, the signals become a property of the object that emits them. and then you use the connect function of that property. Here’s the example using the new style-signals:
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyKDE4 import kdeui
class MyGUI(QtGui.QWidget):
def __init__(self, parent=None):
super(MyGUI, self).__init__(parent)
self.pushbutton = kdeui.KPushButton()
self.pushbutton.setText("Push me!")
# New style
self.pushbutton.clicked.connect(self.button_pushed)
def button_pushed(self):
print "Button clicked"
As you can see it’s much clearer, and much more Pythonic. Also, typos will trigger an AttributeError, which means you’ll be able to track where the problem is.
What about overloaded signals? Normally the first defined is the default, but you can use a dictionary-like syntax to access other overloads (signal names are completely made up here):
# One signal is without arguments, the other has a bool # Signal without arguments self.my_widget.connected.connect(self.handle_errors) # Signal with a book self.my_widget.connected[bool].connect(self.handle_errors)
Signals are emitted with the emit() function and disconnected with the disconnect() function:
# Emit a signal self.pushbutton.clicked.emit() # Emit a signal with a value (an int) self.my_widget.valueChanged.emit(int) # Disconnect another self.my_tabwidget.currentIndexChanged.disconnect()
To define new signals, you can use the pyqtSignal function, specifying which values will the signal take (if any): just define that as a class constant (like in the example) and then you can access them like the wrapped ones:
class MyWidget(QWidget):
# Signal with no arguments
operationPerformed = QtCore.pyqtSignal()
# Signal that takes arguments
valueChanged = QtCore.pyqtSignal(int)
I merely scratched the surface with this. For more information, check out PyQt’s reference manual, which also covers other cases.
Hi,
i’m unable to find a way to do a really simple thing in PyQt, maybe you could help me ?
I have a QScrollwidget containing a lot of QWidgets. Each QWidget have a UID attribute. I would like the following thing : when I click on a QWidget, it will emit a signal containing the UID argument.
Actually, each QWidget represents an email, and when I click on an email-widget, I would like to open it in another window, using his UID.
Do you have an idea ?
Regards,
gml
I’m not sure if QWidget has the clicked() signal: if it doesn’t have it, you’ll have to implement it yourself in the appropriate event. As for the rest, you just define a signal in your class
openWindow = QtCore.pyqtSignal(int)
(assuming that you store the ID as an int)
then something like this, in the appropriate place of the code(assuming you have the id in “my_id”):
self.widget.clicked.connect(self.openWindowl.emit(my_id))
Indeed, QWidget doesn’t have clicked(), i’ve implemented it.
I’ll try your solution as soon as i can, thanks you !
It doesn’t seem to work, or i’m just too new to Qt to understand…
I use this to make QWidget clickable : http://diotavelli.net/PyQtWiki/Making%20non-clickable%20widgets%20clickable to make my QWidget clickable.
Then, here is my code :
self.openWindow = QtCore.pyqtSignal(int)
self.clickable(message).connect(self.openWindow.emit(11))
And when I try it :
self.clickable(message).connect(self.openWindow.emit(11))
AttributeError: ‘PyQt4.QtCore.pyqtSignal’ object has no attribute ‘emit’
What i’m doing wrong ?
Ok, when I set :
openWindow = QtCore.pyqtSignal(int)
within my class (not the Widget class, the upper one), but outside the __init__, it works.
Then I get another problem :
uid = 1
message.button.clicked.connect(self.openWindow.emit(uid))
(I’ve used message.button, which is a QPushButton, to be sure that the problem doesn’t come from clickable or not)
I get this :
message.button.clicked.connect(self.openWindow.emit(uid))
TypeError: connect() slot argument should be a callable or a signal, not ‘NoneType’
It doesn’t make sense for me :/
I found out the reason: it must be a callable, and instead (my bad, because I didn’t realize it) you call the function inside, which returns None as it’s a signal. So, to fix this you have two options:
1. Use partial from functools:
from functools import partial
uid = 1
wrapped_slot = partial(self.openWindow, uid)
self.message.button.clicked.connect(wrapped_slot)
2. Make a whole slot for it, but in that case you need to put uid in an instance variable or something:
self._uid = 1
self.message.button.clicked.connect(self.open_window)
def open_window(self):
self.openWindow.emit(self._uid)
Thanks you, I’ll try it. This night I’ve avoid the problem by a work-around : keeping updated a list of email widgets, then when the clicked event is trigged, searching for the email which is set “selected” with the mouse events.
Not really beautiful and efficient, but it works.
I will try your solution this afternoon, it can reduce my code size a little, and it’s much more sexy. Thanks again for your help and your patience !