Tkinter: clicking buttons in menu is triggering function with wrong argument - python

Hi,
Im struggling with strange for me error, so I want to have buttons in cascade menu, and each of them should trigger function, passing they name to it.
The code is as follows:
from tkinter import *
def say_hi(name):
print(name)
root = Tk()
menu = Menu(root)
root.config(menu=menu)
subjects_menu = Menu(menu)
menu.add_cascade(label="Subjects", menu=subjects_menu)
d = ["name1", "name2"]
for name in d:
subjects_menu.add_command(label=name, command=lambda:say_hi(name))
'''
this is also not working
name = "Math"
subjects_menu.add_command(label=name, command=lambda:say_hi(name))
name = "Physics"
subjects_menu.add_command(label=name, command=lambda:say_hi(name))
'''
root.mainloop()
So I want to print name of clicked button in console, and add buttons by iteration, not one by one (I cannot predict number of them)
After running program I get no errors, and window shows. Whichever button I click, console prints "name2".
I did add few more names - it always prints name of the last option.
Thanks in advance

Another (IMO neater) way to do this is to use functools.partial:
from functools import partial
for name in d:
subjects_menu.add_command(label=name, command=partial(say_hi, name))

Lambdas are tricky, but you can do what you want to do with a lambda argument:
for name in d:
subjects_menu.add_command(label=name, command=lambda x=name:say_hi(x))
This should solve your problem.

Related

Generating a menu for a tkinter window doesn’t work using a function but works just fine if the steps are executed on a python console

I’m trying to generate a set of menus for a program from a dictionary using a more complex version of the function build_menu below. The function detects what it is supposed to do correctly (detects the structure of submenus and menu entries as desired) but it fails to add them to the window.
Things I have tried:
running the code step by step in a python console - works as intended
storing the menu objects in a global list so they don’t go out of scope in case they get garbage collected - didn’t work
What am I missing?
import tkinter as tk
def not_implemented():
pass
def build_menu(structure_dict, menu):
for entry in structure_dict:
if isinstance(structure_dict[entry], dict):
submenu = tk.Menu(menu, tearoff=False)
build_menu(structure_dict[entry], submenu)
menu.add_cascade(label=entry, menu=submenu)
if callable(structure_dict[entry]):
menu.add_command(label=entry, command=structure_dict[entry])
# format:
# "":{} -> menu or submenu
# "":function -> menu entry
menu_structure = {
"Help": {
"Manual...": not_implemented,
"About...": not_implemented,
}
}
main_window = tk.Tk()
menubar = tk.Menu(main_window)
menubar = build_menu(menu_structure, menubar)
main_window.config(menu=menubar)
main_window.mainloop()
Turns out I am an idiot.
I accidentally assigned build_menu to the variable holding the menu bar.
Correct code near the end is this:
menubar = tk.Menu(main_window)
build_menu(menu_structure, menubar)
main_window.config(menu=menubar)
main_window.mainloop()

Creating hyperlink Labels in tkinter using for loop, while binding each Label to different callbacks

I got a dict like this:
dict = {"site1": link to site1,
"site2": link to site2,
"site3": link to site3,
[...]
}
and I don't know in advance how many sites will be inside the dict.
What I'm trying to achieve is binding different links to Labels created dynamically inside a for loop. I want to make a window with a list of names and when you click the name it will redirect you on the site.
I'm using this example below but without success.
from tkinter import Tk, Label, StringVar
import webbrowser
root = Tk()
list1 = ["site1-google","site2-facebook"]
list2 = ["google.com", "fb.com"]
for v1, v2 in zip(list1, list2):
item_values = '{}'.format(v1)
sv = StringVar()
lbl = Label(root, width="100", height="2",textvariable=sv)
lbl.pack()
lbl.bind("<Button-1>", lambda e: callback(v2))
sv.set(item_values)
def callback(url):
webbrowser.open_new(url)
root.mainloop()
When I run the program it just bind the "last site" from list2 to all the freshly created Labels.
I read somewhere that lambda is late-binding, so it will bind every time the last value of the given list, but I don't know how to fix the problem!
How do I go on?

