可扩展和可折叠的Tkinter动态帧

时间:2018-01-24 16:39:45

标签: python tkinter

我在下面的代码基于这个问题: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中的项目。

第二帧功能正常,但第一帧不起作用,因为它被第二帧覆盖。

所以代码最初给你这个:

enter image description here

如果您点击' +'在item_1旁边,没有任何反应。

如果您点击' +'在item_2旁边,会发生这种情况。

enter image description here

点击' +'在item_1旁边,我希望sub_frame显示在item_1下面(在item_1和item_2之间),就像点击' +' item_2旁边显示了它下面的子框架。

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

非常感谢

2 个答案:

答案 0 :(得分:1)

替换:

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的字典,然后为每个labels生成一个frames的字典:

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

答案 1 :(得分: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