代码之家  ›  专栏  ›  技术社区  ›  Lehtim

tkinter OptionMenu与trace一起抛出“TclError:不能再分配菜单”

  •  0
  • Lehtim  · 技术社区  · 7 年前

    我想用python编写一个代码生成器,让用户通过ttk.OptionMenu选择变量的类型。在这个选项菜单后面,如果是布尔类型,用户必须在ttk.Checkbutton中定义值;如果是整数或字符串,则必须在条目中定义值。图形用户界面如下所示:

    enter image description here

    如果我想将类型更改为int或string ttk.复选按钮 后面应该换成 ttk.条目 ,但程序确实会崩溃,因为它无法分配更多菜单[ _tkinter.TclError:无法再分配菜单。 ]. 我不知道该怎么办,也没有找到解决办法。我正在通过trace连接我的checkbutton和一个函数,该函数在optionmenu更改时一直被调用。

    下面是我的简单编码示例:

    import tkinter as tk
    import tkinter.ttk as ttk
    from functools import partial
    
    
    class MyWindow(ttk.Frame):
        def __init__(self, parent):
            ttk.Frame.__init__(self, parent)
            line = [tk.StringVar(value="bool"), tk.BooleanVar(value=True)]
            self.vars = [line]
            self.show()
    
        def show(self):
            for widget in self.children.values():
                widget.grid_forget()
            x = 0
            for var in self.vars:
                ttk.OptionMenu(self, var[0], var[0].get(), *["bool", "int", "string"]).grid(row=x, column=0)
                var[0].trace("w", partial(self.menu_changed, line=var))
                if var[0].get() == "bool":
                    ttk.Checkbutton(self, variable=var[1].get()).grid(row=x, column=1)
                else:
                    ttk.Entry(self, textvariable=var[1].get()).grid(row=x, column=1)
                x += 1
            ttk.Button(self, text="+", command=self.add, width=3).grid(row=x, column=0)
            self.place(x=10, y=10, anchor=tk.NW)
    
        def add(self):
            line = [tk.StringVar(value="bool"), tk.BooleanVar(value=True)]
            self.vars.append(line)
            self.show()
    
        def menu_changed(self, *_, line):
            if line[0].get() == "bool":
                line[1] = tk.BooleanVar(value=True)
            elif line[0].get() == "int":
                line[1] = tk.IntVar(value=0)
            elif line[0].get() == "string":
                line[1] = tk.StringVar()
            self.show()
    
    
    root = tk.Tk()
    window = MyWindow(root)
    root.mainloop()
    

    我希望你知道怎么了。提前谢谢。

    2 回复  |  直到 7 年前
        1
  •  2
  •   fhdrsdg    7 年前

    代码中有两个主要问题。第一个是你每次都在制作新的小部件 self.show() self.menu_changed() 第二个是在 自我展示() 函数,因此执行多次。

    我重新排列了您的代码,只创建了新的小部件并在 self.add() 功能。这样就不会创建过多的小部件。唯一的“问题”是我用 StringVar 对于Checkbutton/条目,即使选择了布尔值或整数,所有值都将转换为字符串。我还添加了一个打印按钮来打印所有当前值。

    import tkinter as tk
    import tkinter.ttk as ttk
    from functools import partial
    
    
    class MyWindow(ttk.Frame):
        def __init__(self, parent):
            ttk.Frame.__init__(self, parent)
            self.lines = []
            self.add_button = ttk.Button(self, text="+", command=self.add, width=3)
            self.print_button = ttk.Button(self, text="Print", command=self.print_values)
            self.place(x=10, y=10, anchor=tk.NW)
            self.add()
    
        def show(self):
            for widget in self.children.values():
                widget.grid_forget()
            x = 0
            for line in self.lines:
                line[2].grid(row=x, column=0)
                if line[0].get() == "bool":
                    line[3].grid(row=x, column=1)
                else:
                    line[4].grid(row=x, column=1)
                x += 1
            self.add_button.grid(row=x, column=0)
            self.print_button.grid(row=x, column=1)
    
        def add(self):
            line = [tk.StringVar(value="bool"), tk.StringVar(value="1")]
            line.append(ttk.OptionMenu(self, line[0], line[0].get(), *["bool", "int", "string"]))
            line.append(ttk.Checkbutton(self, variable=line[1]))
            line.append(ttk.Entry(self, textvariable=line[1]))
            line[0].trace("w", partial(self.menu_changed, line=line))
            self.lines.append(line)
            self.show()
    
        def menu_changed(self, *_, line):
            if line[0].get() == "bool":
                line[1].set("1")
            elif line[0].get() == "int":
                line[1].set("0")
            elif line[0].get() == "string":
                line[1].set("")
            self.show()
    
        def print_values(self):
            print("Current values:")
            for line in self.lines:
                print(line[1].get())
    
    
    root = tk.Tk()
    window = MyWindow(root)
    root.mainloop()
    

    顺便说一下,您实际上不需要跟踪StringVar,您可以向OptionMenu添加一个命令参数,如:

    ttk.OptionMenu(self, line[0], line[0].get(), *["bool", "int", "string"], command=partial(self.menu_changed, line=line))
    
        2
  •  0
  •   Lehtim    7 年前

    根据@fhdrsdg先前的回答,我发现了这个错误。我写过。。。

    ttk.OptionMenu(self, var[0], var[0].get(), *["bool", "int", "string"]).grid(row=x, column=0)
    var[0].trace("w", partial(self.menu_changed, line=var))
    

    ... 在跟踪之前做了网格(…)

    如果您改为编写以下命令,并将该命令放在optionmenu的声明中,则在grid()之前跟踪它并运行。下面是代码的工作行:

    cmd = partial(self.menu_changed, line=var)
    ttk.OptionMenu(self, var[0], var[0].get(), *["bool", "int", "string"], command=cmd).grid(row=x, column=0)
    

    但是非常感谢@fhdrsdg,因为如果没有他的提示,我永远不会发现这个错误。