Dynamically creating a menu in Tkinter. (lambda expressions?)
First of all, your problem doesn't have anything to do with Tkinter; it's best if you reduce it down to a simple piece of code demonstrating your problem, so you can experiment with it more easily. Here's a simplified version of what you're doing that I experimented with. I'm substituting a dict in place of the menu, to make it easy to write a small test case.
items = ["stack", "over", "flow"]map = { }for item in items: def new_command(): print(item) map[item] = new_commandmap["stack"]()map["over"]()map["flow"]()
Now, when we execute this, as you said, we get:
flowflowflow
The issue here is Python's notion of scope. In particular, the for
statement does not introduce a new level of scope, nor a new binding for item
; so it is updating the same item
variable each time through the loop, and all of the new_command()
functions are referring to that same item.
What you need to do is introduce a new level of scope, with a new binding, for each of the item
s. The easiest way to do that is to wrap it in a new function definition:
for item in items: def item_command(name): def new_command(): print(name) return new_command map[item] = item_command(item)
Now, if you substitute that into the preceding program, you get the desired result:
stackoverflow
I had a similar error. Only the last item in the list was shown. Fixed by setting
command=lambda x=i: f(x)
Note the x=i
after lambda
. This assignment makes your local variable i
go right into the f(x)
function of your command
. Hope, this simple example will help:
# Using lambda keyword to create a dynamic menu.import tkinter as tkdef f(x): print(x)root = tk.Tk()menubar = tk.Menu(root)root.configure(menu=menubar)menu = tk.Menu(menubar, tearoff=False)l = ['one', 'two', 'three']for i in l: menu.add_command(label=i, command=lambda x=i: f(x))menubar.add_cascade(label='File', menu=menu)root.mainloop()