The wxGrid Class

Table Interactions

This workaround code stores a Python weak reference to your wxPyGridTableBase instance, overriding the GetTable and SetTable methods of the wxGrid.

   1 import weakref
   2 class _PropertyGrid( wxGrid ):
   3         def SetTable( self, object, *attributes ):
   4                 self.tableRef = weakref.ref( object )
   5                 return wxGrid.SetTable( self, object, *attributes )
   6         def GetTable( self ):
   7                 return self.tableRef()

Note: you can only call SetTable once for any given wxGrid. Because of this, it is generally necessary to have your wxPyGridTableBase class alter the grid's size and shape to reflect any changes in your table's dimensions. See wxGrid Size/Shape Management below for details.

Changing Size/Shape of Grid/Table

The methods described in this section control the grid's overall data structures (for instance the number of grid rows/columns). When using a data table (recommended), is often necessary to alter the grid's size to reflect changes in the data table. Here is a recipe for such changes:

   1 class BasePropertyTable( wxPyGridTableBase):
   2         def ResetView(self):
   3                 """Trim/extend the control's rows and update all values"""
   4                 self.getGrid().BeginBatch()
   5                 for current, new, delmsg, addmsg in [
   6                         (self.currentRows, self.GetNumberRows(), wxGRIDTABLE_NOTIFY_ROWS_DELETED, wxGRIDTABLE_NOTIFY_ROWS_APPENDED),
   7                         (self.currentColumns, self.GetNumberCols(), wxGRIDTABLE_NOTIFY_COLS_DELETED, wxGRIDTABLE_NOTIFY_COLS_APPENDED),
   8                 ]:
   9                         if new < current:
  10                                 msg = wxGridTableMessage(
  11                                         self,
  12                                         delmsg,
  13                                         new,    # position
  14                                         current-new,
  15                                 )
  16                                 self.getGrid().ProcessTableMessage(msg)
  17                         elif new > current:
  18                                 msg = wxGridTableMessage(
  19                                         self,
  20                                         addmsg,
  21                                         new-current
  22                                 )
  23                                 self.getGrid().ProcessTableMessage(msg)
  24                 self.UpdateValues()
  25                 self.getGrid().EndBatch()
  26 
  27                 # The scroll bars aren't resized (at least on windows)
  28                 # Jiggling the size of the window rescales the scrollbars
  29                 h,w = grid.GetSize()
  30                 grid.SetSize((h+1, w))
  31                 grid.SetSize((h, w))
  32                 grid.ForceRefresh()
  33 
  34         def UpdateValues( self ):
  35                 """Update all displayed values"""
  36                 msg = wxGridTableMessage(self, wxGRIDTABLE_REQUEST_VIEW_GET_VALUES)
  37                 self.getGrid().ProcessTableMessage(msg)

Note: this code assumes that you record self.currentRows and self.currentCols on your initial table set up to allow for calculating the new size relative to the old size. It should be possible to use wxGrid::GetNumberRows() if you prefer.

Also note: the code uses getGrid() to return a pointer to the wxGrid, depending on your version of wxPython, you may be able to use wxPyGridTableBase::GetGrid()or wxPyGridTableBase::GetView() instead. See: Table Interactions (above) for details on this work-around.

Last note: At least on windows, the grid's scroll bars are not resized when rows and columns are deleted. Jiggling the size of the grid and forcing a refresh fixing this problem. The fix for this has been added to the demo code above.

Other size/shape management methods:

Data Types, Renderers and Editors

Your wxPyGridTableBase's GetTypeName returns data-type specifiers for a given row,col pair, the grid uses that to look up the appropriate renderer and editor for the cell.

Data Type Specifiers

Usage patterns? I automate their generation, not sure if most people do that.

Edit/Render Objects/Controls

Selections and the Cursor

The grid has both a selection set (which may include disjoint ranges) and a single-cell focus cursor. The selection set can contain arbitrary blocks of cells, while the cursor can only point to a single cell.

