I am trying to use lambda to create callbacks for tkinter buttons.
There are multiple buttons and each callback needs to pass an object inside it. Following code is what I am doing and is running fine
var0 = tk.StringVar()
label = tk.Label(top, bg = "White",height = 2, width = 12,textvariable=var0, justify="right")
def b0Callback(var):
var.set(var.get()+"0")
return
# creating a label which will print value of the any of the 0-9 button pressed
# creating a button 0
b0 = tk.Button(numFrame0, height = 1, width = 4, bg = "grey", text =
"0",command = lambda: b0Callback(var0))
#there are more buttons like that
var0 is used to update a label. Above code is working fine but I have to create callback for 0 to 9 and I have to just repeat above definition. So I tried using following example from this tutorial
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2)
mytripler = myfunc(3)
print(mydoubler(11))
print(mytripler(11))
Using it I did following
def Callback(n):
return lambda var.set(var.get()+n)
b0Callback = Callback("0")
This shows error invalid index in the return line at var.set
Is there any way to pass var0 in this case to avoid this error?
Maybe its only me, but I don't see a reason for using lambda if you just want to add a number to the label text.
Lets make a function for it that gets your StringVar() as a variable and adds some number to it:
def button_callback(str_var, number):
str_var.set(str_var.get() + str(number))
To run this command we simply put it in the code as a lambda function, otherwise it will run upon initialization (because we are providing a function instead of a reference).
So to run it within a button we declare it like this:
my_button = Button(root, text='Some text here', command=lambda: button_callback(my_string_var, 5))
The '5' could be potentially changed to any other number.
I have now solved the problem, here is the final code:
I have also changed the number of buttons to 300 and added code to arrange them all in a nice grid, just for fun. (You can change this to however many you want by changing for number in range(1, whatever).
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.var0 = tk.StringVar()
self.var0.set('0')
# creating buttons and adding them to dictionary
self.buttons = {}
for number in range(1, 301):
self.buttons.update({'button' + str(number): tk.Button(self, height=1, width=4, bg="grey", text=number)})
label = tk.Label(self, textvariable=self.var0, font='none 50')
label.grid(column=0, row=0)
for button in self.buttons:
self.buttons[button].bind('<Button-1>', lambda event, num=button[6:]: self.Callback(event, num))
self.buttons[button].grid(column=(int(button[6:]) % 10), row=(int(button[6:]) / 10) + 1)
def Callback(self, event, num):
self.var0.set(num)
self.update()
Related
I'm new to Tkinter and as my first project I wanted to create a Tic Tac Toe.
I want to create 9 buttons, that will change their background image when I click on them, the problem is that I dont want to create a function for every single button but one function that will take the button in argument and will change its background image.
The code I wrote:
def play(bid):
if player == "X":
bid.config(image=cross)
if player == "O":
bid.config(image=circle)
b1 = tk.Button(app, text="", image=white, command=lambda id=b1: play(id))
b1.grid(column=0, row=0)
How can I pass b1 as an argument to play() function?
Thanks
I tried to use b1 as an argument to play(), and use play() to change b1's image.
When I try to run this code I get "name b1 is not defined".
Define a single function to create, configure, and bind your button; then your callback can close over the necessary variable referring to your button. Something like
def play(bid):
if player == "X":
bid.config(image=cross)
if player == "O":
bid.config(image=circle)
def add_button(app, r, c):
b = tk.Button(app, text="", image=white)
b.config(command=lambda: play(b))
b.grid(column=c, row=r)
return b
for row in [0,1,2]:
for col in [0,1,2]:
# Save the return value somewhere if necessary
addButton(app, row, col)
The lambda expression contains a free variable b, which refers to the variable b in the closest enclosing scope, which is the call to add_button where the lambda expression is evaluated.
One way you can do it is to separate the creation of the button from the assignment using the .config method.
b1 = tk.Button(app, text="", image=white)
b1.config(command=lambda btn_id=b1: play(btn_id))
This is not really the best way to go about it, it's better to instead pass something that defines the button like an Enum or text.
b1 = tk.Button(app, text="", image=white, command=lambda : play("b1"))
then check that "b1" is the input in your function, this way b1 can be redefined as what would happen in loops.
Here is what I wrote at my leisure, it may be useful. Just create a folder 'icons' and put 100x100 px images there
from tkinter import *
import random
class Main:
def __init__(self):
self.root = Tk()
#self.root.geometry('900x100')
def run(self):
self.variables()
self.interface()
self.root.mainloop()
def variables(self):
self.PHOTO_COUNTER = 0
self.photo_list = [
PhotoImage(file="icons/bublegum.png"),
PhotoImage(file="icons/fin.png"),
PhotoImage(file="icons/jake.png"),
PhotoImage(file="icons/marcelin.png"),
PhotoImage(file="icons/navel.png"),
PhotoImage(file="icons/winter_king.png"),
]
def interface(self):
self.Buttons = []
for i in range(10):
item = random.choice(self.photo_list)
self.btn = Button(self.root, image=item, command=lambda c=i: self.click(c))
self.btn.pack(fill=BOTH, expand=1, side=LEFT)
self.Buttons.append(self.btn)
def click(self, i):
btn = self.Buttons[i]
item = random.choice(self.photo_list)
btn.config(image=item)
A = Main()
A.run()
I am trying to create a game level editor. The program reads a binary file, associate the byte to a image and then, with a loop, it shows all the images in canvas (Canvas B). The images are Radiobutton. This part works properly.
Below (Canvas A) I have a set of images that are radiobuttons too. Here I choose the image I want to use by clicking on it. This image will be used to redesign the Canvas B Then I go to the Canvas B, decide which tile I want to change and click on it. And it works: the radiobutton has now the chosen image.
In fact it works every time but only the first time. If I change my mind and want to change a radiobutton already changed nothing happens.
I tried to understand what the problem is by printing the variable of the radiobutton with .get()and I see that it stored the value of the last rabiobutton clicked. I tried to reset this value even with del but it doesn't work.
Here's the code (canvas B)
img_list=[]
n_row = 0
n_col = 0
index = 0
x = IntVar()
for f in os.listdir(path):
img_list.append(ImageTk.PhotoImage(Image.open(os.path.join(path,f))))
n_col +=1
index +=1
if n_col > 21:
n_row +=1
n_col = 1
tile = Radiobutton(A, image=img_list[index-1], indicatoron=0, bd=2, variable = x, value = index, selectcolor="red", command=several)
tile.grid(row=n_row, column = n_col)
And here's Canvas A
def erase():
global val_t_e_x
del val_t_e_x
val_t_e_x=t_e_x.get()
print(val_t_e_x)
img_qui=[]
for f in os.listdir(path):
img_qui.append(ImageTk.PhotoImage(Image.open(os.path.join(path,f))))
def several_editor():
global codice_bb
global val_x
global val_t_e_x
val_t_e_x=t_e_x.get()
print(val_t_e_x)
row_qui=row_list[val_t_e_x-1]
col_qui=col_list[val_t_e_x-1]
tile_editor=Radiobutton(B, image=img_qui[val_x-1], variable = val_t_e_x, value = rev, indicatoron=0, bd=0, selectcolor="blue",
highlightbackground="black", highlightcolor="black", command=erase)
tile_editor.grid(row=row_qui, column=col_qui)
col_b=0
row_b=9
l_editor=[]
row_list=[]
col_list=[]
rev=0
t_e_x = IntVar()
for x, y in zip(line[::2], line[1::2]):
a= ("./gfx/"+(x+y)+".png")
row_b-=1
rev+=1
if row_b<1:
col_b+=1
row_b=8
im = Image.open(a)
ph = ImageTk.PhotoImage(im)
l_editor.append(ph)
tile_editor = Radiobutton(B, image=l_editor[rev-1], variable = t_e_x, value = rev, indicatoron=0, bd=0, selectcolor="blue",
highlightbackground="black", highlightcolor="black", command=several_editor)
tile_editor.grid(row=row_b, column=col_b)
row_list.append(row_b)
col_list.append(col_b)
I suppose that the problem is in the function def several_editor()
tile_editor=Radiobutton(B, image=img_qui[val_x-1], variable = val_t_e_x, value = rev,
indicatoron=0, bd=0, selectcolor="blue", highlightbackground="black",
highlightcolor="black", command=erase)
and that I am not handling the val_t_e_x variable properly.
Hope you can help me.
The reason your function isn't returning is because you don't know what the input is for several_editor(). You need to anonymize it with lambda.
I'm going to provide a simple example of why this is necessary:
import tkinter, os, sys
import tkinter.ttk as ttk
class T():
def __init__(self):
labelstr=["ascii"]
top = tkinter.Tk()
top.geometry('200x50')
top.title('lambda')
for label in labelstr:
self.lab = tkinter.Label(top, text=label, width=len(label)).grid(row=labelstr.index(label)+1, column=0)
self.ent = tkinter.Entry(top, width=10).grid(row=labelstr.index(label)+1, column=1)
but = tkinter.Button(top, text="submit", command=lambda: self.submit(top))
but.grid(row=labelstr.index(label)+1, column=3)
top.mainloop()
def submit(self, frame):
for i in frame.winfo_children():
if i.winfo_class() == "Entry":
i.delete(0, tkinter.END)
i.insert(0, "HelloWorld!")
I can't really test the code where I am at the moment, but this should work.
The above is to provide a functional example of proper use of a lambda function within your code and demonstrate why you'd need it. Try removing the lambda and see that the function runs only once and will no longer run properly. This symptom should look familiar to you as it's exactly as you described it. Add the lambda back and the function runs again each time you hit the button.
I am somewhat new to Python, and extremely new to class structures. I am trying to build a calculator GUI with buttons that insert a value into an Entry display. My calculator works without using class structures (the functional code is below), but I'm trying to make the same code work while incorporating the classes. My reason for doing this is to add a "shift" function that changes the values of text on the buttons, and what values (same as the text) they insert into the display. The values as you can see below are class variables, and in order to change the text on the buttons, the shift button is pressed which changes the value of the variables (I have gotten this to work in a non-tkinter file without buttons, etc). The problem is I am getting the GUI with an Entry and two Buttons (good) and '1' in the Entry. (not good) The buttons also don't do anything. Here is my code: (The one that works first)
from tkinter import *
import parser
import math
root = Tk()
root.title('Calculator')
displayb = Entry(root)
displayb.grid(row = 3, columnspan = 6, ipady = 7, ipadx = 100)
def display(e):
global i
i = len(displayb.get())
displayb.insert(i,e)
one = Button(root, text = '1', width = 10, height = 1, command = lambda : display(1))
one.grid(row = 4, column = 0)
root.mainloop()
New Code:
from tkinter import *
import parser
class Calc:
text1 = '1'
shft = 'shft1'
def __init__(self,master):
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.one = Button(master, text = Calc.text1, command = self.display((Calc.text1)))
self.one.grid(row = 4, column = 1)
self.update = Button(master, text = Calc.shft, command = self.update((Calc.shft[4:5])))
self.update.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
Calc.text1 = 'A'
Calc.shft = 'shft2'
else:
Calc.text1 = '1'
Calc.shft = 'shft1'
return
def display(self,e):
i = len(self.displaya.get())
self.displaya.insert(i,e)
return
root = Tk()
calc = Calc(root)
root.mainloop()
Can you help me find a solution that makes my code work properly? Thanks
Edit: I attempted using StringVar() and .set, but on the interface, the shift button, instead of saying shft1 or even shft2, said "PY_VAR1". The one button said 1, but inserted "PY_VAR0" to the display. When pressed, the shift button raised this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tkinter/__init__.py", line 1699, in __call__
return self.func(*args)
File "/Users/ryanflynn/Classesmod.py", line 13, in <lambda>
self.update_button = Button(master, text = self.shft, command = lambda: self.update(int(self.shft[4:5]))) # use int()
TypeError: 'StringVar' object is not subscriptable
Your code has few errors in it, here is a list of all of them and a modified version of your code:
1st: Your button has the same name as one of your function, change it to a unique name.
2nd: Don't use the class's namespace unless it's necessary, use self instead.
3rd: You only need one set of brackets when calling a function, else it's redundant.
4th: Use lambda or functools.partial to set a command that has arguments.
5th: You can use END(since you did from tkinter import *, otherwise tkinter.END) to specify the ending instead of doing len(self.displaya.get()).
6th: Calc.shft[4:5] returns a string not an int. So in order to get your result, use int(Calc.shft[4:5]).
7th: I didn't see you using parser, so I don't see why you need it.
8th: Don't add a return at the end of a function that returns nothing, it's redundant. Since that's the default for all functions.
This is a modified(working) version of your code:
from tkinter import *
class Calc:
def __init__(self,master):
self.text1 = '1' # make them self's not Calc's
self.shft = 'shft1'
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.one = Button(master, text = self.text1, command = lambda: self.display(self.text1)) # use lambda and single brackets
self.one.grid(row = 4, column = 1)
self.update_button = Button(master, text = self.shft, command = lambda: self.update(int(self.shft[4:5]))) # use int()
self.update_button.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
self.text1 = 'A'
self.shft = 'shft2'
else:
self.text1 = '1'
self.shft = 'shft1'
def display(self,e):
self.displaya.insert(END,e) # use END instead
root = Tk()
calc = Calc(root)
root.mainloop()
This code does work but the button text shouldn't change
EDIT:
If you want to use string variable to make the text change, you will need a StringVar as well:
from tkinter import *
class Calc:
def __init__(self,master):
self.master = master
self.displaya = Entry(master)
self.displaya.grid(row = 1, columnspan = 6)
self.text1 = StringVar(value = '1') # change to this
self.shft = StringVar(value = 'shft1')
self.one = Button(master, textvariable = self.text1, command = lambda: self.display(self.text1.get())) # Use .get()
self.one.grid(row = 4, column = 1)
self.update_button = Button(master, textvariable = self.shft, command = lambda: self.update(int(self.shft.get()[4:5])))
self.update_button.grid(row = 0, column = 0)
def update(self, mode):
if mode == 1:
self.text1.set('A') # set the variable
self.shft.set('shft2')
else:
self.text1.set('1')
self.shft.set('shft1')
def display(self,e):
self.displaya.insert(END,e) # use END instead
root = Tk()
calc = Calc(root)
root.mainloop()
When you provide a function to the command argument, you need to provide the actual function, and not the result of calling the function. In other words, you can't have () on the end. One hacky way of handling this is to use lambda as you did earlier:
self.one = Button(master, text = Calc.text1, command = lambda: self.display((Calc.text1)))
A slightly less hacky way is to use functools.partial:
from functools import partial
# ...
self.one = Button(master, text = Calc.text1, command = partial(self.display, Calc.text1))
IMO the best way is to make a real method to use.
self.one = Button(master, text = Calc.text1, command = self.one_pushed)
# ...
def one_pushed(self):
self.display(Calc.text1)
As a side note: tkinter will automatically calculate the end point for you if you pass 'end' as the position:
def display(self,e):
self.displaya.insert('end',e)
I'm creating a GUI where I need to create certain number of entries and buttons in Tkinter. I'd like to create all these in a for loop. As actions, when I press any of the button, it should transfer the value of the Entry to the callback of the button next to it.
This is what I've done so far but it's not working yet.
n=0
self.button = []
self.entFreq = []
for calVal in calibration:
lbl = Label(self.calFrame)
lbl.configure(text = "Set amplitud to " + calVal)
lbl.configure(background=self.bg_App, fg = "white")
lbl.grid(row=n, column=0)
self.entFreq.append(Entry(self.calFrame, width=10))
self.entFreq[n].grid(row=n, column=1, padx = 10)
#Construction Button send frequency
self.button.append(Button(self.calFrame, text="Cal", borderwidth=0, relief="groove", command = lambda n=self.entFreq[n].get(): self.get_val(n)))
self.button[n].configure(bg="#FFF3E0")
self.button[n].grid(row=n, column=2)
n+=1
def get_val(self, var):
print "Got this:", str(var)
I'm just getting blank in the var function. How to link those two?
You're putting too much code into your lambdas. You only need to pass in n, and get_val can do the rest of the work:
self.button.append(Button(..., command=lambda n=n: self.get_val(n)))
...
def get_val(self, n):
value = self.entFreq[n].get()
print "Got this:", value
You might want to consider defining a class for this set of label, entry and button, since they are designed to work together and you're making several sets.
You could, for example,pass in a label and a function to call when the user clicks the button. For example:
class LabelEntry(object):
def __init__(self, parent, text, command):
self.command = command
self.label = Label(parent, text=text)
self.entry = Entry(parent)
self.button = Button(parent, text="Cal", command=self.call_command)
def call_command(self):
value = self.entry.get()
self.command(value)
You would use it something like this:
def some_function(self, value):
print "the value is", value
...
for calVal in calibration:
le = LabelEntry(frame,
text="Set aplitud to " + calVal,
command=self.some_function)
le.label.grid(...)
le.entry.grid(...)
le.button.grid(...)
I want to be able to click the button and have the message box display the code generated. Here's part of the code:
global s
letters = [random.choice('BCDFGHJKMPQRTVWXYYZ') for x in range(19)]
numbers = [random.choice('2346789') for x in range(6)]
s = letters + numbers
random.shuffle(s)
s = ''.join(s)
global Code
Code = Entry(state='readonly')
def callback():
Code = Entry(state='readonly', textvariable=s)
Code.grid(row=0, pady=20)
generate=PhotoImage(file='generate.gif')
G = Button(image=generate , command=callback, compound=CENTER)
G.grid(row=1, padx=206.5, pady=20)
Fixed a few things up with comments:
from Tkinter import *
import random
root = Tk()
letters = [random.choice('BCDFGHJKMPQRTVWXYYZ') for x in range(19)]
numbers = [random.choice('2346789') for x in range(6)]
s = letters + numbers
random.shuffle(s)
s = ''.join(s)
# Rather than use global variables, which is generally a bad idea, we can make a callback creator function, which takes the formerly global variables as arguments, and simply uses them to create the callback function.
def makeCallback(sVariable):
def callback():
# This will set the text of the entry
sVar.set(s)
return callback
# Use a StringVar to alter the text in the Entry
sVar = StringVar(root)
# You can use an Entry for this, but it seems like a Label is more what you're looking for.
Code = Entry(root, state='readonly', textvariable=sVar)
# Create a callback function
callback = makeCallback(sVar)
Code.grid(row=0, pady=20)
generate=PhotoImage(file='generate.gif')
G = Button(root, image=None , command=callback, compound=CENTER)
G.grid(row=1, padx=206.5, pady=20)