1

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

Spot
  • 11
  • 4
  • I edited your post to include the Python-3.x tag. In the future, please include this; there are two incompatible Python releases, 2.x and 3.x, and it's useful to know which one the asker is using when answering a question. – Matthew Jul 31 '14 at 18:55
  • Have you checked this? Seems like the same problem. http://stackoverflow.com/a/17252390/3134251 – Lafexlos Jul 31 '14 at 22:37
  • Hello, thank you for your answer. Yes i've seen it and rewrote my code to use a class, but the program still shows the same behaviour. After reviewing my code i've seen that after you make one choice (set one of the available menus), the following actions only updates the last option menu choice (the menus are updated correctly...i've edited my question)...but i can't see why. – Spot Aug 03 '14 at 12:48

2 Answers2

3

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!

  • Dear sephirothrr, i just wanted to post my solution and saw your anser...thank you for your effort. If someone wants a solution without ttk, see my next post. Greets Spot – Spot Sep 28 '14 at 09:23
0

its been a while and ive found a possible solution for my problem...here is the code:

from tkinter import *
from tkinter import _setit

# for example 5 fields
number_of_fields = 5
starting_list = ["choose","item1","item2","item3","item4","item5"]

entry_list = []
option_list = []
option_var = []

def quit():
    raise SystemExit()

# print entry_field text and selected option_menu item
def output():
    print("---------------------------------------")
    for nr,item in enumerate(entry_list):
        if(item.get() != ""):
            print(item.get() + " --> " + option_var[nr].get())
    print("---------------------------------------")

# if an item is selected in one of the
# menus run this function
def reset_menu(*some_args):
    for field in range(number_of_fields):
        new_list = []
        selection = option_var[field].get()
        for option in starting_list[1:]:
            marker = 0
            for j in range(number_of_fields):
                if(str(option_var[j].get()) == "choose"):
                    continue
                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))

        option_list[field]["menu"].delete(0, "end")
        option_list[field]["menu"].insert(0, "command", label="choose", command=_setit(option_var[field], "choose"))
        # add new menu items
        for i in range(len(new_list)):
            option_list[field]["menu"].insert(i+1, "command", label=new_list[i], command=_setit(option_var[field], new_list[i]))



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):
    # set "choose" as default value
    option_var[i].set("choose")
    # trace each variable and call "reset_menu" function
    # if variable change
    option_var[i].trace("w", reset_menu)

# create menu for each field
for i in range(number_of_fields):
    option_list.append(OptionMenu(root, option_var[i], *starting_list))

# create entry for each field
for i in range(number_of_fields):
    entry_list.append(Entry(root))
    entry_list[i].insert(0, "entry"+str(i))

# 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)
button1 = Button(root, text="OK", command=quit)
button2 = Button(root, text="PRINT", command=output)
button1.grid(row=number_of_fields, column=0, sticky=N+S+W+E)
button2.grid(row=number_of_fields, column=1, sticky=N+S+W+E)

mainloop()

This solution also runs under python 2.7, just change "from tkinter ..." to "from Tkinter ...".

Please take a look at the smarter solution sephirothrr has posted (see post above)!

Regards Spot

Spot
  • 11
  • 4