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
)
223 # copy the image but only to the size of the grid cell
224 width
, height
= bmp
.GetWidth(), bmp
.GetHeight()
226 if width
> rect
.width
-2:
229 if height
> rect
.height
-2:
230 height
= rect
.height
-2
232 dc
.Blit(rect
.x
+1, rect
.y
+1, width
, height
,
237 class MegaFontRenderer(Grid
.PyGridCellRenderer
):
238 def __init__(self
, table
, color
="blue", font
="ARIAL", fontsize
=8):
239 """Render data in the specified color and font and fontsize"""
240 Grid
.PyGridCellRenderer
.__init
__(self
)
243 self
.font
= wx
.Font(fontsize
, wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
, 0, font
)
244 self
.selectedBrush
= wx
.Brush("blue", wx
.SOLID
)
245 self
.normalBrush
= wx
.Brush(wx
.WHITE
, wx
.SOLID
)
249 def Draw(self
, grid
, attr
, dc
, rect
, row
, col
, isSelected
):
250 # Here we draw text in a grid cell using various fonts
251 # and colors. We have to set the clipping region on
252 # the grid's DC, otherwise the text will spill over
254 dc
.SetClippingRect(rect
)
256 # clear the background
257 dc
.SetBackgroundMode(wx
.SOLID
)
260 dc
.SetBrush(wx
.Brush(wx
.BLUE
, wx
.SOLID
))
261 dc
.SetPen(wx
.Pen(wx
.BLUE
, 1, wx
.SOLID
))
263 dc
.SetBrush(wx
.Brush(wx
.WHITE
, wx
.SOLID
))
264 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
))
265 dc
.DrawRectangleRect(rect
)
267 text
= self
.table
.GetValue(row
, col
)
268 dc
.SetBackgroundMode(wx
.SOLID
)
270 # change the text background based on whether the grid is selected
273 dc
.SetBrush(self
.selectedBrush
)
274 dc
.SetTextBackground("blue")
276 dc
.SetBrush(self
.normalBrush
)
277 dc
.SetTextBackground("white")
279 dc
.SetTextForeground(self
.color
)
280 dc
.SetFont(self
.font
)
281 dc
.DrawText(text
, rect
.x
+1, rect
.y
+1)
283 # Okay, now for the advanced class :)
284 # Let's add three dots "..."
285 # to indicate that that there is more text to be read
286 # when the text is larger than the grid cell
288 width
, height
= dc
.GetTextExtent(text
)
290 if width
> rect
.width
-2:
291 width
, height
= dc
.GetTextExtent("...")
292 x
= rect
.x
+1 + rect
.width
-2 - width
293 dc
.DrawRectangle(x
, rect
.y
+1, width
+1, height
)
294 dc
.DrawText("...", x
, rect
.y
+1)
296 dc
.DestroyClippingRegion()
299 # --------------------------------------------------------------------
300 # Sample Grid using a specialized table and renderers that can
301 # be plugged in based on column names
303 class MegaGrid(Grid
.Grid
):
304 def __init__(self
, parent
, data
, colnames
, plugins
=None):
305 """parent, data, colnames, plugins=None
306 Initialize a grid using the data defined in data and colnames
307 (see MegaTable for a description of the data format)
308 plugins is a dictionary of columnName -> column renderers.
311 # The base class must be initialized *first*
312 Grid
.Grid
.__init
__(self
, parent
, -1)
313 self
._table
= MegaTable(data
, colnames
, plugins
)
314 self
.SetTable(self
._table
)
315 self
._plugins
= plugins
317 self
.Bind(Grid
.EVT_GRID_LABEL_RIGHT_CLICK
, self
.OnLabelRightClicked
)
320 """reset the view based on the data in the table. Call
321 this when rows are added or destroyed"""
322 self
._table
.ResetView(self
)
324 def OnLabelRightClicked(self
, evt
):
325 # Did we click on a row or a column?
326 row
, col
= evt
.GetRow(), evt
.GetCol()
327 if row
== -1: self
.colPopup(col
, evt
)
328 elif col
== -1: self
.rowPopup(row
, evt
)
330 def rowPopup(self
, row
, evt
):
331 """(row, evt) -> display a popup menu when a row label is right clicked"""
332 appendID
= wx
.NewId()
333 deleteID
= wx
.NewId()
334 x
= self
.GetRowSize(row
)/2
336 if not self
.GetSelectedRows():
340 xo
, yo
= evt
.GetPosition()
341 menu
.Append(appendID
, "Append Row")
342 menu
.Append(deleteID
, "Delete Row(s)")
344 def append(event
, self
=self
, row
=row
):
345 self
._table
.AppendRow(row
)
348 def delete(event
, self
=self
, row
=row
):
349 rows
= self
.GetSelectedRows()
350 self
._table
.DeleteRows(rows
)
353 self
.Bind(wx
.EVT_MENU
, append
, id=appendID
)
354 self
.Bind(wx
.EVT_MENU
, delete
, id=deleteID
)
355 self
.PopupMenu(menu
, (x
, yo
))
360 def colPopup(self
, col
, evt
):
361 """(col, evt) -> display a popup menu when a column label is
363 x
= self
.GetColSize(col
)/2
368 xo
, yo
= evt
.GetPosition()
370 cols
= self
.GetSelectedCols()
372 menu
.Append(id1
, "Delete Col(s)")
373 menu
.Append(sortID
, "Sort Column")
375 def delete(event
, self
=self
, col
=col
):
376 cols
= self
.GetSelectedCols()
377 self
._table
.DeleteCols(cols
)
380 def sort(event
, self
=self
, col
=col
):
381 self
._table
.SortColumn(col
)
384 self
.Bind(wx
.EVT_MENU
, delete
, id=id1
)
387 self
.Bind(wx
.EVT_MENU
, sort
, id=sortID
)
389 self
.PopupMenu(menu
, (xo
, 0))
393 # -----------------------------------------------------------------
395 # data is in the form
396 # [rowname, dictionary]
397 # where dictionary.get(colname, None) -> returns the value for the cell
399 # the colname must also be supplied
401 colnames
= ["Row", "This", "Is", "A", "Test"]
405 for row
in range(1000):
407 for name
in ["This", "Test", "Is"]:
408 d
[name
] = random
.random()
412 # the "A" column can only be between one and 4
413 d
["A"] = random
.choice(range(4))
414 data
.append((str(row
), d
))
416 class MegaFontRendererFactory
:
417 def __init__(self
, color
, font
, fontsize
):
419 (color, font, fontsize) -> set of a factory to generate
420 renderers when called.
421 func = MegaFontRenderFactory(color, font, fontsize)
422 renderer = func(table)
426 self
.fontsize
= fontsize
428 def __call__(self
, table
):
429 return MegaFontRenderer(table
, self
.color
, self
.font
, self
.fontsize
)
432 #---------------------------------------------------------------------------
434 class TestFrame(wx
.Frame
):
435 def __init__(self
, parent
, plugins
={"This":MegaFontRendererFactory("red", "ARIAL", 8),
436 "A":MegaImageRenderer
,
437 "Test":MegaFontRendererFactory("orange", "TIMES", 24),}):
438 wx
.Frame
.__init
__(self
, parent
, -1,
439 "Test Frame", size
=(640,480))
441 grid
= MegaGrid(self
, data
, colnames
, plugins
)
445 #---------------------------------------------------------------------------
447 def runTest(frame
, nb
, log
):
448 win
= TestFrame(frame
)
454 overview
= """Mega Grid Example
456 This example attempts to show many examples and tricks of
457 using a virtual grid object. Hopefully the source isn't too jumbled.
461 <li>Uses a virtual grid
462 <li>Columns and rows have popup menus (right click on labels)
463 <li>Columns and rows can be deleted (i.e. table can be
465 <li>Dynamic renderers. Renderers are plugins based on
466 column header name. Shows a simple Font Renderer and
470 Look for 'XXX' in the code to indicate some workarounds for non-obvious
471 behavior and various hacks.
476 if __name__
== '__main__':
479 run
.main(['', os
.path
.basename(sys
.argv
[0])] + sys
.argv
[1:])