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])] + sys
.argv
[1:])