from Tkinter import *
from collections import OrderedDict, defaultdict
import itertools
class PannedGrid(Frame):
def __init__(self, master, orient=HORIZONTAL, minsize=20, columnWidths = {}, rowHeights={}, fixedPanes = []):
Frame.__init__(self,master)
self.clear_table()
if orient in (HORIZONTAL, VERTICAL):
self.orient = orient
else:
raise Exception("orient must be 'horizontal' or 'vertical, not '%s'." % orient)
if type(minsize) is int:
self.minsize = minsize
else:
raise Exception("Minsize must be an integer.")
self.fixedPanes = set(fixedPanes)
self.__packSideBetweenPanes = LEFT if self.orient == HORIZONTAL else TOP
self.__packSideInsidePane = TOP if self.orient == HORIZONTAL else LEFT
self.__cellFill = X if self.orient == HORIZONTAL else Y
self.userDefinedWidths = columnWidths
self.userDefinedHeights = rowHeights
self.updated = False
self.areDimensionsCalculated = False
def clear_table(self):
self.__gridOfWidgets = {}
self.__rows = defaultdict(set)
self.__columns = defaultdict(set)
self.__panes = {}
self.rowHeightList = {}
self.columnWidthList = {}
def clear_definedWidths(self):
self.userDefinedWidths = {}
def clear_definedHeights(self):
self.userDefinedHeights = {}
def rowIndices(self):
return self.__rows.keys()
def columnIndices(self):
return self.__column.keys()
def numberOfRows(self):
return len(self.__rows)
def numberOfColumns(self):
return len(self.__columns)
def update_cell(self, coordinates, widget):
if not self.updated:
raise Exception("First you must to update the pannedgrid.")
if coordinates in self.__gridOfWidgets:
master = self.__gridOfWidgets[coordinates].pack_info()['in']
self.__gridOfWidgets[coordinates].destroy()
self.__gridOfWidgets[coordinates] = widget
widget.pack(in_=master, expand=YES, fill=BOTH)
else:
raise Exception("There is no widget with coordiantes: %s." % coordinates)
def update(self):
if not self.__gridOfWidgets: return
self.panedWindows = []
if not self.areDimensionsCalculated:
self.calculate_list_of_cell_widths_and_heights()
listOfCellCoordinates = list(itertools.product(self.__rows, self.__columns))
if self.orient == HORIZONTAL:
listOfCellCoordinates.sort(key = lambda item: item[1])
else:
listOfCellCoordinates.sort()
pane_index = self.__columns[0] if self.orient == HORIZONTAL else self.__rows[0]
position_of_outer_index = 1 if self.orient == HORIZONTAL else 0
newPanedWindow = True
for cell_coordinates in listOfCellCoordinates:
outer_index = cell_coordinates[position_of_outer_index]
if pane_index != outer_index:
# Creating a new pane
pane_index = outer_index
if outer_index in self.fixedPanes:
frameOfPane = Frame(self)
frameOfPane.pack(side=self.__packSideBetweenPanes)
newPanedWindow = True
else:
if newPanedWindow:
paneMaster = PanedWindow(self,orient=self.orient )
paneMaster.pack(side=self.__packSideBetweenPanes)
self.panedWindows.append(paneMaster)
newPanedWindow = False
frameOfPane = Frame(paneMaster)
frameOfPane.pack()
paneMaster.add(frameOfPane)
paneMaster.paneconfigure(frameOfPane, minsize=self.minsize)
sizeOfPane = self.columnWidthList[outer_index] if self.orient == HORIZONTAL else self.rowHeightList[outer_index]
if self.orient == HORIZONTAL:
frameOfPane.configure(width=sizeOfPane, height=self.tableHeight)
else:
frameOfPane.configure(width=self.tableWidth, height=sizeOfPane)
frameOfPane.pack_propagate(False)
self.__panes[pane_index] = frameOfPane
cellFrame = Frame(frameOfPane, bd=1, relief=SUNKEN)
cellFrame.pack(side=self.__packSideInsidePane, expand=YES, fill=self.__cellFill)
cellFrame.pack_propagate(False)
widget = self.__gridOfWidgets[cell_coordinates]
if widget:
widget.lift()
widget.pack(in_=cellFrame, expand=YES, fill=BOTH)
if self.orient == HORIZONTAL:
cellFrame.configure(
height=self.rowHeightList[ cell_coordinates[0] ],
)
else:
cellFrame.configure(
width=self.columnWidthList[ cell_coordinates[1] ]
)
self.updated = True
def calculate_list_of_cell_widths_and_heights(self):
self.rowHeightList = self.userDefinedHeights.copy()
self.columnWidthList = self.userDefinedWidths.copy()
for coordinate, widget in self.__gridOfWidgets.items():
i , j = coordinate
if i not in self.userDefinedHeights:
self.rowHeightList[i] = max(self.rowHeightList.get(i,0),widget.winfo_reqheight())
if j not in self.userDefinedWidths:
self.columnWidthList[j] = max(self.columnWidthList.get(j,0),widget.winfo_reqwidth())
self.tableHeight = sum(self.rowHeightList.values())
self.tableWidth = sum(self.columnWidthList.values())
self.areDimensionsCalculated = True
def rowconfigure(self,row, height=None, fixed = None):
if row not in self.__rows:
raise Exception("Row %d doesn't exists." % column)
self.updated = False
if height is not None:
self.areDimensionsCalculated = False
if type(height) is not int: raise Exception("The height must be an integer:%s"%size)
self.userDefinedHeights[row] = height
if fixed is not None:
if self.orient == HORIZONTAL:
raise Exception("You can't set fixed property on a row because 'orient' is VERTICAL.")
self.fixPane(index, fixed)
def columnconfigure(self,column, width=None, fixed = None):
if column not in self.__columns:
raise Exception("Column %d doesn't exists." % column)
self.updated = False
if width is not None:
self.areDimensionsCalculated = False
if type(width) is not int: raise Exception("The width must be an integer:%s"%size)
self.userDefinedWidths[column] = width
if fixed is not None:
if self.orient == VERTICAL:
raise Exception("You can't set fixed property on a column because 'orient' is HORIZONTAL.")
self.fixPane(index, fixed)
def fixPane(index, fixed=True):
if fixed == True:
self.fixedPanes.add(index)
elif fixed == False:
self.fixedPanes.discard(index)
else:
raise Exception("fixed must be 'True' or 'False'.")
def fixAllPanes():
fixedPanes_list = self.__columns.keys() if self.orient == HORIZONTAL else self.__rows.keys()
self.fixedPanes = set(fixedPanes_list)
def realeaseAllPanes():
self.fixedPanes = set()
def __getitem__(self, coordinates):
return self.__gridOfWidgets.get(coordinates)
def __setitem__(self,coordinates, widget):
self.updated = False
self.areDimensionsCalculated = False
self.__gridOfWidgets[coordinates] = widget
i,j = coordinates
self.__rows[i].add(j)
self.__columns[j].add(i)
def __delitem__(self, coordinates ):
self.updated = False
self.areDimensionsCalculated = False
del self.__gridOfWidgets[coordinates]
i,j = coordinates
self.__rows[i].discard(j)
if not self.__rows[i]: del self.__rows[i]
self.__columns[j].discard(i)
if not self.__columns[j]: del self.__columns[j]
def grid(self, *args, **kargs):
if not self.updated: self.update()
Frame.grid(self,*args, **kargs)
def pack(self, *args, **kargs):
if not self.updated: self.update()
Frame.pack(self,*args, **kargs)
def test():
import random
root = Tk()
def table_of_random_widgets(master, numRows,numColumns, **kwargs):
import ttk
# Falta pensar un nombre para estos atributos: columnWidthList = None, rowHeightList = None
demo = PannedGrid(master, **kwargs)
tkinterWidgets = (
(Label, {'text':'This is a label'}),
(Label, {'text':'This is another label', 'bg':'yellow'}),
(Checkbutton,{}),
(Button, {'text':'Click me'}),
(ttk.Combobox, {'values':('item1', 'item2','item3','item4')})
)
for i in range(numRows):
for j in range(numColumns):
widgetClass, args = random.choice(tkinterWidgets)
widget = widgetClass(demo,**args)
demo[i,j] = widget
return demo
Label(root, text="orient=VERTICAL, minsize = 20, fixed rows = [2,5]").pack(anchor=NW, pady=6, padx='2m')
table = table_of_random_widgets(root, numRows=6,numColumns=5, orient=VERTICAL, minsize=20, fixedPanes=[2,5])
table.pack(anchor=NW,pady=2, padx='2m')
emptySpace = Frame(root, height =20)
emptySpace.pack()
Label(root, text='''orient=HORIZONTAL, minsize = 30, fixed columns = [2,5], columnWidths={0:200, 2: 150, 3:250}, rowHeights={0:150}\nOn this table we will update later the cell (1,2)''', justify=LEFT).pack(anchor=NW, pady=6, padx='2m')
table = table_of_random_widgets(root, numRows=3,numColumns=6, minsize=30, fixedPanes=[2,5], columnWidths={0:200, 2: 150, 3:250}, rowHeights={0:90})
table.pack(anchor=NW,pady=2, padx='2m')
table.update_cell((1,2), Label(table,text="Cell updated",bg="green") )
emptySpace = Frame(root, height =20)
emptySpace.pack()
print "On the last table:"
print "\tnumber of rows: ", table.numberOfRows()
print "\tnumber of columns: ", table.numberOfColumns()
root.mainloop()
if __name__ == '__main__':
test()
Diff to Previous Revision
--- revision 4 2014-05-07 15:52:22
+++ revision 5 2014-06-03 20:45:59
@@ -1,125 +1,314 @@
from Tkinter import *
+from collections import OrderedDict, defaultdict
+import itertools
+
class PannedGrid(Frame):
- def __init__(self, master, tableOfWidgets, minsize='auto', columnWidthList= None, fixedColumns=[]):
+ def __init__(self, master, orient=HORIZONTAL, minsize=20, columnWidths = {}, rowHeights={}, fixedPanes = []):
Frame.__init__(self,master)
- self.numberOfRows = len(tableOfWidgets)
- self.numberOfColumns = len(tableOfWidgets[0])
- self.rowHeightList = [0]*self.numberOfRows
-
- self.columnWidthList = [0]*self.numberOfColumns
-
- self.listOfColumnsFrames = []
-
- self.gridOfCellWidgets = \
- [[None for j in range(self.numberOfColumns)] for i in range(self.numberOfRows)]
-
-
- typeOfColumn = 'fixed'
-
- for j in range(self.numberOfColumns):
- if j in fixedColumns:
- masterFrame = Frame(self)
- typeOfColumn = 'fixed'
- elif typeOfColumn == 'fixed':
- masterFrame = PanedWindow(self,orient=HORIZONTAL )
- typeOfColumn = 'mobile'
-
- masterFrame.pack(side=LEFT)
-
- columnFrame = Frame(masterFrame)
- columnFrame.pack()
- columnFrame.pack_propagate(False)
-
- if typeOfColumn == 'mobile':
- masterFrame.add(columnFrame)
-
- self.listOfColumnsFrames.append((typeOfColumn,masterFrame,columnFrame))
-
- for i in range(self.numberOfRows):
- cellFrame = Frame(columnFrame, bd=1, relief=SUNKEN)
- cellFrame.pack(expand=YES, fill=X)
- cellFrame.pack_propagate(False)
-
- widgetDefinition = tableOfWidgets[i][j]
-
- classObject, kwargs = widgetDefinition
-
- widget = classObject(cellFrame, **kwargs)
- widget.pack(expand=YES, fill=BOTH)
-
- self.rowHeightList[i] = max(self.rowHeightList[i],widget.winfo_reqheight())
-
- self.columnWidthList[j] = max(self.columnWidthList[j],widget.winfo_reqwidth())
-
- self.gridOfCellWidgets[i][j]= cellFrame
-
-
- tableHeight = sum(self.rowHeightList)
-
-
- for indexColumn, (typeOfColumn,masterFrame,columnFrame) in enumerate(self.listOfColumnsFrames):
- if columnWidthList:
- if columnWidthList[indexColumn] == 'auto':
- columnWidth = self.columnWidthList[indexColumn]
+ self.clear_table()
+
+ if orient in (HORIZONTAL, VERTICAL):
+ self.orient = orient
+ else:
+ raise Exception("orient must be 'horizontal' or 'vertical, not '%s'." % orient)
+
+ if type(minsize) is int:
+ self.minsize = minsize
+ else:
+ raise Exception("Minsize must be an integer.")
+
+ self.fixedPanes = set(fixedPanes)
+
+ self.__packSideBetweenPanes = LEFT if self.orient == HORIZONTAL else TOP
+ self.__packSideInsidePane = TOP if self.orient == HORIZONTAL else LEFT
+ self.__cellFill = X if self.orient == HORIZONTAL else Y
+
+ self.userDefinedWidths = columnWidths
+ self.userDefinedHeights = rowHeights
+
+ self.updated = False
+ self.areDimensionsCalculated = False
+
+
+ def clear_table(self):
+ self.__gridOfWidgets = {}
+
+ self.__rows = defaultdict(set)
+ self.__columns = defaultdict(set)
+
+ self.__panes = {}
+
+ self.rowHeightList = {}
+ self.columnWidthList = {}
+
+ def clear_definedWidths(self):
+ self.userDefinedWidths = {}
+
+ def clear_definedHeights(self):
+ self.userDefinedHeights = {}
+
+ def rowIndices(self):
+ return self.__rows.keys()
+
+ def columnIndices(self):
+ return self.__column.keys()
+
+ def numberOfRows(self):
+ return len(self.__rows)
+
+ def numberOfColumns(self):
+ return len(self.__columns)
+
+ def update_cell(self, coordinates, widget):
+ if not self.updated:
+ raise Exception("First you must to update the pannedgrid.")
+
+ if coordinates in self.__gridOfWidgets:
+ master = self.__gridOfWidgets[coordinates].pack_info()['in']
+ self.__gridOfWidgets[coordinates].destroy()
+ self.__gridOfWidgets[coordinates] = widget
+ widget.pack(in_=master, expand=YES, fill=BOTH)
+ else:
+ raise Exception("There is no widget with coordiantes: %s." % coordinates)
+
+
+ def update(self):
+ if not self.__gridOfWidgets: return
+
+ self.panedWindows = []
+
+ if not self.areDimensionsCalculated:
+ self.calculate_list_of_cell_widths_and_heights()
+
+ listOfCellCoordinates = list(itertools.product(self.__rows, self.__columns))
+
+ if self.orient == HORIZONTAL:
+ listOfCellCoordinates.sort(key = lambda item: item[1])
+ else:
+ listOfCellCoordinates.sort()
+
+ pane_index = self.__columns[0] if self.orient == HORIZONTAL else self.__rows[0]
+
+ position_of_outer_index = 1 if self.orient == HORIZONTAL else 0
+
+ newPanedWindow = True
+
+ for cell_coordinates in listOfCellCoordinates:
+
+ outer_index = cell_coordinates[position_of_outer_index]
+ if pane_index != outer_index:
+ # Creating a new pane
+ pane_index = outer_index
+
+ if outer_index in self.fixedPanes:
+ frameOfPane = Frame(self)
+ frameOfPane.pack(side=self.__packSideBetweenPanes)
+ newPanedWindow = True
else:
- columnWidth = columnWidthList[indexColumn]
- else:
- columnWidth = self.columnWidthList[indexColumn]
-
- columnFrame.configure(width=columnWidth, height=tableHeight)
-
- if typeOfColumn == 'mobile':
- if minsize == 'auto':
- masterFrame.paneconfigure(columnFrame, minsize=self.columnWidthList[indexColumn])
- elif type(minsize) is int:
- masterFrame.paneconfigure(columnFrame, minsize=minsize)
+ if newPanedWindow:
+ paneMaster = PanedWindow(self,orient=self.orient )
+ paneMaster.pack(side=self.__packSideBetweenPanes)
+
+ self.panedWindows.append(paneMaster)
+ newPanedWindow = False
+
+ frameOfPane = Frame(paneMaster)
+ frameOfPane.pack()
+ paneMaster.add(frameOfPane)
+
+ paneMaster.paneconfigure(frameOfPane, minsize=self.minsize)
+
+ sizeOfPane = self.columnWidthList[outer_index] if self.orient == HORIZONTAL else self.rowHeightList[outer_index]
+
+ if self.orient == HORIZONTAL:
+ frameOfPane.configure(width=sizeOfPane, height=self.tableHeight)
else:
- raise Exception("Minsize must be 'auto' or an integer.")
-
-
- for i in range(self.numberOfRows):
- for j in range(self.numberOfColumns):
- self.gridOfCellWidgets[i][j].configure(height=self.rowHeightList[i])
-
+ frameOfPane.configure(width=self.tableWidth, height=sizeOfPane)
+
+ frameOfPane.pack_propagate(False)
+
+ self.__panes[pane_index] = frameOfPane
+
+ cellFrame = Frame(frameOfPane, bd=1, relief=SUNKEN)
+ cellFrame.pack(side=self.__packSideInsidePane, expand=YES, fill=self.__cellFill)
+ cellFrame.pack_propagate(False)
+
+ widget = self.__gridOfWidgets[cell_coordinates]
+
+ if widget:
+ widget.lift()
+ widget.pack(in_=cellFrame, expand=YES, fill=BOTH)
+
+ if self.orient == HORIZONTAL:
+ cellFrame.configure(
+ height=self.rowHeightList[ cell_coordinates[0] ],
+ )
+ else:
+ cellFrame.configure(
+ width=self.columnWidthList[ cell_coordinates[1] ]
+ )
+
+ self.updated = True
+
+ def calculate_list_of_cell_widths_and_heights(self):
+ self.rowHeightList = self.userDefinedHeights.copy()
+ self.columnWidthList = self.userDefinedWidths.copy()
+
+ for coordinate, widget in self.__gridOfWidgets.items():
+ i , j = coordinate
+ if i not in self.userDefinedHeights:
+ self.rowHeightList[i] = max(self.rowHeightList.get(i,0),widget.winfo_reqheight())
+
+ if j not in self.userDefinedWidths:
+ self.columnWidthList[j] = max(self.columnWidthList.get(j,0),widget.winfo_reqwidth())
+
+
+ self.tableHeight = sum(self.rowHeightList.values())
+ self.tableWidth = sum(self.columnWidthList.values())
+
+ self.areDimensionsCalculated = True
+
+ def rowconfigure(self,row, height=None, fixed = None):
+ if row not in self.__rows:
+ raise Exception("Row %d doesn't exists." % column)
+
+ self.updated = False
+
+ if height is not None:
+ self.areDimensionsCalculated = False
+
+ if type(height) is not int: raise Exception("The height must be an integer:%s"%size)
+
+ self.userDefinedHeights[row] = height
+
+ if fixed is not None:
+ if self.orient == HORIZONTAL:
+ raise Exception("You can't set fixed property on a row because 'orient' is VERTICAL.")
+
+ self.fixPane(index, fixed)
+
+ def columnconfigure(self,column, width=None, fixed = None):
+ if column not in self.__columns:
+ raise Exception("Column %d doesn't exists." % column)
+
+ self.updated = False
+
+ if width is not None:
+ self.areDimensionsCalculated = False
+
+ if type(width) is not int: raise Exception("The width must be an integer:%s"%size)
+
+ self.userDefinedWidths[column] = width
+
+ if fixed is not None:
+ if self.orient == VERTICAL:
+ raise Exception("You can't set fixed property on a column because 'orient' is HORIZONTAL.")
+
+ self.fixPane(index, fixed)
+
+ def fixPane(index, fixed=True):
+ if fixed == True:
+ self.fixedPanes.add(index)
+ elif fixed == False:
+ self.fixedPanes.discard(index)
+ else:
+ raise Exception("fixed must be 'True' or 'False'.")
+
+ def fixAllPanes():
+ fixedPanes_list = self.__columns.keys() if self.orient == HORIZONTAL else self.__rows.keys()
+ self.fixedPanes = set(fixedPanes_list)
+
+ def realeaseAllPanes():
+ self.fixedPanes = set()
+
+ def __getitem__(self, coordinates):
+ return self.__gridOfWidgets.get(coordinates)
+
+ def __setitem__(self,coordinates, widget):
+ self.updated = False
+ self.areDimensionsCalculated = False
+
+ self.__gridOfWidgets[coordinates] = widget
+
+ i,j = coordinates
+ self.__rows[i].add(j)
+ self.__columns[j].add(i)
+
+ def __delitem__(self, coordinates ):
+ self.updated = False
+ self.areDimensionsCalculated = False
+
+ del self.__gridOfWidgets[coordinates]
+
+ i,j = coordinates
+
+ self.__rows[i].discard(j)
+ if not self.__rows[i]: del self.__rows[i]
+
+ self.__columns[j].discard(i)
+ if not self.__columns[j]: del self.__columns[j]
+
+
+ def grid(self, *args, **kargs):
+ if not self.updated: self.update()
+ Frame.grid(self,*args, **kargs)
+
+ def pack(self, *args, **kargs):
+ if not self.updated: self.update()
+ Frame.pack(self,*args, **kargs)
def test():
import random
root = Tk()
- def build_random_table(master, numRows,numColumns, minsize, fixedColumns, columnWidthList = None):
+ def table_of_random_widgets(master, numRows,numColumns, **kwargs):
import ttk
-
+ # Falta pensar un nombre para estos atributos: columnWidthList = None, rowHeightList = None
+
+ demo = PannedGrid(master, **kwargs)
+
tkinterWidgets = (
- (Label, {'text':'This is a label'}),
+ (Label, {'text':'This is a label'}),
(Label, {'text':'This is another label', 'bg':'yellow'}),
(Checkbutton,{}),
(Button, {'text':'Click me'}),
(ttk.Combobox, {'values':('item1', 'item2','item3','item4')})
)
- tableGrid = []
-
for i in range(numRows):
- row = [random.choice(tkinterWidgets) for j in range(numColumns) ]
-
- tableGrid.append(row)
-
- PannedGrid(master, tableGrid,minsize=minsize,fixedColumns=fixedColumns, columnWidthList=columnWidthList).pack(anchor=NW,pady=2, padx='2m')
-
- Label(root, text="minsize = 20, fixed columns = [2,5]").pack(anchor=NW, pady=6, padx='2m')
- build_random_table(root, numRows=4,numColumns=6, minsize=20, fixedColumns=[2,5])
-
- emptySpace = Frame(root, height =30)
+ for j in range(numColumns):
+ widgetClass, args = random.choice(tkinterWidgets)
+ widget = widgetClass(demo,**args)
+
+ demo[i,j] = widget
+
+ return demo
+
+ Label(root, text="orient=VERTICAL, minsize = 20, fixed rows = [2,5]").pack(anchor=NW, pady=6, padx='2m')
+ table = table_of_random_widgets(root, numRows=6,numColumns=5, orient=VERTICAL, minsize=20, fixedPanes=[2,5])
+ table.pack(anchor=NW,pady=2, padx='2m')
+
+ emptySpace = Frame(root, height =20)
emptySpace.pack()
-
- Label(root, text="minsize = auto, fixed columns = [2,5], columnWidthList=[200, 'auto', 200, 'auto', 250, 'auto']").pack(anchor=NW, pady=6, padx='2m')
- build_random_table(root, numRows=4,numColumns=6, minsize='auto', columnWidthList=[200, 'auto', 200, 'auto', 250, 'auto'], fixedColumns=[2,5])
-
+
+
+ Label(root, text='''orient=HORIZONTAL, minsize = 30, fixed columns = [2,5], columnWidths={0:200, 2: 150, 3:250}, rowHeights={0:150}\nOn this table we will update later the cell (1,2)''', justify=LEFT).pack(anchor=NW, pady=6, padx='2m')
+
+ table = table_of_random_widgets(root, numRows=3,numColumns=6, minsize=30, fixedPanes=[2,5], columnWidths={0:200, 2: 150, 3:250}, rowHeights={0:90})
+ table.pack(anchor=NW,pady=2, padx='2m')
+
+ table.update_cell((1,2), Label(table,text="Cell updated",bg="green") )
+
+ emptySpace = Frame(root, height =20)
+ emptySpace.pack()
+
+ print "On the last table:"
+ print "\tnumber of rows: ", table.numberOfRows()
+ print "\tnumber of columns: ", table.numberOfColumns()
root.mainloop()
-
if __name__ == '__main__':