Difference between revisions of "Development/Tutorials/Python introduction to signals and slots"

Jump to: navigation, search
(start on python signal and slots tutorial)
 
m (Text replace - "</code>" to "</syntaxhighlight>")
 
(31 intermediate revisions by 9 users not shown)
Line 1: Line 1:
{{warning|This tutorial is work in progress.}}
+
==Abstract==
  
 +
The signal and slot architecture is designed to simplify communication between objects.  GUI programming is mostly event-driven and conventionally uses callbacks.  The limitations of callbacks are partly resolved by the signal and slot architecture that Qt uses. The idea is that all objects can emit signals.
  
==Abstract==
+
When a button is clicked, for example, it emits a “clicked()” signal. Signals do nothing alone, but once connected to a slot, the code in the slot will be executed whenever the signal is emitted.
The signal and slot architecture is design to simplify communication between objects.  It's a fact that GUI programming is mostly event driven.  The traditionally approach  to event driven programming is to use callbaks. Callbacks have a number of limitations that Qt tries to solve with it's signal and slot architecture. The concept is that every object can emit signals. For example when a button is clicked it emits a “clicked()” signal. Signals does not do anything alone, but when connected to a slot the code in the slot will be executed every time the signal is emitted. In python every function is a slot.  It's possible to connect one signal to multiple slots
+
 
 +
In the Python programs, every function is a slot.  It is possible to connect one signal to multiple slots, and to connect slots consecutively. For instance, one event activates its slot and related subsequent events trigger another signal and the code in its slot to be executed.
 +
 
 +
==Prerequisites==
 +
General understanding of the python programming language. No prior knowledge of QT is required.
  
  
 
==Connecting signals and slots.==
 
==Connecting signals and slots.==
This example demonstrates how to use the connect method to connect signals and slots.
+
We use the QObject.connect() method to connect signals and slots.  
  
<code python>
+
<syntaxhighlight lang="python">
 +
bool connect (QObject, SIGNAL(), callable, Qt.ConnectionType = Qt.AutoConnection)
 +
</syntaxhighlight>
 +
 
 +
The first argument is the name of the object that is emitting the signal. The second argument is the signal, and the third argument is the slot. The slot has to bee a python callable object. Note that only QObjects and objects inheriting from QObject can emit signals.
 +
 
 +
 
 +
'''Example'''
 +
<syntaxhighlight lang="python">
 
from PyQt4.QtGui import *
 
from PyQt4.QtGui import *
 
from PyQt4.QtCore import *
 
from PyQt4.QtCore import *
 
import sys
 
import sys
 +
 +
#First we create a QApplication and QPushButton
 +
app=QApplication(sys.argv)
 +
exitButton=QPushButton("Exit")
 +
 +
#Here we connect the exitButton's "clicked()" signals to the app's exit method.
 +
#This will have the effect that every time some one clicks the exitButton
 +
#the app.exit method will execute and the application will close.
 +
QObject.connect(exitButton,SIGNAL("clicked()"),app.exit)
 +
 +
exitButton.show()
 +
#Start the evnt loop
 +
sys.exit(app.exec_())
 +
</syntaxhighlight>
 +
 +
==Emitting signals==
 +
Only QObjects and objects inheriting from QObject can emit signals. To emit a signal we use the QObject.emit() method.
 +
 +
<syntaxhighlight lang="python">
 +
QObject.emit (self, SIGNAL(), ...)
 +
</syntaxhighlight>
 +
 +
emit() is an object method, the self parameter is therefor automatically inserted by the python interpreter. The next argument is the signal we would like to emit, for example it could have been SIGNAL("myfirstsignal()") if we wanted to emit a signal with that name. The next parameters is optional parameters that can be sent with the signal, will come back to that in more detail later.
 +
 +
'''Example:'''In this example we have a class with a function "afunc" that emits the signal "doSomePrinting()". The class also have function "bfunc" that prints "Hello world". First we create a object of the class then we connect the "doSomePrinting()" to "bfunc". After that we call "afunc". This will result in the printing of "Hello World" to the standard output
 +
 +
<syntaxhighlight lang="python">
 +
import sys
 +
from time import time
 +
from PyQt4.QtCore import *
 +
 
 +
class A (QObject):
 +
    def __init__(self):
 +
        QObject.__init__(self)
 +
   
 +
    def afunc (self):
 +
        self.emit(SIGNAL("doSomePrinting()"))   
 +
       
 +
    def bfunc(self):
 +
        print "Hello World!"
 +
 +
if __name__=="__main__":
 +
    app=QCoreApplication(sys.argv)
 +
    a=A()
 +
    QObject.connect(a,SIGNAL("doSomePrinting()"),a.bfunc)
 +
    a.afunc()   
 +
    sys.exit(app.exec_())
 +
 +
