How to update the command of an OptionMenu - python

I am trying to set or update the command of an OptionMenu after its instantiation.
The widget.configure(command=foo) statement works for Button and CheckButton, but not for OptionMenu.
The following code raises this error: _tkinter.TclError: unknown option "-command"
from Tkinter import Tk, OptionMenu, StringVar
root = Tk()
var = StringVar()
def foo(val):
print val, var.get()
widget = OptionMenu(root, var, "one", 'two')
widget.configure(command=foo)
widget.pack()
root.mainloop()

Good question! Its a good thing I never had to do this in any one of my projects before because (unless someone proves me wrong here) you can't set/update the command of a OptionMenu widget once its already defined.
If Tkinter wanted you to be able to do that, it definitely would've included it to be edited by .configure()
There is a handy function called .keys() which you can call with a widget object to see all available traits that can be used with .configure().
Button example:
from tkinter import *
master = Tk()
def callback():
print ("click!")
b = Button(master, text="OK", command=callback)
print (b.keys()) #Printing .keys()
b.pack()
mainloop()
Which results in :
Notice how in this huge list of keys, 'command' is on the second line? That is because a button's command CAN be used in .configure()
OptionMenu example:
from tkinter import *
root = Tk()
var = StringVar()
def foo(val):
print ("HI")
widget = OptionMenu(root, var, "one", 'two')
print(widget.keys())
widget.pack()
root.mainloop()
Which results in:
Notice how there is no 'command' on line 2 this time. This is because you cant configure command with an OptionMenu widget.
Hopefully this problem doesn't hinder your program too much and I hope my answer helped you understand better!

I think what you're really asking is how to associate a command to an Optionmenu, rather than update a command (there is no command, so there's nothing to update).
If you want a function to be called every time a value is selected from an Optionmenu, you can add a trace on the related variable. The trace will call a function whenever that variable changes, whether through the Optionmenu or any other means.
For example:
...
var = tk.StringVar()
def foo(*args):
print "the value changed...", var.get()
var.trace("w", foo)
...
When the function is called it will pass three arguments, which you can safely ignore in this case.
For more information on variable traces see http://effbot.org/tkinterbook/variable.htm
You might also want to consider switching to the ttk combobox. It supports binding to <<ComboboxSelected>>, which is every so slightly less clunky than doing a variable trace.

