tkinter OptionMenu与跟踪抛出一起出现“ TclError:无法分配更多菜单”

时间:2018-08-07 07:16:00

标签: python tkinter ttk optionmenu

我想用python编写代码生成器,并要求用户通过ttk.OptionMenu选择变量的类型。在此OptionMenu的后面,用户必须在ttk.Checkbutton(如果它是布尔类型)中定义值,或者在项目(它是整数或字符串)中定义值。 GUI如下所示:

enter image description here

如果我想将类型更改为int或字符串,则后面的 ttk.Checkbutton 应该更改为 ttk.Entry ,但是程序确实崩溃了,因为它可以不再分配更多菜单[ _tkinter.TclError:无法分配更多菜单。]。我不知道该怎么办,没有找到解决方法。我正在通过跟踪连接我的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)
        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 个答案:

答案 0 :(得分:2)

您的代码中有两个主要问题。第一个是您每次调用self.show()self.menu_changed()时都会制作新的小部件,第二个是您在self.show()函数内部设置了变量的跟踪,因此该函数会被多次执行时间。

我重新排列了代码,仅创建了新的小部件并在self.add()函数中设置了跟踪。这样,您创建的窗口小部件不会超出必要的数量。唯一的“问题”是即使您选择了Boolean或Integer,我也为Checkbutton / Entry使用了StringVar,因此所有值都转换为字符串。我还添加了一个打印按钮,以打印出所有当前值。

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

答案 1 :(得分:0)

借助@fhdrsdg的先前答案,我发现了错误。我写过...

{
    "version": "0.2.0",
    "configurations": [{
        "type": "chrome",
        "request": "launch",
        "name": "Launch Chrome against localhost",
        "url": "http://site.example.com/",
        "webRoot": "${workspaceFolder}"
    }]
}

...并且在执行trace(...)之前做了grid(...)

如果您改写以下内容,然后将命令放在optionmen的声明中,则它会在grid()之前被跟踪并起作用。这是代码的工作行:

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

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