</syntaxhighlight>
 +
 +
==Signals and slots with parameters==
 +
The signal and slots mechanism is type safe. In C++ this implies that both the number of arguments and the type of the arguments in a signal must match the arguments in the receiving slot. In Qt's Signal and slots architecture the receiving slot can actually have fewer parameters than the emitted signal, the extra arguments will then be ignored. Because of pythons dynamically typed nature it not possible to do any type checking in advance. It is therefor important to make sure that the emitted object is of the expected type or of a type that can be automatically converted to the expected type. For example a python string will automatically be converted to QString. If we send a object of an incompatible type we will get an runtime error.
 +
 +
'''Example:'''
 +
This  example will create a slider and display it. Every time the value of the slider is changed the new value will be printed to the standard output. The references documentation for QSlider can be found [http://www.riverbankcomputing.com/Docs/PyQt4/html/qslider.html here], the valueChanged signal is inherited from [http://www.riverbankcomputing.com/Docs/PyQt4/html/qabstractslider.html QAbstractSlider]
 +
<syntaxhighlight lang="python">
 +
from PyQt4.QtGui import *
 +
from PyQt4.QtCore import *
 +
import sys
 +
 +
def printNumber(number):
 +
    print number
  
 
if __name__=="__main__":
 
if __name__=="__main__":
     #First we create a QApplication and QPushButton
+
     #First we create a QApplication and QSlider
 
     app=QApplication(sys.argv)
 
     app=QApplication(sys.argv)
     exitButton=QPushButton("Exit")
+
     slider=QSlider(Qt.Horizontal)
 
      
 
      
 
    #Her we connect the exitButton's "clicked()" signals to the app's exit method.
 
    #This will have the effect that every time someone clicks the exitButton the app.exit method will execute and the application will close.
 
    QObject.connect(exitButton,SIGNAL("clicked()"),app.exit)
 
 
      
 
      
 +
    QObject.connect(slider,SIGNAL("valueChanged(int)"),printNumber)
 
      
 
      
     button.show()
+
      
 +
    slider.show()
 
     #Start the evnt loop
 
     #Start the evnt loop
 
     sys.exit(app.exec_())
 
     sys.exit(app.exec_())
</code>
+
</syntaxhighlight>
  
 +
==Python objects as parameters==
 +
It's possible to send a python object of any type using PyQt_PyObject in the signature. This is recommended when both signal and slot is implemented in python. By using PyQt_PyObject we avoid unnecessary conversions between python objects and C++ types and it is more consistent with python dynamically typed nature.
 +
 +
'''Example'''
 +
<syntaxhighlight lang="python">
 +
import sys
 +
from time import time
 +
from PyQt4.QtCore import *
 +
 +
       
 +
class A (QObject):
 +
    def __init__(self):
 +
        QObject.__init__(self)
 +
   
 +
    def send (self):
 +
        msg=[1234,"1234",{1:2}]
 +
        self.emit(SIGNAL("asignal(PyQt_PyObject)"),msg)   
 +
       
 +
    def receive(self,msg):
 +
        print msg
 +
 +
def p(msg): print int(time()-start),msg
 +
       
 +
if __name__=="__main__":
 +
    app=QCoreApplication(sys.argv)
 +
    a=A()
 +
    QObject.connect(a,SIGNAL("asignal(PyQt_PyObject)"),a.receive)
 +
    a.send()   
 +
   
 +
    sys.exit(app.exec_())
 +
</syntaxhighlight>
 +
 +
==Short-circuit Signal==
 +
PyQt4 has a special type of signal called a short-circuit Signal. This signal implicitly declares all arguments to be of type PyQt_PyObject. Short-circuited signals do not have argument lists or parentheses. Short-circuited signals can only be connected to python slots.
 +
 +
'''The same example as above, using short-circuited signals. '''
 +
<syntaxhighlight lang="python">
 +
import sys
 +
from time import time
 +
from PyQt4.QtCore import *
 +
 +
       
 +
class A (QObject):
 +
    def __init__(self):
 +
        QObject.__init__(self)
 +
   
 +
    def send (self):
 +
        msg=[1234,"1234",{1:2}]
 +
        self.emit(SIGNAL("asignal"),msg)   
 +
       
 +
    def receive(self,msg):
 +
        print msg
 +
 +
def p(msg): print int(time()-start),msg
 +
       
 +
if __name__=="__main__":
 +
    app=QCoreApplication(sys.argv)
 +
    a=A()
 +
    QObject.connect(a,SIGNAL("asignal"),a.receive)
 +
    a.send()   
 +
   
 +
    sys.exit(app.exec_())
 +
</syntaxhighlight>
 +
 +
==Signals and slots and threading==
 +
To send signal across threads we have to use the Qt.QueuedConnection parameter. Without this parameter the code will be executed in the same thread.
 +
<syntaxhighlight lang="python">
 +
import sys
 +
from time import time
 +
from PyQt4.QtCore import *
 +
 +
class A (QThread):
 +
    def __init__(self):
 +
        QThread.__init__(self)
 +
   
 +
    def afunc (self):
 +
        p("starting in a()")
 +
        self.emit(SIGNAL("asignal"))
 +
        p("finished in a()")
 +
     
 +
    def bfunc(self):
 +
        p("starting in b()")
 +
        self.sleep(3)
 +
        p("finished in b()")
 +
               
 +
    def run(self):
 +
        self.exec_()
 +
       
 +
 +
def p(msg): print str(int(time()-start)) + "s",msg
 +
       
 +
if __name__=="__main__":
 +
    start=time()
 +
    p("starting in __main__")
 +
    app=QCoreApplication(sys.argv)
 +
    a=A()
 +
    a.start()
 +
    QObject.connect(a,SIGNAL("asignal"),a.bfunc,Qt.QueuedConnection)
 +
    a.afunc()
 +
    p("finished in __main__")
 +
     
 +
    sys.exit(app.exec_())
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang="text">
 +
Output:
 +
0s starting in __main__
 +
0s starting in a()
 +
0s finished in a()
 +
0s finished in __main__
 +
0s starting in b()
 +
3s finished in b()
 +
 +
 +
without Qt.QueuedConnection the example will output:
 +
0s starting in __main__
 +
0s starting in a()
 +
0s starting in b()
 +
3s finished in b()
 +
3s finished in a()
 +
3s finished in __main__
 +
</syntaxhighlight>
  
==Signals and slots with parameters==
 
  
 +
==QueuedConnections==
 
[[Category:Python]]
 
[[Category:Python]]

Latest revision as of 21:54, 29 June 2011

Contents

[edit] Abstract

The signal and slot architecture is designed to simplify communication between objects. GUI programming is mostly event-driven and conventionally uses callbacks. The limitations of callbacks are partly resolved by the signal and slot architecture that Qt uses. The idea is that all objects can emit signals.

When a button is clicked, for example, it emits a “clicked()” signal. Signals do nothing alone, but once connected to a slot, the code in the slot will be executed whenever the signal is emitted.

In the Python programs, every function is a slot. It is possible to connect one signal to multiple slots, and to connect slots consecutively. For instance, one event activates its slot and related subsequent events trigger another signal and the code in its slot to be executed.

[edit] Prerequisites

General understanding of the python programming language. No prior knowledge of QT is required.


[edit] Connecting signals and slots.

We use the QObject.connect() method to connect signals and slots.

bool connect (QObject, SIGNAL(), callable, Qt.ConnectionType = Qt.AutoConnection)

The first argument is the name of the object that is emitting the signal. The second argument is the signal, and the third argument is the slot. The slot has to bee a python callable object. Note that only QObjects and objects inheriting from QObject can emit signals.


Example

from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
 
#First we create a QApplication and QPushButton
app=QApplication(sys.argv)
exitButton=QPushButton("Exit")
 
#Here we connect the exitButton's "clicked()" signals to the app's exit method. 
#This will have the effect that every time some one clicks the exitButton 
#the app.exit method will execute and the application will close.
QObject.connect(exitButton,SIGNAL("clicked()"),app.exit)
 
exitButton.show()
#Start the evnt loop
sys.exit(app.exec_())

[edit] Emitting signals

Only QObjects and objects inheriting from QObject can emit signals. To emit a signal we use the QObject.emit() method.

QObject.emit (self, SIGNAL(), ...)

emit() is an object method, the self parameter is therefor automatically inserted by the python interpreter. The next argument is the signal we would like to emit, for example it could have been SIGNAL("myfirstsignal()") if we wanted to emit a signal with that name. The next parameters is optional parameters that can be sent with the signal, will come back to that in more detail later.

Example:In this example we have a class with a function "afunc" that emits the signal "doSomePrinting()". The class also have function "bfunc" that prints "Hello world". First we create a object of the class then we connect the "doSomePrinting()" to "bfunc". After that we call "afunc". This will result in the printing of "Hello World" to the standard output

import sys
from time import time
from PyQt4.QtCore import *
 
class A (QObject):
    def __init__(self):
        QObject.__init__(self)
 
    def afunc (self):
        self.emit(SIGNAL("doSomePrinting()"))    
 
    def bfunc(self):
        print "Hello World!"
 
if __name__=="__main__":
    app=QCoreApplication(sys.argv)
    a=A()
    QObject.connect(a,SIGNAL("doSomePrinting()"),a.bfunc)
    a.afunc()    
    sys.exit(app.exec_())

[edit] Signals and slots with parameters

The signal and slots mechanism is type safe. In C++ this implies that both the number of arguments and the type of the arguments in a signal must match the arguments in the receiving slot. In Qt's Signal and slots architecture the receiving slot can actually have fewer parameters than the emitted signal, the extra arguments will then be ignored. Because of pythons dynamically typed nature it not possible to do any type checking in advance. It is therefor important to make sure that the emitted object is of the expected type or of a type that can be automatically converted to the expected type. For example a python string will automatically be converted to QString. If we send a object of an incompatible type we will get an runtime error.

Example: This example will create a slider and display it. Every time the value of the slider is changed the new value will be printed to the standard output. The references documentation for QSlider can be found here, the valueChanged signal is inherited from QAbstractSlider

from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
 
def printNumber(number):
    print number
 
if __name__=="__main__":
    #First we create a QApplication and QSlider
    app=QApplication(sys.argv)
    slider=QSlider(Qt.Horizontal)
 
 
    QObject.connect(slider,SIGNAL("valueChanged(int)"),printNumber)
 
 
    slider.show()
    #Start the evnt loop
    sys.exit(app.exec_())

[edit] Python objects as parameters

It's possible to send a python object of any type using PyQt_PyObject in the signature. This is recommended when both signal and slot is implemented in python. By using PyQt_PyObject we avoid unnecessary conversions between python objects and C++ types and it is more consistent with python dynamically typed nature.

Example

import sys
from time import time
from PyQt4.QtCore import *
 
 
class A (QObject):
    def __init__(self):
        QObject.__init__(self)
 
    def send (self):
        msg=[1234,"1234",{1:2}]
        self.emit(SIGNAL("asignal(PyQt_PyObject)"),msg)    
 
    def receive(self,msg):
        print msg
 
def p(msg): print int(time()-start),msg
 
if __name__=="__main__":
    app=QCoreApplication(sys.argv)
    a=A()
    QObject.connect(a,SIGNAL("asignal(PyQt_PyObject)"),a.receive)
    a.send()    
 
    sys.exit(app.exec_())

[edit] Short-circuit Signal

PyQt4 has a special type of signal called a short-circuit Signal. This signal implicitly declares all arguments to be of type PyQt_PyObject. Short-circuited signals do not have argument lists or parentheses. Short-circuited signals can only be connected to python slots.

The same example as above, using short-circuited signals.

import sys
from time import time
from PyQt4.QtCore import *
 
 
class A (QObject):
    def __init__(self):
        QObject.__init__(self)
 
    def send (self):
        msg=[1234,"1234",{1:2}]
        self.emit(SIGNAL("asignal"),msg)    
 
    def receive(self,msg):
        print msg
 
def p(msg): print int(time()-start),msg
 
if __name__=="__main__":
    app=QCoreApplication(sys.argv)
    a=A()
    QObject.connect(a,SIGNAL("asignal"),a.receive)
    a.send()    
 
    sys.exit(app.exec_())

[edit] Signals and slots and threading

To send signal across threads we have to use the Qt.QueuedConnection parameter. Without this parameter the code will be executed in the same thread.

import sys
from time import time
from PyQt4.QtCore import *
 
class A (QThread):
    def __init__(self):
        QThread.__init__(self)
 
    def afunc (self):
        p("starting in a()")
        self.emit(SIGNAL("asignal"))
        p("finished in a()")
 
    def bfunc(self):
        p("starting in b()")
        self.sleep(3) 
        p("finished in b()")
 
    def run(self):
        self.exec_()
 
 
def p(msg): print str(int(time()-start)) + "s",msg
 
if __name__=="__main__":
    start=time()
    p("starting in __main__")
    app=QCoreApplication(sys.argv)
    a=A()
    a.start()
    QObject.connect(a,SIGNAL("asignal"),a.bfunc,Qt.QueuedConnection)
    a.afunc()
    p("finished in __main__")
 
    sys.exit(app.exec_())
Output:
0s starting in __main__
0s starting in a()
0s finished in a()
0s finished in __main__
0s starting in b()
3s finished in b()
 
 
without Qt.QueuedConnection the example will output:
0s starting in __main__
0s starting in a()
0s starting in b()
3s finished in b()
3s finished in a()
3s finished in __main__


[edit] QueuedConnections


This page was last modified on 29 June 2011, at 21:54. This page has been accessed 54,890 times. Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal