Python Tkinter / 如何让多个OptionMenu共享一个项目列表?

1 投票
2 回答
1355 浏览
提问于 2025-04-18 15:32

我正在尝试创建多个选项菜单,这些菜单共享同一个“基础项目列表”。在不同的菜单中,不能同时选择同一个项目,所以当在一个菜单中选择了某个项目时,所有菜单都需要更新。

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()

现在一切看起来都很好,直到我尝试更新菜单。新的菜单项目列表生成得很正确(可以通过打印语句看到),菜单中也有正确的项目,但在选择一个菜单后,只有最后一个菜单的选中状态会改变。有什么想法吗?

问候,Spot

2 个回答

0

我已经有一段时间没更新了,现在我找到了一个可能解决我问题的方法……下面是代码:

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()

这个解决方案也可以在python 2.7上运行,只需要把 "from tkinter ..." 改成 "from Tkinter ..." 就可以了。

请看看sephirothrr发布的更聪明的解决方案(见上面的帖子)!

祝好,Spot

3

我看到你的问题是因为我也在尝试完成同样的任务。在查看了一下tkinter的内容后,我找到了一个解决方案,这让我决定注册一个账号来分享。

我在代码中保留了你原来的评论,标记了我没有修改的部分。

首先,你生成选项的代码有点复杂。与其从空列表手动添加选项,不如从完整的列表中删除不需要的项,这样看起来更简洁。

你现在使用的是tkinter.OptionMenu()。如果你改用tkinter.ttk.OptionMenu(),它有一个叫做set_menu(*values)的方法,可以接受任意数量的值作为参数,并将菜单的选项设置为这些参数。

不过,切换后有一点需要注意:ttk的OptionMenu不允许在下拉菜单中选择默认值,所以建议把这个默认值设为空,就像我在starting_list的声明中做的那样。

为了保持空选项的存在,我添加了一个额外的空选项,这样它就可以被选择。这样,如果你不小心选择了错误的选项,可以轻松撤回你的选择。

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()

你可能还想考虑让选项生成的过程更高效。目前,对于n个选项,你的菜单循环了n^2次。我建议在回调中传递刚刚选择的值,而不是每次都去查找每个菜单之前选择的内容。

另外一个小问题是,你的“确定”按钮会导致崩溃。我不确定这是故意的,还是我系统的某种问题,或者其他原因。

希望这些对你有帮助!

撰写回答