Tkinter OptionMenu disabled but expandable

Is there any solution of write-protecting a tkinter OptionMenu while retaining the possibility to inspect the available Options?
Background: I have a tkinter OptionMenu containing a selection of files that the user can "quick-load" into the application. However it might be that the user does not have permission to load new files.
I now indicate this by putting the OptionMenu in disabled state. But then the dropdown cannot be expanded anymore; meaning that the user cannot look at the available files.
Yes, it is possible to disable the menu and still be able to open it just to see the list. The menu used in OptionMenu is tkinter Menu() and you can access it.
Example:
Op = OptionMenu(root, var, 'First', 'Second', 'Third')
Op.pack()
# Op_Menu is the Menu() class used for OptionMenu
Op_Menu = Op['menu']
Then you can do anything with the Op menu same as Menu()
In your case , How to disable?
We can use menu.entryconfig(index, options) to configure state = 'disabled' / 'normal' as per the user.
Example:
import tkinter as tk
root = tk.Tk()
root.geometry('250x250+100+100')
str = tk.StringVar()
str.set('Select')
Op = tk.OptionMenu(root, str, "First", "Second", "Third")
Op.pack()
# This will disable the First and Third entries in the Op
# state = 'disable' / 'normal'
Op['menu'].entryconfig(0, state='disable')
Op['menu'].entryconfig("Third", state='disable')
entries = Op['menu'].index('end') # This will get the total no. of entries.
# If you want to disable all of the entries uncomment below 2 lines.
# for i in range(entries+1):
# Op['menu'].entryconfig(i, state='disable')
root.mainloop()
For better understanding on how the Menu() is defined inside OptionMenu class can check the source code of OptionMenu() class. (from line 3959)
You can disable each entry of the menu instead of disabling the optionmenu totally using menu.entryconfigure(<index>, state='disabled').
The menu of an optionmenu is stored in the 'menu' property:
import tkinter as tk
root = tk.Tk()
var = tk.StringVar(root)
opmenu = tk.OptionMenu(root, var, *['item %i' % i for i in range(5)])
opmenu.pack()
menu = opmenu['menu']
for i in range(menu.index('end') + 1):
menu.entryconfigure(i, state='disabled')
So you can view all the items in the menu but they are not clickable.

Cannot type in Python Entry Widget

I've got an interesting problem with the tk Entry widget. If I run the following test code,
from Tkinter import *
root =Tk()
def pfunc(self):
print Input.get()
f=Frame(root)
f.pack()
Input=Entry(f)
#Input.bind("<Return>",pfunc)
Input.pack()
root.mainloop()
I can properly enter into the widget and print to console; however the following code, as part of a larger GUI, does not allow me to click in the Entry boxes at all.
self.Tlabel = Label(self.TempFrame, text="Temp")
self.Tlabel.pack( side = LEFT)
self.Tenter = Entry(self.TempFrame,width=10, bd =5)
self.Tenter.bind("<Return>",self.getFlux)
self.Tenter.pack (side=RIGHT)
self.Flabel = Label(self.FluxFrame, text="Flux")
self.Flabel.pack( side = LEFT)
self.Fenter = Entry(self.FluxFrame, width=10, bd =5)
self.Fenter.bind("<Return>",self.getTemp)
self.Fenter.pack(side = RIGHT)
def getFlux(self):
for i in range(len(self.fit_tuples)):
if self.fit_tuples[i][0]==self.currentBFMdate and self.fit_tuples[i][1]==self.cell.get():
fit_data=self.fit_tuples[i][2]
self.Fenter.set(fit_data[0]*np.exp(fit_data[1]*int(self.Tenter.get())))
else:
self.Fenter.set("Invalid")
def getTemp(self):
for i in range(len(self.fit_tuples)):
if self.fit_tuples[i][0]==self.currentBFMdate and self.fit_tuples[i][1]==self.cell.get():
fit_data=self.fit_tuples[i][2]
self.Tenter.set(np.log(float(self.Fenter.get())/fit_data[0])/fit_data[1])
else:
self.Tenter.set("Invalid")
Furthermore, if I run both codes on a separate windows PC I have the same problem. The only difference I can possibly think of is that I am using instance variables within a class; but it seems that other widgets are bound and working properly.
Basically, the bind method is passing "Return" as a parameter to getTemp. As another user suggested, just add another parameter to the function.
If you use bind method, callback is called with an event object. You should add event parameter to the method/function. (See Events and Bindings - An Introduction to Tkinter )
So, rpelcae following lines:
def getFlux(self, event):
...
def getTemp(self, event):
...
The first program work unintentionally. Its parameter name should be event, not self.

