Tkinter button command curiousity

M

mksql

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?
 
H

Hans-Joachim Widmaier

Am Thu, 08 Jan 2004 15:08:56 -0500 schrieb mksql:
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.

There is no "extra step," as you will see.
This causes the "graph" function to execute when the button is created:
Button(root, text='OK', command=graph(canvas)))

Here you are calling graph() immediatly and bind the result to the
parameter 'command' - most likely not the intended effect (as you
observed).
However, this waits until the button is pressed (the desired behavior):
def doit():
graph(canvas)
Button(root, text='OK', command=doit))

Here you bind the function object 'doit' to the command parameter, which
will then be called when the button is clicked on. You could also bind
the 'graph' function object to command, but then you cannot give it your
argument 'canvas'. Lambdas are often used to overcome this problem, but
your 'doit' local function essentially amounts to the same.
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?

The difference is, again:

def function(blah)
pass

Here, 'function(x)' calls the function, whereas 'function' (without the
parenthethis) is just the function object (which can be bound to a name
which then later can be used to call it).

A better method? Would you call this better?

Button(root, text='OK', command=lambda event, cvs=canvas: graph(cvs))

Usually, Tkinter programs employ a class that implements the GUI. Here you
can keep your canvas in a class attribute and use it in the callback
method:

class myGUI(Frame):
# ....
self.canvas = Canvas(root, ...)
Button(root, text='OK', command=self.graph)

def graph(self, event=None):
# Use self.canvas

Hope this helps,
Hans-Joachim
 
R

Russell E. Owen

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,767
Messages
2,569,570
Members
45,045
Latest member
DRCM

Latest Threads

Top