【问题标题】:Tkinter Dynamic Frames that are Expandable and Collapsible可扩展和可折叠的 Tkinter 动态框架
【发布时间】:2018-01-24 16:39:45
【问题描述】:

我有以下基于此问题的代码:Expandable and contracting frame in Tkinter

from tkinter import *
from tkinter import ttk

class Example:
    def __init__(self, root):
        list_1 = ['item_1', 'item_2']
        list_2 = ['a', 'b', 'c']

        for item in list_1 :
            frame = Frame(root, width=500, height=22)
            frame.pack(side=TOP, anchor=W)
            frame.propagate(0)

            Label(frame, text=item).pack(side=LEFT, anchor=W)
            entry = Entry(frame, width=11)
            entry.pack(side=RIGHT, pady=2, anchor=E)

            var = IntVar()
            var.set(0)

            sub_frame = Frame(root, relief="sunken", width=400, height=22, borderwidth=1)

            toggle_btn = ttk.Checkbutton(frame, width=2, text='+', command=lambda: self.toggle(var, toggle_btn, sub_frame),
                                     variable=var, style='Toolbutton')
            toggle_btn.pack(side=LEFT, anchor=E)

            for item in list_2:
                Label(sub_frame, text=item).pack(side=TOP)

    def toggle(self, show, toggle_button, sub_frame):
        if bool(show.get()):
            sub_frame.pack(fill="x", expand=1)
            sub_frame.propagate(1)
            toggle_button.configure(text='-')
        else:
            sub_frame.forget()
            toggle_button.configure(text='+')

def main():
    root = Tk()
    open = Example(root)
    root.mainloop()

if __name__ == '__main__':
    main()

我想在这里从 list_1 创建两个可扩展框架,并用 list_2 中的标签填充每个框架。这两个框架应该可以展开和折叠以显示和隐藏 list_2 中的项目。

第二帧正常运行,但第一帧不能正常运行,因为它被第二帧覆盖了。

所以代码最初给你的是这个:

如果您单击 item_1 旁边的“+”,则不会发生任何事情。

如果您点击 item_2 旁边的“+”,就会发生这种情况。

通过单击 item_1 旁边的“+”,我希望 sub_frame 显示在 item_1 下方(在 item_1 和 item_2 之间),就像单击 item_2 旁边的“+”显示它下方的 sub_frame 一样。

关于如何让两个框架都能正常工作的任何帮助?我已经阅读了有关将创建的帧存储在列表中的信息,但是我无法让它为我工作。

非常感谢

【问题讨论】:

  • 我已经更新了代码,已经完成了。
  • 谢谢,但您遇到的问题仍然不是minimal reproducible example。您可以进一步缩小范围。
  • 谢谢,我已经更新了代码

标签: python tkinter


【解决方案1】:

问题的根源在于 lambda 绑定到变量,但您希望它在创建 lambda 时绑定到变量的 。这是使用 lambda 时非常非常常见的错误。

这个网站上有很多关于这个问题的问题和答案。以下是一些:


虽然可以通过修改lambda 的使用来修复您编写的代码,但我建议更好的解决方案是创建一个代表一个可折叠框架的新类。如果这个新类继承自 Frame,您可以像使用任何其他小部件一样使用它。

这样您就可以消除使用lambda 的需要,并且可以更轻松地将有关可折叠框架的所有内容封装在一个对象中。

例如:

class CustomFrame(tk.Frame):
    def __init__(self, parent, label, items):
        tk.Frame.__init__(self, parent)
        header = tk.Frame(self)
        self.sub_frame = tk.Frame(self, relief="sunken",
                                  width=400, height=22, borderwidth=1)
        header.pack(side="top", fill="x")
        self.sub_frame.pack(side="top", fill="both", expand=True)

        self.var = tk.IntVar(value=0)
        self.label = tk.Label(header, text=label)
        self.toggle_btn = ttk.Checkbutton(header, width=2, text="+",
                                          variable=self.var, style='Toolbutton',
                                          command=self.toggle)
        self.entry = tk.Entry(header, width=11)

        self.label.pack(side="left")
        self.toggle_btn.pack(side="left")
        self.entry.pack(side="right", pady=2, anchor="e")
        self.sub_frame.pack(side="top", fill="both", expand=True)

        for item in items:
            tk.Label(self.sub_frame, text=item).pack(side="top")

        # this sets the initial state
        self.toggle(False)

    def toggle(self, show=None):
        show = self.var.get() if show is None else show
        if show:
            self.sub_frame.pack(side="top", fill="x", expand=True)
            self.toggle_btn.configure(text='-')
        else:
            self.sub_frame.forget()
            self.toggle_btn.configure(text='+')