Simple app creating Python tkinter

I was trying to create a simple app for illustration purposes. The idea is as follows:
Create an application which will run script files associated only to the selected courses (radio buttons). So, I create radio buttons which list out subjects (to click on). Once the subjects are selected the user has to hit the Enter button. This should run all the .py files for the selected subjects (execute_script function).
However, when I run my code, I get 4 messageboxes with 'None' written inside. After clicking ok on them, I get a square windows with only the enter button. What can I do to correct this problem?
def check(file_name, relStatus):
radioValue = relStatus.get()
tkMessageBox.showinfo('You checked', radioValue)
been_clicked.append(file_name)
return
def execute_script():
for name in been_cliked:
subprocess.Popen(['python', 'C:\Users\Max\Subjects\{}'.format(name)])
yield
def main():
#Create application
app = Tk()
app.title('Coursework')
app.geometry('450x300+200+200')
#Header
labelText = StringVar()
labelText.set('Select subjects')
#Dictionary with names
product_names = {}
names = []
file_name = []
names = ['Math', 'Science', 'English', 'French']
file_name = ['calc.py', 'physics.py', 'grammar.py', 'livre.py']
product_names = OrderedDict(zip(names, file_name))
#Create radio buttons
global been_clicked
been_clicked = []
relStatus = StringVar()
relStatus.set(None)
for name,file_name in product_names.iteritems():
radio1 = Radiobutton(app, text=name, value=name, \
variable=relStatus, command=check(file_name, relStatus))
button = Button(app, text='Click Here', width=20, command=execute_script())
button.pack(side='bottom', padx=15, pady=15)
app.mainloop()
if __name__ == '__main__': main()
There are a few issues with your script:
1) A typo in your execute_script() function: for name in been_cliked
2) You are actually calling the check() function when you create your radio buttons. That's why you're seeing the windows pop up when you run your program.
You need to change this:
radio1 = Radiobutton(app, text=name, value=name, \
variable=relStatus, command=check(file_name, relStatus))
to this:
radio1 = Radiobutton(app, text=name, value=name, \
variable=relStatus, command=check)
See how check no longer has brackets? That's means you're passing the function name as an argument, instead of actually calling the function. Of course, you'll see an immediate problem is that you can no longer pass arguments to your callback function! That's a bigger issue. Here's a couple links to help get you started:
How to pass an argument to event handler in tkinter?
How can I pass arguments to Tkinter button's callback command?
Here is the solution:
Change this:
command=check(file_name, reStatus)
to this:
command = lambda: check(file_name, relStatus)
3) You don't actually pack() your radio buttons anywhere. Add something like this just after you create your radio buttons in your for loop: radio1.pack(side='top')
4) You have the same problem with your callback for your Click Here button. You need to change your command to not call the function, but just refer to it: command = execute_script
5) In execute_script(), make sure you import subprocessing
6) Are you sure you want yield instead of return in your execute_script() function?
7) In all your functions, you need to make sure that been_clicked is global.
I think if you fix these issues you'll be closer to getting what you're looking for. Good luck.!

Categories

Resources