Changing the options of a OptionMenu when clicking a Button

Question:

Say I have an option menu network_select that has a list of networks to connect to.

import Tkinter as tk

choices = ('network one', 'network two', 'network three')
var = tk.StringVar(root)
network_select = tk.OptionMenu(root, var, *choices)

Now, when the user presses the refresh button, I want to update the list of networks that the user can connect to.

  • I don’t I can use .config because I looked through network_select.config() and didn’t see an entry that looked like the choices I gave it.
  • I don’t think this is something one can change using a tk variable, because there is no such thing as a ListVar.
Asked By: charmoniumQ

||

Answers:

I modified your script to demonstrate how to do this:

import Tkinter as tk

root = tk.Tk()
choices = ('network one', 'network two', 'network three')
var = tk.StringVar(root)

def refresh():
    # Reset var and delete all old options
    var.set('')
    network_select['menu'].delete(0, 'end')

    # Insert list of new options (tk._setit hooks them up to var)
    new_choices = ('one', 'two', 'three')
    for choice in new_choices:
        network_select['menu'].add_command(label=choice, command=tk._setit(var, choice))

network_select = tk.OptionMenu(root, var, *choices)
network_select.grid()

# I made this quick refresh button to demonstrate
tk.Button(root, text='Refresh', command=refresh).grid()

root.mainloop()

As soon as you click the “Refresh” button, the options in network_select are cleared and the ones in new_choices are inserted.

Answered By: user2555451

The same, but with tk.Menu widget:

# Using lambda keyword and refresh function to create a dynamic menu.
import tkinter as tk

def show(x):
    """ Show menu items """
    var.set(x)

def refresh(l):
    """ Refresh menu contents """
    var.set('')
    menu.delete(0, 'end')
    for i in l:
        menu.add_command(label=i, command=lambda x=i: show(x))

root = tk.Tk()
menubar = tk.Menu(root)
root.configure(menu=menubar)
menu = tk.Menu(menubar, tearoff=False)
menubar.add_cascade(label='Choice', menu=menu)

var = tk.StringVar()
l = ['one', 'two', 'three']
refresh(l)
l = ['four', 'five', 'six', 'seven']
tk.Button(root, text='Refresh', command=lambda: refresh(l)).pack()
tk.Label(root, textvariable=var).pack()
root.mainloop()
Answered By: FooBar167

In case of using ttk, there is a convenient set_menu(default=None, values) method on the OptionMenu object.

Answered By: Eva Lond

I know I’m a bit late to this party, but I was searching for the same thing, couldn’t find a nice way to do it including having a selected index. So I did the following:

You can inherit a widget in a new class and create a new object that you can customize.

import tkinter

