try: from Tkinter import PhotoImage, Frame, Label from Tkconstants import * except ImportError: from tkinter import PhotoImage, Frame, Label from tkinter.constants import * class CollapsibleFrame(Frame): def __init__(self, master, text, borderwidth=2, inner_width=0, inner_height=16, ipadx=0, ipady=8, background=None, caption_separation=4, caption_font=None, button_padx=3): Frame.__init__(self, master) if background is None: background = self.cget("background") self.configure(background=background) self._is_opened = False self._ipadx=ipadx self._ipady=ipady self._iconOpen = PhotoImage(data="R0lGODlhEAAQAKIAAP///9TQyICAgEBAQAAAAAAAAAAAAAAAACwAAAAAEAAQAAADNhi63BMgyinFAy0HC3Xj2EJoIEOM32WeaSeeqFK+say+2azUi+5ttx/QJeQIjshkcsBsOp/MBAA7") self._iconClose = PhotoImage(data="R0lGODlhEAAQAKIAAP///9TQyICAgEBAQAAAAAAAAAAAAAAAACwAAAAAEAAQAAADMxi63BMgyinFAy0HC3XjmLeA4ngpRKoSZoeuDLmo38mwtVvKu93rIo5gSCwWB8ikcolMAAA7") max_height_of_icon = max(self._iconOpen.height(), self._iconClose.height()) max_width_of_icon = max(self._iconOpen.width(), self._iconClose.width()) containerFrame_pady = (max_height_of_icon//2) +1 self._inner_height = inner_height self._inner_width = inner_width self._containerFrame = Frame(self, borderwidth=borderwidth, width=inner_width, height=inner_height, relief=RIDGE, background=background) self._containerFrame.pack(expand=True, fill=X, pady=containerFrame_pady) self.interior = Frame(self._containerFrame, background=background) self._collapseButton = Label(self, borderwidth=0, image=self._iconOpen, relief=RAISED) self._collapseButton.place(in_= self._containerFrame, x=button_padx, y=-(max_height_of_icon//2), anchor=N+W, bordermode="ignore") self._collapseButton.bind("<Button-1>", lambda event: self.toggle()) self._captionLabel = Label(self, anchor=W, borderwidth=1, text=text) if caption_font is not None: self._captionLabel.configure(font=caption_font) x = caption_separation + button_padx + max_width_of_icon y = -(self._captionLabel.winfo_reqheight()//2) self._captionLabel.place(in_= self._containerFrame, x=x, y=y, anchor=N+W, bordermode="ignore") def update_width(self, width=None): # Update could be devil self.after(0, lambda width=width:self._update_width(width)) def _update_width(self, width): self.update() if width is None: width=self.interior.winfo_reqwidth() if isinstance(self._ipady, (list, tuple)): width += self._ipady[0] + self._ipady[1] else: width += 2*self._ipady width = max(self._inner_width, width) self._containerFrame.configure(width=width) def open(self): self._collapseButton.configure(image=self._iconClose) self._containerFrame.configure(height=self.interior.winfo_reqheight()) self.interior.pack(expand=True, fill=X, padx=self._ipadx, pady =self._ipady) self._is_opened = True def close(self): self.interior.pack_forget() self._containerFrame.configure(height=self._inner_height) self._collapseButton.configure(image=self._iconOpen) self._is_opened = False def toggle(self): if self._is_opened: self.close() else: self.open() if __name__ == "__main__": try: from Tkinter import Tk, Button except ImportError: from tkinter import Tk, Button root = Tk() root.wm_geometry("400x300+0+0") cf1 = CollapsibleFrame(root, text ="Frame1", ipadx=6) cf1.pack() for i in range(5): Button(cf1.interior, text="button %s"%i).pack() cf1.update_width() root.mainloop()