Welcome, guest | Sign In | My Account | Store | Cart
# Author: Miguel Martinez Lopez
#
# Version: 0.3
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))


try:
    from Tkinter import PhotoImage, Frame, Label, Widget
    from Tkconstants import *
except ImportError:
    from tkinter import PhotoImage, Frame, Label, Widget
    from tkinter.constants import *

class CollapsibleFrame(Frame):
    def __init__(self, master, text=None, borderwidth=2, width=0, height=16, interior_padx=0, interior_pady=8, background=None, caption_separation=4, caption_font=None, caption_builder=None, icon_padx=3):
        Frame.__init__(self, master)
        if background is None:
            background = self.cget("background")

        self.configure(background=background)

        self._is_opened = False

        self._interior_padx = interior_padx
        self._interior_pady = interior_pady

        self._iconOpen = PhotoImage(data="R0lGODlhEAAQAKIAAP///9TQyICAgEBAQAAAAAAAAAAAAAAAACwAAAAAEAAQAAADNhi63BMgyinFAy0HC3Xj2EJoIEOM32WeaSeeqFK+say+2azUi+5ttx/QJeQIjshkcsBsOp/MBAA7")
        self._iconClose = PhotoImage(data="R0lGODlhEAAQAKIAAP///9TQyICAgEBAQAAAAAAAAAAAAAAAACwAAAAAEAAQAAADMxi63BMgyinFAy0HC3XjmLeA4ngpRKoSZoeuDLmo38mwtVvKu93rIo5gSCwWB8ikcolMAAA7")
        
        height_of_icon = max(self._iconOpen.height(), self._iconClose.height())
        width_of_icon = max(self._iconOpen.width(), self._iconClose.width())
        
        containerFrame_pady = (height_of_icon//2) +1

        self._height = height
        self._width = width

        self._containerFrame = Frame(self, borderwidth=borderwidth, width=width, height=height, relief=RIDGE, background=background)
        self._containerFrame.pack(expand=True, fill=X, pady=(containerFrame_pady,0))
        
        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=icon_padx, y=-(height_of_icon//2), anchor=N+W, bordermode="ignore")
        self._collapseButton.bind("<Button-1>", lambda event: self.toggle())

        if caption_builder is None:
            self._captionLabel = Label(self, anchor=W, borderwidth=1, text=text)
            if caption_font is not None:
                self._captionLabel.configure(font=caption_font)
        else:
            self._captionLabel = caption_builder(self)
            
            if not isinstance(self._captionLabel, Widget):
                raise Exception("'caption_builder' doesn't return a tkinter widget")

        self.after(0, lambda: self._place_caption(caption_separation, icon_padx, width_of_icon))

    def update_width(self, width=None):
        # Update could be devil
        # http://wiki.tcl.tk/1255
        self.after(0, lambda width=width:self._update_width(width))

    def _place_caption(self, caption_separation, icon_padx, width_of_icon):
        self.update()
        x = caption_separation + icon_padx + 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):
        self.update()
        if width is None:
            width=self.interior.winfo_reqwidth()

        if isinstance(self._interior_pady, (list, tuple)):
            width += self._interior_pady[0] + self._interior_pady[1]
        else:
            width += 2*self._interior_pady
            
        width = max(self._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._interior_padx, pady =self._interior_pady)

        self._is_opened = True

    def close(self):
        self.interior.pack_forget()
        self._containerFrame.configure(height=self._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", interior_padx=6)
    cf1.pack()
    
    for i in range(3):
        Button(cf1.interior, text="button %s"%i).pack(side=LEFT)
    
    cf1.update_width()
    root.mainloop()

Diff to Previous Revision

--- revision 4 2017-03-04 00:39:12
+++ revision 5 2017-03-05 18:07:57
@@ -1,19 +1,19 @@
 # Author: Miguel Martinez Lopez
 #
-# Version: 0.1
+# Version: 0.3
 # Uncomment the next line to see my email
 # print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))
 
 
 try:
-    from Tkinter import PhotoImage, Frame, Label
+    from Tkinter import PhotoImage, Frame, Label, Widget
     from Tkconstants import *
 except ImportError:
-    from tkinter import PhotoImage, Frame, Label
+    from tkinter import PhotoImage, Frame, Label, Widget
     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):
+    def __init__(self, master, text=None, borderwidth=2, width=0, height=16, interior_padx=0, interior_pady=8, background=None, caption_separation=4, caption_font=None, caption_builder=None, icon_padx=3):
         Frame.__init__(self, master)
         if background is None:
             background = self.cget("background")
@@ -22,53 +22,64 @@
 
         self._is_opened = False
 
-        self._ipadx=ipadx
-        self._ipady=ipady
+        self._interior_padx = interior_padx
+        self._interior_pady = interior_pady
 
         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())
+        height_of_icon = max(self._iconOpen.height(), self._iconClose.height())
+        width_of_icon = max(self._iconOpen.width(), self._iconClose.width())
         
-        containerFrame_pady = (max_height_of_icon//2) +1
+        containerFrame_pady = (height_of_icon//2) +1
 
-        self._inner_height = inner_height
-        self._inner_width = inner_width
+        self._height = height
+        self._width = 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._containerFrame = Frame(self, borderwidth=borderwidth, width=width, height=height, relief=RIDGE, background=background)
+        self._containerFrame.pack(expand=True, fill=X, pady=(containerFrame_pady,0))
         
         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.place(in_= self._containerFrame, x=icon_padx, y=-(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)
+        if caption_builder is None:
+            self._captionLabel = Label(self, anchor=W, borderwidth=1, text=text)
+            if caption_font is not None:
+                self._captionLabel.configure(font=caption_font)
+        else:
+            self._captionLabel = caption_builder(self)
+            
+            if not isinstance(self._captionLabel, Widget):
+                raise Exception("'caption_builder' doesn't return a tkinter widget")
 
-        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")
+        self.after(0, lambda: self._place_caption(caption_separation, icon_padx, width_of_icon))
 
     def update_width(self, width=None):
         # Update could be devil
         # http://wiki.tcl.tk/1255
         self.after(0, lambda width=width:self._update_width(width))
-    
+
+    def _place_caption(self, caption_separation, icon_padx, width_of_icon):
+        self.update()
+        x = caption_separation + icon_padx + 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):
         self.update()
         if width is None:
             width=self.interior.winfo_reqwidth()
 
-        if isinstance(self._ipady, (list, tuple)):
-            width += self._ipady[0] + self._ipady[1]
+        if isinstance(self._interior_pady, (list, tuple)):
+            width += self._interior_pady[0] + self._interior_pady[1]
         else:
-            width += 2*self._ipady
+            width += 2*self._interior_pady
             
-        width = max(self._inner_width, width)
+        width = max(self._width, width)
 
         self._containerFrame.configure(width=width)
         
@@ -76,13 +87,13 @@
         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.interior.pack(expand=True, fill=X, padx=self._interior_padx, pady =self._interior_pady)
 
         self._is_opened = True
 
     def close(self):
         self.interior.pack_forget()
-        self._containerFrame.configure(height=self._inner_height)
+        self._containerFrame.configure(height=self._height)
         self._collapseButton.configure(image=self._iconOpen)
 
         self._is_opened = False
@@ -102,7 +113,7 @@
     root = Tk()
     root.wm_geometry("400x300+0+0")
     
-    cf1 = CollapsibleFrame(root, text ="Frame1", ipadx=6)
+    cf1 = CollapsibleFrame(root, text ="Frame1", interior_padx=6)
     cf1.pack()
     
     for i in range(3):

History