PyGTK For GUI Programming/Signals
As you learned in the previous chapter, signals are emitted by widgets to allow your application to respond to specific actions, for example a button press. These responses are created by writing a function in the form:
def callback_func(widget, callback_data=None)
and registering it with a specific widget and signal by using the widget's connect method:
widget.connect(signal_name, callback_function, callback_data)
where signal_name is a string indicating the name of the signal to respond to, callback_function is a reference to your callback function (without brackets and arguments), and callback_data is an optional arbitrary object to pass to your callback function.
If you are structuring your application inside a Python class, as demonstrated in the previous chapter, then your callback 'method' will need an additional argument as a reference to the class instance:
class App: ... snip ... def callback_method(self, widget, callback_data): ... snip ...
For organizational purposes it is often advisable to group all your callback methods at the end of the class, or to suffix them with _callback.
GTK+ events are similar to signals: they are 'emitted' by specific widgets and can be handled by callback functions. The only differences as far as the PyGTK programmer is concerned are the arguments to a callback function and its return value. As an example, we'll use the 'button_pressed_event' emitted by the gtk.Button widget, which can be connected to a callback function the same way a signal is:
button.connect('button_press_event', callback_func, callback_data)
where button is an instance of the gtk.Button object. The callback_data argument, as explained in the previous chapter, is optional. The definition of the callback function contains three arguments: a reference to the widget that emitted the signal, the event and the callback data:
def callback_func(widget, event, callback_data=None)
The value returned from this function must be a boolean value (unlike callback functions for signals). This return value is an important message in the GTK+ event mechanism:
- False means that the event was not fully processed so GTK+ should continue doing whatever it usually does when this event occurs and the signal should propagate further.
- True means that the event was fully handled and GTK+ no longer needs to do any further processing in response to the event.
As an example, the 'delete_event' event is emitted from a gtk.Window when the user tries to close the window; if we connect a callback function to this and the function returns False, GTK+ will go on to close the window and emit the 'destroy' signal. If our callback function returns True, however, GTK+ does not close the window itself or emit the 'destroy' signal. This allows us to intervene when someone tries to close a window so we have to opportunity to ask them if, for example, they want to save their work, and then either instruct GTK+ to close the window or leave it open by returning the appropriate values from our callback function.
GTK+ main loop
The GTK+ toolkit is known as 'event-driven' because it sleeps in a loop until a signal or event is emitted, at which point it adds the signal to the end of a queue. Another part of GTK+ takes signals from the top of the queue, one at a time, and runs whatever callback functions are registered with the signal or event before moving to the next item in the queue. This is why you need to run the gtk.main() function, which serves to prevent your application from halting as soon as it has set up the GUI, and run any callback functions you have registered with .connect() when signals and events are emitted.
It is important to note, however, that GTK+ does not run callback functions in new threads; if one callback function takes a long time to run, then the rest of the GUI will remain unresponsive until that function finishes. Below is a quick example to demonstrate this point:
1 #!/usr/bin/env python 2 3 import pygtk 4 pygtk.require('2.0') 5 import gtk 6 import time 7 8 class App: 9 def __init__(self): 10 self.window = gtk.Window(type=gtk.WINDOW_TOPLEVEL) 11 self.hbox = gtk.HBox() 12 self.button_print = gtk.Button(label='Print something') 13 self.button_sleep = gtk.Button(label='Hang for 5 seconds') 14 15 self.hbox.pack_start(self.button_print) 16 self.hbox.pack_start(self.button_sleep) 17 18 self.button_print.connect('clicked', self.print_hi) 19 self.button_sleep.connect('clicked', self.sleep) 20 self.window.connect('destroy', self.quit) 21 22 self.window.add(self.hbox) 23 self.window.show_all() 24 25 gtk.main() 26 27 def print_hi(self, widget, callback_data=None): 28 print 'Hi there!' 29 30 def sleep(self, widget, callback_data=None): 31 time.sleep(5) 32 33 def quit(self, widget, callback_data=None): 34 gtk.main_quit() 35 36 if __name__ == "__main__": 37 app = App()
If you run the program above from a terminal, you should see a window with two buttons. Pressing the first button causes 'Hi there!' to be printed on the terminal. If you press the second button, the callback function hangs for five seconds, meaning that by pressing the second button and then the first in quick succession, the 'Hi there!' message won't be printed until 5 seconds has passed, demonstrating how signals are added to the queue which are executed one at a time.