7 class MegaTable(Grid
.PyGridTableBase
): 
   9     A custom wx.Grid Table using user supplied data 
  11     def __init__(self
, data
, colnames
, plugins
): 
  12         """data is a list of the form 
  13         [(rowname, dictionary), 
  14         dictionary.get(colname, None) returns the data for column 
  17         # The base class must be initialized *first* 
  18         Grid
.PyGridTableBase
.__init
__(self
) 
  20         self
.colnames 
= colnames
 
  21         self
.plugins 
= plugins 
or {} 
  23         # we need to store the row length and column length to 
  24         # see if the table has changed size 
  25         self
._rows 
= self
.GetNumberRows() 
  26         self
._cols 
= self
.GetNumberCols() 
  28     def GetNumberCols(self
): 
  29         return len(self
.colnames
) 
  31     def GetNumberRows(self
): 
  34     def GetColLabelValue(self
, col
): 
  35         return self
.colnames
[col
] 
  37     def GetRowLabelValue(self
, row
): 
  38         return "row %03d" % int(self
.data
[row
][0]) 
  40     def GetValue(self
, row
, col
): 
  41         return str(self
.data
[row
][1].get(self
.GetColLabelValue(col
), "")) 
  43     def GetRawValue(self
, row
, col
): 
  44         return self
.data
[row
][1].get(self
.GetColLabelValue(col
), "") 
  46     def SetValue(self
, row
, col
, value
): 
  47         self
.data
[row
][1][self
.GetColLabelValue(col
)] = value
 
  49     def ResetView(self
, grid
): 
  51         (Grid) -> Reset the grid view.   Call this to 
  52         update the grid if rows and columns have been added or deleted 
  56         for current
, new
, delmsg
, addmsg 
in [ 
  57             (self
._rows
, self
.GetNumberRows(), Grid
.GRIDTABLE_NOTIFY_ROWS_DELETED
, Grid
.GRIDTABLE_NOTIFY_ROWS_APPENDED
), 
  58             (self
._cols
, self
.GetNumberCols(), Grid
.GRIDTABLE_NOTIFY_COLS_DELETED
, Grid
.GRIDTABLE_NOTIFY_COLS_APPENDED
), 
  62                 msg 
= Grid
.GridTableMessage(self
,delmsg
,new
,current
-new
) 
  63                 grid
.ProcessTableMessage(msg
) 
  65                 msg 
= Grid
.GridTableMessage(self
,addmsg
,new
-current
) 
  66                 grid
.ProcessTableMessage(msg
) 
  67                 self
.UpdateValues(grid
) 
  71         self
._rows 
= self
.GetNumberRows() 
  72         self
._cols 
= self
.GetNumberCols() 
  73         # update the column rendering plugins 
  74         self
._updateColAttrs
(grid
) 
  76         # update the scrollbars and the displayed part of the grid 
  77         grid
.AdjustScrollbars() 
  81     def UpdateValues(self
, grid
): 
  82         """Update all displayed values""" 
  83         # This sends an event to the grid table to update all of the values 
  84         msg 
= Grid
.GridTableMessage(self
, Grid
.GRIDTABLE_REQUEST_VIEW_GET_VALUES
) 
  85         grid
.ProcessTableMessage(msg
) 
  87     def _updateColAttrs(self
, grid
): 
  89         wx.Grid -> update the column attributes to add the 
  90         appropriate renderer given the column name.  (renderers 
  91         are stored in the self.plugins dictionary) 
  93         Otherwise default to the default renderer. 
  97         for colname 
in self
.colnames
: 
  98             attr 
= Grid
.GridCellAttr() 
  99             if colname 
in self
.plugins
: 
 100                 renderer 
= self
.plugins
[colname
](self
) 
 103                     grid
.SetColSize(col
, renderer
.colSize
) 
 106                     grid
.SetDefaultRowSize(renderer
.rowSize
) 
 108                 attr
.SetReadOnly(True) 
 109                 attr
.SetRenderer(renderer
) 
 111             grid
.SetColAttr(col
, attr
) 
 114     # ------------------------------------------------------ 
 115     # begin the added code to manipulate the table (non wx related) 
 116     def AppendRow(self
, row
): 
 120         for name 
in self
.colnames
: 
 121             entry
[name
] = "Appended_%i"%row
 
 124         # entry["A"] can only be between 1..4 
 125         entry
["A"] = random
.choice(range(4)) 
 126         self
.data
.insert(row
, ["Append_%i"%row
, entry
]) 
 128     def DeleteCols(self
, cols
): 
 130         cols -> delete the columns from the dataset 
 131         cols hold the column indices 
 133         # we'll cheat here and just remove the name from the 
 134         # list of column names.  The data will remain but 
 141             self
.colnames
.pop(i
-deleteCount
) 
 142             # we need to advance the delete count 
 143             # to make sure we delete the right columns 
 146         if not len(self
.colnames
): 
 149     def DeleteRows(self
, rows
): 
 151         rows -> delete the rows from the dataset 
 152         rows hold the row indices 
 159             self
.data
.pop(i
-deleteCount
) 
 160             # we need to advance the delete count 
 161             # to make sure we delete the right rows 
 164     def SortColumn(self
, col
): 
 166         col -> sort the data based on the column indexed by col 
 168         name 
= self
.colnames
[col
] 
 171         for row 
in self
.data
: 
 173             _data
.append((entry
.get(name
, None), row
)) 
 178         for sortvalue
, row 
in _data
: 
 179             self
.data
.append(row
) 
 181     # end table manipulation code 
 182     # ---------------------------------------------------------- 
 185 # -------------------------------------------------------------------- 
 186 # Sample wx.Grid renderers 
 188 class MegaImageRenderer(Grid
.PyGridCellRenderer
): 
 189     def __init__(self
, table
): 
 191         Image Renderer Test.  This just places an image in a cell 
 192         based on the row index.  There are N choices and the 
 193         choice is made by  choice[row%N] 
 195         Grid
.PyGridCellRenderer
.__init
__(self
) 
 197         self
._choices 
= [images
.getSmilesBitmap
, 
 198                          images
.getMondrianBitmap
, 
 199                          images
.get_10s_Bitmap
, 
 200                          images
.get_01c_Bitmap
] 
 205     def Draw(self
, grid
, attr
, dc
, rect
, row
, col
, isSelected
): 
 206         choice 
= self
.table
.GetRawValue(row
, col
) 
 207         bmp 
= self
._choices
[ choice 
% len(self
._choices
)]() 
 208         image 
= wx
.MemoryDC() 
 209         image
.SelectObject(bmp
) 
 211         # clear the background 
 212         dc
.SetBackgroundMode(wx
.SOLID
) 
 215             dc
.SetBrush(wx
.Brush(wx
.BLUE
, wx
.SOLID
)) 
 216             dc
.SetPen(wx
.Pen(wx
.BLUE
, 1, wx
.SOLID
)) 
 218             dc
.SetBrush(wx
.Brush(wx
.WHITE
, wx
.SOLID
)) 
 219             dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
)) 
 220         dc
.DrawRectangleRect(rect
) 
 222         #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height)) 
 224         # copy the image but only to the size of the grid cell 
 225         width
, height 
= bmp
.GetWidth(), bmp
.GetHeight() 
 227         if width 
> rect
.width
-2: 
 230         if height 
> rect
.height
-2: 
 231             height 
= rect
.height
-2 
 233         dc
.Blit((rect
.x
+1, rect
.y
+1), (width
, height
), 
 235                 (0, 0), wx
.COPY
, True) 
 238 class MegaFontRenderer(Grid
.PyGridCellRenderer
): 
 239     def __init__(self
, table
, color
="blue", font
="ARIAL", fontsize
=8): 
 240         """Render data in the specified color and font and fontsize""" 
 241         Grid
.PyGridCellRenderer
.__init
__(self
) 
 244         self
.font 
= wx
.Font(fontsize
, wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
, 0, font
) 
 245         self
.selectedBrush 
= wx
.Brush("blue", wx
.SOLID
) 
 246         self
.normalBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
 250     def Draw(self
, grid
, attr
, dc
, rect
, row
, col
, isSelected
): 
 251         # Here we draw text in a grid cell using various fonts 
 252         # and colors.  We have to set the clipping region on 
 253         # the grid's DC, otherwise the text will spill over 
 255         dc
.SetClippingRect(rect
) 
 257         # clear the background 
 258         dc
.SetBackgroundMode(wx
.SOLID
) 
 261             dc
.SetBrush(wx
.Brush(wx
.BLUE
, wx
.SOLID
)) 
 262             dc
.SetPen(wx
.Pen(wx
.BLUE
, 1, wx
.SOLID
)) 
 264             dc
.SetBrush(wx
.Brush(wx
.WHITE
, wx
.SOLID
)) 
 265             dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
)) 
 266         dc
.DrawRectangleRect(rect
) 
 268         #dc.DrawRectangle((rect.x, rect.y), (rect.width, rect.height)) 
 270         text 
= self
.table
.GetValue(row
, col
) 
 271         dc
.SetBackgroundMode(wx
.SOLID
) 
 273         # change the text background based on whether the grid is selected 
 276             dc
.SetBrush(self
.selectedBrush
) 
 277             dc
.SetTextBackground("blue") 
 279             dc
.SetBrush(self
.normalBrush
) 
 280             dc
.SetTextBackground("white") 
 282         dc
.SetTextForeground(self
.color
) 
 283         dc
.SetFont(self
.font
) 
 284         dc
.DrawText(text
, (rect
.x
+1, rect
.y
+1)) 
 286         # Okay, now for the advanced class :) 
 287         # Let's add three dots "..." 
 288         # to indicate that that there is more text to be read 
 289         # when the text is larger than the grid cell 
 291         width
, height 
= dc
.GetTextExtent(text
) 
 293         if width 
> rect
.width
-2: 
 294             width
, height 
= dc
.GetTextExtent("...") 
 295             x 
= rect
.x
+1 + rect
.width
-2 - width
 
 296             dc
.DrawRectangle((x
, rect
.y
+1), (width
+1, height
)) 
 297             dc
.DrawText("...", (x
, rect
.y
+1)) 
 299         dc
.DestroyClippingRegion() 
 302 # -------------------------------------------------------------------- 
 303 # Sample Grid using a specialized table and renderers that can 
 304 # be plugged in based on column names 
 306 class MegaGrid(Grid
.Grid
): 
 307     def __init__(self
, parent
, data
, colnames
, plugins
=None): 
 308         """parent, data, colnames, plugins=None 
 309         Initialize a grid using the data defined in data and colnames 
 310         (see MegaTable for a description of the data format) 
 311         plugins is a dictionary of columnName -> column renderers. 
 314         # The base class must be initialized *first* 
 315         Grid
