forced single row selection in wxGrid (was RE: m ulti-line fields in wxListCtrl?)

I (Will Sadkin) wrote:

1) I need to have clicking in any part of the row select
   the entire row, and I need to enforce that only one row
   be selected at a time. I thought I had a solution for this,
   but it doesn't work right.
[...]

In the code using the class, I've also registered a handler on
EVT_GRID_RANGE_SELECT, to wit:
  EVT_GRID_RANGE_SELECT(self.mygrid, self.OnSelectRule)

  def OnSelectRule(self, event=None):
        print 'parent's OnSelectRule'
    [...]

but it *never fires*, even though I'm clearly calling evt.skip()...
So I don't understand why the GRID_RANGE_SELECT event doesn't
propagate up the event handler chain, so the parent can take action
when the row selection changes.

Can anyone tell my why this doesn't work, or what I have to do
to get it to work?
[...]

Well, I found a workaround for this issue (1 of 3).

The behavior I'm seeing would be explained if the
EVT_GRID_RANGE_SELECT event was not derived from wxCommandEvent
(like it says it is in the documentation), and so isn't pushed
up the event chain like (at least many) other wxGrid events.
I don't have ready access to the C++, so I can't tell for sure,
but it seems plausible...

However, wxPython is nothing if not flexible, so...
I invented my own EVT_GRID_ROW_SELECTED event, and now fire
that from the grid's internal EVT_GRID_RANGE_SELECT routine.
I also had to change the code to use wxCallAfter to get the
row selection to work properly. so, the code is now of
the form:

wxEVT_GRID_ROW_SELECTED = wxNewEventType()
def EVT_GRID_ROW_SELECTED(win, id, func):
    """Used to trap events indicating that the current
    row selection of the control has been changed."""
    win.Connect(id, -1, wxEVT_GRID_ROW_SELECTED, func)

class wxGridRowSelectedEvent(wxPyCommandEvent):
    def __init__(self, id, row = 0, object=None):
        wxPyCommandEvent.__init__(self,
              wxEVT_GRID_ROW_SELECTED, id)

        self.__row = row
        self.SetEventObject(object)

    def GetRow(self):
        """Retrieve the value of the control at the time
        this event was generated."""
        return self.__row

class myGrid(wxGrid):
    def __init__(self, parent, id=-1, pos=wxDefaultPosition,
                 size=wxDefaultSize, style=wxWANTS_CHARS):
        wxGrid.__init__(self, parent, id, pos=pos, size=size)

        self.SetSelectionMode(wxGrid.wxGridSelectRows)
        self.__leftClickRow = None
    [...]
        EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect)

    def OnRangeSelect(self, evt):
            
        top_left, bottom_right = evt.GetTopLeftCoords(), \
             evt.GetBottomRightCoords()
        if top_left[0] != bottom_right[0] and evt.Selecting():
            if self.__leftClickRow is not None:
                # pick last row selected
                if top_left[0] == self.__leftClickRow:
                    selectrow = bottom_right[0]
                else:
                    selectrow = top_left[0]
                self.__leftClickRow = None # reset for next event
            else:
                # pick top left (arbitrarily)
                selectrow = top_left[0]
        else:
            selectrow = top_left[0]
        selected = self.GetSelectedRows()
        if( evt.Selecting() and len(selected) != 1
            or (len(selected) == 1 and selected[0] != selectrow)):
            wxCallAfter(self.SelectRow, selectrow )
        evt.Skip()

    def SelectRow(self, i):
        wxGrid.SelectRow(self, i)
        try:
            self.GetEventHandler().ProcessEvent(
                wxGridRowSelectedEvent( self.GetId(), i, self ) )
        except RangeError:
            return

It'd be nice if someone could confirm my suspicions re:
EVT_GRID_RANGE_SELECT and let me know if I should file a
bugreport about this. Also, if there's an easier way to
get the effect I want, I'm all ears!

(Note: I'm still looking for answers on my other two wxGrid
questions.)

Regards,
/Will Sadkin
Parlance Corporation