Arguments for button command via Tkinter?

D

dakman

Recently, I have been needing to do this alot and I can never find a
way around it, the main reason I want to do this is because for example
in the application I am making right now, it creates a grid of buttons
in a loop and they all have the same purpose so they need to call the
same method, within this method they need to change the background
color of the button clicked, I've tried stuff like...

button['command'] = lambda: self.fill(button)

def fill(self, wid):
button['bg'] = '#ff0000'

But that's no good, everytime it I do click a button the lambda method
I have setup keeps getting recreated so all of the button commands
point to the same lambda method, I have also tried appending them to an
array, and calling them from that.

Anyone have any clue as of what to do? Thanks.
 
D

dakman

And yet the stupidity continues, right after I post this I finnally
find an answer in a google search, It appears the way I seen it is to
create a class for each button and have it call the method within that.
If anyone else has any other ideas please tell.
 
S

Steve Holden

And yet the stupidity continues, right after I post this I finnally
find an answer in a google search, It appears the way I seen it is to
create a class for each button and have it call the method within that.
If anyone else has any other ideas please tell.
I'm hoping you have simply mis-stated your correct understanding of the
problem, and that what you really propose to do is create a button class
of which each button in your interface is an instance.

Let's suppose you want each button to toggle between two colors, but
that the colors for each button are different. The thing to do is create
a button class that subclasses the button class of your GUI package
(whatever that may be), storing the required color values as instance
attributes.

In wxPython, for example, I would write something like (warning: untested):

import wx

class myButton(wx.Button):

def __init__(self, color1, color2, *args, **kw):
wx.Button.__init__(self, *args, **kw)
self.c1 = color1
self.c2 = color2
self.state = False

def onClick(self, event):
if self.state:
self.SetBackgroundColor(self.c1)
else:
self.SetBaclgroundColor(self.c2)
self.state = not self.state

Then when you create a button with, say,

but1 = myButton(wx.RED, wx.BLUE, ...)

you can associate a click on that button with a bound instance method,
which you'd do in wxPython like:

wx.EVT_BUTTON(parent, but1, but2.onClick)

but similar considerations apply to Tkinter, and you appear to have the
wit to be able to extend the argument to that toolkit.

regards
Steve
 
F

Francesco Bochicchio

Il Mon, 31 Oct 2005 06:23:12 -0800, (e-mail address removed) ha scritto:
And yet the stupidity continues, right after I post this I finnally
find an answer in a google search, It appears the way I seen it is to
create a class for each button and have it call the method within that.
If anyone else has any other ideas please tell.

This is how I do it: Supposing I have three buttons b1, b2 and b3, and I
want for each button to call the same callback with, as argument, the
button itself:


def common_callback(button):
# callback code here


class CallIt(objetc):
def __init__(function, *args ):
self.function, self.args = function, args
def __call__(self, *ignore):
self.function(button, *self.args)

b1['command']= CallIt(common_callback, b1)
b2['command']= CallIt(common_callback, b2)
b3['command']= CallIt(common_callback, b3)

This way you need only one class (a sort of custom callable) and
its instances gets called by Tkinter and in turn calls your
callback with the proper arguments.

Ciao
 
M

Mike Meyer

Recently, I have been needing to do this alot and I can never find a
way around it, the main reason I want to do this is because for example
in the application I am making right now, it creates a grid of buttons
in a loop and they all have the same purpose so they need to call the
same method, within this method they need to change the background
color of the button clicked, I've tried stuff like...

button['command'] = lambda: self.fill(button)

def fill(self, wid):
button['bg'] = '#ff0000'

But that's no good, everytime it I do click a button the lambda method
I have setup keeps getting recreated so all of the button commands
point to the same lambda method, I have also tried appending them to an
array, and calling them from that.

Anyone have any clue as of what to do? Thanks.

People have pointed out how to do this with a class, but you can do it
with lambdas as well. I suspect the problem you're having is that
python is binding values at call time instead of at definition time,
but it's hard to tell without more of your source code.

If I'm right, one solution is to make the values you want bound at
definition time optional arguments to the lambda, bound to the correct
value then. So you'd do:

button['command'] = lambda b = button: self.fill(b)

The version you used will look up the name button when the lambda is
invoked. My version will look it up when the lambda is defined.

<mike
 
S

Steve Holden

Francesco said:
Il Mon, 31 Oct 2005 06:23:12 -0800, (e-mail address removed) ha scritto:

