Connecting with signals and slots
Signals and slots come in two basic
varieties: Vanilla, or C++ signals and slots (as defined in the
Qt library) and Pythonic (signals and slots defined in Python).
Any function of any object can be used as a slot in Python (you
don't even have to inherit from QObject).
This contrasts to C++, where you need to specially mark a
function as a slot in order to be able to connect it to a signal
(and have to inherit QObject).
Every class that descends from
QObject is eligible for the sending
(emitting is the technical term) and
connecting of signals to its own methods. That means that if
your Python class is to emit signals it has to ultimately
inherit QObject.
Connections are made using the
connect() method. This is a class method of
QObject, and you can, according to your
preference, use the method on QObject, or
on the actual object you're working with.
You can connect signals to slots, but also
to other signals, creating a chain of notifications. If you want
to disconnect signals from slots, you can use
QObject.disconnect().
If you want to emit signals from a Python object, you can use
the QObject.emit()
function.
The connect function can take the following
parameters:
sender — the QObject that will
send the signal.
signal — the signal that must be connected
receiver — the QObject that has
the slot method that will be called when the signal is
emitted.
slot — the slot method that will be called when the
signal is emitted.
If you're connecting your signals from within a class, you can
often omit the third parameter — the receiver.
PyQt defines three special functions that
appear to be macros (because of their all-caps spelling, as in
C++) but are in fact just functions. (In fact, there are no
macros in Python). These are SLOT(),
SIGNAL() and
PYSIGNAL().
Two of these functions are meant for
signals and slots defined in C++; the other is meant for signals
defined in Python. Signals and slots defined in C++ are
connected on the level of C++ (i.e., not in the sip registry)
and can be a bit faster.
The first function is
SLOT(), which marks its only argument, a
string, as a slot defined in the Qt library, i.e. in C++. The
corresponding SIGNAL, which also has one
string argument, marks its argument as a signal as defined in
Qt.
For instance, from the documentation of
QListview we can learn that this class
possesses the slot invertSelection(). From the
documentation of QButton we learn that it
can emit a signal clicked(). We can
connect a button press to this slot as follows:
Example 7-4. Connecting a signal to a slot
#
# lsv.py - connect a button to a listview
#
import sys
from qt import *
class MainWindow(QMainWindow):
def __init__(self, *args):
apply(QMainWindow.__init__, (self, ) + args)
self.mainWidget=QWidget(self);
self.vlayout = QVBoxLayout(self.mainWidget, 10, 5)
self.lsv = QListView(self.mainWidget)
self.lsv.addColumn("First column")
self.lsv.setSelectionMode(QListView.Multi)
self.lsv.insertItem(QListViewItem(self.lsv, "One"))
self.lsv.insertItem(QListViewItem(self.lsv, "Two"))
self.lsv.insertItem(QListViewItem(self.lsv, "Three"))
self.bn = QPushButton("Push Me", self.mainWidget)
self.vlayout.addWidget(self.lsv)
self.vlayout.addWidget(self.bn)
QObject.connect(self.bn, SIGNAL("clicked()"),
self.lsv, SLOT("invertSelection()"))
self.setCentralWidget(self.mainWidget)
def main(args):
app=QApplication(args)
win=MainWindow()
win.show()
app.connect(app, SIGNAL("lastWindowClosed()")
, app
, SLOT("quit()")
)
app.exec_loop()
if __name__=="__main__":
main(sys.argv)
Note that the arguments of SIGNAL and SLOT
are used as an index of the dictionary
sip keeps of available slots and
signals, and that you should match the definition of the signal
and slot as given in the class documentation exactly.
A more complicated signal/slot combination
can pass an integer along (or even a complete object). Let's
connect the knob of a QDial to a few
functions, creating an color dialer. A
QDial generates the
valueChanged(int) signal, which passes the
current value of the dial in the form of an integer to every
slot that's connected to the signal. You need to explicitly
enter the types of the signal arguments, but not their
names.
Example 7-5. Connection a dial to a label with signals and slots
#
# dial.py — connecting a QDial to a QLabel or two
#
import sys
from qt import *
class MainWindow(QMainWindow):
def __init__(self, *args):
apply(QMainWindow.__init__, (self, ) + args)
self.vlayout = QVBoxLayout(self, 10, 5)
self.hlayout = QHBoxLayout(None, 10, 5)
self.labelLayout=QHBoxLayout(None, 10, 5)
self.red = 0
self.green = 0
self.blue = 0
self.dialRed = QDial(0, 255, 1, 0, self)
self.dialRed.setBackgroundColor(QColor("red"))
self.dialRed.setNotchesVisible(1)
self.dialGreen = QDial(0, 255, 1, 0, self)
self.dialGreen.setBackgroundColor(QColor("green"))
self.dialGreen.setNotchesVisible(1)
self.dialBlue = QDial(0, 255, 1, 0, self)
self.dialBlue.setBackgroundColor(QColor("blue"))
self.dialBlue.setNotchesVisible(1)
self.hlayout.addWidget(self.dialRed)
self.hlayout.addWidget(self.dialGreen)
self.hlayout.addWidget(self.dialBlue)
self.vlayout.addLayout(self.hlayout)
self.labelRed = QLabel("Red: 0", self)
self.labelGreen = QLabel("Green: 0", self)
self.labelBlue = QLabel("Blue: 0", self)
self.labelLayout.addWidget(self.labelRed)
self.labelLayout.addWidget(self.labelGreen)
self.labelLayout.addWidget(self.labelBlue)
self.vlayout.addLayout(self.labelLayout)
QObject.connect(self.dialRed, SIGNAL("valueChanged(int)"),
self.slotSetRed)
QObject.connect(self.dialGreen, SIGNAL("valueChanged(int)"),
self.slotSetGreen)
QObject.connect(self.dialBlue, SIGNAL("valueChanged(int)"),
self.slotSetBlue)
QObject.connect(self.dialRed, SIGNAL("valueChanged(int)"),
self.slotSetColor)
QObject.connect(self.dialGreen, SIGNAL("valueChanged(int)"),
self.slotSetColor)
QObject.connect(self.dialBlue, SIGNAL("valueChanged(int)"),
self.slotSetColor)
def slotSetRed(self, value):
self.labelRed.setText("Red: " + str(value))
self.red = value
def slotSetGreen(self, value):
self.labelGreen.setText("Green: " + str(value))
self.green = value
def slotSetBlue(self, value):
self.labelBlue.setText("Blue: " + str(value))
self.blue = value
def slotSetColor(self, value):
self.setBackgroundColor(QColor(self.red, self.green, self.blue))
self.labelRed.setBackgroundColor(QColor(self.red, 128, 128))
self.labelGreen.setBackgroundColor(QColor(128, self.green, 128))
self.labelBlue.setBackgroundColor(QColor(128, 128, self.blue))
def main(args):
app=QApplication(args)
win=MainWindow()
win.show()
app.connect(app, SIGNAL("lastWindowClosed()")
, app
, SLOT("quit()")
)
app.exec_loop()
if __name__=="__main__":
main(sys.argv)
Note that we connect the C++ signals
(SIGNAL), to Python functions. You simply give the function
object as the slot argument— not the result of the
function call. Consider the difference between:
QObject.connect(self.dialBlue,
SIGNAL("valueChange(int)"),
self.slotSetColor())
which is wrong, and:
QObject.connect(self.dialBlue,
SIGNAL("valueChange(int)"),
self.slotSetColor)
which is right. All that difference for two little brackets!
This is a rather frequent typo or thinko. (However, to give you a
glimpse of the dynamic nature of Python, if you have a function
that returns the correct function to connect to the signal, you
do want a function call in
connect().)
Note also that the number and type of arguments of the
signal and the slot you want to connect have to match. When
connecting C++ signals to C++ slots, there is also a bit of
type-checking done.
Python signals are indicated by the
PYSIGNAL() function, which also takes a
string. There is no PYSLOT() function
corresponding to SLOT(), because you can
use any function as a slot in
Python.
The argument of PYSIGNAL() is a simple
string that is unique for the class from which the signal is
emitted. It performs the same function as the
occasion string in the small
registry.py script. The difference is that
PYSIGNAL() string needs to be unique only
for the class, and not the whole application.
Connecting to a Python signal doesn't
differ much from connecting to a C++ signal, except that you
don't have to worry so much about the type and number of
arguments of the signal. To rewrite the
registry.py example:
Example 7-6. Python signals and slots
#
# sigslot.py — python signals and slots
#
from qt import *
class Button(QObject):
def clicked(self):
self.emit(PYSIGNAL("sigClicked"), ())
class Application(QObject):
def __init__(self):
QObject.__init__(self)
self.button=Button()
self.connect(self.button, PYSIGNAL("sigClicked"),
self.doAppSpecificFunction)
self.connect(self.button, PYSIGNAL("sigClicked"),
self.doSecondFunction)
def doAppSpecificFunction(self):
print "Function called"
def doSecondFunction(self):
print "A second function is called."
app=Application()
app.button.clicked()
Running this example from the command line gives the
following output:
/home/boudewijn/doc/pyqt/ch6 $ python sigslot.py
A second function is called.
Function called
The Button emits the
Python signal. Note the construction: the second argument to the
emit function is a
tuple that contains the arguments you want
to pass on. It must always be a tuple, even if it has to be an
empty tuple, or a tuple with only one element. This is shown in
the next example, in which we have to explicitly create an empty
tuple, and a tuple with one element from a single argument, by
enclosing the argument in brackets and adding a comma:
Example 7-7. Python signals and slots with arguments
#
# sigslot2.py — python signals and slots with arguments
#
from qt import *
class Widget(QObject):
def noArgument(self):
self.emit(PYSIGNAL("sigNoArgument"), ())
def oneArgument(self):
self.emit(PYSIGNAL("sigOneArgument"), (1, ))
def twoArguments(self):
self.emit(PYSIGNAL("sigTwoArguments"), (1, "two"))
class Application(QObject):
def __init__(self):
QObject.__init__(self)
self.widget = Widget()
self.connect(self.widget, PYSIGNAL("sigNoArgument"),
self.printNothing)
self.connect(self.widget, PYSIGNAL("sigOneArgument"),
self.printOneArgument)
self.connect(self.widget, PYSIGNAL("sigTwoArguments"),
self.printTwoArguments)
self.connect(self.widget, PYSIGNAL("sigTwoArguments"),
self.printVariableNumberOfArguments)
def printNothing(self):
print "No arguments"
def printOneArgument(self, arg):
print "One argument", arg
def printTwoArguments(self, arg1, arg2):
print "Two arguments", arg1, arg2
def printVariableNumberOfArguments(self, *args):
print "list of arguments", args
app=Application()
app.widget.noArgument()
app.widget.oneArgument()
app.widget.twoArguments()
Note the usage of the *arg argument
definition. This Python construct means that a variable length
list of un-named arguments can be passed to a function. Thus
printVariableNumberOfArguments(self, *args)
fits every signal that you care to connect it to.
It's an interesting test to run this script
several times: you will notice that the order in which the
signals generated by twoArguments() arrive
at their destination is not fixed. This means that if a signal
is connected to two or more slots, the slots are not called in
any particular order. However, if two signals are connected to
two separate slots, then the slots are called in the order in
which the signals are emitted.
The following combinations of arguments to the
connect() function are possible:
Table 7-1. Matrix of
QObject.connect()
combinations.
Signal
| Connected to
| Syntax (Note that you can replace
QObject.connect() by
self.connect() everywhere.)
|
---|
C++
| C++ slot of another object
| QObject.connect(object1, SIGNAL("qtSignal()"), object2,
SLOT("qtSlot()"))
|
C++
| C++ slot of ‘self' object
| QObject.connect(object1, SIGNAL("qtSignal()"), SLOT("qtSlot()"))
|
C++
| Python slot of another object
| QObject.connect(object1, SIGNAL("qtSignal()"), object2,
pythonFunction)
|
C++
| Python slot of ‘self' object
| QObject.connect(object1, SIGNAL("qtSignal()"),
self.pythonFunction)
|
C++
| C++ signal of another object
| QObject.connect(object1, SIGNAL("qtSignal()"), object2,
SIGNAL("qtSignal()")
|
C++
| C++ signal of ‘self' object
| QObject.connect(object1, SIGNAL("qtSignal()"), self,
SIGNAL("qtSignal()")
|
Python
| C++ slot of another object
| QObject.connect(object1, PYSIGNAL("pySignal()"), object2,
SLOT("qtSlot()"))
|
Python
| C++ slot of ‘self' object
| QObject.connect(object1, PYSIGNAL("pySignal()"),
SLOT("qtSlot()"))
|
Python
| Python slot of another object
| QObject.connect(object1, PYSIGNAL("pySignal()"),
object2.pythonFunction))
|
Python
| Python slot of ‘self' object
| QObject.connect(object1, PYTHON("pySignal()"),
self.pythonFunction))
|
Python
| C++ signal of another object
| QObject.connect(object1, OYSIGNAL("pySignal()"), object2,
SIGNAL("qtSignal()")
|
Python
| C++ signal of ‘self' object
| QObject.connect(object1, PYSIGNAL("pySignal()"), self,
SIGNAL("qtSignal()")
|
Python
| Python signal of another object
| QObject.connect(object1, PYSIGNAL("pySignal()"), object2,
PYSIGNAL("pySignal()")
|
Python
| Python signal of ‘self' object
| QObject.connect(object1, PYSIGNAL("pySignal()"),
PYSIGNAL("pySignal()")
|