It is possible to change the commands associated with OptionMenu widets if you're careful (as #Bryan Oakley commented). Below is an example of doing it.
The tricky part is you have to reconfigure all the menu items, not just one of them. This requires some extra bookkeeping (and some processing overhead, but it's unnoticeable).
Initially the menu has three items, each with a different function to be called when selected, one of which changes the menu. If the latter is selected the menu is changed to only have two menu items both of which call the same function.
from tkinter import *
root = Tk()
var = StringVar()
var.set('Select')
def foo(value):
var.set(value)
print("foo1" + value)
def foo2(value):
var.set(value)
print("foo2 " + value)
def foo3(value):
var.set(value)
print("foo3 " + value)
def change_menu(value):
var.set('Select')
print('changing optionmenu commands')
populate_menu(optionmenu, one=foo3, two=foo3)
def populate_menu(optionmenu, **cmds):
menu = optionmenu['menu']
menu.delete(0, "end")
for name, func in cmds.items():
menu.add_command(label=name, command=
lambda name=name, func=func: func(name))
optionmenu = OptionMenu(root, var, ()) # no choices supplied here
optionmenu.pack()
Label(root, textvariable=var).pack()
populate_menu(optionmenu, one=foo, two=foo2, change=change_menu)
root.mainloop()

Related

With printing Entry´s text, last character is dismissed. How to fix?

I want to print the text of an Entry each time a new character is written.
By doing this with binding and a command to the widget the last character isn't printed.
I guess that the parameter 'textvariable' is getting updated after binding command had been executed. How to fix this?
from tkinter import *
master = Tk()
var = StringVar()
def execute_e(key):
print(var.get())
E = Entry(master, width=30, textvariable=var)
E.pack()
E.bind('<Key>', execute_e)
It's because the bound event function is being executed before the new key has been added.
Here's a simple workaround that uses the ability to add validation to an Entry widget (the validator accepts any key because it always returns True). The trick is that validator function is set-up to receive the value that the text will have if the change is allowed by specifying the %P when it's configuration as part of the Entry construction via the validatecommand=(validator_command, '%P').
Here's some documentation about adding validation to Entry widgets with details about how it works.
from tkinter import *
master = Tk()
var = StringVar()
def validator(new_value):
print(f'new_value: {new_value}')
return True
validator_command = master.register(validator)
E = Entry(master, width=30, textvariable=var,
validate='key',
validatecommand=(validator_command, '%P'))
E.pack()
master.mainloop()
I feel like this is a question of event handler. When a key is typed, your code first register a key press and execute the bound command execute_e. Only after it has processed this and your event handler has return will it update the entry with the character and proceed to update the tkinter variable.
Your print command therefore comes in before your variable have been updated and you print the previous version of your variable. If you try deleting a character from your entry, you'll see that you get the previous string with the character you have just erased.
The easiest way around that problem for you is probably to bind the command to the tkinter variable rather than the keybind. Do so using trace when the variable is writen like so :
var.trace('w', execute_e)
There are also some methods to manipulate the event handler and decide in which order to execute commands. root.after_idle will execute a command when the code has nothing else to do (when it has computed everything else you asked it to do). Try out this version of your code :
from tkinter import *
master = Tk()
var = StringVar()
def execute_e(*key):
def printy():
print(var.get())
master.after_idle(printy)
E = Entry(master, width=30, textvariable=var)
E.pack()
E.bind('<Key>', execute_e)
master.mainloop()

Python/Tkinter OptionMenu Update creates a "Shadow" of the Menu

So I'm still quite new to this (my code should make this obvious) and I am working on a GUI with tkinter.
I am trying to have an OptionMenu which shows Keys from a dict and and upon clicking on a key I would like to see the value.
I want to modify that dict and would like to be able to update said OptionMenu.
So far, so good. Now I've been able to get this to "work" - but when I update the menu (after changing it or not) I get a shadow of the menu itself.
I've conconcted a small test program:
import tkinter as tk
from tkinter import ttk
class MyApp():
def __init__(self,master):
self.master = master
self.myDict = {'Key1':1, 'Key2': 2, 'Key3':3}
self.valueVar = tk.StringVar()
self.valueVar.set("0.00")
self.RCS = tk.Label(master, textvariable=self.valueVar).grid(row=5, column=3)
updateButton = tk.Button(text= "Update List", command = self.update)
updateButton.grid(row=4,column=4)
changeButton = tk.Button(text= "Change list", command = self.changeDict)
changeButton.grid(row=5,column=4)
self.keyVar = tk.StringVar(master)
self.om = ttk.OptionMenu(self.master, self.keyVar, "Select Key ", *self.myDict, command = self.setKey )
self.om.configure(width=20)
self.om.grid(row=4, column=3)
def setKey(self,Surface):
self.valueVar.set(self.myDict[Surface])
def update(self):
menu = self.om["menu"]
menu.delete(0,"end")
menu.destroy
menu = ttk.OptionMenu(self.master, self.keyVar, "Select Key", *self.myDict, command = self.setKey )
menu.grid(row=4, column=3)
def changeDict(self):
self.myDict = {'Key4':4, 'Key5': 5, 'Key6':6}
root = tk.Tk()
app = MyApp(root)
root.mainloop()
What do I have to change? Why?
Usually I work with Matlab. I guess it shows.
Much appreciated!
As far as I understand this mini-program you which for it to display an optionmenu with the keys of a dict and then when you press "change dict" and then update it should switch the optionmenu to the other set of items? In this case, you were calling the destroy on the wrong widget. The only issue was with the update function which should be changed to:
def update(self):
#menu = self.om["menu"]
#menu.delete(0,"end")
#The above two menu items are not neede dif you are just going to destroy the widget
self.om.destroy()
self.om = ttk.OptionMenu(self.master, self.keyVar, "Select Key", *self.myDict, command = self.setKey )
self.om.configure(width=20)
self.om.grid(row=4, column=3)
This will do what I think you want it to do. Just so you know, the optionmenu widget is actually a combination of a button and a menu widget. So when you do menu = self.om["menu"], you are actually getting the menu object of the optionmenu widget and then destroying that. Then you are replacing that variable with an optionmenu and losing the original menu whilst not destroying the original optionmenu (self.om). This is why you got the shadow. Some other notes though:
There is no need for the user to have to press 2 buttons when switching lists - call the update function from the changedict function so that it automatically populates.
You can iterate over the menu items as you began to do but you need to decide on one or the other. I can't comment on the efficiency of completely destroying and enabling the widget or changing the menu items of the current widget as I am still rather experienced.
Hopefully that helps!

How to create entry inputs in a toplevel window

i have a problem and can't get my head around it. How to create a child window on pressing a button using tkinter in python were I can entry values like for example:
import tkinter
root = Tk()
Button(root, text='Bring up Message', command=Window).pack()
root.mainloop()
def messageWindow():
win = Toplevel()
-------->calculate------
Label(win, text=message).pack()
Button(win, text='OK', command=win.destroy).pack()
and on the message window i would like to have two entry fields were I can enter a and b and afterwards it should calc a+b and give me the result.
Thank you.
First, you should use from tkinter import * since there isn't a tkinter. preceding the module's classes used in your script.
Also, is your "Bring up Message" button supposed to call the messageWindow() function? Right now it's calling an undefined function Window. If so, you should change the Button's command and move your messageWindow() function above the line where you created the button or else it will call the function before it is defined and generate an error.
The syntax of an Entry widget in Tkinter goes as follows:
entry = Entry(root, *options)
entry.pack()
You need to pack() the entry widget after you define it. You won't be able to retrieve the input inside it if you pack() it on the same line as you define it as it will become a NoneType object.
You will need at least two Entry widgets, one to enter input a and one to enter input b.
You can also add a third Entry to print the result of the sum of a and b to, though you can use a label or just print it to the console.
entry_a = Entry(win)
entry_a.pack()
entry_b = Entry(win)
entry_b.pack()
# Optional answer entry
entry_ans = Entry(win)
entry_ans.pack()
You should then create a function (still within the messageWindow() function) that will retrieve the input from the two entries and add them, as well as another Button to call that function. I implemented some additional error-checking in the form of a try-except for when the entries are blank or contain something other than integers:
def add():
try:
a = int(entry_a.get())
b = int(entry_b.get())
ab_sum = a + b
# Optional printing to answer entry
entry_ans.delete(0, 'end')
entry_ans.insert(0, ab_sum)
except:
pass
Button(win, text="Add", command=add).pack()
"How to create entry inputs in a toplevel window"
import tkinter as tk
...
toplevel = tk.Toplevel(...)
tk.Entry(toplevel)
"How to create a child window on pressing a button..."
import tkinter as tk
...
def create_child_window(widget):
tk.Toplevel(widget)
...
root = tk.Tk()
tk.Button(root, command=lambda w = root: create_child_window(w))

Getting a value from Tkinter OptionMenu in Python

I'm trying to print out the value selected in the option menu but only the first value gets printed everytime I run the code, even if I change my selection to b or c. Not sure where I'm wrong.This is my code:
from tkinter import *
window=Tk()
window.geometry("700x400")
options=StringVar(window)
options.set("a")
menu=OptionMenu(window,options, "a","b","c")
menu.grid(row=2,column=2)
selection=options.get()
print(selection)
Instead of tracing the variable, you can use the command option of the OptionMenu. Each time a menu item is clicked, the command is called. This command takes one argument: the item which is selected.
import tkinter as tk
def callback(selection):
print(selection)
root = tk.Tk()
options = tk.StringVar()
menu = tk.OptionMenu(root, options, 'a', 'b', 'c', command=callback)
menu.pack()
options.set('a')
root.mainloop()
In this case, the initially selected item is not printed because the user have not clicked on it. If you need options.set('a') to trigger your callback, then you will have to trace the variable like in mentalita's answer.
First of all, you need to invoke Tk's mainloop at the end of your code. Also, try tracing the options class variable.
From the docs: You can use the trace method to attach “observer” callbacks to the variable. The callback is called whenever the contents change.
import tkinter as tk
root = tk.Tk()
options = tk.StringVar()
options.trace_add('write', lambda *args: print(options.get()))
menu = tk.OptionMenu(root, options, 'a', 'b', 'c')
menu.pack()
options.set('a')
root.mainloop()
You're assigning selection before the user has the chance to change the selected option.
Putting it in a function that calls when a 'save' button is pressed would likely be the simplest fix. You could also use a recursion loop to update it in real time but it's not a clean solution.
I'm assuming you've already included root.mainloop() in your actual script.
try this
import tkinter
window=tkinter.Tk()
window.geometry("700x400")
option_Menu = tkinter.StringVar(window)
options = ("a","b","c")
menu = tkinter.OptionMenu(window,option_Menu,*options)
menu.grid(row=2,column=2)
option_Menu.set(2)
selection=option_Menu.get()
print(selection)
tkinter.mainloop()

Convert Tkinter textbox entry into Python Variable

(Long Question)
I'm trying to write a piece of code that will take a file path from the user using a tkinter textbox when a button is pressed. It would then convert that textbox entry to a string attached to a normal python variable so I can use that variable in a function later to use that file. The code I attached can make the label copy the text box entry, but I cannot use that variable or myvar in "normal python code". Also, in the code I tried returning myvar.get() through the function mywarWritten, but I cant set a variable equal to the mywarWritten(parameters) because that is dependent on the textbox entry that doesn't happen until the button is pressed. When the button is pressed the print function works printing the statement but it doesn't return please when the function is set equal to attempt.
(In Short)
I want to take a value, or string, from the user using a Tkinter text box, and use the entry as a normal python variable. Preferably the value in the text box would only be taken when a button is pressed.
from Tkinter import *
import Tkinter as tk
root = Tk()
root.title("MyApp")
myvar = StringVar()
def mywarWritten(*args):
print "mywarWritten",myvar.get()
please = myvar.get()
return please
#trying to make the function return the textbox entry but fails
attempt = mywarWritten()
print "plz %s" % (attempt)
#trying to just set it equal too but also fails
python_variable = myvar.get()
label = Label(root, textvariable=myvar)
label.pack()
text_entry = tk.Entry(root, textvariable=myvar)
button1 = tk.Button(root, text="Back to Home", command=lambda: mywarWritten())
button1.pack()
text_entry.pack()
#trying attempt and pythonvariable in "normal python code"
print attempt
print pythonvariable
root.mainloop()
Thanks for the help in advance.
You seem to have a few misunderstandings about scope, imports, references, and functions. myvar is already accessible, and all you have to do to access it is get() it. Don't import the same module multiple times, and try to avoid from x import *. Returning a value to a button doesn't make any sense and has no effect. Every line of code not in a function or class is executed immediately, so attempt = mywarWritten() and all of the other several times you did that outside a function will get the value of that StringVar as soon as the program runs, before there's anything in it. And lambda: func() is just func.
import Tkinter as tk
root = tk.Tk()
root.title("MyApp")
myvar = tk.StringVar()
def mywarWritten(*args):
print "mywarWritten", myvar.get()
label = tk.Label(root, textvariable=myvar)
label.pack()
text_entry = tk.Entry(root, textvariable=myvar)
button1 = tk.Button(root, text="Back to Home", command=mywarWritten)
button1.pack()
text_entry.pack()
root.mainloop()
Any time you want to access the contents of that entry widget, just do myvar.get() and there it will be.
You also have mywarWritten instead of my_var_written, with a v for var.
Overall, I very highly recommend you read the official Python tutorial (and use Python 3, because it's better than Python 2).

Categories