I use Tkinter for show database search results and show data with a for loop, and I set the command parameter [for widget tkinter.Button] with lambda func, but when I click on a data for full show, instead of running show func with correct parameters, I run show func with last data. My Code:
import tkinter
from tkinter import Button, ttk
def show_results(result: dict):
def show(text):
w = tkinter.Tk()
w.geometry('500x450')
result_lb = ttk.Label(master=w, text=text)
result_lb.place(x=5, y=5)
w.mainloop()
win = tkinter.Tk()
win.geometry('500x800')
x = 5
y = 5
for res in result:
r = result[res]
print(r)
cmd = lambda: show(r)
Button(master=win, text=f" {r} ", command=cmd).place(x=x, y=y)
y += 25
win.mainloop()
show_results({'0': ['jack', '18', 'newyork'], '1': ['alex', '20', 'texas']})
Also I use tkinter.ttk.Button, but this bug not solved.
The bug is due to the fact that the lambda finds the value of r
at the point that the button is called, and, as the for
loop has already finished, the value would be the last index, or 1. This explained more clearly in this post here: tkinter creating buttons in for loop passing command arguments (pointed out by @001)
When you use that lambda to define your function, the open_this call doesn't get the value of the variable i at the time you define the function. Instead, it makes a closure, which is sort of like a note to itself saying "I should look for what the value of the variable i is at the time that I am called". Of course, the function is called after the loop is over, so at that time i will always be equal to the last value from the loop.
To fix this, we need to do (also from the same answer) cmd = lambda row=r: show(row)
, here defining the value of row
to r
, but not when cmd
is called, instead, when cmd
is assigned.
A simple example of this is this:
import tkinter
from tkinter import Button, ttk
def show(text):
w = tkinter.Tk()
w.geometry('500x450')
result_lb = ttk.Label(master=w, text=text)
result_lb.place(x=5, y=5)
w.mainloop()
def show_results(result: dict):
win = tkinter.Tk()
win.geometry('500x800')
x = 5
y = 5
for key, value in result.items():
r = value
print(key, value)
cmd = lambda row=r: show(row)
Button(master=win, text=f" {r} ", command=cmd).place(x=x, y=y)
y += 25
win.mainloop()
testvar = {'0': ['jack', '18', 'newyork'],
'1': ['alex', '20', 'texas']}
show_results(testvar)
The reason that this works is because the variable row
is getting defined when the lambda
evaluates (probably not the right word for it though), not when the lambda
is called.