Two naive Tkinter questions

A

Andrew Koenig

from Tkinter import *

class Application(Frame):

def setcolor(self):
self["bg"] = "blue"

def createWidgets(self):
self.b1 = Button(self, bg = "red", command = self.setcolor)
self.b1.place(height = 50, width = 50)

self.b2 = Button(self, text = "Exit", command = self.quit)
self.b2.place(height = 50, width = 50, x = 50)

def __init__(self, master=None):
Frame.__init__(self, master)
self.place(height = 50, width = 100)
self.createWidgets()

app = Application()
app.mainloop()


1) When I run this program, it displays two buttons. When I click the
button on the left, I would like the color of that button to change from red
to blue. This code is obviously the wrong way to accomplish this, because
when setcolor is called, it gets the button's parent, not the button itself.
How do I arrange for setcolor to get the right object?

2) The window in which these buttons appear is the wrong size, and does not
depend on the height and width given to self.place in __init__. Yet the
height and width arguments do something, because if I set width to 75, it
cuts off half the right-hand button. How do I say how large I want the
window to be?
 
M

Martin v. =?iso-8859-15?q?L=F6wis?=

Andrew Koenig said:
1) When I run this program, it displays two buttons. When I click the
button on the left, I would like the color of that button to change from red
to blue. This code is obviously the wrong way to accomplish this, because
when setcolor is called, it gets the button's parent, not the button itself.
How do I arrange for setcolor to get the right object?

In the specific example, you could just *know* that setcolor deals
with self.b1. In the more general example, you can create dynamic
callback functions:

self.b1 = Button(self, bg = "red",
command = lambda: self.b1.config(bg="blue"))

This uses a number of tricks: the lambda function has no arguments,
yet it uses self - so it is a nested function. Also, inside a lambda
function, you can have only expressions, so self.b1['bg']='blue' would
not be allowed. In the general case, and not assuming nested
functions, you would write

def createWidgets(self):
def b1_setcolor(self=self):
self.b1['bg']='blue'
self.b1 = Button(self, bg = "red", command=b1_setcolor)
2) The window in which these buttons appear is the wrong size, and does not
depend on the height and width given to self.place in __init__. Yet the
height and width arguments do something, because if I set width to 75, it
cuts off half the right-hand button. How do I say how large I want the
window to be?

The problem is that there is another toplevel widget around your
frame; the frame itself has the size you have specified. You could
either use Toplevel instead of Frame as a base, or you could adjust
the size of the root window, e.g. through

app.master.wm_geometry("100x50")

HTH,
Martin
 
A

Andrew Koenig

In the specific example, you could just *know* that setcolor deals
with self.b1. In the more general example, you can create dynamic
callback functions:

self.b1 = Button(self, bg = "red",
command = lambda: self.b1.config(bg="blue"))

This uses a number of tricks: the lambda function has no arguments,
yet it uses self - so it is a nested function. Also, inside a lambda
function, you can have only expressions, so self.b1['bg']='blue' would
not be allowed. In the general case, and not assuming nested
functions, you would write

def createWidgets(self):
def b1_setcolor(self=self):
self.b1['bg']='blue'
self.b1 = Button(self, bg = "red", command=b1_setcolor)

I worked out something similar, but I must confess that it appears
needlessly complicated. I was hoping for a simpler solution, such as a
variation of the "command" attribute that would cause its associated
argument to be called with the button rather than its parent.

Your first suggestion, knowing that setcolor deals with self.b1, doesn't
work with my application because I'm going to have lots of these buttons,
and I want to be able to set their colors independently.
The problem is that there is another toplevel widget around your
frame; the frame itself has the size you have specified. You could
either use Toplevel instead of Frame as a base, or you could adjust
the size of the root window, e.g. through

app.master.wm_geometry("100x50")

Gotcha -- thanks.
 
P

Peter Otten

Andrew said:
work with my application because I'm going to have lots of these buttons,
and I want to be able to set their colors independently.

If you have many buttons with similar functionality, I'd suggest using a
subclass, e. g.:

import Tkinter as tk

class ColorButton(tk.Button):
def __init__(self, master, text, color):
tk.Button.__init__(self, master, text=text, command=self.execute)
self.color = color
def execute(self):
self["background"] = self.color


root = tk.Tk()
for color in "red green blue yellow".split():
ColorButton(root, text=color.capitalize(), color=color).pack()
root.mainloop()

Peter
 
A

Alan Gauld

Your first suggestion, knowing that setcolor deals with self.b1, doesn't
work with my application because I'm going to have lots of these buttons,
and I want to be able to set their colors independently.

A slight variant on the technique using lambda is:

def setcolor(self,widget=self):
widget['bg']='blue'
def createWidgets(self):
self.b1 = Button(self, bg = "red",
command=lambda: self.setcolor(self.b1))

Here we use the lambda to call the setcolor method with
the widget parameter and use that within the setcolor method.

This way you keep one method but call it from several places.
The downside is you introduce an extra function call, but in
a GUI event handler that's not going to be a problem!

HTH,

Alan G.




Author of the Learn to Program website
http://www.freenetpages.co.uk/hp/alan.gauld
 
M

Martin Franklin

In the specific example, you could just *know* that setcolor deals
with self.b1. In the more general example, you can create dynamic
callback functions:

self.b1 = Button(self, bg = "red",
command = lambda: self.b1.config(bg="blue"))

This uses a number of tricks: the lambda function has no arguments,
yet it uses self - so it is a nested function. Also, inside a lambda
function, you can have only expressions, so self.b1['bg']='blue' would
not be allowed. In the general case, and not assuming nested
functions, you would write

def createWidgets(self):
def b1_setcolor(self=self):
self.b1['bg']='blue'
self.b1 = Button(self, bg = "red", command=b1_setcolor)

I worked out something similar, but I must confess that it appears
needlessly complicated. I was hoping for a simpler solution, such as a
variation of the "command" attribute that would cause its associated
argument to be called with the button rather than its parent.

Your first suggestion, knowing that setcolor deals with self.b1, doesn't
work with my application because I'm going to have lots of these buttons,
and I want to be able to set their colors independently.


For this I would use a callback class and it's __call__ method

(untested)

class Callback:
def __init__(self, button, colour):
self.button = button
self.colour = colour


def __call__(self):
self.button.config(background = self.colour)





self.b1 = Button(self, background = "red")
self.b1.config(command = Callback(self.b1, "blue"))
self.b1.pack(.....)



Regards
Martin
 

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

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,043
Latest member
CannalabsCBDReview

Latest Threads

Top