This question already has answers here:
Python Tkinter: Passing arguments to Button widget
(1 answer)
commands in tkinter when to use lambda and callbacks
(2 answers)
Closed 7 years ago.
I want to have UI where when I press a button, stuff pops up in the console. The issue is the stuff prints into the console before I press the button. After some testing, I have found that if I don't use parenthesis to call my function in the command argument, it works fine.
ex: A function called hello prints hello world in the console, so I would call it like button=Button(master, command=hello) instead of button=Button(master, command=hello())
The issue with this way is I can't use parameters.
here is an example of a code similar to mine:
index={'Food':['apple', 'orange'], 'Drink':['juice', 'water', 'soda']}
Names=['Food', 'Drink']
def display(list):
for item in list:
print(item)
from tkinter import *
mon=Tk()
app=Frame(mon)
app.grid()
for item in Names:
button=Button(mon, text=item, command=diplay(index[item]))
button.grid()
mon.mainloop()
Any ideas of how to be able to use parameters? I hope this all made sense, but if it didn't please leave a comment. Thank You.
You are looking for the lambda keyword:
from tkinter import *
index={'Food':['apple', 'orange'], 'Drink':['juice', 'water', 'soda']}
Names=['Food', 'Drink']
def display(list):
for item in list:
print(item)
mon=Tk()
app=Frame(mon)
app.grid()
for item in Names:
Button(mon, text=item, command= lambda name = item: display(index[name])).grid()
mon.mainloop()
You have to use name = item so that every time a button is initialized, it takes the current value of item from the for loop.
If for example you used lambda: display(index[item]), both buttons would display the values for 'Drink' because that is the last value of the lambda function initialized in the loop.
Related
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed last year.
I face an issue which is that my first button is using the second button's command. I have faced this logic error multiple times when trying to create buttons programmatically with different functions, is there a way to resolve this or is this a limitation to Tkinter? The gif below illustrates my issue.
import tkinter as tk
root = tk.Tk()
root.geometry("400x400")
def print_when_clicked(message):
print(message)
array = ["hi", "bye"]
for i in array:
tk.Button(root, text=i, command=lambda:print_when_clicked(i)).pack()
You have fallen in to one of the classic Python traps. The issue is that the lambda captures the i variable, NOT the value of i. So, after the loop finishes, i is bound to "bye", and both functions use it.
The trick to work around this is to use a fake default argument:
for i in array:
tk.Button(root, text=i, command=lambda i=i:print_when_clicked(i)).pack()
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 5 years ago.
I don't get how to reference the button being clicked in tkinter.
My code:
for file in files:
btn = Button(root, text=file).pack()
Now e.g. 50 buttons are generated from files which is the source.
However when I click any button, only the LAST button is being referenced, but not the button I really want to use/click.
In JavaScript we use this to reference the object we really clicked, however I couldn't find any solution in Python for this.
This can be done with something like the below:
from tkinter import *
root = Tk()
files = [] #creates list to replace your actual inputs for troubleshooting purposes
btn = [] #creates list to store the buttons ins
for i in range(50): #this just popultes a list as a replacement for your actual inputs for troubleshooting purposes
files.append("Button"+str(i))
for i in range(len(files)): #this says for *counter* in *however many elements there are in the list files*
#the below line creates a button and stores it in an array we can call later, it will print the value of it's own text by referencing itself from the list that the buttons are stored in
btn.append(Button(root, text=files[i], command=lambda c=i: print(btn[c].cget("text"))))
btn[i].pack() #this packs the buttons
root.mainloop()
So what this does is create a list of buttons, each button has a command assigned to it which is lambda c=i: print(btn[c].cget("text").
Let's break this down.
lambda is used so that the code following isn't executed until the command is called.
We declare c=i so that the value i which is the position of the element in the list is stored in a temporary and disposable variable c, if we don't do this then the button will always reference the last button in the list as that is what i corresponds to on the last run of the list.
.cget("text") is the command used to get the attribute text from a specific tkinter element.
The combination of the above will produce the result you want, where each button will print it's own name after being pressed, you can use similar logic to apply it to call whatever attribute or event you need.
This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 6 years ago.
I can't figure out why the function that I've called auto runs when I run the script, without pressing the button.
import tkinter
from tkinter import filedialog
root = tkinter.Tk ()
root.title("fool")
root.geometry("300x300")
br = tkinter.Button(root, text ="Carica File", command = filedialog.askopenfile(mode="r"))
br.pack()
Right now, you're passing the result of the call
filedialog.askopenfile(mode="r")
to the command parameter. To be able to get this result, the function is executed and you're seeing the dialog right away. What you probably want to do is just provide the name of a function to call when the button is pressed, so you could define one as
def foo():
filedialog.askopenfile(mode="r")
and use
command = foo
In the Button call. What you're doing in your code above corresponds to command = foo() instead (which executes the function), and not command = foo.
If you want to do everything in the same line, and not define an extra function, you could also use a lambda and write:
command = lambda: filedialog.askopenfile(mode="r")
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 years ago.
I am using python 3 with tkinter and I am having issues with a command I want to execute from a button. A variable number of buttons are generated, one for each visitor, and I am trying to call the function signOut from the button press whilst passing the relevent item (visitor) from the list to it.
I realise that the issue is with the for loop as by the time the button is pressed, i will == the last item in the list. How can I make it specific to the actual visitor. I can't seem to think of the solution. Any advice is appreciated.
buttonDictionary = {}
for i in range(0,len(currentVisitors)):
buttonDictionary[i] = Button(bottomFrame, text=currentVisitors[i], command=lambda: signOut(topFrame, bottomFrame, currentVisitors[i]))
buttonDictionary[i].pack()
It is my understanding that e.g. i in a lambda within a loop like this is referring to the variable i itself, not the value of the variable on each iteration, so that when the command callback is called, it will use the value of i at that moment, which as you noticed is the value on the last iteration.
One way to solve this is with a partial. partial will, in effect "freeze" the arguments at their current state in the loop and use those when calling the callback.
Try using a partial instead of a lambda like this:
from functools import partial
buttonDictionary = {}
for i in range(0,len(currentVisitors)):
buttonDictionary[i] = Button(bottomFrame, text=currentVisitors[i], command=partial(signOut, topFrame, bottomFrame, currentVisitors[i]))
buttonDictionary[i].pack()
Another way I have seen this done, but haven't tried, is to assign i to a new variable in your lambda each time:
command=lambda i=i: signOut(topFrame, bottomFrame, currentVisitors[i])
I have gotten burned bad more than once when I first started with Python by using lambdas in loops (including a case very similar to this trying to assign a callback to dynamically generated buttons in a loop). I even created a snippet that would expand to think_about_it('are you sure you want to use a lambda?') whenever I typed lambda just to remind me of the pain I caused myself with that...
This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 6 months ago.
I am writing a GUI with Python TKinter where I have a grid of some 24 buttons which I have created using a loop (not individually). Is there any way in which I can get the Text of the button that I pressed.
Since it is in loop, a callback function even with lambda is not helping me. I don't wish to write separate code for, what happens when each different button is pressed. I just need to know the text of the corresponding button so that I could initiate another generic function which works with that text only.
ps: I am able to do the same task but with List and curselection() and don't want it this way.
self.num = 11
for r in range(0,5):
for c in range(0,3):
R = r; C = c
resNum = "Ch0"+str(self.num);
self.button1_rex = tk.Button(self.frame, text = resNum,font=("Helvetica", 14), justify = CENTER,width = 20, command = self.new_window)
self.button1_rex.grid(row=R, column=C, sticky=W)
self.num = self.num+1
self.new_window is the function that opens a new window and needs to do other functionalities based on the button number (like "Ch011" etc)
The simplest way is just to, when you are constructing the button, bind the name to the command, either using functools.partial or a lambda.
Using functools.partial:
self.button1_rex = tk.Button(..., command=functools.partial(self.new_window, resNum))
Using a lambda:
self.button1_rex = tk.Button(..., command=lambda r=resNum: self.new_window(r))
For more infomation about the lambda, see What is a lambda (function)? and Python tkinter creating buttons ... arguments.