Cursor Manipulation

The API for interacting with the focus cursor is fairly simple.

Selection Set Management

The grid class does not provide a simple API for retrieving the current selection set. Instead, it is necessary to catch two different events and store their values to determine the current selection set. The following sample code illustrates how this is done. The _OnSelectedRange method is mapped using EVT_GRID_RANGE_SELECT( self.grid, self._OnSelectedRange ), while the _OnSelectedCell method is mapped using EVT_GRID_SELECT_CELL( self.grid, self._OnSelectedCell ). Because these are both command events, they can be mapped from the grid's parent window.

The following code sample shows tracking of currently selected rows in a table:

   1         def _OnSelectedRange( self, event ):
   2                 """Internal update to the selection tracking list"""
   3                 if event.Selecting():
   4                         # adding to the list...
   5                         for index in range( event.GetTopRow(), event.GetBottomRow()+1):
   6                                 if index not in self.currentSelection:
   7                                         self.currentSelection.append( index )
   8                 else:
   9                         # removal from list
  10                         for index in range( event.GetTopRow(), event.GetBottomRow()+1):
  11                                 while index in self.currentSelection:
  12                                         self.currentSelection.remove( index )
  13                 self.ConfigureForSelection()
  14                 event.Skip()
  15         def _OnSelectedCell( self, event ):
  16                 """Internal update to the selection tracking list"""
  17                 self.currentSelection = [ event.GetRow() ]
  18                 self.ConfigureForSelection()
  19                 event.Skip()

Note: the selection of a single cell will cause a range selection event with Selecting() == false and the entire grid as the selection set to be sent before the single-cell selection occurs.

Note: the Selecting() == false ranges will be sent if the user is control-clicking to deselect particular cells within the current selection set. Because of this, the "removal from list" sub-block above becomes somewhat complex when you need to store cell-selections, not just row selections. The wxoo project has a partial implementation of a grid-selection tracking class gridselectionset.py, but it merely forces the whole selection set to be represented as individual cells when a deselection is called (which can be spectacularly inefficient). Improved code welcome.

Other selection-set-management methods:

General Display Configuration/Queries

Geometry Queries

Feedback about the geometry displayed in the grid window.

Column/Row Sizing

Default Sizes

The following methods allow for specifying default height and/or width values. These values are used when there is no explicitly set value for column width/height, (and where no auto-sizing is occurring (see below)).

Auto-sizing of Cells to Contents

The following methods set the grid's cells to automatically size themselves to their content's prefered sizes. The prefered sizes are the values returned by the wxPyGridCellRenderer's GetBestSize method. XXX are these sizes stored (potential memory exhaustion), or re-queried for each redisplay?

Note: there is a bug in the current grid implementation that makes it impossible to edit cells which are wider than the grid's displayed client area (i.e. the size of the grid window minus the labels). For this reason you should ensure that your renderer's return conservative values from GetBestSize or avoid using automatic sizing until the bug is fixed.

Overflow (Multiple-cell display/edit)

wxPython version 2.4.x has introduced a new (default) functionality which allows renderers and editors displayed in a particular cell to overwrite neighboring cells if their data values would not normally fit in the space allotted to the cell. This will break most renderers written for earlier versions of wxPython, so you may want to disable the functionality by default, or disable it for particular cells/rows/columns.

The overflow state is stored in wxGridCellAttr objects, with functions available on the grid for setting the entire-grid-default, and for setting the mode for individual cells/ranges. See below for discussion of wxGridCellAttr objects and their interactions with the grid.

Setting Individual Heights/Widths (Exceptions to Defaults)

The following methods can explicitly query and or set individual column or row width or height values. Note: the grid will set up an array for storing exceptions to default widths, so extremely large grids may consume considerable memory storing these exceptions. I am unsure whether the return values from grid cell renderers are similarly stored (I would think not).

Interactive Resizing

The following methods allow for querying and changing the grid's ability to resize columns and/or rows by dragging on the borders between columns or rows. The grid can also (and does by default) allow for changing heights/widths by dragging grid lines in the cell area of the grid display.

Autosizing labels

wxPython does not (yet) allow auto sizing of row or column labels, but a little fiddling with device contexts can acheive this. This has been tested on Windows only and your mileage may vary.

   1         def AutosizeLabels(self):
   2                 # Common setup.
   3 
   4                 devContext = wxScreenDC()
   5                 devContext.SetFont(self.gridCtrl.GetLabelFont())
   6 
   7                 # First do row labels.
   8 
   9                 maxWidth = 0
  10                 curRow = self.gridCtrl.GetNumberRows() - 1
  11                 while curRow >= 0:
  12                         curWidth = devContext.GetTextExtent("M%s"%(self.gridCtrl.GetRowLabelValue(curRow)))[0]
  13                         if curWidth > maxWidth:
  14                                 maxWidth = curWidth
  15                         curRow = curRow - 1
  16                 self.gridCtrl.SetRowLabelSize(maxWidth)
  17 
  18                 # Then column labels.
  19 
  20                 maxHeight = 0
  21                 curCol = self.gridCtrl.GetNumberCols() - 1
  22                 while curCol >= 0:
  23                         (w,h,d,l) = devContext.GetFullTextExtent(self.gridCtrl.GetColLabelValue(curCol))
  24                         curHeight = h + d + l + 4
  25                         if curHeight > maxHeight:
  26                                 maxHeight = curHeight
  27                         curCol = curCol - 1
  28                 self.gridCtrl.SetColLabelSize(maxHeight)

This code operates on a control with a wxGrid-derived instance named gridCtrl. I haven't bothered to implement this very cleanly (ie. as a class inherited from wxGrid). I leave that as an exercise for the reader :-).

The code basically traverses each row label, calculating its width (including an extraneous "M" for spacing) and storing the widest label. Then it simply sets the row label column to that width.

It also does a similar thing for column label heights, except it calculates the desired height using character height, descender and leading gap and adding a nice 4-pixel buffer for padding.

Update:

According to the docs here, as of wxPython version 2.8.8. the following should auto-size column and row labels. I replaced the code snippet above in my latest project (a Virtual Grid using wx.2.9.3 on Windows 7) with:

        self.SetRowLabelSize(wx.grid.GRID_AUTOSIZE)
        self.SetColLabelSize(wx.grid.GRID_AUTOSIZE)

and it appears to be working well. In my situation, wx.grid.GRID_AUTOSIZE fixed the problem I was having with auto-sizing multi-line column labels. --js

Attribute Objects (Cell Display Properties)

wxGridCellAttr objects provide a mechanism for altering the display of particular rows, columns, or individual cells. In table-based grids, you determine what attribute object will be used for a particular grid cell by overriding the wxPyGridTableBase's GetAttr method to return a grid cell attribute object.

Note: the following methods are not really appropriate for a table-based grid, as this will force the grid to create storage for each column's attribute (which may exhaust memory on large grids). See: wxPyGridTableBase's GetAttr method for the appropriate table-based approach. With that said, there might be some use for them where you want to customise particular rows in the grid and for some reason want to keep that code out of your table.

Note: there are methods to set the default values for the default attribute object used by the grid when no other attributes available. See section "Defaults and Overall Settings" below.

Label (Header) Display/Edit Configuration

Each column and row in the table has an associated label (columns having their labels at the top of the grid, rows on the left-hand side).

Label Queries Passed to Table

Cell Display/Edit Configuration

Generic Cell Display (alignment, fonts, colours)

See also:

Defaults and Overall Settings

DragAndDrop onto Grid

wxGrid like most windows can be the target of a DragAndDrop action such as wxFileDropTarget. Note that for wx < 2.9, one should set the drop target to be the appropriate subwidget(s) of the grid [e.g. grid.GetGridWindow().SetDropTarget(drptgt) or GetGridRowLabelWindow(), etc.] Do not use the grid itself or code will not be completely portable (see wxPython-users on drop problem with grid on Mac).

The basic way of to handle the drop is as follows:

   1 class GridFileDropTarget(wxFileDropTarget):
   2     def __init__(self, grid):
   3         wxFileDropTarget.__init__(self)
   4         self.grid = grid
   5 
   6     def OnDropFiles(self, x, y, filenames):
   7         row, col = self.grid.XYToCell(x, y)
   8         if row > -1 and col > -1:
   9             self.grid.SetValue(row, col, filenames[0])
  10             self.grid.Refresh()

Note: I'm assuming here that the grid has a user supplied method self.SetValue. This method can either just call wxGrid.SetCellValue or do the appropriate logic for virtual grids. self.grid.Refresh() just tells the grid that it needs to be redrawn for the new data.

Virtual grids, however, have a significant problem here. It seems that wxGrid.XYToCell is broken for virtual grids. (A virtual grid is a grid that uses wxGrid.SetTable on a PyGridTableBase instance.) Here is a replacement to compute XYToCell to compute the current cell positions. Simply add this method to your virtual grid and away you go!

   1     def XYToCell(self, x, y):
   2         # For virtual grids, XYToCell doesn't work properly
   3         # For some reason, the width and heights of the labels
   4         # are not computed properly and thw row and column
   5         # returned are computed as if the window wasn't
   6         # scrolled
   7         # This function replaces XYToCell for Virtual Grids
   8 
   9         rowwidth = self.GetGridRowLabelWindow().GetRect().width
  10         colheight = self.GetGridColLabelWindow().GetRect().height
  11         yunit, xunit = self.GetScrollPixelsPerUnit()
  12         xoff =  self.GetScrollPos(wxHORIZONTAL) * xunit
  13         yoff = self.GetScrollPos(wxVERTICAL) * yunit
  14 
  15         # the solution is to offset the x and y values
  16         # by the width and height of the label windows
  17         # and then adjust by the scroll position
  18         # Then just go through the columns and rows
  19         # incrementing by the current column and row sizes
  20         # until the offset points lie within the computed
  21         # bounding boxes.
  22         x += xoff - rowwidth
  23         xpos = 0
  24         for col in range(self.GetNumberCols()):
  25             nextx = xpos + self.GetColSize(col)
  26             if xpos <= x <= nextx:
  27                 break
  28             xpos = nextx
  29 
  30         y += yoff - colheight
  31         ypos = 0
  32         for row in range(self.GetNumberRows()):
  33             nexty = ypos + self.GetRowSize(row)
  34             if ypos <= y <= nexty:
  35                 break
  36             ypos = nexty
  37 
  38         return row, col

DragAndDrop From Grid

