Tkinter 在 for 循环中为 IntVar 添加跟踪。

huangapple go评论59阅读模式
英文:

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=&quot;Filter&quot;, relief=&quot;raised&quot;)
        menubtn.grid(row=1, column=0)

        menu = tk.Menu(menubtn, tearoff=0)
        menubtn[&quot;menu&quot;] = menu

        self.vars = {}  # an instance dictionary to hold the variables
        for subject in [&quot;math&quot;, &quot;english&quot;, &quot;physics&quot;, &quot;chemistry&quot;]:
            var = tk.IntVar(self, value=0)
            var.trace(&quot;w&quot;, 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=&quot;Filter&quot;, relief=&quot;raised&quot;)
        menubtn.grid(row=1, column=0)

        menu = tk.Menu(menubtn, tearoff=0)
        menubtn[&quot;menu&quot;] = menu

        self.vars = {}
        for subject in [&quot;math&quot;, &quot;english&quot;, &quot;physics&quot;, &quot;chemistry&quot;]:
            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=&quot;Filter&quot;, relief=&quot;raised&quot;)

        menu = tk.Menu(menubtn, tearoff=0)

        for subject in [&quot;math&quot;, &quot;english&quot;, &quot;physics&quot;, &quot;chemistry&quot;]:
            var = tk.IntVar(self, value=0)
            self.vars.append(var)
            var.trace(&quot;w&quot;, self.func)
            menu.add_checkbutton(label=subject, variable=var)

        menubtn[&quot;menu&quot;] = 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) -&gt; None:
        super().__init__(master, *args, **kwargs)

        self.vars = {
            &quot;math&quot;: tk.IntVar(value=0),
            &quot;english&quot;: tk.IntVar(value=0),
            &quot;physics&quot;: tk.IntVar(value=0),
            &quot;chemistry&quot;: tk.IntVar(value=0),
        }

        self.user = user
        self.main_scrn()


    def func(self, varname, index, opname):
        for key in self.vars.keys():
            print(f&quot;{key:10}: {self.vars[key].get()}&quot;)

    def main_scrn(self):

        menubtn = tk.Menubutton(self, text=&quot;Filter&quot;, relief=&quot;raised&quot;)

        menu = tk.Menu(menubtn, tearoff=0)

        for subject in self.vars.keys():
            var = self.vars[subject]
            var.trace_add(&quot;write&quot;, self.func)
            menu.add_checkbutton(label=subject, variable=var)

        menubtn[&quot;menu&quot;] = menu

        menubtn.grid(row=1, column=0)

huangapple
  • 本文由 发表于 2023年6月12日 20:45:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76456802.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定