Signals And events in PyGTK
In this part of the PyGTK programming tutorial, we will talk about signals & events.All GUI applications are event driven. PyGTK applications are no exception. The applications start a main loop with the gtk.main() call, which continuously checks for newly generated events. If there is no event, the application waits and does nothing.
Events are messages from the X server to the application. When we click on a button widget, the clicked signal will be emitted. There are signals that all widgets inherit, such as destroy, and there are signals that are widget specific, such as toggled on a toggle button.
Programmers use signal handlers to react to various signals. These handlers are called callbacks among GTK programmers.
handler_id = button.connect("clicked", self.on_clicked)Here we use the connect() method of the GObject class, (GtkButton is a GObject), to connect a callback on_clicked() to a signal called clicked.
The connect() method returns a handler id, which is used to uniquely identify the callback method. The id can be used with the following methods:
def disconnect(handler_id)These methods enable to disconnect a handler from an GObject, or block/unblock it.
def handler_disconnect(handler_id)
def handler_is_connected(handler_id)
def handler_block(handler_id)
def handler_unblock(handler_id)
Signals vs events
There is generally a lot of confusion about the difference between the two.Signals and events are two different things. An event is an almost one-to-one mapping of window system events. Key press, window resizement or button press are typical window system events. Window system events are reported to the application main loop. Gdk interprets the window system events and passes them along via signals.
A signal is nothing other than a callback mechanism. If one object wants to be notified about an other object's action or state change, it registers a callback. When the object emits a signal, it looks in the list of callbacks which have been registered with it and calls the callback(s) for the specific signal. It can optionally send some predefined data with it.
Signals are a general purpose notification framework. They are not used only for notifications about UI changes. They can be used for notifications about application state changes. Signals are general, powerful, their usage is very broad. Any GObject can emit and receive a signal. A type may have one or more signals, each of which may have an argument list and return value. Handlers can then be connected to instances of the type. When the signal is emitted on an instance, each of the connected handlers will be called.
The only connection between signals and events is that signals are used to send notifications about events from the X server.
Signals are a feature of gtk.Object and its subclasses, events are a Gdk/Xlib concept.
Simple example
The next example shows, how we react to two basic signals.quitbutton.py
The destroy signal is triggered, when we close the window. By default, the application does not quit, when we click on the close button in the titlebar.
#!/usr/bin/python
# ZetCode PyGTK tutorial
#
# The example shows how to work with
# destroy and clicked signals
#
# author: jan bodnar
# website: zetcode.com
# last edited: February 2009
import gtk
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.set_title("Quit Button")
self.set_size_request(250, 200)
self.set_position(gtk.WIN_POS_CENTER)
self.connect("destroy", self.on_destroy)
fixed = gtk.Fixed()
quit = gtk.Button("Quit")
quit.connect("clicked", self.on_clicked)
quit.set_size_request(80, 35)
fixed.put(quit, 50, 50)
self.add(fixed)
self.show_all()
def on_destroy(self, widget):
gtk.main_quit()
def on_clicked(self, widget):
gtk.main_quit()
PyApp()
gtk.main()
self.connect("destroy", self.on_destroy)The connect() method plugs the on_destroy() method to the destroy signal.
quit.connect("clicked", self.on_clicked)Pressing the quit button, the clickedsignal is triggered. When we click on the quit button, we call the on_clicked() method.
def on_destroy(self, widget):In the on_destroy() method, we react to the destroy signal. We call the gtk.main_quit() method, which terminates the application.
gtk.main_quit()
def on_clicked(self, widget):Here is the on_clicked() method. It takes two parameters. The widget parameter is the object, which triggered this signal. In our case it is the quit button. Different objects send different signals. Signals and the parameters sent to methods can be found in the reference manual of the PyGTK library. pygtk.org/docs/pygtk/index.html
gtk.main_quit()
Creating a custom signal
In the following code example, we create and send a custom singal.customsignal.py
#!/usr/bin/pythonWe create two GObjects. Sender and receiver objects. The sender emits a signal, which is received by the receiver object. We also plug a callback to the signal.
# ZetCode PyGTK tutorial
#
# This example shows how to create
# and send a custom singal
#
# author: jan bodnar
# website: zetcode.com
# last edited: February 2009
import gobject
class Sender(gobject.GObject):
def __init__(self):
self.__gobject_init__()
gobject.type_register(Sender)
gobject.signal_new("z_signal", Sender, gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ())
class Receiver(gobject.GObject):
def __init__(self, sender):
self.__gobject_init__()
sender.connect('z_signal', self.report_signal)
def report_signal(self, sender):
print "Receiver reacts to z_signal"
def user_callback(object):
print "user callback reacts to z_signal"
if __name__ == '__main__':
sender = Sender()
receiver = Receiver(sender)
sender.connect("z_signal", user_callback)
sender.emit("z_signal")
class Sender(gobject.GObject):This is a sender object. It is created with a default constructor.
def __init__(self):
self.__gobject_init__()
gobject.type_register(Sender)We register a new object and a new signal. The signal_new() function registers a signal called z_signal for the Sender object. The SIGNAL_RUN_FIRST parameter means that the default handler of the object that receives the signal is called as first. The last two parameters are the return value type and parameter types. In our example we do not return any value and send no parameters.
gobject.signal_new("z_signal", Sender, gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ())
sender.connect('z_signal', self.report_signal)The receiver listens for the z_signal.
sender = Sender()Sender and receiver objects are instantiated. The receiver takes a sender as a parameter, so that it can listen to its signals.
receiver = Receiver(sender)
sender.connect("z_signal", user_callback)Here we plug the signal to the user callback.
sender.emit("z_signal")The z_signal is being emitted.
class Sender(gobject.GObject):We can also use the __gsignals__class attribute to register a new singal.
__gsignals__ = {
'z_signal': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
}
def __init__(self):
self.__gobject_init__()
gobject.type_register(Sender)
Predefined signal handlers
Objects in PyGTK may have predefined signal handlers. These handlers begin with do_*. For example do_expose(), do_show() or do_clicked().move.py
When we move or resize a window, the X server sends configure events. These are then transformed into configure-event signals.
#!/usr/bin/python
# ZetCode PyGTK tutorial
#
# This example overrides predefined
# do_configure_event() signal handler
#
# author: jan bodnar
# website: zetcode.com
# last edited: February 2009
import gtk
import gobject
class PyApp(gtk.Window):
__gsignals__ = {
"configure-event" : "override"
}
def __init__(self):
super(PyApp, self).__init__()
self.set_size_request(200, 150)
self.set_position(gtk.WIN_POS_CENTER)
self.connect("destroy", gtk.main_quit)
self.show_all()
def do_configure_event(self, event):
title = "%s, %s" % (event.x, event.y)
self.set_title(title)
gtk.Window.do_configure_event(self, event)
PyApp()
gtk.main()
In our code example, we display the x, y coordinates of the top-left corner of the window in the titlebar. We could simply connect a signal handler to the configure-event signal. But we take a different strategy. We override the default class handler, where we implement the logic needed.
__gsignals__ = {This tells, that we are going to override the default on_configure_event()method.
"configure-event" : "override"
}
def do_configure_event(self, event):Here we override the predefined do_configure_event() method. We set the x, y coordinates of the window to the title of the window. Also note the last line. It explicitly calls the superclass do_configure_event() method. This is beacuse it does some important job. Try to comment this line to see what happens. Resizing of windows would not work correctly. If we override a default handler, we may or may not call the superclass method. In our case we have to.
title = "%s, %s" % (event.x, event.y)
self.set_title(title)
gtk.Window.do_configure_event(self, event)
Figure: Configure singal
Signals of a button
The following example shows various button signals.buttonsignals.py
#!/usr/bin/pythonA button can emit more than just one type of signal. We work with three of them. The clicked, pressed and released signals. We also show, how an event signal triggers another signal.
# ZetCode PyGTK tutorial
#
# This program shows various signals
# of a button widget
# It emits a button-release-event which
# triggers a released singal
#
# author: jan bodnar
# website: zetcode.com
# last edited: February 2009
import gtk
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.set_title("Signals")
self.set_size_request(250, 200)
self.set_position(gtk.WIN_POS_CENTER)
self.connect("destroy", gtk.main_quit)
fixed = gtk.Fixed()
self.quit = gtk.Button("Quit")
self.quit.connect("pressed", self.on_pressed)
self.quit.connect("released", self.on_released)
self.quit.connect("clicked", self.on_clicked)
self.quit.set_size_request(80, 35)
fixed.put(self.quit, 50, 50)
self.add(fixed)
self.show_all()
self.emit_signal()
def emit_signal(self):
event = gtk.gdk.Event(gtk.gdk.BUTTON_RELEASE)
event.button = 1
event.window = self.quit.window
event.send_event = True
self.quit.emit("button-release-event", event)
def on_clicked(self, widget):
print "clicked"
def on_released(self, widget):
print "released"
def on_pressed(self, widget):
print "pressed"
PyApp()
gtk.main()
self.quit.connect("pressed", self.on_pressed)We register callbacks for all three signals.
self.quit.connect("released", self.on_released)
self.quit.connect("clicked", self.on_clicked)
self.emit_signal()Upon the start of the application, we emit a specific signal.
def emit_signal(self):We emit the button-release-event signal. It takes an Event object as a parameter. After the application starts, we should see "released" text in our console window. When we click on the button, all three signals are triggered.
event = gtk.gdk.Event(gtk.gdk.BUTTON_RELEASE)
event.button = 1
event.window = self.quit.window
event.send_event = True
self.quit.emit("button-release-event", event)
Blocking an event handler
We can block a signal handler. The next example shows this.block.py
#!/usr/bin/pythonIn the code example, we have a button and a check box. We show "clicked" text in the console, when we click on the button and the check box is active. The check box blocks/unblocks a handler method from the button clicked signal.
# ZetCode PyGTK tutorial
#
# This example shows how to block/unblock
# a signal handler
#
# author: jan bodnar
# website: zetcode.com
# last edited: February 2009
import gtk
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.set_title("Blocking a callback")
self.set_size_request(250, 180)
self.set_position(gtk.WIN_POS_CENTER)
fixed = gtk.Fixed()
button = gtk.Button("Click")
button.set_size_request(80, 35)
self.id = button.connect("clicked", self.on_clicked)
fixed.put(button, 30, 50)
check = gtk.CheckButton("Connect")
check.set_active(True)
check.connect("clicked", self.toggle_blocking, button)
fixed.put(check, 130, 50)
self.connect("destroy", gtk.main_quit)
self.add(fixed)
self.show_all()
def on_clicked(self, widget):
print "clicked"
def toggle_blocking(self, checkbox, button):
if checkbox.get_active():
button.handler_unblock(self.id)
else:
button.handler_block(self.id)
PyApp()
gtk.main()
self.id = button.connect("clicked", self.on_clicked)The connect() method returns a handler id. This id is used to block and unblock the handler.
def toggle_blocking(self, checkbox, button):These lines block and unblock the callback with the appropriate methods.
if checkbox.get_active():
button.handler_unblock(self.id)
else:
button.handler_block(self.id)
Figure: Blocking a callback
In this chapter of the PyGTK tutorial, we worked with signals.
No comments:
Post a Comment