Tkinter button command curiousity

Russell E. Owen no at spam.invalid
Thu Jan 8 17:00:07 EST 2004


In article <srdrvv47qvrrb6r26bkuh925phb1lpqkno at 4ax.com>,
 mksql at yahoo.com wrote:

>New to Tkinter. Initially, I had some code that was executing button commands 
>at
>creation, rather than waiting for user action. Some research here gave me a
>solution, but I am not sure why the extra step is necessary.
>
>This causes the "graph" function to execute when the button is created:
>	Button(root, text='OK', command=graph(canvas)))
>
>However, this waits until the button is pressed (the desired behavior):
>	def doit():
>	     graph(canvas)
>	Button(root, text='OK', command=doit))
>
>
>Functionally, what is the difference? Why do I need to create a function, to
>call a function, simply to make a button command wait until pressed? Is there 
>a
>better method?

This is a standard issue with callback functions.

Suppose you have a trivial function foo:
def foo(arg1, arg2):
  print "foo(%r, %r)" % (arg1, arg2)

To use foo as a callback function you need to pass it *AS* a function:
  anobj(callback=foo)
and that callback had better include the necessary args. If you try to 
specify args when specifying the callback, you end up passing the RESULT 
of the function (the mistake):
  anobj(callback=foo("bar", "baz")) 
foo gets called just once, when creating an obj, and callback gets set 
to None (the result of calling foo with args "bar" and "baz"). Later 
when anobj wants to call the callback, it has nothing to call!

There are various solutions. The one you chose is excellent. Others 
include:

-Use lambda to avoid creating a named function, but I find this much 
less readable.

- Use a "currying" class; this takes an existing function and 
pre-defined arguments and returns a new function that you use as your 
callback. A good example of such a class is: 
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549>. It's a 
nice thing to have around if you do a lot of callbacks, but otherwise I 
find your solution the most readable.

- If your widget is a class, you may be able to pass your data as class 
instances. This works if there is only one obvious canvas to graph. For 
example:
class mywdg(Tkinter.Frame):
  def __init__(self, master, etc.)
    Tkinter.Frame.__init__(self, master)
    self.canvas = Tkinter.Canvas(self,...)
    self.button = Tkinter.Button(self, command=self.graph)
    ...
  def graph(self):
      # do stuff to self.canvas

- As a more sophisticated variant, if you several canvases to graph, and 
want one button for each, you could make the canvases into objects 
(similar to the example above) and pass the right one to the button, 
e.g.:
  for cnvsobj in listofobj:
    abutton = Tkinter.Button(self, command= cnvsobj.graph)

- Also note that a very few Tkinter functions (such as after) allow you 
to specify a function and additional arguments.

-- Russell



More information about the Python-list mailing list