Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Help for Tkinter and python's programming

3 views
Skip to first unread message

aurel-le-gnou

unread,
Jun 1, 2002, 10:30:41 PM6/1/02
to
Hi,


I'm french and just beginning in Python's programming.
I'm doing a really simple GUI with the module "Tkinter" for the
implementation of a meta search engine based on Google and altavista.

This Gui is quite finished but i've a problem. I'm sure it's really
stupid but I really not understand the reasons of this pb.
If someone can Help me, it would be really fine.


Here is my problem:

I create a GUI with some buttons and entry, textbox, radiobuttons...
Then there is a mainloop wainting for events.
When the button 1 is pressed on a button, i want to get each content of
these entries... and the state of the radiobuttons...
Then launch a program with the differents values and contents found just
before.
I don't success in associating the event and the function to execute
with the different arguments


All my tkinter widgets are created in a simple main by calling some
functions (no object implementation, this interface is really simple).
Particularly these two functions define naturally before the main:


def make_button(frame, text, color, side, padx, pady):

button = Button(frame, text=text, fg=color, bg='white')
button.pack(side=side,padx=padx, pady=pady)
return button


def make_call(frame_left, frame_right, list_one, list_two,
button_number, button_search, button_language, button_rank,
button_engine):

....
....

return button_language_content, button_number_content,
button_search_content, list_one, list_two, button_rank_content,
button_engine_content

The goal of the function make_call is:
- to get the differents arguments in the diferents Entry and the state
of different radiobuttons
- then call an other program program with these arguments.

In my main:

...

button_go = make_button(bottom,'GO','black',LEFT, '5m',0)

...
...


button_go.bind("<Button-1>", make_call(frame_left, frame_right,
list_one, list_two, button_number, button_search, button_language,
button_rank, button_engine))
# the arguments of make_call() are visible and defined in the main when
I do this "bind".

....

root.mainloop()


So, I just want to execute the fonction make_call with some arguments
(which corresponds to the different entries and radiobuttons to check
their content) when the button1 is pressed on my button_go. (this
arguments are visible in my main)
But the problem is that even if the user doesn't click on the button the
associate function is executed.
I've also problem in giving arguments to make_call()
(the number of arguments is sometimes wrong ... Is there a default
argument "event" given to the function with some informations ?)


Thanks a lot for your help and your criticm about what i've done wrong.

Aurélien Violet

aurelie...@noos.fr


Russell E. Owen

unread,
Jun 2, 2002, 12:57:26 AM6/2/02
to
In article <mailman.1022985123...@python.org>,
aurel-le-gnou <aurelie...@noos.fr> wrote:

>...
>button_go.bind("<Button-1>", make_call(frame_left, frame_right,
>list_one, list_two, button_number, button_search, button_language,
>button_rank, button_engine))
># the arguments of make_call() are visible and defined in the main when
>I do this "bind".

This is a common problem with callbacks in Python. When you supply
make_call(frame_left...) as a callback argument, you are actually
executing the function at that very moment and passing the result of its
execution to button_go.bind. In other words, you aren't passing a
function, you are passing a result of calling a function. Thus nothing
at all happens later when you press the button.

You must pass in the name of a function WITHOUT the parenthesis, e.g.
make_call, not make_call(frame_left...). But there is a hitch.
button_go.bind requires a callback function that takes exactly one
argument: an event. Yet you want to pass in a lot of extra information.
What you want is some way of saying "here is a function with some stuff
I know now and some other stuff I am going to specify later".

The thing I usually do in this situation is to create a callable object.
This is handy because an object can hold state, i.e. the "stuff I know
now", yet can also have a __call__ method which allows you to call the
object just like a function.

This comes up often enough that I have a class GenericCallback which
makes it easy (see code at the end, with thanks to Eric Brunel). Here is
an example, a simplified version of your code:

Suppose I want to call a function from a button press event, and that
function has two pieces of info I know in advance: frame_left and
frame_right. A callback function bound to a button press event must take
exactly one argument: the event (which in your case you don't care
about, but you still have to accept it!).

First define a function that takes all arguments, starting with the ones
you know in advance (frame_left and frame_right) and ending with the
ones that will be sent to the callback (evt):

def myFunc(frame_left, frame_right, evt):
...your code here...

Now create a GenericCallback object that can be called by bind. It must
take just one argument (evt), so you specify all the other arguments to
GenericCallback. This code is complete:

myCallback = GenericCallback(myFunc, frame_left, frame_right)
button_go.bind("<Button-1>", myCallback)

