We're back after a server migration that caused effbot.org to fall over a bit harder than expected. Expect some glitches.

The tkController Module

Updated October 24, 2006 | March 1998 | Fredrik Lundh

The following module provides a WCK-compatible controller base class. A controller represents a number of event bindings, and the code needed to handle those events. Controllers can be attached to any Tkinter widget.

A WCK-compatible Controller Base Class for Tkinter
# File: tkController.py

import Tkinter

PREFIX = "tkController"

class Controller:

    def __init__(self, master=None):
        if master is None:
            master = Tkinter._default_root
        assert master is not None
        self.tag = PREFIX + str(id(self))
        def bind(event, handler):
            master.bind_class(self.tag, event, handler)
        self.create(bind)

    def install(self, widget):
        widgetclass = widget.winfo_class()
        # remove widget class bindings and other controllers
        tags = [self.tag]
        for tag in widget.bindtags():
            if tag != widgetclass and tag[:len(PREFIX)] != PREFIX:
                tags.append(tag)
        widget.bindtags(tuple(tags))

    def create(self, handle):
        # override if necessary
        # the default implementation looks for decorated methods
        for key in dir(self):
            method = getattr(self, key)
            if hasattr(method, "tkevent") and callable(method):
                handle(method.tkevent, method)

##
# Simple event decorator for Python 2.4 and later.

def bind(event):
    def decorator(func):
        func.tkevent = event
        return func
    return decorator

To create a specific controller, subclass the Controller base class, implement one or more event handler methods, and use the bind decorator to flag them as such:

class myController(Controller):

    @bind("<Button-1>") # binds method to event
    def click(self, event):
        print "click at", event.x, event.y

frame = Frame()

frame_controller = myController(frame)
frame_controller.install(frame)

If you’re using an older Python version, or have special requirements, you can override the create method instead:

class myController(Controller):

    def create(self, bind):
        bind("<Button-1>", self.click)

    def click(self, event):
        print "click at", event.x, event.y

frame = Frame()

frame_controller = myController(frame)
frame_controller.install(frame)

The Controller constructor calls the create method, passing in a callable bind handler that should be used to set up event bindings.

To install a controller on a widget, create an instance of the controller, and call the install method. This removes the existing controller from the widget, if any, and also removes all standard widget bindings. When install returns, all events will be routed to the new controller.

You can switch controllers at any time (for example to switch between different tools).

Examples

Drawing Canvas Items

This example uses a simple controller to draw red rectangles on a Tkinter canvas. The example creates two canvas widgets, and attaches the same controller instance to both. This works because the user can only draw on one canvas at a time, and the controller can use the widget event attribute to distinguish between the widgets.

Drawing Rectangles on a Canvas
import Tkinter
from tkController import Controller, bind

class myController(Controller):

    @bind("<Button-1>")
    def click(self, event):
        self.anchor = event.x, event.y
        self.item = None

    @bind("<B1-Motion>")
    def drag(self, event):
        bbox = self.anchor + (event.x, event.y)
        if self.item is None:
            self.item = event.widget.create_rectangle(bbox, fill="red")
        else:
            event.widget.coords(self.item, *bbox)

# create widgets

canvas1 = Tkinter.Canvas(bg="white")
canvas1.pack(side="left")

canvas2 = Tkinter.Canvas(bg="black")
canvas2.pack(side="left")

canvas_controller = myController()

canvas_controller.install(canvas1)
canvas_controller.install(canvas2)

Tkinter.mainloop()

More examples to be added…