Why don't my buttons / menus work properly ?

Callbacks

In Tkinter, a callback is Python code that is called by Tk when something happens. For example, the Button widget provides a command callback which is called when the user clicks the button. Menu items have something similar.

You can use any callable Python object as a callback. This includes ordinary functions, bound methods, lambda expressions, and callable objects.

Using Functions

In Python, functions are objects, and can be handled like any other object. The def statement creates an object and binds it to a name. To call the function, use the () operator on the object:

>>> def spam():
...    return "hello"
...
>>> spam
<function spam at 790a90>
>>> spam()
'hello'

To use a function object as a callback, pass it directly to Tkinter.

   1 from Tkinter import *
   2 
   3 def callback():
   4     print "clicked!"
   5 
   6 b = Button(text="click me", command=callback)
   7 b.pack()
   8 
   9 mainloop()
  10 

For each function object, the Tkinter interface layer registers a Tk command with a unique name. When that Tk command is called by the Button implementation, the command calls the corresponding Python function.

If you’re curious, you can ask the widget for the name of the Tk command:

b = Button(text="click me", command=callback)
b.pack()

print b.cget("command")

This prints something like 9182704callback (that is, a unique integer followed by the function name).

Passing Arguments to Callbacks

Tkinter’s Button widget doesn’t pass any information to the callback. This makes things a bit complicated if you want to use the same callback for several buttons, like in this example:

def callback():
    print "button", "?"

Button(text="one",   command=callback).pack()
Button(text="two",   command=callback).pack()
Button(text="three", command=callback).pack()

A common beginner’s mistake is to call the callback function when constructing the widget. That is, instead of giving just the function’s name (e.g. “callback”), the programmer adds parentheses and argument values to the function:

def callback(number):
    print "button", number

Button(text="one",   command=callback(1)).pack()
Button(text="two",   command=callback(2)).pack()
Button(text="three", command=callback(3)).pack()

If you do this, Python will call the callback function before creating the widget, and pass the function’s return value to Tkinter. Tkinter then attempts to convert the return value to a string, and tells Tk to call a function with that name when the button is activated. This is probably not what you wanted.

For simple cases like this, you can use a lambda expression:

def callback(number):
    print "button", number

Button(text="one",   command=lambda: callback(1)).pack()
Button(text="two",   command=lambda: callback(2)).pack()
Button(text="three", command=lambda: callback(3)).pack()

Lambda

the expression lambda arguments: expression yields a function object. The unnamed object behaves like a function object defined with

def name(arguments):
    return expression

So, in effect, lambda: callback(1) creates something that behaves like this function :

def name():
    callback(1)

try this at the python prompt:

>>> def callback(number):
        print "button", number

>>> a = lambda: callback(1)
>>> b = lambda: callback(2)
>>> a
<function <lambda> at 0x01B867F0>
>>> b
<function <lambda> at 0x01B998F0>
>>> a()
button 1
>>> b()
button 2

Hopefully this has made things a little clearer.

Moving on, see what you can make of this:

>>> def add2(x):
        return x+2

>>> command = lambda x=5: add2(x)
>>> command
<function <lambda> at 0x01B867F0>
>>> command()
7
>>> command(4)
6

Notice that when called without an argument, 'command' uses x=5 as a default argument, and the result is 7. Using this idea of default arguments, you can pass as many arguments to a callback as you need to.

Creating Buttons or Menu items in a loop:

   1 from Tkinter import *
   2 
   3 root = Tk()
   4 b = Menubutton(root, text='Menu', relief='raised')
   5 b.pack()
   6 m = Menu(b)
   7 
   8 def callback(i):
   9     print 'processed', i
  10     
  11 for i in range(1,6):
  12     m.add_command(label='Option %s' % (i), command=lambda i=i: callback(i))
  13 b.configure(menu=m)
  14 
  15 root.mainloop()
  16 

Note the use of i = i. This provides a snapshot of i at the time when each function object is created . If you leave out 'i=i' from this example, then things don't work correctly. Each menu command ends up processing the same value - the final value of the loop variable.

Some people try to avoid using lambda for reasons of style or clarity. Without going into the details, there are plenty of alternatives around. [closures][partial functions]...

Bound Methods

   1 from Tkinter import *
   2 
   3 class Program:
   4 
   5     def __init__(self):
   6         b = Button(text="click me", command=self.callback)
   7         b.pack()
   8 
   9     def callback(self):
  10         print "clicked!"
  11 
  12 program = Program()
  13 
  14 mainloop()
  15 

In comp.lang.python, Lundh had this to say:

A bound method is what you get from the instance.method syntax. this is a single callable object, which knows what instance it came from, and what method to call (a smart function pointer, if you prefer)

in the following example, "foo" is an instance of class "Foo", and "callback" a method:

    >>> Foo.callback # the class method
    <unbound method Foo.callback>
    >>> foo = Foo()
    >>> foo.callback # a bound method
    <method Foo.callback of Foo instance at 431283>
    >>> foo.callback()
    callback called!!!
    >>> cb = foo.callback
    >>> cb
    <method Foo.callback of Foo instance at 431283>
    >>> cb()
    callback called!!!

in the following Tkinter example, "app" is a class instance, and "do_print" is a method:

    menu.add_command(label="Print", command=app.do_print)

when called, the self argument to do_print will contain "app".

With a number of large Tkinter/uiToolkit projects behind me, I find that I hardly ever use anything but bound methods for callbacks:

As usual, bend these rules as you wish, if you know exactly why you're doing it...


Much of this material is from Fredrik Lundh's website (tkinter-callbacks)

tkinter: CallbackConfusion (last edited 2011-05-04 10:48:17 by AnthonyMuss)