tkinter Menu, multiple entries always calling the same callback [duplicate] - python

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 2 years ago.
I am configuring a tkinter Menu option on the fly, with a passed in list of menu items and callback functions.
However, whichever menu item is actually selected the same callback is called. I think it is to do with the cmd_func var used to build the lambda changing as the loop that builds the menu options iterates. The value in the lambda ends up changing as the loop iterates so all the lambda's end up pointing ot the last value cmd_func takes? However I can't see how to solve the problem? - I did try enumerating menu_cmds when building the menu, and using the index var to index directly into the array menu_cmds to get at the function value rather than having the intermediate cmd_func var, but that didnt help.
This code replicates the issue;
from tkinter import *
class a:
def __init__(self):
self.root=Tk()
self.m=[ ('name_a',self.command_a),
('name_b',self.command_b) ]
self.control_frame=Frame(self.root)
self.control_frame.pack(side=TOP,fill=X)
self.control=control(self.control_frame,self.m)
self.control.pack()
def command_a(self,data):
print("command_a %s,%s" % data)
def command_b(self,data):
print("command_b %s,%s" % data)
class control(Frame):
def __init__(self,parent,menu_cmds):
Frame.__init__(self,parent)
self.menu_cmds=menu_cmds
self.canvas=Canvas(self,width=800,height=400)
self.canvas.pack(side=LEFT,anchor=W)
self.canvas.create_rectangle(100,100,300,300,fill="#FF0000")
self.canvas.bind("<Button-3>", self.canvas_context_popup)
self.build_canvas_context_menu()
def build_canvas_context_menu(self):
self.canvas_context_menu=Menu(self.canvas, tearoff=0)
for menu_text,cmd_func in self.menu_cmds:
print("menu %s" % menu_text)
cmd=lambda : self.canvas_context_menu_select(cmd_func)
self.canvas_context_menu.add_command(label=menu_text,
command=cmd)
def canvas_context_popup(self, event):
try:
self.popup_at_x=event.x_root-self.canvas.winfo_rootx()
self.popup_at_y=event.y_root-self.canvas.winfo_rooty()
self.canvas_context_menu.tk_popup(event.x_root, event.y_root, 0)
finally:
self.canvas_context_menu.grab_release()
def canvas_context_menu_select(self,cmd_func):
x=self.popup_at_x
y=self.popup_at_y
cmd_func((x,y))
GUI = a()
GUI.root.geometry('1000x600')
GUI.root.update()
GUI.root.mainloop()
Any help much appreciated!

manveti and furas above had it right away, thankyou!
The solution is to change the lambda to be
cmd=lambda x=cmd_func : self.canvas_context_menu_select(x)
For a better explanation than I can give; refer to the comment and info linked by manveti above.

Related

Creating dynamic menus (eg Recent file list) with Python's Tkinter [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 2 years ago.
I am an experienced Python programmer but currently need to implement a basic GUI through Tkinter. It is clear that Python is not rearing at the bit to become the best UI/UX platform, which I complete get, but I am stuck with a basic problem that I cannot seem to find an easy resolution to on the web.
I need to implement a Recent Files list of variable length. So I try to do this:
def __file_menu(self):
menu = tk.Menu(self, tearoff=False)
menu.add_command(label="New Project...", command=self.__file_new, state=tk.DISABLED)
menu.add_command(label="Open Project...", command=self.__file_open)
recent_list = tk.Menu(menu, tearoff=False)
for index, project in enumerate(self._recent_projects):
name = os.path.basename(project).title()
recent_list.add_command(label=name, command=lambda: self.__open_recent(index))
menu.add_cascade(label="Recent Projects...", menu=recent_list)
This works a charm for displaying the correct items but what I cannot get right is to pass a unique value in the lambda function that identifies which of the menu items have been selected so I can use it over here:
def __open_recent(self, item):
self.__load_project(self._recent_projects[item])
The value of index is always the same namely its last value. So, how do I get a unique identifier (a name, an index etc) from the menu item selected?
You have to change the lambda to something like this:
recent_list.add_command(label=name, command=lambda i = index: self.__open_recent(i))
This passes the correct index to the function.

