This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed last year.
I'm trying to create a few Buttons (with a for loop) like so:
def a(self, name):
print(name)
users = {"Test": "127.0.0.0", "Test2": "128.0.0.0"}
row = 1
for name in users:
user_button = Tkinter.Button(self.root, text=name,
command=lambda: self.a(name))
user_button.grid(row=row, column=0)
row += 1
and for the buttons to each get their own parameter (Test getting "Test" and Test2 getting "Test2"), but when I press the buttons they both print "Test2" which means they are using the same function with the same parameter.
How can I solve this?
The problem is your lambda expression in the for loop. It is using the name variable, but the name variable gets reassigned each iteration of the for loop, so in the end, all of the buttons get the last value that name was assigned to in the for loop. To avoid this you can use default keyword parameters in your lambda expression like so:
user_button = Tkinter.Button(self.root, text=name,
command=lambda name=name: self.a(name))
This binds the current value of the name variable to the lambda's name keyword argument each time through the loop, producing the desired effect.
Related
I'm trying to add two integers dynamically with tkinter. My idea is to get the sum after typing in a integer.
My idea:
import tkinter as tk
root = tk.Tk()
root.geometry("600x100")
v = tk.IntVar()
w = tk.IntVar()
eingabefeld_0 = tk.Entry(master=root, textvariable = v)
eingabefeld_0.place(x=80, y=40)
eingabefeld_1 = tk.Entry(master=root, textvariable = w)
eingabefeld_1.place(x=320, y=40)
label = tk.Label(master=root, textvariable = v)
label.place(x=80, y=80)
label = tk.Label(master=root, textvariable = str(int(v.get())+int(w.get())))
label.place(x=320, y=80)
root.mainloop()
If start the program the result of the addition is not presented in the label. What is missing here?
You aren't using the textvariable option correct. The value passed to that option needs to be an instance of one of the tkinter variable objects (StringVar, IntVar, etc). You can't just pass an expression to it.
You need to write a function that calculates the result and sets the value of this variable. The function can then be called from a button, or you can set a trace on the variable and update it every time the value changes. You'll have to make sure to take care of the case where one of the values you're trying to add is not a valid integer.
For example, start by creating a new variable for the result, and using it in your label. In this case I recommend StringVar so that we can display something like "???" if we can't compute the value.
resultvar = tk.StringVar()
label = tk.Label(master=root, textvariable = resultvar)
Next, create a function that computes the result and stores it in this variable. You need to use try/except since it's possible for the user to delete everything in the entry widget in which case the call to .get() will fail.
def compute_result():
try:
result = v.get() + w.get()
resultvar.set(result)
except Exception as e:
resultvar.set("???")
Now you just need to add code that calls this function. You can do it several ways:
you can bind to the return key so that the result is updated when the user presses that key
you can bind to any key release so that the result is updated when the user presses and releases any key
you can add a button that the user has to click to update the value
you can add a trace to the input variables, which will cause a function to be called whenever the values change
To use a trace, call trace_variable telling it when to do the trace ("w" means whenever the value is written/changed), and what function to call. The trace will append some arguments to the function that we don't use, so we'll have to change the function to accept these arguments
def compute_result(*args):
try:
result = v.get() + w.get()
resultvar.set(result)
except Exception as e:
resultvar.set(f"???")
v.trace_add("write", compute_result)
w.trace_add("write", compute_result)
With that, whenever the value in the variable v or w changes, the function will be called and the result variable will be updated.
sair_grid = self.root.ids["sairler_screen"].ids["sair_grid"]
for a in dler:
with open(a,"r",encoding="utf-8") as file1:
for b in file1:
btn = Button(text=b[:-5],on_press=lambda y:self.sairi(b))
sair_grid.add_widget(btn)
def sairi(self,x):
pass
No matter which button I clicked to calling the function, it is giving the same result. I want every button to take different b values and call the function with different arguments. All buttons created with different names but each of them calls with same last argument of the file. I am new in python and kivy so every help is really appreciated.
There is only one variable named b here and it changes its value until the last one remains last time the loop executes:
for b in file1:
btn = Button(text=b[:-5],on_press=lambda y:self.sairi(b))
sair_grid.add_widget(btn)
So, you are creating many lambdas, but they all use the same b from the closure.
A typical simple workaround for that problem is to bind the current value of b to a default value of the lambda argument:
for b in file1:
btn = Button(text=b[:-5],on_press=lambda y, b=b:self.sairi(b))
sair_grid.add_widget(btn)
Now each lambda you create has a locally bound b argument which has its own default value. The default value of b is now different for each lambda you create.
I want to get the value in a variable to place it to a text file. But I don't know what method should I do. I tried to convert it using the statement self.score = StringVar() or self.score = str(self.score) or self.score = IntVar(). But it results to what I am not expecting. I need help to fix this for my project. These piece of my codes may help you understand. Thank you.
self.score = IntVar ()
f = open('E.txt','a')
f.write(self.name_ent.get() + '-' + self.score.get() + '\n')
f.close()
self.score.get was error, because it's an int.
If you have an instance of IntVar, the .get() method returns an integer -- that's why you would use IntVar vs. StringVar. Like any other integer in python, you convert it with str:
score = str(self.score.get())
You get an error from trying to concatenate an integer with a string when forming the string to write to the file. This is the basis of your error. You can fix it by making the 'int' into a 'str' with a simple cast, str(some_int). In your case, changing self.score.get() to str(self.score.get()) on line 3 will do the trick.
Alternatively, you could make self.score a StringVar() instead of an IntVar() on line 1, and then the problem on line 3 goes away. You still need to assign a value to the score, which you can do with self.score.set(value).
By default, an IntVar() gets the value 0 and a StringVar() get the value of "" (the empty string). You can assign a value to an IntVar in at least two ways:
(1) on creation, with iv = IntVar(value=1)
and
(2) after creation, with iv.set(1)
The same goes for StringVar(), as well.
Changing self.score to a StringVar() from an IntVar() may seem a little strange at first, but just be aware of the difference in the default values.
First of all, I'm a begginer in Python, so get ready for a nooby question ))
In one of the topics on this site I've found quite a useful piece of advice concerning the use of lambda function.
Here's my code before correcting:
def entree1(self): #function that is supposed to change text in
self.configure(text = "X") the label block from whatever it is to 'X'
fen = Tk()
pole1 = Label(fen, text = '|_|')
pole1.bind("<Button-1>", lambda: entree1(pole1)) #event handler reffering to the function above
Here's my code after correcting:
def entree1(self): #function that is supposed to change text in
self.configure(text = "X") the label block from whatever it is to 'X'
fen = Tk()
pole1 = Label(fen, text = '|_|')
pole1.bind("<Button-1>", lambda x: entree1(pole1)) #event handler reffering to the function above
In a nutshell I changed lambda: some func to lambda x: some func.
And it worked, which is great, although I can't figure out the difference between this two variants.
Could you please tell me what exactly changed after I added x?
Thank you for your time!
Let me translate lambda expressions to function definitions that you are probably more used to:
lambda : entree1(pole1)
is the same as
def lambdaFunc():
global pole1
return entree1(pole1)
Your corrected function is
lambda x : entree1(pole1)
which is the same as
def lambdaFunc(x):
global pole1
return entree1(pole1)
You need the extra argument because Tk buttons call the function they are bound to with a variable (I forget what the variable is, exactly), therefore calling a function with an input variable when it doesn't take one causes errors.
I have a function, say foo and it's supposed to bind an event
def foo:
for x in widget_list:
widget.config(command = lambda: self.function(widget_list.index(x))
def function(index):
do something here....
print index
The widget list contains buttons, and whenever I click corresponding buttons, they're supposed to print to the IDLE their index, but what happens is that all the buttons I click end up printing the last index of widget_list. I'm guessing that while for iterates, the argument of the function also changes thus only the last index is preserved. Is there anyway to bind the previous indexes to the button?
The simplest fix is to make sure that your lambda is using the current value of x:
widget.config(command = lambda x=x: self.function(widget_list.index(x))
This forces x to be a default parameter to the function, and the default value will be the value of x at the time the lambda is defined.