For complete functionality, wxGrids require access to most mouse functions. While the main grid window can be used as a source for mouse drags, this can become problematic if you still want to resize cells or allow clicks and double clicks to activate the cell. A good comprimise is to allow dragging from the rows of the grids. The following Mixin enables row labels to be used as drag sources. The drag source activates a TextDrag that contains the string represenation of the selected wells, i.e. "[0,1,2,3]". More complicated DragAndDrop sources can be enabled by overriding the StartDrag method function of the DragGridRowMixin below.

   1 '''DragGridRowMixin
   2 
   3 Allows users to drag rows off of grids as Drag and Drop actions.
   4 Usage:
   5   mixin with a Grid class.
   6 
   7   class MyGrid(Grid, DragGridRowMixin):
   8       def __init__(self, parent, id):
   9           # must initialize grid before DragGridRowMixin
  10           Grid.__init__(self, parent, id)
  11           DragGridRowMixin.__init__(self)
  12 
  13 To change the type of data being sent by the Grid, override the method function
  14     def StartDrag(self, selectedrows):
  15         """start a drag operation using selectedrows"""
  16 
  17 Currently a drag operation is performed with a PyTextDataObject containing
  18 the data str(selectedrows)
  19 '''
  20 import wx
  21 from wx.grid import Grid
  22 
  23 
  24 class DragGridRowMixin:
  25     """This mixin allows the use of grid rows as drag sources, you can drag
  26     rows off of a grid to a text target.  Only row labels are draggable, internal
  27     cell contents are not.
  28 
  29     You must Initialize this class *after* the wxGrid initialization.
  30     example
  31     class MyGrid(Grid, DragGridRowMixin):
  32         def __init__(self, parent, id):
  33             Grid.__init__(self, parent, id)
  34             DragGridRowMixin.__init__(self)
  35     """
  36     def __init__(self):
  37         rowWindow = self.GetGridRowLabelWindow()
  38         # various flags to indicate whether we should have a select
  39         # event or a drag event
  40         self._potentialDrag = False
  41         self._startRow = None
  42         self._shift, self._ctrl = None, None
  43 
  44         wx.EVT_LEFT_DOWN(rowWindow, self.OnDragGridLeftDown)
  45         wx.EVT_LEFT_UP(rowWindow, self.OnDragGridLeftUp)
  46         wx.EVT_LEFT_UP(self, self.OnDragGridLeftUp)
  47         wx.EVT_MOTION(rowWindow, self.OnDragGridMotion)
  48         wx.EVT_KEY_DOWN(self, self.OnDragGridKeyDown)
  49         wx.EVT_KEY_UP(self, self.OnDragGridKeyUp)
  50 
  51     def OnDragGridKeyDown(self, evt):
  52         """Set the states of the shift and ctrl keys"""
  53         self._shift, self._ctrl = (evt.ShiftDown(), evt.ControlDown())
  54 
  55     def OnDragGridKeyUp(self, evt):
  56         """unset the states of the shift and ctrl keys"""
  57         self._shift, self._ctrl = None, None
  58 
  59     def OnDragGridLeftDown(self, evt):
  60         """The left button is down so see if we are selecting rows or not
  61         and do the appropriate thing.  We need to do this because we
  62         are blocking the rowlabels so rows won't be selected."""
  63         x,y = evt.GetX(), evt.GetY()
  64         row, col = self.DragGridRowXYToCell(x,y, colheight=0)
  65 
  66         if not self._shift and not self._ctrl:
  67             self._startRow = row
  68             self.SelectRow(row)
  69         elif self._shift and not self._ctrl:
  70             if self._startRow > row:
  71                 start, end = row, self._startRow
  72             else:
  73                 start, end = self._startRow, row
  74             for row in range(start, end+1):
  75                 self.SelectRow(row, True)
  76         elif self._ctrl:
  77             self.SelectRow(row, True)
  78         self._potentialDrag = True
  79 
  80     def OnDragGridLeftUp(self, evt):
  81         """We are not dragging anymore, so unset the potentialDrag flag"""
  82         self._potentialDrag = False
  83         evt.Skip()
  84 
  85     def OnDragGridMotion(self, evt):
  86         """We are moving so see whether this should be a drag event or not"""
  87         if not self._potentialDrag:
  88             evt.Skip()
  89             return
  90 
  91         x,y = evt.GetX(), evt.GetY()
  92         row, col = self.DragGridRowXYToCell(x,y, colheight=0)
  93         rows = self.GetSelectedRows()
  94         if not rows or row not in rows:
  95             evt.Skip()
  96             return
  97         self.StartDrag(rows)
  98 
  99     def DragGridRowXYToCell(self, x, y, colheight=None, rowwidth=None):
 100         # For virtual grids, XYToCell doesn't work properly
 101         # For some reason, the width and heights of the labels
 102         # are not computed properly and thw row and column
 103         # returned are computed as if the window wasn't
 104         # scrolled
 105         # This function replaces XYToCell for Virtual Grids
 106 
 107         if rowwidth is None:
 108             rowwidth = self.GetGridRowLabelWindow().GetRect().width
 109         if colheight is None:
 110             colheight = self.GetGridColLabelWindow().GetRect().height
 111         yunit, xunit = self.GetScrollPixelsPerUnit()
 112         xoff =  self.GetScrollPos(wx.HORIZONTAL) * xunit
 113         yoff = self.GetScrollPos(wx.VERTICAL) * yunit
 114 
 115         # the solution is to offset the x and y values
 116         # by the width and height of the label windows
 117         # and then adjust by the scroll position
 118         # Then just go through the columns and rows
 119         # incrementing by the current column and row sizes
 120         # until the offset points lie within the computed
 121         # bounding boxes.
 122         x += xoff - rowwidth
 123         xpos = 0
 124         for col in range(self.GetNumberCols()):
 125             nextx = xpos + self.GetColSize(col)
 126             if xpos <= x <= nextx:
 127                 break
 128             xpos = nextx
 129 
 130         y += yoff - colheight
 131         ypos = 0
 132         for row in range(self.GetNumberRows()):
 133             nexty = ypos + self.GetRowSize(row)
 134             if ypos <= y <= nexty:
 135                 break
 136             ypos = nexty
 137 
 138         return row, col
 139 
 140     def StartDrag(self, selectedrows):
 141         """This starts the drag event, override this to send different drag
 142         types."""
 143         tdo = wx.PyTextDataObject(str(selectedrows))
 144         # Create a Drop Source Object, which enables the Drag operation
 145         tds = wx.DropSource(self.GetGridRowLabelWindow())
 146         # Associate the Data to be dragged with the Drop Source Object
 147         tds.SetData(tdo)
 148         # Intiate the Drag Operation
 149         tds.DoDragDrop(wx.true)
 150 
 151 if __name__ == "__main__":
 152     # -----------------------------------------------------------------------
 153     # Testing
 154     class GridTextDropTarget(wx.TextDropTarget):
 155         def __init__(self, grid):
 156             wx.TextDropTarget.__init__(self)
 157             self.grid = grid
 158 
 159         def OnDropText(self, x, y, text):
 160             # XYToCell doesn't behave quite right, so we'll just
 161             # grab the DragGridRowXYToCell that we fixed,
 162             # see the wx.Grid wiki for a better explanation
 163             # http://wiki.wxpython.org/index.cgi/wxGrid
 164             row, col = self.grid.DragGridRowXYToCell(x, y)
 165             if row > -1 and col > -1:
 166                 self.grid.SetCellValue(row, col, text)
 167                 self.grid.Refresh()
 168 
 169     class T(Grid, DragGridRowMixin):
 170         def __init__(self, parent, id, title):
 171             Grid.__init__(self, parent, id)
 172             self.CreateGrid(25,25)
 173             DragGridRowMixin.__init__(self)
 174             self.SetDropTarget(GridTextDropTarget(self))
 175 
 176     app = wx.PySimpleApp()
 177     frame = wx.Frame(None, -1, "hello")
 178     foo = T(frame, -1, "hello")
 179     frame.Show()
 180     app.MainLoop()

