# Version 0.2
# Author: Miguel Martinez Lopez
# Email: To see my email uncomment the next line
# print '61706c69636163696f6e616d656469646140676d61696c2e636f6d'.decode('hex')
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
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 8 2014-06-03 21:04:26
+++ revision 9 2014-06-03 21:08:11
@@ -2,7 +2,6 @@
# Author: Miguel Martinez Lopez
# Email: To see my email uncomment the next line
# print '61706c69636163696f6e616d656469646140676d61696c2e636f6d'.decode('hex')
-
from Tkinter import *
from collections import OrderedDict, defaultdict
@@ -272,7 +271,6 @@
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)