I've been searching around and i am not able to find a proper explanation of the syntax of OptionMenu within Tkinter.
how would i get the current chosen option with in the OptionMenu?
def homeTeamOption(self, frame, sortedList):
def func():
print(homeTeamName)
return
homeTeam = tk.StringVar(frame)
returnValueAwayTeam = []
options = sortedList
homeTeamName = tk.StringVar()
drop = OptionMenu(frame, homeTeamName, *options, command=func())
drop.place(x=200, y= 100, anchor="nw")
To get the value of the OptionMenu you need to get the value of the associated variable. In your case it would be:
homeTeamName.get()
If you want to do this via the command, you must set the option to a reference to the function:
drop = OptionMenu(...command=func)
Related
I'm still a beginner with Tkinter and I'm not quite sure how the Entry widget work. I can't seem to get the value I enter I tried binding the root window to this function but I can't figure out why it's not working.
def get_value(event):
current_obj = root.focus_get()
if (current_obj in entries):
text = current_obj.get()
data.append(text)
You can use get to get the value from the entry.
First you define the entry like this:
e = tk.Entry()
e.pack()
Then you can have a static function which gets the value of the entry by calling entry.get()
def get_entry_value(entry)
entry.get()
Alternatively, if you have multiple entries in your app which are all contained in some iterable:
def get_entries(self, event=None):
data = list()
for e in self.entries:
data.append(e.get())
return data
from tkinter import *
import pandas as pd
df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})
def input_data(df):
box = Tk()
height = str(int(25*(df.shape[0]+2)))
box.geometry("320x" + height)
box.title("my box")
#initialise
params, checkButtons, intVars = [], [], []
default_vals = list(df.default_vals)
itemList = list(df.item)
for i,label in enumerate(itemList):
Label(box, text = label).grid(row = i, sticky = W)
params.append(Entry(box))
params[-1].grid(row = i, column = 1)
params[-1].insert(i, default_vals[i])
intVars.append(IntVar())
checkButtons.append(Checkbutton(variable = intVars[-1]))
checkButtons[-1].grid(row = i, column = 3)
def sumbit(event=None):
global fields, checked
fields = [params[i].get() for i in range(len(params))]
checked = [intVars[i].get() for i in range(len(intVars))]
box.destroy()
#add submit button
box.bind('<Return>', sumbit)
Button(box, text = "submit",
command = sumbit).grid(row = df.shape[0]+3, sticky = W)
box.focus_force()
mainloop()
return fields, checked
I am new to tkinter and not sure what I a trying to do is possible.
At present, my script (simplified here to a function rather than a class) builds a box with all the default values entered in the fields:
Instead, I want to start with empty fields which, once the corresponding checkButton is clicked will get the default value (should still be able to manually change it through the field as happens now), and also, once any value is entered in a given field, the corresponding checkButton is selected.
Are these possible?
It is possible, but let me preface my solution with a few cautions on your current code:
It's rarely advisable to do a star import (from tkinter import *) as you don't have any control over what gets imported into your namespace. It's more advisable to explicitly import what you need as a reference:
import tkinter as tk
tk.Label() # same as if you wrote Label()
tk.IntVar() # same as if you called IntVar()
The behaviour you wanted, while possible, might not be necessarily user friendly. What happens when a user has already entered something, and unchecks the checkbox? Or what happens if the checkbox was selected and then the user deleted the information? These might be things you want to think about.
Having said that, the solution is to use add a trace callback function over your variable(s). You'll also need to add a StringVar() for the Entry boxes as you wanted a two way connection:
# add strVars as a list of StringVar() for your Entry box
params, checkButtons, intVars, strVars = [], [], [], []
During your iteration of enumerate(itemList), add these:
# Create new StringVar()
strVars.append(StringVar())
# add a trace callback for tracking changes over the StringVar()
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
# update your Entry to set textvariable to the new strVar
params.append(Entry(box, textvariable=strVars[-1]))
# similarly, add a trace for your IntVar
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
You'll need to define the two trace callback functions before you iterate through the widget creations:
def trace_intVar(idx):
# if Checkbox is checked and Entry is empty...
if intVars[idx].get() and not params[idx].get():
# prefill Entry with default value
params[idx].insert(0, df.default_vals[idx])
def trace_strVar(idx):
# if Entry has something...
if strVars[idx].get():
# and Checkbox is not checked...
if not intVars[idx].get():
# Set the checkbox to checked.
intVars[idx].set(True)
# but if Entry is empty...
else:
# Set the Checkbox to uncheck.
intVars[idx].set(False)
Remember I mentioned the behaviour - I took a little liberty to clear the Checkbox if Entry is empty. If you however don't wish to do that, you'll need to modify the handling a little.
Note on the way the trace_add is written. The callback function is always passed with three default arguments, namely the Variable Name, The Variable Index (if any) and Operation (see this great answer from Bryan Oakley). Since we don't need any in this case (we can't reverse reference the variable name to the linked index between the variable lists), we'll have to manually wrap the callback with another lambda and ignore the three arguments:
lambda var, # reserve first pos for variable name
var_idx, # reserve second pos for variable index
oper, # reserve third pos for operation
idx=i: # pass in i by reference for indexing point
trace_intVar(idx) # only pass in the idx
You cannot just pass lambda...: trace_intVar(i) as i will be passed by value instead of reference in that case. Trust me, I've made this error before. Therefore we pass another argument idx with its default set to i, which will now be passed by reference.
If trace_add doesn't work, use trace('w', ...) instead.
For prosperity, here's the complete implemented solution to your question:
from tkinter import *
import pandas as pd
df = pd.DataFrame({'item': list('abcde'), 'default_vals': [2,6,4,5,1]})
def input_data(df):
box = Tk()
height = str(int(25*(df.shape[0]+2)))
box.geometry("320x" + height)
box.title("my box")
#initialise
params, checkButtons, intVars, strVars = [], [], [], []
default_vals = list(df.default_vals)
itemList = list(df.item)
def trace_intVar(idx):
if intVars[idx].get() and not params[idx].get():
params[idx].insert(0, df.default_vals[idx])
def trace_strVar(idx):
if strVars[idx].get():
if not intVars[idx].get():
intVars[idx].set(True)
else:
intVars[idx].set(False)
for i,label in enumerate(itemList):
Label(box, text = label).grid(row = i, sticky = W)
strVars.append(StringVar())
strVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_strVar(idx))
params.append(Entry(box, textvariable=strVars[-1]))
params[-1].grid(row = i, column = 1)
#params[-1].insert(i, default_vals[i]) # <-- You don't need this any more
intVars.append(IntVar())
intVars[-1].trace_add('write', lambda var, var_idx, oper, idx=i: trace_intVar(idx))
checkButtons.append(Checkbutton(variable = intVars[-1]))
checkButtons[-1].grid(row = i, column = 3)
def sumbit(event=None):
global fields, checked
fields = [params[i].get() for i in range(len(params))]
checked = [intVars[i].get() for i in range(len(intVars))]
box.destroy()
#add submit button
box.bind('<Return>', sumbit)
Button(box, text = "submit",
command = sumbit).grid(row = df.shape[0]+3, sticky = W)
box.focus_force()
mainloop()
return fields, checked
I am trying to create option menus in a loop, and the number of option menus is dependent on a variable. So I'm trying to use exec in my code.
I used the following to pass the value of 'i' to connect to which variable is changing the value.
But once I call the trace, the option I select in the Option menu, does not get updated in the Option menu box. If I do not call the trace funtion, it is getting updated in the display.
trackProcessMenu is the callback function.
Please let me know, where I am making the mistake.
Adding my code:
for i in range(0,numOfLibFiles):
exec('self.processOptionMenuVar_%d = StringVar()'%i)
process_menu = ("ff","ss","tt","fff","sss","ttt")
exec('self.processOptionMenu_%d = OptionMenu(self, self.processOptionMenuVar_%d, *process_menu )'%(i,i))
exec('self.processOptionMenu_%d.config(indicatoron=0,compound=RIGHT,image= self.downArrowImage, anchor = CENTER , direction = RIGHT)'%i)
exec('self.processOptionMenuVar_%d.set("--")'%i)
exec('self.processOptionMenu_%d.grid(row = i, column =1, sticky = N ,padx=30, pady =7 )'%i)
def trackProcessMenu(self,*args):
i = args[0]
exec('process = self.processOptionMenuVar_%d.get()'%i)
You should not use exec this way. A good rule of thumb is that you should never use exec until you can answer the question "why should I never use exec?" :-) exec has it's uses, but this isn't one of them.
Instead of trying to automagically generate variable names, keep your widgets in a list or dictionary.
For example:
option_vars = []
option_menus = []
for i in range(0,numOfLibFiles):
process_menu = ("ff","ss","tt","fff","sss","ttt")
var = StringVar()
om = OptionMenu(self, var, *process_menu)
om.config(indicatoron=0,compound=RIGHT,image= self.downArrowImage, anchor = CENTER , direction = RIGHT)
var.set("--")
om.grid(row = i, column =1, sticky = N ,padx=30, pady =7)
option_vars.append(var)
option_menus.append(om)
With the above, you can now reference the variables and menus with a simple index:
print("option 1 value is:", option_vars[1].get())
I want to get a list of all options from an OptionMenu widget in tkinter like so:
import tkinter
root = tkinter.Tk()
var = tkinter.StringVar(root)
var.set('OptionMenu')
optionMenu = tkinter.OptionMenu(root, var, 'foo1', 'foo2')
optionMenu.pack()
listOfAllOptions = optionMenu.getOptions()
# listOfAllOptions == ['foo1', 'foo2']
root.mainloop()
Is there a function that achieve that ?
If not what is the workaround?
You can get the menu associated with the optionmenu (eg: optionMenu["menu"]), and with that you can use menu methods to get the items. It takes several lines of code. But honestly, the easiest thing to do is put the values in a list that you attach to the widget (eg: optionMenu.items = the_list_of_values)
If you want to pull the data from the actual widget, you would do something like this:
menu = optionMenu["menu"]
last = menu.index("end")
items = []
for index in range(last+1):
items.append(menu.entrycget(index, "label"))
print "items:", items
So I have this code snippet:
for p in self.ProductNames:
OptionMenuVar = StringVar()
menu = OptionMenu(self.FrameProducts, OptionMenuVar, *self.ProductNames)
OptionMenuVar.set(p)
AgeVar = StringVar()
AgeEntry = Entry(self.FrameProducts,width=15,textvariable=AgeVar,state="readonly",justify=CENTER)
which generates this UI:
Question
How do I trace changes in OptionMenuVar and update AgeVar based on the selected value?
I've read The Variable Classes. I guess I know how to trace changes in OptionMenuVar but I still don't know how to:
detect the new value
update AgeVar according to the new value
So this is the way to do it:
def OnOptionMenuChnage(omv,av, *pargs):
print omv.get(), av.get()
# do more. set av value based on omv value
OptionMenuVar.trace("w", lambda *pargs: OnOptionMenuChnage(OptionMenuVar,AgeVar, *pargs))
Complete code
for p in self.ProductNames:
OptionMenuVar = StringVar()
menu = OptionMenu(self.FrameProducts, OptionMenuVar, *self.ProductNames)
OptionMenuVar.set(p)
AgeVar = StringVar()
AgeEntry = Entry(self.FrameProducts,width=15,textvariable=AgeVar,state="readonly",justify=CENTER)
def OnOptionMenuChnage(omv,av, *pargs):
print omv.get(), av.get()
# do more. set av value based on omv value
OptionMenuVar.trace("w", lambda *pargs: OnOptionMenuChnage(OptionMenuVar,AgeVar, *pargs))
All the credit to Marcin answer.