Capturing Cell Edits when focus changes

The default behavior of the grid control is to revert to the old value if the user changes focus before hitting enter. This can frustrate users when their edits are gone because they have clicked on another region of the GUI.

A wxGrid is actually a control made of several wxWindow components: The main grid, the table of cells, and a cell editor when a value is being modified.

To make the grid save its cell data, it's necessary to first bind to the EVT_GRID_EDITOR_CREATED event. Inside that event, then bind to the EVT_KILL_FOCUS event for the newly created control.

Note: This code is written from the perspective of the Frame containing the grid. It may of course be rewritten to embed the event handling within a class inheriting from wxGrid.

   1   def __init__(self):
   2       self.grid = wx.grid.Grid(self, -1)
   3       self.Bind(wx.EVT_GRID_EDITOR_CREATED, self.OnGridEditorCreated)
   4 
   5   def OnGridEditorCreated(self, event):
   6       """ Bind the kill focus event to the newly instantiated cell editor """
   7       editor = event.GetControl()
   8       editor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
   9       event.Skip()
  10 
  11   def OnKillFocus(self, event):
  12       # Cell editor's grandparent, the grid GridWindow's parent, is the grid.
  13       grid = event.GetEventObject().GetGrandParent()
  14       grid.SaveEditControlValue()
  15       grid.HideCellEditControl()
  16       event.Skip()

