英文:
Tkinter adding trace to IntVar in a for loop
问题
class SelectFrame(tk.Frame):
def __init__(self, master, user, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.user = user
self.main_scrn()
def main_scrn(self):
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menu = tk.Menu(menubtn, tearoff=0)
for subject in ["math", "english", "physics", "chemistry"]:
var = tk.IntVar(self, value=0)
var.trace("w", self.func)
menu.add_checkbutton(label=subject, variable=var)
menubtn["menu"] = menu
menubtn.grid(row=1, column=0)
英文:
class SelectFrame(tk.Frame):
def __init__(self, master, user, *args, **kwargs) -> None:
super().__init__(master, *args, **kwargs)
self.user = user
self.main_scrn()
def main_scrn(self):
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menu = tk.Menu(menubtn, tearoff=0)
for subject in ["math", "english", "physics", "chemistry"]:
var = tk.IntVar(self, value=0)
var.trace("w", self.func)
menu.add_checkbutton(label=subject, variable=var)
menubtn["menu"] = menu
menubtn.grid(row=1, column=0)
When the checkbutton
is toggled, the variable trace function is not triggering. The class is inherited from a tk.Frame
答案1
得分: 1
因为这些变量是局部变量,将在退出函数后被垃圾回收。 您需要使用一个实例字典来存储这些变量:
```python
class SelectFrame(tk.Frame):
...
def main_scrn(self):
menubtn = tk.Menubutton(self, text="筛选", relief="raised")
menubtn.grid(row=1, column=0)
menu = tk.Menu(menubtn, tearoff=0)
menubtn["menu"] = menu
self.vars = {} # 一个实例字典来保存变量
for subject in ["数学", "英语", "物理", "化学"]:
var = tk.IntVar(self, value=0)
var.trace("w", self.func)
menu.add_checkbutton(label=subject, variable=var)
self.vars[subject] = var # 保存变量
# 示例 func() 显示所选科目
def func(self, *args):
print({subject:var.get() for subject, var in self.vars.items()})
实际上,您可以使用 add_checkbutton()
的 command
选项,而不是使用 .trace()
:
class SelectFrame(tk.Frame):
...
def main_scrn(self):
menubtn = tk.Menubutton(self, text="筛选", relief="raised")
menubtn.grid(row=1, column=0)
menu = tk.Menu(menubtn, tearoff=0)
menubtn["menu"] = menu
self.vars = {}
for subject in ["数学", "英语", "物理", "化学"]:
var = tk.IntVar(self, value=0)
menu.add_checkbutton(label=subject, variable=var, command=self.func)
self.vars[subject] = var
def func(self):
print({subject:var.get() for subject, var in self.vars.items()})
<details>
<summary>英文:</summary>
It is because those variables are local variables which will be garbage collected after exiting the function. You need to use an instance dictionary to store those variables:
```python
class SelectFrame(tk.Frame):
...
def main_scrn(self):
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menubtn.grid(row=1, column=0)
menu = tk.Menu(menubtn, tearoff=0)
menubtn["menu"] = menu
self.vars = {} # an instance dictionary to hold the variables
for subject in ["math", "english", "physics", "chemistry"]:
var = tk.IntVar(self, value=0)
var.trace("w", self.func)
menu.add_checkbutton(label=subject, variable=var)
self.vars[subject] = var # save the variable
# sample func() to show selected subjects
def func(self, *args):
print({subject:var.get() for subject, var in self.vars.items()})
Actually you can use command
option of add_checkbutton()
instead of using .trace()
:
class SelectFrame(tk.Frame):
...
def main_scrn(self):
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menubtn.grid(row=1, column=0)
menu = tk.Menu(menubtn, tearoff=0)
menubtn["menu"] = menu
self.vars = {}
for subject in ["math", "english", "physics", "chemistry"]:
var = tk.IntVar(self, value=0)
menu.add_checkbutton(label=subject, variable=var, command=self.func)
self.vars[subject] = var
def func(self):
print({subject:var.get() for subject, var in self.vars.items()})
答案2
得分: 1
问题的根本在于您正在使用局部变量来存储IntVar
的实例。Tkinter不会保留引用,因此这些变量在函数返回时会被垃圾回收器清理掉。
您可以通过将它们设为全局变量或对象的属性来进行验证。一个简单的方法是将它们保存到列表中,并将列表设为对象的属性。
在下面的示例中,我们将self.vars
定义为一个列表,并将每个IntVar
附加到列表中。这样做后,每当选择或取消选择项目时,您的函数都会被调用。
class SelectFrame(tk.Frame):
...
def main_scrn(self):
self.vars = []
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menu = tk.Menu(menubtn, tearoff=0)
for subject in ["math", "english", "physics", "chemistry"]:
var = tk.IntVar(self, value=0)
self.vars.append(var)
var.trace("w", self.func)
menu.add_checkbutton(label=subject, variable=var)
menubtn["menu"] = menu
menubtn.grid(row=1, column=0)
这解决了直接的问题,并允许在选择项目时每次调用函数。但是,由于您需要记住变量的顺序,因此很难知道选择了哪些值。解决该问题的一种方法是使用字典而不是列表。然后,您可以迭代字典以查看选择了哪些值。
下面的示例将self.vars
的定义移到__init__
函数中,并添加了func
函数,用于显示字典中的当前值。
class SelectFrame(tk.Frame):
def __init__(self, master, user, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.vars = {
"math": tk.IntVar(value=0),
"english": tk.IntVar(value=0),
"physics": tk.IntVar(value=0),
"chemistry": tk.IntVar(value=0),
}
self.user = user
self.main_scrn()
def func(self, varname, index, opname):
for key in self.vars.keys():
print(f"{key:10}: {self.vars[key].get()}")
def main_scrn(self):
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menu = tk.Menu(menubtn, tearoff=0)
for subject in self.vars.keys():
var = self.vars[subject]
var.trace_add("write", self.func)
menu.add_checkbutton(label=subject, variable=var)
menubtn["menu"] = menu
menubtn.grid(row=1, column=0)
英文:
The root of the problem is that you're using a local variable for the instances of IntVar
. Tkinter doesn't hold on to a reference, so those variables are cleaned up by the garbage collector when the function returns.
You can verify this by making them global, or an attribute on the object. A simple way to do that is to save them to a list, and make the list an attribute.
In the following example we define self.vars
as a list, and append each IntVar
to the list. When you do this, your function will be called each time an item is selected or deselected.
class SelectFrame(tk.Frame):
...
def main_scrn(self):
self.vars = []
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menu = tk.Menu(menubtn, tearoff=0)
for subject in ["math", "english", "physics", "chemistry"]:
var = tk.IntVar(self, value=0)
self.vars.append(var)
var.trace("w", self.func)
menu.add_checkbutton(label=subject, variable=var)
menubtn["menu"] = menu
menubtn.grid(row=1, column=0)
That solves the immediate problem and allows the function to be called each time you select an item, but it becomes difficult to know which values were selected since you have to remember the order of the variables. A way around that problem is to use a dictionary rather than a list. You can then iterate over the dictionary to see which values are selected.
The following example moves the definition of self.vars
to the __init__
function, and adds func
which displays the current values from that dictionary.
class SelectFrame(tk.Frame):
def __init__(self, master, user, *args, **kwargs) -> None:
super().__init__(master, *args, **kwargs)
self.vars = {
"math": tk.IntVar(value=0),
"english": tk.IntVar(value=0),
"physics": tk.IntVar(value=0),
"chemistry": tk.IntVar(value=0),
}
self.user = user
self.main_scrn()
def func(self, varname, index, opname):
for key in self.vars.keys():
print(f"{key:10}: {self.vars[key].get()}")
def main_scrn(self):
menubtn = tk.Menubutton(self, text="Filter", relief="raised")
menu = tk.Menu(menubtn, tearoff=0)
for subject in self.vars.keys():
var = self.vars[subject]
var.trace_add("write", self.func)
menu.add_checkbutton(label=subject, variable=var)
menubtn["menu"] = menu
menubtn.grid(row=1, column=0)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论