That's all there is to it! I've appended my GenericCallback.py. I save
it as a file and import it when I need it, and I use it a lot. There are
other approaches. Perhaps if I understood Python's scoping rules better
I could use non-local variables in the function, but I find the rules
non-obvious (whether the variable is local or global depends critically
on how it first appears in the function--on the right or left of an
assignment) and prefer to make things more explicit. Some folks use
lambdas, but I personally find them ugly.

-- Russell

Here is GenericCallback.py:

"""From Scott David Daniels
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549>.
My changes include:
- Used a name suggested by Eric Brunel
- Created a simpler and faster class that does not handle initial
keyword arguments
- Created a factory function to return the fastest callable object.
"""
def GenericCallback(callback, *firstArgs, **firstKWArgs):
"""Returns a callable object that when called with (*args, **kwArgs),
calls a callback function with arguments: (*(firstArgs + args),
**allKWArgs),
where allKWArgs = firstKWArgs updated with kwArgs

Note that if a given keyword argument is specified both at
instantiation
(in firstKWArgs) and when the object is called (in kwArgs),
the value in the call (kwArgs) is used. No warning is issued.
"""
if firstKWArgs:
return GC(callback, *firstArgs, **firstKWArgs)
else:
return GCNoKWArgs(callback, *firstArgs)


class GC:
"""A generic callback class."""
def __init__(self, callback, *firstArgs, **firstKWArgs):
self.__callback = callback
self.__firstArgs = firstArgs
self.__firstKWArgs = firstKWArgs

def __call__(self, *lastArgs, **kwArgs):
if kwArgs:
netKWArgs = self.__firstKWArgs.copy()
netKWArgs.update(self.__kwArgs)
else:
netKWArgs = self.__firstKWArgs
return self.__callback (*(self.__firstArgs + lastArgs),
**netKWArgs)


class GCNoKWArgs:
"""A generic callback class optimized for the case
of no stored keyword arguments. This has the potential
to significantly speed up execution of the callback.
"""
def __init__(self, callback, *firstArgs):
self.__callback = callback
self.__firstArgs = firstArgs

def __call__(self, *args, **kwArgs):
return self.__callback (*(self.__firstArgs + args), **kwArgs)

paul colby

unread,
Jun 2, 2002, 12:38:09 PM6/2/02
to
Russell E. Owen wrote:

<snip great response>

> The thing I usually do in this situation is to create a callable object.
> This is handy because an object can hold state, i.e. the "stuff I know
> now", yet can also have a __call__ method which allows you to call the
> object just like a function.
>

I think your suggestion is great and has many uses. One that I find pleasing
is passing a member function to handle events. When a member function is
passed the "self" argument tags along for the ride thus giving your callback
access to all the members of the class as well as all the member data of the
instance. For example consider a box drawn on a canvas. Lets say we want to
make the box red when clicked and blue when clicked again,

from Tkinter import *

canvas = Canvas(Tk(),bg='white')
canvas.pack(fill=BOTH,expand=1)

class Box:

def __init__(self, canvas, x, y):
self.canvas = canvas
self.tag = canvas.create_rectangle(x-10,y-10,x+10,y+10,fill='blue')
# here we set the callback
canvas.tag_bind(self.tag,'<Button-1>',self.mouseClick)

# here we define the callback. Note the "self" argument gets
# handled by python's class mechanisms
def mouseClick(self,event):
color = self.canvas.itemcget(self.tag,'fill')
if color == 'blue':
self.canvas.itemconfigure(self.tag,fill='red')
else:
self.canvas.itemconfigure(self.tag,fill='blue')

b1 = Box(canvas,30,30)
b2 = Box(canvas,50,100)


Regards
Paul Colby

Steve Tregidgo

unread,
Jun 3, 2002, 3:18:49 PM6/3/02
to
"Russell E. Owen" <ow...@nojunk.invalid> wrote in message news:<adc8kj$1ovk$1...@nntp6.u.washington.edu>...
[snip to code fragment of GC class]

> def __call__(self, *lastArgs, **kwArgs):
> if kwArgs:
> netKWArgs = self.__firstKWArgs.copy()
> netKWArgs.update(self.__kwArgs)
> else:
> netKWArgs = self.__firstKWArgs
> return self.__callback (*(self.__firstArgs + lastArgs),
> **netKWArgs)

I think this code contains a mistake. 'self.__kwArgs' should probably
be simply 'kwArgs' (4th line above). Convenient class, anyhow!

Cheers,
Steve

0 new messages