.Grid
.__init
__(self
, parent
, -1) 
 316         self
._table 
= MegaTable(data
, colnames
, plugins
) 
 317         self
.SetTable(self
._table
) 
 318         self
._plugins 
= plugins
 
 320         self
.Bind(Grid
.EVT_GRID_LABEL_RIGHT_CLICK
, self
.OnLabelRightClicked
) 
 323         """reset the view based on the data in the table.  Call 
 324         this when rows are added or destroyed""" 
 325         self
._table
.ResetView(self
) 
 327     def OnLabelRightClicked(self
, evt
): 
 328         # Did we click on a row or a column? 
 329         row
, col 
= evt
.GetRow(), evt
.GetCol() 
 330         if row 
== -1: self
.colPopup(col
, evt
) 
 331         elif col 
== -1: self
.rowPopup(row
, evt
) 
 333     def rowPopup(self
, row
, evt
): 
 334         """(row, evt) -> display a popup menu when a row label is right clicked""" 
 335         appendID 
= wx
.NewId() 
 336         deleteID 
= wx
.NewId() 
 337         x 
= self
.GetRowSize(row
)/2 
 339         if not self
.GetSelectedRows(): 
 343         xo
, yo 
= evt
.GetPosition() 
 344         menu
.Append(appendID
, "Append Row") 
 345         menu
