tkinter OptionMenu in combination with trace throws "TclError: No more menus can be allocated"
tkinter OptionMenu in combination with trace throws "TclError: No more menus can be allocated"
我想在 python 中编写一个代码生成器,并通过 ttk.OptionMenu 变量类型要求用户 select。就在这个 OptionMenu 后面,用户必须在 ttk.Checkbutton 中定义值(如果它是布尔类型)或条目(如果它是整数或字符串)并定义一个值。 GUI 如下所示:
如果我想将类型更改为 int 或 string 后面的 ttk.Checkbutton 应该更改为 ttk.Entry,但是程序确实崩溃了,因为它不能分配更多的菜单[_tkinter.TclError:不能分配更多的菜单。]。我不知道该怎么办,也没有找到解决办法。我通过跟踪连接我的复选按钮和一个函数,该函数在选项菜单更改时一直被调用。
这是我的简单编码示例:
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()
我希望你知道哪里出了问题。提前致谢。
您的代码中存在两个主要问题。第一个是每次调用 self.show()
或 self.menu_changed()
时你都在不断地制作新的小部件,第二个是你在 self.show()
函数中设置变量的跟踪,因此执行了很多次次。
我重新安排了您的代码,只创建了新的小部件并在 self.add()
函数中设置了跟踪。这样您就不会创建不必要的小部件。唯一的 "problem" 是我对 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))
在@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))
... 并在执行 trace(...) 之前执行 grid(...)
如果您改写以下内容并将命令放在选项菜单的声明中,它会在 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,因为如果没有他的提示我永远不会发现这个错误。
我想在 python 中编写一个代码生成器,并通过 ttk.OptionMenu 变量类型要求用户 select。就在这个 OptionMenu 后面,用户必须在 ttk.Checkbutton 中定义值(如果它是布尔类型)或条目(如果它是整数或字符串)并定义一个值。 GUI 如下所示:
如果我想将类型更改为 int 或 string 后面的 ttk.Checkbutton 应该更改为 ttk.Entry,但是程序确实崩溃了,因为它不能分配更多的菜单[_tkinter.TclError:不能分配更多的菜单。]。我不知道该怎么办,也没有找到解决办法。我通过跟踪连接我的复选按钮和一个函数,该函数在选项菜单更改时一直被调用。
这是我的简单编码示例:
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()
我希望你知道哪里出了问题。提前致谢。
您的代码中存在两个主要问题。第一个是每次调用 self.show()
或 self.menu_changed()
时你都在不断地制作新的小部件,第二个是你在 self.show()
函数中设置变量的跟踪,因此执行了很多次次。
我重新安排了您的代码,只创建了新的小部件并在 self.add()
函数中设置了跟踪。这样您就不会创建不必要的小部件。唯一的 "problem" 是我对 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))
在@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))
... 并在执行 trace(...) 之前执行 grid(...)
如果您改写以下内容并将命令放在选项菜单的声明中,它会在 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,因为如果没有他的提示我永远不会发现这个错误。