Expandable and contracting frame in Tkinter
Asked Answered
I

3

18

Does anyone know if there is already a widget/class to handle expanding/contracting a frame based on a toggled button (checkbutton) in tkinter/ttk?

This question stems from my attempt to clean up a cluttered gui that has lots of options categorized by specific actions. I would like something along the lines of:

enter image description here
example found on google

However instead of just text, allow for buttons, entries, any of tkinter's widgets. If this doesn't already exist, would it be possible/useful to create a class that inherits the tkinter Frame:

import tkinter as tk
import ttk

class toggledFrame(tk.Frame):
    def __init__(self):
        self.show=tk.IntVar()
        self.show.set(0)
        self.toggleButton=tk.Checkbutton(self, command=self.toggle, variable=self.show)
        self.toggleButton.pack()
        self.subFrame=tk.Frame(self)

    def toggle(self):
        if bool(self.show.get()):
            self.subFrame.pack()
        else:
            self.subFrame.forget()

Note: this code is untested, just presenting concept

Ivied answered 30/10, 2012 at 14:34 Comment(0)
I
25

I am actually surprised at how close I was to getting functioning code. I decided to work on it some more and have develop a simple little class to perform exactly what I wanted (comments and suggestions on the code are welcome):

import tkinter as tk
from tkinter import ttk 


class ToggledFrame(tk.Frame):

    def __init__(self, parent, text="", *args, **options):
        tk.Frame.__init__(self, parent, *args, **options)

        self.show = tk.IntVar()
        self.show.set(0)

        self.title_frame = ttk.Frame(self)
        self.title_frame.pack(fill="x", expand=1)

        ttk.Label(self.title_frame, text=text).pack(side="left", fill="x", expand=1)

        self.toggle_button = ttk.Checkbutton(self.title_frame, width=2, text='+', command=self.toggle,
                                            variable=self.show, style='Toolbutton')
        self.toggle_button.pack(side="left")

        self.sub_frame = tk.Frame(self, relief="sunken", borderwidth=1)

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


if __name__ == "__main__":
    root = tk.Tk()

    t = ToggledFrame(root, text='Rotate', relief="raised", borderwidth=1)
    t.pack(fill="x", expand=1, pady=2, padx=2, anchor="n")

    ttk.Label(t.sub_frame, text='Rotation [deg]:').pack(side="left", fill="x", expand=1)
    ttk.Entry(t.sub_frame).pack(side="left")

    t2 = ToggledFrame(root, text='Resize', relief="raised", borderwidth=1)
    t2.pack(fill="x", expand=1, pady=2, padx=2, anchor="n")

    for i in range(10):
        ttk.Label(t2.sub_frame, text='Test' + str(i)).pack()

    t3 = ToggledFrame(root, text='Fooo', relief="raised", borderwidth=1)
    t3.pack(fill="x", expand=1, pady=2, padx=2, anchor="n")

    for i in range(10):
        ttk.Label(t3.sub_frame, text='Bar' + str(i)).pack()

    root.mainloop()

This code produces:
enter image description here

Ivied answered 1/11, 2012 at 1:13 Comment(3)
Does anyone know if there is someplace that is collecting these custom widgets for other people to use?Ivied
@Gonzo that link is dead. Do you know of any mirrors?Weapon
I have a collection of widgets: pip install tk_toolsIzanagi
J
0

To my knowledge, Tkinter/ttk does no provide such widgets. You might mimic your example (expand/collapse label list) with a tkinter.ttk.Treeview.

It is perfectly acceptable1 to develop your own widgets, and your code seems a right start.

Jehias answered 30/10, 2012 at 16:2 Comment(1)
Thanks. I have used treeviews before but it is not what I am looking for.Ivied
I
0

Basically the same as the accepted answer, but this example took care of having the subclass taking in the keyword arguments fg, font and bg and made some different styling choices, while showing how it could be used.

The basic idea is to subclass tk.Frame having two additional frames in it. One, in this example named frame, to enclose the content self.label and self.button. This is necessary to avoid the pack manager of tkinter to place the widgets in an order we don't like. Two, in this example named self.extension a frame that will be packed and unpacked by toggling the button.

root = tk.Tk()

class ModernExpandableFrame(tk.Frame):

    def __init__(self, master, **kwargs):
        bg = kwargs.pop('bg', None)
        fg = kwargs.pop('fg', None)
        tx = kwargs.pop('text', '')
        ft = kwargs.pop('font', '10')
        super().__init__(master, bg=bg, **kwargs)

        frame = tk.Frame(self,relief=tk.SOLID, bd=1)
        self.label = tk.Label(
            frame, bg=bg, fg=fg, text=tx)
        self.button = tk.Button(
            frame, bg=bg, fg=fg, text='↲', relief=tk.FLAT,
            font=ft, command=self.toggle)
        frame.pack(side=tk.TOP,expand=True,fill=tk.BOTH)
        self.label.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.button.pack(side=tk.RIGHT,fill=tk.Y)
        self.extension = tk.Frame(self, bg=bg, **kwargs)
        return

    def toggle(self):
        if self.extension not in self.pack_slaves():
            self.extension.pack(side=tk.BOTTOM,expand=True,fill=tk.BOTH)
        else:
            self.extension.pack_forget()
    pass

ex_frame = ModernExpandableFrame(root,
                              bg='grey', fg='white',
                              text='Super Frame')
ex_frame.pack(expand=True, fill=tk.BOTH)
extension = ex_frame.extension
for i in range (10):
    tk.Button(extension, text=i, command=lambda i=i: print(i)).pack()

root.mainloop()
Instil answered 27/7, 2024 at 16:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.