The above code snippet handles the case where a user changes focus by clicking on another control in your interface. However, clicking on the menu of a frame does not remove focus from the edit control, so the above code won't be triggered. If your users are like mine and fond of choosing Save from the menus without closing the cell editor, you can modify the above to compensate.

   1   def __init__(self):
   2       self.grid = wx.grid.Grid(self, -1)
   3       self.Bind(wx.EVT_GRID_EDITOR_CREATED, self.OnGridEditorCreated)
   4       self.Bind(wx.EVT_MENU_OPEN, self.OnMenuOpen)
   5 
   6   def OnGridEditorCreated(self, event):
   7       """ Bind the kill focus event to the newly instantiated cell editor """
   8       editor = event.GetControl()
   9       editor.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
  10       event.Skip()
  11 
  12   def OnKillFocus(self, event):
  13       # Cell editor's grandparent, the grid GridWindow's parent, is the grid.
  14       grid = event.GetEventObject().GetGrandParent()
  15       grid.SaveEditControlValue()
  16       grid.HideCellEditControl()
  17       event.Skip()
  18 
  19   def OnMenuOpen(self, event):
  20       self.grid.HideCellEditControl()

The EVT_MENU_OPEN handler above will generate an EVT_KILL_FOCUS event, saving the contents of the editor. Neither call to hide generates an EVT_GRID_EDITOR_HIDDEN event, but the call to Save does generate an EVT_GRID_CELL_CHANGE event, so you only want to call it once, in the EVT_KILL_FOCUS handler.

Back to the wxGrid Manual


   1     def _OnRangeSelect:
   2         top,bottom = event.GetTopRow(),event.GetBottomRow()+1
   3         left,right = event.GetLeftCol(),event.GetRightCol()+1
   4 
   5         if event.Selecting():
   6             # adding to the list...
   7             if bottom-top == self.GetNumberRows():
   8                 self.currentSelection[0] = range(top, bottom)
   9                 for index in range(left,right):
  10                     if index not in self.currentSelection[1]:
  11                         self.currentSelection[1].append(index)
  12             elif right-left == self.GetNumberCols():
  13                 self.currentSelection[1] = range(left, right)
  14                 for index in range(top,bottom):
  15                     if index not in self.currentSelection[0]:
  16                         self.currentSelection[0].append(index)
  17             else:
  18                 for index in range(top, bottom):
  19                     if index not in self.currentSelection[0]:
  20                         self.currentSelection[0].append(index)
  21                 for index in range(left, right):
  22                     if index not in self.currentSelection[1]:
  23                         self.currentSelection[1].append(index)
  24         else:
  25             # removal from list
  26             if bottom-top == self.GetNumberRows():
  27                 for index in range(left,right):
  28                     while index in self.currentSelection[1]:
  29                         self.currentSelection[1].remove(index)
  30             elif right-left == self.GetNumberCols():
  31                 for index in range(top,bottom):
  32                     while index in self.currentSelection[0]:
  33                         self.currentSelection[0].remove(index)
  34             else:
  35                 for index in range(top, bottom):
  36                     while index in self.currentSelection[0]:
  37                         self.currentSelection[0].remove(index)
  38                 for index in range(left, right):
  39                     while index in self.currentSelection[1]:
  40                         self.currentSelection[1].remove(index)

Additionally one has to catch the mouse click on the row/col labels to reset the current selection:

   1     def __init__(self,...):
   2         ...
   3         self.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClick)
   4         ...
   5 
   6     def OnLabelLeftClick(self, evt):
   7         self.currentSelection = [[],[]]
   8         evt.Skip()

--Christian

wxGrid (last edited 2013-01-06 17:21:38 by 173-228-91-121)