sidnical
sidnical

Reputation: 459

Tkinter optionmenu add_command strings do not show on widget when selected

from tkinter import *


main = Tk()

var = StringVar()
options = OptionMenu(main, var, 'option1')
options.grid()
options['menu'].add_command(label='option2')


main.mainloop()

When you select 'option1' in the menu it shows that string on the widget. If you select 'option2' it does not show that string on the widget. If you set a command for option2 it will run. Why is 'option2' not showing in the widget when it is selected?

python 3.5

UPDATE:

By adding strings through a for loop I run into a problem where the command for each string uses the same variable.

from tkinter import *

def _print(var, string):
    print(string)
    var.set(string)

lst = ['hello', 'bob', 'testing']

main = Tk()
var = StringVar()
options = OptionMenu(main, var, 'option1')
options.grid()

for string in lst:
    options['menu'].add_command(label=string, command=lambda: _print(var, string))

main.mainloop()

I tried using lambda event, i=string: _print(var, i)) in the loop but lamdba doesnt like it. Says its missing 'event'. I've put event in the _print function and the call to _print but I get the same error that lamdba is missing event. Any idea how I can pass the right variable for each command in the loop?

Upvotes: 0

Views: 4848

Answers (2)

Sun Bear
Sun Bear

Reputation: 8244

It works when you declared 'option2' in the Optionmenu() widget. More examples can be read from here and here.

from tkinter import *

main = Tk()

var = StringVar()
options = OptionMenu(main, var, 'option1', 'option2')
options.grid()
#options['menu'].add_command(label='option2')


main.mainloop()

I found that the menu option is used for creating items in a Menu() widget but not for a Optionmenu() widget. This is stated here. You can read more about how to declare a .Menu() widget here. All the best to your tkinter developments.

Edit1:

The reason why it was failing was because you did not declare the command option associated with the .add_command method. Having done this alone, you will notice that the Optionmenu still does not get updated with the new label of the added menu item. To resolve this issue, you have to use the .set() method of the Control Variable StringVar to do the updating. See revised script below.

from tkinter import *

def _print1(value):
    # By default, callback to OptionMenu has a positional argument for the 
    #  menu item's label. 
    print(value)
    print(var.get())
    
def _print2():
    var.set('option2')
    print(var.get())

main = Tk()

var = StringVar()
options = OptionMenu(main, var, 'option1',command=_print1)
options.grid()
options['menu'].add_command(label='option2', command=_print2)
# To add more clickable menu items, the 'add_command' method requires you to
# to declare it's options 'label' and 'command'.

main.mainloop()

Edit2:

  1. Remark, I have added the command aspect to options in Edit1. I did not address earlier but thought it needed to be shown for completeness.
  2. Responding to your question in your UPDATE, to do what you want, I rewrote the script as a class object. Also I used the internal class _setit found in tkinter, which the OptionMenu widget had used to do configure the callback used by command. This approach overcame the issue you encountered.

Revised Code:

from tkinter import *

class App(Tk):

    def __init__(self, parent=None):
        Tk.__init__(self, parent)
        self.parent=parent
        self.createOM()
        self.grid()

    def createOM(self):
        # Create OptionMenu
        omlist=['option1']
        self.var = StringVar()
        self.options = OptionMenu(self, self.var, *omlist,
                                  command=self._print1)
        self.options.grid()
        
        values = ['hello', 'bob', 'testing']
        for v in values:
            self.options['menu'].add_command(
                label=v, command=_setit(self.var, v, self._print2))
        # To add more clickable menu items, the 'add_command' method requires you to
        # to declare it's options 'label' and 'command'.
       
    def _print1(self, value, *args):
        #callback for OptionMenu has these arguments because inherently it
        #uses the _setit class to configure the callback with these arguments. 
        print()        
        print(value)        
        #self.var.set('option10') #Uncomment to change OptionMenu display
        print(self.var.get())

    def _print2(self, value, *args):
        print()        
        print(value)        
        #self.var.set('option20') #Uncomment to change OptionMenu display
        print(self.var.get())
   
#The following class is extracted from tkinter.
class _setit:
    """Internal class. It wraps the command in the widget OptionMenu."""
    def __init__(self, var, value, callback=None):
        self.__value = value
        self.__var = var
        self.__callback = callback
    def __call__(self, *args):
        self.__var.set(self.__value)
        if self.__callback:
            self.__callback(self.__value, *args)


if __name__ == "__main__":
    app = App()
    app.mainloop()

Upvotes: 2

Nae
Nae

Reputation: 15335

It doesn't select anything because it's not supposed to. You're basically hacking OptionMenu's visual part. The items you pass as values are first added to the menu and then are given the command option to modify the selection. When you call options['menu'].add_command(label='option2') you're basically only modifying the visual part of the widget, 'option2' is not added to values, and thus command option isn't set. See for yourself in the source code here.

Upvotes: 1

Related Questions