# 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):