2.7 Controllers

In GTK+, all user events are interpreted by signals that are generated by the widgets what are manipulated. This means obvious stuff like button clicks and typing text into a GtkEntry, but it also includes less obvious events like the initial rendering of the widget and moving the mouse over a widget. To a signal, we can attach a function that is called when it is triggered, and this function is usually called a signal handler.

Many widgets have default handlers attached to their signals (these are coded in the actual GTK+ source code); a default handler, for example, is what makes the text that you type into an Entry actually display inside its white box, and what makes the checkbutton depress and change state when you click on it. However, there are many cases when you want to do something special based on a certain signal occurring. A pushbutton (GtkButton) is a good example: it doesn't do anything by default when clicked beyond depressing, so you practically always will want to connect to its "clicked" signal. Developing a graphical application involves selecting which signals you think are important in the interface widgets, and attaching functions to them.

In Kiwi, we suggest grouping the relevant signal handlers for an interface into a class. This means that instead of using a number of independent functions, the signal handlers are really methods of a class. This class is called the Controller. The Controller is conceptually the part of the framework that handles events that are generated in the UI; click a button, a controller method is called.

Since the View holds the interface, it makes sense to attach a controller to each view5, and vice-versa; the Controller constructor takes a View instance and ties itself to it. Since the controller needs to define special methods, it should be subclassed in your code, and the methods implemented.

The Kiwi Controller has a special feature: if you write your method names using a certain syntax, it "discovers" what widget and signal you want, and attaches the handler automatically for you. This works for any type of View, even GladeViews, which means no more messing around with signal_autoconnect() and that pesky signal dialog in Glade. The handler's method name should be written as follows:

  1. Start the name with on_ or after_. Use on_ to connect before the default handler for that widget's signal; use after_ to connect after it. It is more common to use on_6.

  2. Add the widget name: this is the name of the View's instance variable for that widget. If your view has a widget called quitbutton, you would use "quitbutton".

  3. Follow the widget name by two underscores (__).

  4. Finish the name by appending the signal name you want to capture. For instance, if you wanted to handle that quitbutton's clicked signal, you would use "clicked". The final method name would be on_quitbutton__clicked.

Note that the widget must be attached directly to the controller's corresponding View; it can't be attached using this syntax to a slave of that view, for instance (you'll have to call connect() directly in that case). Note also that signal handler's parameters will vary from signal to signal, and the only thing that can be trusted is that the first parameter will be the widget which generated the signal. You can use a method signature like the following to avoid problems:

    def on_mybutton__clicked(self, button, *args):

This way, args will hold as many arguments as the signal defines for that function (and you can connect this method explicitly to more than one kind of signal, if you need to). Let's see a simple example to make these concepts more concrete (included in the Kiwi tarball at Kiwi/examples/Faren/faren.py):

Let's have a look at the code. I define a Controller FarenControl that inherits from BaseController, and which defines two methods that are signal handlers - one for the "clicked" event for the widget quitbutton, and another for the "insert_text" signal for temperature. I attach the view to the controller, and tell the view to show itself and run the event loop. Not much else is worth noting, apart from the fact that the signal handlers receive the widget as the first parameter, and that the GTK+ text widgets (GtkEntry, GtkLabel, GtkText) usually take and return strings, which makes us do conversion here and there.

Thus, the event loop now has two signal handlers that will be triggered according to the user's interaction: one called when clicking the quitbutton and one when inserting text into the entry. The user can type numbers into the entry, and through after_temperature__insert_text(), the celsius and farenheit labels are changed automatically. Clicking quit calls a special method in the view, hide_and_quit(), that hides the window and quits the event loop. Note that the widgets "celsius" and "farenheit" are empty labels that appear right next to the labels that are written "Celsius" and "Farenheit"; if you are confused look at the glade file faren.glade.



Footnotes

... view5
Ancient versions of Kiwi had a MultiView class, that held various subviews for portions of it, and required one controller for each subview. The new SlaveView approach is nicer, because it allows reusing the actual interface in a much more flexible manner.
...on_6
Hint: if you are connecting to a GtkEntry's insert_text signal and you want to use entry.get_text(), use after_entrywidget__insert_text -- the default signal handler is responsible for inserting the text into the entry and you will need it to run before your handler.



Subsections