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