2

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()
Pablo
  • 983
  • 10
  • 24
  • optionmenus don't have a command, so there's nothing to _update_. Are you wanting to update the _values_ that appear? Or, are you wanting to _define_ a command to run when the value changes? That's possible, so please clarify what you're actually trying to accomplish. – Bryan Oakley Jun 08 '16 at 15:36
  • I want to define a command to run when a selection is made so I can send the selected value to the backend. – Pablo Jun 08 '16 at 15:47
  • @Bryan: `OptionMenu`s _do_ accept a `command=` option argument and will call the target function specified when something on it is selected (and pass it the value of the associated menu item as an argument). – martineau Aug 13 '17 at 15:00

3 Answers3

5

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 : enter image description here

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: enter image description here

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!

Gunner Stone
  • 997
  • 8
  • 26
  • 1
    This limitation prevents me from doing dynamic binding of the GUI with my backend. – Pablo Jun 08 '16 at 15:25
  • @Pablo: why don't you just recreate the optionmenu? – Bryan Oakley Jun 08 '16 at 15:35
  • 2
    Actually, you _can_ change the command of the option menu, but there's a reason it's hidden -- the command attribute of each menu item is used internally to change the value of the optionmenu when someone picks an item from the list. If you change this command, you may break the optionmenu behavior. Ultimately, however, the option menu is nothing more than a Menubutton and a Menu, and internally tkinter uses the command attribute on the menu to keep them in sync. – Bryan Oakley Jun 08 '16 at 15:37
  • Gunner: See [my answer](https://stackoverflow.com/a/45663207/355230) for an example of doing it. – martineau Aug 13 '17 at 17:50
4

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.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
1

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()
martineau
  • 119,623
  • 25
  • 170
  • 301