.Append(deleteID
, "Delete Row(s)") 
 347         def append(event
, self
=self
, row
=row
): 
 348             self
._table
.AppendRow(row
) 
 351         def delete(event
, self
=self
, row
=row
): 
 352             rows 
= self
.GetSelectedRows() 
 353             self
._table
.DeleteRows(rows
) 
 356         self
.Bind(wx
.EVT_MENU
, append
, id=appendID
) 
 357         self
.Bind(wx
.EVT_MENU
, delete
, id=deleteID
) 
 358         self
.PopupMenu(menu
, (x
, yo
)) 
 363     def colPopup(self
, col
, evt
): 
 364         """(col, evt) -> display a popup menu when a column label is 
 366         x 
= self
.GetColSize(col
)/2 
 371         xo
, yo 
= evt
.GetPosition() 
 373         cols 
= self
.GetSelectedCols() 
 375         menu
.Append(id1
, "Delete Col(s)") 
 376         menu
.Append(sortID
, "Sort Column") 
 378         def delete(event
, self
=self
, col
=col
): 
 379             cols 
= self
.GetSelectedCols() 
 380             self
._table
.DeleteCols(cols
) 
 383         def sort(event
, self
=self
, col
=col
): 
 384             self
._table
.SortColumn(col
) 
 387         self
.Bind(wx
.EVT_MENU
, delete
, id=id1
) 
 390             self
.Bind(wx
.EVT_MENU
, sort
, id=sortID
) 
 392         self
.PopupMenu(menu
, (xo
, 0)) 
 396 # ----------------------------------------------------------------- 
 398 # data is in the form 
 399 # [rowname, dictionary] 
 400 # where dictionary.get(colname, None) -> returns the value for the cell 
 402 # the colname must also be supplied 
 404 colnames 
= ["Row", "This", "Is", "A", "Test"] 
 408 for row 
in range(1000): 
 410     for name 
in ["This", "Test", "Is"]: 
 411         d
[name
] = random
.random() 
 415     # the "A" column can only be between one and 4 
 416     d
["A"] = random
.choice(range(4)) 
 417     data
.append((str(row
), d
)) 
 419 class MegaFontRendererFactory
: 
 420     def __init__(self
, color
, font
, fontsize
): 
 422         (color, font, fontsize) -> set of a factory to generate 
 423         renderers when called. 
 424         func = MegaFontRenderFactory(color, font, fontsize) 
 425         renderer = func(table) 
 429         self
.fontsize 
= fontsize
 
 431     def __call__(self
, table
): 
 432         return MegaFontRenderer(table
, self
.color
, self
.font
, self
.fontsize
) 
 435 #--------------------------------------------------------------------------- 
 437 class TestFrame(wx
.Frame
): 
 438     def __init__(self
, parent
, plugins
={"This":MegaFontRendererFactory("red", "ARIAL", 8), 
 439                                         "A":MegaImageRenderer
, 
 440                                         "Test":MegaFontRendererFactory("orange", "TIMES", 24),}): 
 441         wx
.Frame
.__init
__(self
, parent
, -1, 
 442                          "Test Frame", size
=(640,480)) 
 444         grid 
= MegaGrid(self
, data
, colnames
, plugins
) 
 448 #--------------------------------------------------------------------------- 
 450 def runTest(frame
, nb
, log
): 
 451     win 
= TestFrame(frame
) 
 457 overview 
= """Mega Grid Example 
 459 This example attempts to show many examples and tricks of 
 460 using a virtual grid object.  Hopefully the source isn't too jumbled. 
 464    <li>Uses a virtual grid 
 465    <li>Columns and rows have popup menus (right click on labels) 
 466    <li>Columns and rows can be deleted (i.e. table can be 
 468    <li>Dynamic renderers.  Renderers are plugins based on 
 469       column header name.  Shows a simple Font Renderer and 
 473 Look for 'XXX' in the code to indicate some workarounds for non-obvious 
 474 behavior and various hacks. 
 479 if __name__ 
== '__main__': 
 482     run
.main(['', os
.path
.basename(sys
.argv
[0])])