Reputation: 1005
So as the title suggests, I'm having trouble passing in 2 arguments to a function I want to call when an option menu is changed.
Here is the code below:
OPTIONS = [
"Fire",
"Ice",
"Storm",
"Life",
"Myth",
"Death",
"Balance"
]
var = StringVar(frame)
var.set("Select School") # initial value
option = OptionMenu(frame, var,*OPTIONS,command= lambda frame,var : changeSchool(frame,var))
option.grid(row=0,column=0,sticky=(N,W))
I've done some research and I think I've done everything correctly but I get the following error when I select an option in the option menu:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Gunner\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:\Users\Gunner\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 3287, in __call__
self.__callback(self.__value, *args)
TypeError: <lambda>() missing 1 required positional argument: 'var'
I thought I've passed in var into the changeSchool function.
I appreciate any help!
EDIT: Here's my changeSchool function for those who wish to see it:
def changeSchool(frame,school):
print (school)
WizTool.schoolType = school
self.initBT(frame,WizTool.schoolType)
print (WizTool.schoolType)
EDIT: Here's the entire program (showing how frame is not in scope)
from tkinter import *
from tkinter import ttk
from functools import partial
import webbrowser
import pprint
class WizTool:
schoolType = "Fire"
#initialize the GUI
def __init__(self, master):
content = ttk.Frame(master, padding=(3,3,12,12))
frame = ttk.LabelFrame(content, borderwidth=5, relief="sunken", width=400, height=400,padding=(3,3,12,12),text = "Blade Tracker")
content.grid(column=0,row=0,sticky=(N,S,E,W))
frame.grid(column=0, row=0, columnspan=4, sticky=(N, S, E, W))
self.master = master
self.initUI(content)
self.initBT(frame,WizTool.schoolType)
def initUI(self,frame):
self.master.title("Bootstrapper")
def changeSchool(frame,school):
print (school)
WizTool.schoolType = school
self.initBT(frame,WizTool.schoolType)
print (WizTool.schoolType)
def initBT(self,frame,mySchool):
#option menu for selecting school
OPTIONS = [
"Fire",
"Ice",
"Storm",
"Life",
"Myth",
"Death",
"Balance"
]
var = StringVar(frame)
var.set("Select School") # initial value
option = OptionMenu(frame, var,*OPTIONS,command= lambda frame,var : changeSchool(frame,var))
option.grid(row=0,column=0,sticky=(N,W))
def main():
root = Tk()
root.geometry("800x500+300+300")
app = WizTool(root)
root.mainloop()
main()
Upvotes: 4
Views: 2383
Reputation: 48599
The command
function for an OptionMenu
widget takes only one argument: the selected item
.
command
function? Answer: No. command
function? Answer: python.Python stores the function you specify somewhere, then at some time in the future after you make a selection in the OptionsMenu, python calls the command
function. So python gets to decide how many arguments it will pass to the command
function, and it turns out that python calls the command
function with one argument:
command(selection)
How do you know that python calls the command
function with one argument? Answer: You check the docs.
What happens if you can't find anything in the docs that describes the type of function you need to assign to command
? Answer: You test it out.
First:
...
...
def do_stuff(x):
print(x)
tk.OptionMenu(
root,
str_var,
*OPTIONS,
command=do_stuff).pack()
...
...
--output:--
Storm
Next:
...
...
def do_stuff(x, y):
print(x, y)
tk.OptionMenu(
root,
str_var,
*OPTIONS,
command=do_stuff).pack()
...
...
--output:--
> TypeError: do_stuff() missing 1 required positional argument: 'y'
There are various ways to solve that problem...
Use python's scoping rules:
import tkinter as tk
OPTIONS = [
"Fire",
"Ice",
"Storm",
"Life",
"Myth",
"Death",
"Balance"
]
root = tk.Tk()
root.title("Hello")
root.geometry("300x200+10+100")
frame = [1, 2, 3]
def do_stuff(selection):
print(selection, frame) #frame is visible inside the def.
str_var = tk.StringVar(root)
str_var.set(OPTIONS[0]) # default value
tk.OptionMenu(
root,
str_var,
*OPTIONS,
command=do_stuff
).pack()
root.mainloop()
But it's not really a good idea to have functions manipulating global variables, so you can...
Use a wrapper function:
import tkinter as tk
OPTIONS = [
"Fire",
"Ice",
"Storm",
"Life",
"Myth",
"Death",
"Balance"
]
root = tk.Tk()
root.title("Hello")
root.geometry("300x200+10+100")
def wrapper(other):
def do_stuff(selection):
print(selection, other)
return do_stuff
str_var = tk.StringVar(root)
str_var.set(OPTIONS[0]) # default value
tk.OptionMenu(
root,
str_var,
*OPTIONS,
command=wrapper('xxxx') #This actually executes wrapper(), and a
#function call in your code is replaced
#by its return value--which happens to
#be the name of a function that takes one
#argument. The one arg function name is
#then assigned to command.
).pack()
root.mainloop()
Use a default value for your extra parameter variable:
frame = [1, 2, 3]
def do_stuff(selection, other=frame): #<****HERE****
print(selection, other)
str_var = tk.StringVar(root)
str_var.set(OPTIONS[0]) # default value
tk.OptionMenu(
root,
str_var,
*OPTIONS,
command=do_stuff
).pack()
Warning: Default values for parameter variables have their own issues. The default value is assigned once when the function is defined. Subsequently, no matter how many times you execute the function the same value is used for the default. That means that if the default value is a list, and the first time you call the function you change that list, then the next time you call the function the default value will be the changed list.
Upvotes: 3