And yet the stupidity continues, right after I post this I finnally
find an answer in a google search, It appears the way I seen it is to
create a class for each button and have it call the method within that.
If anyone else has any other ideas please tell.


This is how I do it: Supposing I have three buttons b1, b2 and b3, and I
want for each button to call the same callback with, as argument, the
button itself:


def common_callback(button):
# callback code here


class CallIt(objetc):
def __init__(function, *args ):
self.function, self.args = function, args
def __call__(self, *ignore):
self.function(button, *self.args)

b1['command']= CallIt(common_callback, b1)
b2['command']= CallIt(common_callback, b2)
b3['command']= CallIt(common_callback, b3)

This way you need only one class (a sort of custom callable) and
its instances gets called by Tkinter and in turn calls your
callback with the proper arguments.
I don't see why this is preferable to having the callback as a bound
method of the button instances. What's the advantage here? It looks
opaque and clunky to me ...

regards
Steve
 
R

Ron Adam

Steve said:
Francesco said:
Il Mon, 31 Oct 2005 06:23:12 -0800, (e-mail address removed) ha scritto:

And yet the stupidity continues, right after I post this I finnally
find an answer in a google search, It appears the way I seen it is to
create a class for each button and have it call the method within that.
If anyone else has any other ideas please tell.



This is how I do it: Supposing I have three buttons b1, b2 and b3, and I
want for each button to call the same callback with, as argument, the
button itself:


def common_callback(button):
# callback code here


class CallIt(objetc):
def __init__(function, *args ):
self.function, self.args = function, args
def __call__(self, *ignore):
self.function(button, *self.args)

b1['command']= CallIt(common_callback, b1)
b2['command']= CallIt(common_callback, b2)
b3['command']= CallIt(common_callback, b3)

This way you need only one class (a sort of custom callable) and
its instances gets called by Tkinter and in turn calls your
callback with the proper arguments.
I don't see why this is preferable to having the callback as a bound
method of the button instances. What's the advantage here? It looks
opaque and clunky to me ...

I'm not sure on the advantage either. I just recently started handling
my buttons with button id's.

def __init__(self, *args, **kwds):
...

b1 = Tk.Button( frame, text=button,
command=self.command(button) )
...

The button label is used as the id above, but a number or code could
also be used.

def command(self, id):
""" Assign a command to an item.
The id is the value to be returned.
"""
def do_command():
self.exit(event=id)
return do_command

In this case it's a dialog button so it calls the exit method which sets
self.return to the button id before exiting.

Cheers,
Ron
 
F

Francesco Bochicchio

Il Mon, 31 Oct 2005 19:23:18 +0000, Steve Holden ha scritto:
Francesco said:
Il Mon, 31 Oct 2005 06:23:12 -0800, (e-mail address removed) ha scritto:

And yet the stupidity continues, right after I post this I finnally
find an answer in a google search, It appears the way I seen it is to
create a class for each button and have it call the method within that.
If anyone else has any other ideas please tell.


This is how I do it: Supposing I have three buttons b1, b2 and b3, and I
want for each button to call the same callback with, as argument, the
button itself:


def common_callback(button):
# callback code here


class CallIt(objetc):
def __init__(function, *args ):
self.function, self.args = function, args
def __call__(self, *ignore):
self.function(button, *self.args)

b1['command']= CallIt(common_callback, b1)
b2['command']= CallIt(common_callback, b2)
b3['command']= CallIt(common_callback, b3)

This way you need only one class (a sort of custom callable) and
its instances gets called by Tkinter and in turn calls your
callback with the proper arguments.
I don't see why this is preferable to having the callback as a bound
method of the button instances. What's the advantage here? It looks
opaque and clunky to me ...

regards
Steve

I'm not saying that my method is better or even 'preferable'. Just
different.

The reason I came up this approach (years ago) is because I was used to
other toolkits that always pass the widget as argument of the callback. So
this was a fast solution to 'normalize' Tkinter with respect to what I
perceived (and still do) as a limitation. As a bonus, you can also pass to
the callback as many other arguments as you want.
Another reason is that my basic approach to coding GUI is to use a class
for each window. To put the code to handle button callbacks in a separate
class feels to me a bit too much dispersive and potentially cumbersome if
callback code has to interact with other window elements (although in
Python nothing is impossible).

Ciao
 

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,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top