这样,您的Example 类变得微不足道,您可以创建任意数量的帧:

class Example:
    def __init__(self, root):
        list_1 = ['item_1', 'item_2']
        list_2 = ['a', 'b', 'c']

        for item in list_1:
            frame = CustomFrame(root, item, list_2)
            frame.pack(side="top", fill="x")

注意:上面的代码假设tkinterttk是这样导入的,和你原来的代码略有不同:

import tkinter as tk
from tkinter import ttk

【讨论】:

    【解决方案2】:

    替换:

    toggle_btn = ttk.Checkbutton(frame, width=2, text='+', command=lambda: self.toggle(var, toggle_btn, sub_frame),
                                         variable=var, style='Toolbutton')
    

    与:

    toggle_btn = ttk.Checkbutton(frame, width=2, text='+',
                                         variable=var, style='Toolbutton')
    toggle_btn['command'] = lambda a1=var, a2=toggle_btn, a3=sub_frame: self.toggle(a1, a2, a3)
    

    why 上查看更多信息。


    上述问题的Minimal, Complete, and Verifiable example 最长应该是:

    from tkinter import *
    
    class Example:
        def __init__(self, root):
            list_1 = ['item_1', 'item_2']
            list_2 = ['a', 'b', 'c']
    
            for item in list_1:
                frame = Frame(root)
                frame.pack()
    
                var = IntVar()
                sub_frame = Frame(root)
                for item in list_2:
                    Label(sub_frame, text=item).pack()
    
                toggle_btn = Checkbutton(frame,
                    command=lambda: self.toggle(var, toggle_btn, sub_frame),
                    variable=var)
                toggle_btn.pack()
    
        def toggle(self, show, toggle_button, sub_frame):
            if bool(show.get()):
                sub_frame.pack()
            else:
                sub_frame.forget()
    
    def main():
        root = Tk()
        open = Example(root)
        root.mainloop()
    
    if __name__ == '__main__':
        main()
    

    如果您不想覆盖,只需使用可迭代类型,例如 listdict,而不是多个对象(例如框架和标签)的单个变量。下面的代码生成一个frames 的字典,然后为每个frames 生成一个labels 的字典:

    import tkinter as tk
    
    
    if __name__ == '__main__':
        list_1 = ['item_1', 'item_2']
        list_2 = ['a', 'b', 'c']
    
        root = tk.Tk()
    
        frames = dict()
    
        for item in list_1:
            frames[item] = tk.Frame(root)
            frames[item].pack()
            frames[item].labels = dict()
            for text in list_2:
                frames[item].labels[text] = tk.Label(frames[item], text=text)
                frames[item].labels[text].pack()
    
        root.mainloop()
    

    【讨论】:

    • 这是怎么回事?即使是这样,我也没有时间“解决”这个问题。我花了大部分时间分配给这个问题来创建它的minimal reproducible example
    • 再次感谢您的回答。更改检查按钮主要是因为现在可以勾选 sub_frame 来切换它们的外观。剩下的问题是,无论何时显示,两个 sub_frames 都被放置在整个框架的底部。通过勾选第一个复选框,我正在寻找 sub_frame 出现在第一个复选框正下方的框架中(即两个复选框之间)。有没有办法做到这一点?我试过使用字典,但我无法弄清楚。非常感谢
    • 最简单的解决方案是将它们分别分配给它们自己的子帧。如果您想使用 OOP,请查看 Bryan's answer,这样可以提供更大的灵活性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-09
    • 1970-01-01
    • 2013-06-05
    • 2014-03-26
    • 2021-06-13
    相关资源
    最近更新 更多