tkinter multiple buttons using lambda function [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
Closed 2 years ago.
I have been trying to create multiple buttons in tkinter library (with numbers as text), using for loop. And i want to print the text of each button every time i click one. Like a calculator. But the problem is that it prints only the last line in the loop.
win=tk.Tk()
def butt(x):
print(x)
for i in range(1,4):
bt=tk.Button(win,text=i,command=lambda: butt(i));bt.pack()
win.mainloop()
One solution of this that i found is to write lambda i=i : ... but i dont understand what this is.
Thank you for your time!!
Lambda expression here returns the method that prints the i value.
import tkinter as tk
win=tk.Tk()
def get_print_method(x):
def print_i():
print(x)
return print_i
for i in range(1,4):
bt=tk.Button(win, text=i, command=get_print_method(i))
bt.pack()
win.mainloop()
Lambda in your case is an equivalent of the get_print_method function.
This a well-known behavior in Python for lambda
This happens because i is not local to the lambdas, but is defined in the outer scope, and it is accessed when the lambda is called — not when it is defined
Python FAQ on lambdas
Their solution, is to give lambda a default parameter
import tkinter as tk
win=tk.Tk()
def butt(x):
print(x)
for i in range(1,4):
bt=tk.Button(win,text=i,command=lambda j=i: butt(j))
bt.pack()
#i = 'This is now a string'
win.mainloop()

how to change values ​in buttons done in a loop Tkinter [duplicate]

This question already has answers here:
tkinter creating buttons in for loop passing command arguments
(3 answers)
How to pass arguments to a Button command in Tkinter?
(18 answers)
Closed 6 months ago.
how to change values ​​in buttons done in a loop?
I mean a different text, a command for each of the resulting buttons.
the program counts files in a given folder, then this value is passed to the loop that displays the appropriate number of buttons.
here is the sample text:
files = os.listdir(".")
files_len = len(files) - 1
print(files_len)
def add():
return
for i in range(files_len):
b = Button(win, text="", command=add).grid()
You don't really need to change the Button's text, because you can just put the filename in when they're created (since you can determine this while doing so).
To change other aspects of an existing Button widget, like its command callback function, you can use the universal config() method on one after it's created. See Basic Widget Methods.
With code below, doing something like that would require a call similar to this: buttons[i].config(command=new_callback).
To avoid having to change the callback function after a Button is created, you might be able to define its callback function inside the same loop that creates the widget itself.
However it's also possible to define a single generic callback function outside the creation loop that's passed an argument that tells it which Button is causing it to be called — which is what the code below (now) does.
from pathlib import Path
import tkinter as tk
win = tk.Tk()
dirpath = Path("./datafiles") # Change as needed.
buttons = []
def callback(index):
text = buttons[index].cget("text")
print(f"buttons[{index}] with text {text!r} clicked")
for entry in dirpath.iterdir():
if entry.is_file():
button = tk.Button(win, text=entry.stem,
command=lambda index=len(buttons): callback(index))
button.grid()
buttons.append(button)
win.mainloop()
Try this. By making a list.
files = os.listdir(".")
files_len = len(files) - 1
print(files_len)
def add():
return
b=[]
for i in range(files_len):
b[i] = Button(win, text="", command=add).grid(row=i)

Can't tie command to button Python 3.7 [duplicate]

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 3 years ago.
I am trying to get a button to do some stuff in Python (using Tkinter). I want the button to get the current choice of a combobox and print it on screen.
The graphical layout is 3 separate frames, but both the button and the combobox reside in the same frame (frame #2).
The problem is that i cannot refer to the combobox. The errors i am getting read:
Frame object has no attribute 'box'
Window object has no attribute 'box'
self.box=ttk.Combobox(self.frame2 , values[...])
self.button1=tk.Button(self.frame2, command= self.wipe(), text=...)
def wipe(self):
self.box.get()
ALTERNATIVELY i tried:
def wipe(self):
self.frame2.box.get()
The goal is to simply get the selected choice from the Combobox.
HERE IS MINIMAL CODING THAT PRODUCES THE SAME ERROR:
import tkinter as tk
from tkinter import ttk
class window():
def __init__(self,root):
self.frame=tk.Frame(root)
self.key=tk.Button(self.frame,text='PRESS ME',command=self.wipe())
self.box=ttk.Combobox(self.frame, options=['1','2','3'])
self.frame.pack()
self.key.pack()
self.box.pack()
def wipe(self):
self.box.get()
master=tk.Tk()
master.geometry('400x400')
app=window(master)
master.mainloop()
I would add the tag "tkinter" to the question.
Try the following:
def wipe(self):
# code
self.box=ttk.Combobox(self.frame2 , values[...])
self.button1=tk.Button(self.frame2, command=wipe, text=...)
Notice the following:
I first defined wipe, only then used it.
I am not quite sure why you wanted to do command=self.wipe(), as there are two issues here. First, you are setting command to the result of self.wipe() which is NOT a function. Second, you haven't defined self.wipe, you defined wipe.
command=wipe Sets the command keyword argument to the
function wipe
Its been long since I dealt with tkinter, if this doesn't work, I'll try to help by checking the docs again.

Python 3.4.2 tkinter Menu Auto Calling Function [duplicate]

This question already has answers here:
All tkinter functions run when program starts
(2 answers)
Closed 7 years ago.
I'm very new to python's tkinter gui and I am trying to use it to build a basic test.
I created the menu where one of the menu items must call a function although when I run the program I can see the output from the function before the menu item has been clicked and when the menu item is clicked it does not call the function.
My code is as follows
from tkinter import *
class cl_main():
def __init__(self, master):
lo_mainmenu = Menu(master)
lo_mainmenu.option_add('*tearOff', FALSE)
master.config(menu=lo_mainmenu)
lo_menugroup = Menu(lo_mainmenu)
lo_mainmenu.add_cascade(label="MenuGroup")
lo_menugroup.add_command(label="Command", command=f_message())
def f_message():
print ("This Function Has Been Called")
root = Tk()
co_main = cl_main(root)
root.mainloop()
I can't see what is wrong with it but I'm sure there is something horribly wrong here
lo_menugroup.add_command(label="Command", command=f_message())
callbacks shouldn't have parentheses. As it is, f_message is called right away and its return value is assigned to command, rather than the function object itself.
lo_menugroup.add_command(label="Command", command=f_message)

Categories

Resources