3

I was wondering if there is any way for me to see what the User has selected among the list displaying, let's say: ["Apple","Orange","Grapes"] right after they select either one of them?

Like when user clicks the optionbox, and clicks Apple, Tkinter will return something

then if he switches his selection to, let's say, Orange, then that will also return something on the spot.

Thanks!


How to put parameter correctly?

from Tkinter import *

def option_changed(a):
    print "the user chose the value {}".format(variable.get())
    print a

master = Tk()

a = "Foo"
variable = StringVar(master)
variable.set("Apple") # default value
variable.trace("w", option_changed(a))

w = OptionMenu(master, variable, "Apple", "Orange", "Grapes")
w.pack()

mainloop()
Kevin
  • 74,910
  • 12
  • 133
  • 166
user3404844
  • 145
  • 2
  • 3
  • 14

2 Answers2

7

Trace the StringVar.

from Tkinter import *

def option_changed(*args):
    print "the user chose the value {}".format(variable.get())
    print a

master = Tk()

a = "Foo"
variable = StringVar(master)
variable.set("Apple") # default value
variable.trace("w", option_changed)

w = OptionMenu(master, variable, "Apple", "Orange", "Grapes")
w.pack()

mainloop()

Here, option_changed will be called whenever the user chooses an option from the option menu.


You can wrap the trace argument in a lambda to specify your own parameters.

def option_changed(foo, bar, baz):
    #do stuff

#...
variable.trace("w", lambda *args: option_changed(qux, 23, "hello"))
Kevin
  • 74,910
  • 12
  • 133
  • 166
  • How can I add Parameter to `.trace` function? – user3404844 Mar 17 '14 at 19:05
  • What kind of parameter? – Kevin Mar 17 '14 at 19:07
  • The code I posted above doesn't seem to be working :( – user3404844 Mar 17 '14 at 19:10
  • You don't need to pass `a` as a parameter to access it within the function. leave `def option_changed(*args):` and `variable.trace("w", option_changed)` as they are here, and `print a` will still work. – Kevin Mar 17 '14 at 19:23
  • Thanks for the reply but I just want to know what if I need to pass a parameter? – user3404844 Mar 17 '14 at 19:51
  • Thanks a lot Kevin! Could you tell me the difference between `lambda *args:....` and `lambda args:.....`? The latter one raises error while the prior one doesn't. – user3404844 Mar 17 '14 at 23:59
  • 1
    The asterisk is the signifier for an [arbitrary argument list](http://docs.python.org/2/tutorial/controlflow.html#arbitrary-argument-lists). When you include it, the function can be called with any number of arguments, and anything not bound to a previous parameter will be available in a tuple. Ex. If I define `def frob(x, y, *z)` and call `frob(4, 8, 15, 16, 23)`, then z will be the tuple `(15, 16, 23)`. – Kevin Mar 18 '14 at 03:26
6

When I come across widgets with annoying interfaces - such as OptionMenu, I generally will write a class around it to abstract away the annoying attributes. In this case, I really dislike the verbosity of using the StringVar every time I want to create a dropdown, so I simply created a DropDown class which includes the StringVar within the class (written in Python 3.5, but translates easily to all):

class DropDown(tk.OptionMenu):
    """
    Classic drop down entry

    Example use:
        # create the dropdown and grid
        dd = DropDown(root, ['one', 'two', 'three'])
        dd.grid()

        # define a callback function that retrieves the currently selected option
        def callback():
            print(dd.get())

        # add the callback function to the dropdown
        dd.add_callback(callback)
    """
    def __init__(self, parent, options: list, initial_value: str=None):
        """
        Constructor for drop down entry

        :param parent: the tk parent frame
        :param options: a list containing the drop down options
        :param initial_value: the initial value of the dropdown
        """
        self.var = tk.StringVar(parent)
        self.var.set(initial_value if initial_value else options[0])

        self.option_menu = tk.OptionMenu.__init__(self, parent, self.var, *options)

        self.callback = None

    def add_callback(self, callback: callable):
        """
        Add a callback on change

        :param callback: callable function
        :return: 
        """
        def internal_callback(*args):
            callback()

        self.var.trace("w", internal_callback)

    def get(self):
        """
        Retrieve the value of the dropdown

        :return: 
        """
        return self.var.get()

    def set(self, value: str):
        """
        Set the value of the dropdown

        :param value: a string representing the
        :return: 
        """
        self.var.set(value)

Example usage:

# create the dropdown and grid, this is the ONLY required code
dd = DropDown(root, ['one', 'two', 'three'])
dd.grid()

# optionally, define a callback function that retrieves the currently selected option then add that callback to the dropdown
def callback():
    print(dd.get())

dd.add_callback(callback)

Edited to add: Not long after creating this post, I got annoyed with a few other attributes of tk and ended up creating a package called tk_tools to make dropdowns and checkbuttons easier along with addressing other annoyances.

slightlynybbled
  • 2,408
  • 2
  • 20
  • 38
  • That's brilliant. However, I tried to 'improve' the code by changing `tk.OptionMenu.__init__(self, parent, self.var, *options)` to the ttk equivalent, i.e. `ttk.OptionMenu.__init__(self, parent, self.var, *options)`. The error I get is `AttributeError: 'DropDown' object has no attribute 'set_menu'`. Why is that so - why would the ttk equivalent fail to initialize? – xax Jun 07 '17 at 17:21
  • tk.OptionMenu does not possess the set_menu method. This is only a property of ttk.OptionMenu. – Jgd10 Apr 02 '19 at 10:42