Python Tkinter / How to let OptionMenus share one item list?
Question: i'm trying to build multiple option menus sharing the same "base item list". A
multiple selection of one item in different menus should not be possible, so
all menus have to be updated when an item is selected in one of the available
menus.

    
    
    from tkinter import *
    
    # for example 5 fields
    number_of_fields = 5
    starting_list = ["item1","item2","item3","item4","item5"]
    entry_list = []
    option_list = []
    option_var = []
    
    
    def quit():
        raise SystemExit()
    
    # if an item is selected in one of the
    # menus run this function
    def reset_menu(sel_item):
        # for each field
        for field in range(number_of_fields):
            new_list = []
            selection = option_var[field].get()
            # look for selected items in all menus
            # and build new list which contains all
            # items from the starting_list minus the
            # items which are already selected
            # keep the one selected (for a menu itself)
            for option in starting_list:
                marker = 0
                for j in range(number_of_fields):
                    if(str(option_var[j].get()) == str(option)):
                        marker = 1
                if(marker == 0):
                    new_list.append(str(option))
                else:
                    pass
                if(str(selection) == str(option)):
                    new_list.append(str(option))
            # print new generated item list
            # just to be sure it works so far
            print("field",field,"new list=",new_list)
    
            # NOW HERE SOMETHING IS WRONG I GUESS
            # empty menu
            option_list[field]["menu"].delete(0, "end")
            # add new menu items
            for item in new_list:
                option_list[field]['menu'].add_command(label=item, command=lambda value=item:option_var[field].set(value))
    
    
    root = Tk()
    root.title("OptionMenu")
    
    # menu variable for each field
    for i in range(number_of_fields):
        option_var.append(StringVar(root))
    
    # initial value for each field 
    for i in range(number_of_fields):
        option_var[i].set("")
    
    # create menu for each field
    for i in range(number_of_fields):
        option_list.append(OptionMenu(root, option_var[i], *starting_list, command=reset_menu))
    
    # create entry for each field
    for i in range(number_of_fields):
        entry_list.append(Entry(root))
    
    # build gui
    for i in range(number_of_fields):
        entry_list[i].grid(row=int(i),column=0,sticky=N+S+W+E)
        option_list[i].grid(row=int(i), column=1,sticky=N+S+W+E)
    button = Button(root, text="OK", command=quit)
    button.grid(row=number_of_fields,column=1,sticky=N+S+W+E)
    
    mainloop()
    

Now everthing seems to be fine until i try to update the menus. The new menu
item lists are generated correctly (see print statement) and the menus have
the right items, but after selected one menu, the only menu that changes its
selected state is the last one. Any ideas?

Regards Spot


Answer: I found your question because I too was trying to complete the same task.
After doing a bit of poking around in dir(tkinter), I have found a solution,
which you have inspired me to create an account to post.

I have left your original comments in the code for sections that I left
unchanged.

First, your code for generating your options is unnecessarily cluttered.
Instead of manually populating the list from empty, it seems cleaner to remove
items from the full list.

You are currently using tkinter.OptionMenu(). If you instead use
tkinter.ttk.OptionMenu(), it has a method called set_menu(*values) that takes
any number of values as its arguments and sets the choices of that menu to be
those arguments.

If you make the switch, there one thing to note - ttk's OptionMenu does not
allow its default value to chosen in the dropdown, so it's recommended to make
that value blank, as I have done in the declaration for starting_list.

In order to persist the blank option, I added an additional blank option, in
order for it to be selectable. This way, if you mistakenly choose the wrong
selection, you can revert your choice.

    
    
    from tkinter import *
    from tkinter.ttk import *
    
    # for example 5 fields
    number_of_fields = 5
    starting_list = ["","item1","item2","item3","item4","item5"]
    entry_list = []
    option_list = []
    option_var = []
    
    
    def quit():
        raise SystemExit()
    
    # if an item is selected in one of the
    # menus run this function
    def reset_menu(sel_item):
        # for each field
        for field in range(number_of_fields):
            new_list = [x for x in starting_list]
            selection = option_var[field].get()
            # look for selected items in all menus
            # and build new list which contains all
            # items from the starting_list minus the
            # items which are already selected
            # keep the one selected (for a menu itself)
            for option in starting_list[1:6]:
                #add selectable blank if option is selected
                if (str(selection) == str(option)):
                        new_list.insert(0,"")
                for j in range(number_of_fields):
                    if(str(selection) != str(option) and str(option_var[j].get()) == str(option)):
                        new_list.remove(option) 
            # print new generated item list
            # just to be sure it works so far
            print("field",field,"new list=",new_list)
            #set new options
            option_list[field].set_menu(*new_list)
    
    root = Tk()
    root.title("OptionMenu")
    
    # menu variable for each field
    for i in range(number_of_fields):
        option_var.append(StringVar(root))
    
    # initial value for each field 
    for i in range(number_of_fields):
        option_var[i].set("")
    
    # create menu for each field
    for i in range(number_of_fields):
        option_list.append(OptionMenu(root, option_var[i], *starting_list, command=reset_menu))
    
    # create entry for each field
    for i in range(number_of_fields):
        entry_list.append(Entry(root))
    
    # build gui
    for i in range(number_of_fields):
        entry_list[i].grid(row=int(i),column=0,sticky=N+S+W+E)
        option_list[i].grid(row=int(i), column=1,sticky=N+S+W+E)
    button = Button(root, text="OK", command=quit)
    button.grid(row=number_of_fields,column=1,sticky=N+S+W+E)
    
    mainloop()
    

Something you may want to look into is making your option generation a bit
more efficient. Right now, for n options, you're looping through your menus
n^2 times. I would suggest looking at passing the value that was just selected
in the callback instead of searching each menu to see what was previously
selected.

As an additional minor note, your "OK" button causes a crash. I'm not sure if
that was intentional behavior, a quirk in my system, or something else.

I hope this helps!