class MyOptionMenu(tkinter.OptionMenu):
    """
    An implementation of the tkinter OptionMenu, which inherets tkinter.OptionMenu.
    It gives you the ability to add / remove items from the Option List,
    Get Options from the List,
    Get the Number of Options in the list
    Return the selected Option Value and selected Option Index
   
    Methods
    -------
    SelectOption(Index)
        Selects an Option in the OptionMenu specified by the Index integer
    AddOptions(LstOptions)
        Adds options to the OptionMenu speciefied by lstOptions list
    AddOption(Option)
        Adds the option to the OptionMenu specified by Option string
    DeleteOption(Index)
        Deletes an option from the OptionMenu specified by the Index integer
    DeleteOptions()
        Deletes all options from the OptionMenu
    GetOption(Index)
        Returns the option as a string specified by the Index integer
    GetOptions()
        Returns all the options contained in the OptionMenu as a list of strings
    GetNumberOrOptions()
        Returns the total number of Options contained in the OptionMenu
    RenameOption(Index, Option)
        Renames an Option to match the Option string specified by the Index integer
    """
    
    def __init__(self, parent, StrVar=None, IntVar=None, LstOptions=None, DefaultValue="", command=None):
        """
        Parameters
        ----------
        parent : tkinter.TK
            the parent container of the OptionMenu
        StrVar : tkinter.StringVar()
            the tkinter string variable used to get / set the selected Option's value
        IntVar : tkinter.IntVar()
            the tkinter integer variable used to get / set the selected Option's index
        LstOptions : list
            the List of strings used to display the Options in the OptionMenu
        DefaultValue : str
            the string used to display a default value in the Option Menu if nothing is selected
            this can happen if the object initializes with no selected value
            this can happen if the selected option is deleted
            this can happen if an index is selected that does not exist
        command : function(Option, Index)
            the function to be called with the parameters Option and Index.
            This function is called when an Option is selected and the selected Option Value and Option Index is returned through the functoin
        """
        if (StrVar is None):
            self.__StrVar = tkinter.StringVar()
        else:
            self.__StrVar = StrVar

        if (IntVar is None):
            self.__IntVar = tkinter.IntVar()
        else:
            self.__IntVar = IntVar


        self.__IntVar.trace("w", self.__evtIntVarSet__)

        self.__StrVar.set(DefaultValue)
        self.__DefaultValue = DefaultValue
        
        super().__init__(parent, value="",variable=StrVar)
        self.__command = command
        self.__lstOptions = LstOptions
           
        self.__menu = self["menu"]      #Get the Menu Object from the OptionMenu Object
        self.__menu.delete(0, "end")    #Delete all of the Objects contained inside the OptionMenu Object

        #Loop through each of the values contained in the list and generate buttons on the menu
        #Assign the Button Label and Command function to return the value and the index
        for index, value in enumerate(LstOptions):
            self.__MenuAdd__(value, index)


    def __MenuAdd__(self, value, index):
        """__Internal Function__ that Adds a Menu Command to the Option Menu
        This function assigns the Menu Button Label text,
        and command that assigns the selected value and index to the tkinter
        String and Intiger values.  Also combine the callback command function specified

        Parameters
        ----------
        value : str
            The value used to display the Option Command Label string
            Also used for the command callback function specified to indicate which Option was selected
        index : int
            The index used to for the command callback function specified to indicate which Option was selected by index
        """
        self.__menu.add_command(label=value,
                                command=lambda value=value, index=index: [self.__StrVar.set(value),
                                                                          self.__IntVar.set(index),
                                                                          self.__command(value, index)])

    def __MenuEdit__(self, value, index):
        """__Internal Function__ that Modifies a Menu Command in the Option Menu
        This function assigns the Menu Button Label text,
        and command that assigns the selected value and index to the tkinter
        String and Intiger values.  Also combine the callback command function specified

        Parameters
        ----------
        value : str
            The value used to display the Option Command Label string
            Also used for the command callback function specified to indicate which Option was selected
        index : int
            The index used to for the command callback function specified to indicate which Option was selected by index
        """
        self.__menu.entryconfigure(index,
                                   label=value,
                                   command=lambda value=value, index=index: [self.__StrVar.set(value),
                                                                             self.__IntVar.set(index),
                                                                             self.__command(value, index)])

    def __evtIntVarSet__(self, var, index, mode):
        """__Internal Function__ that executes when the IntVar changes
        This function assigns StrVar value based on the Option specified by the Index specified by IntVar
        It is used so that the selected option can be set by index
        """
        self.SelectOption(self.__IntVar.get())

    def SelectOption(self, Index):
        """Selects the Option in the Option Menu based on the Index integer
           If the Index is out of range the DefaultValue string is applied

        Parameters
        ----------
        index : int
            The index used to select the Option in the Option Menu
        """
        if self.GetNumberOfOptions() >= Index >= 0:
            self.__StrVar.set(self.GetOption(Index))
        else:
            self.__StrVar.set(self.__DefaultValue)


    def AddOptions(self, LstOptions):
        """Adds Options to the OptionMenu

        Parameters
        ----------
        LstOptions : list
            A list of strings to be added as Options to the OptionMenu
        """
        for Option in LstOptions:
            self.AddOption(Option)

    def AddOption(self, Option):
        """Adds a Option to the OptionMenu

        Parameters
        ----------
        Option : str
            An Option to be added to the OptionMenu
        """
        index = len(self.__lstOptions)   #Get the index value to return (The length of the list of Options)
        self.__lstOptions.append(Option)
        self.__MenuAdd__(Option, index)

    def DeleteOption(self, Index):
        """Deletes an Option from the OptionMenu by the Index Position

        Parameters
        ----------
        Index : int
            The position of the Option to be deleted from the OptionMenu
        """
        del self.__lstOptions[Index]    #Delete the Option from the Option List with the specified Index
        self.__menu.delete(Index)       #Delete the Object from the Option List with the specified Index

        if Index == self.__IntVar.get():  #This means the selected Option is being deleted
            self.__StrVar.set(self.__DefaultValue) #Assign the default value
        else:
            if Index > self.__IntVar.get(): #If the Index is greater than the selected index 
                self.__IntVar.set(Index -1) #This means that the selected index is decremented by one.

        #Need to loop through the remainder to update the indexes
        for i, value in enumerate(self.__lstOptions):
            if i >= Index:
                self.__MenuEdit__(value, i)

    def DeleteOptions(self):
        """Deletes all Options of the OptionMenu
        """
        self.__lstOptions = []          #Clear the Options List
        self.__menu.delete(0, "end")    #Delete all of the Objects contained inside the OptionMenu Object
        self.__StrVar.set(self.__DefaultValue)  #The Option is deleted set the Default Value in the OptionMenu

    def GetOption(self, Index):
        """Returns a Option Label Value of the Specified Index

        Parameters
        ----------
        Index : int
            The position of the Option to have the Label Value returned from the OptionMenu

        Returns
        -------
        str
            The Option Label Value of the specified by the Index
        """
        return self.__lstOptions[Index]

    def GetOptions(self):
        """Returns a list of Option Label Value

        Returns
        -------
        list
            A List of Strings of the Option Label Values
        """
        return self.__lstOptions

    def GetNumberOfOptions(self):
        """Returns the total number of Options contained in the OptionMenu

        Returns
        -------
        int
            The total number of Options contained in the OptionMenu
        """
        return len(self.__lstOptions)

    def RenameOption(self, Index, Option):
        """Renames an Option Label Value of the Specified Index

        Parameters
        ----------
        Value : str
            The value used to display the Option Command Label string
            Also used for the command callback function specified to indicate which Option was selected
        Index : int
            The position of the Option to have the Label Value renamed
        """
        self.__lstOptions[Index] = Option   #Change the Option at the index
        self.__MenuEdit__(Option, Index)
        self.SelectOption(self.__IntVar.get()) #Update the Selected Option based on the current IntVar.  This is incase the selected Option is Renamed
Answered By: MvdM