Confusing ListCtrl behavior.

I've been working on a part of an application that edits rows in a database. An overview of the table (narrowed to the search criteria given) is given in a wx.ListCtrl on one side of the panel, and controls to edit the selected database row are on the other side of the panel. If the user selects a new row to edit before saving the current one, I pop up a save/cancel/discard dialog before allowing the new selection to stick.

It is most important that the selection in the listctrl matches the content of the edit controls (the visual behavior becomes very confusing if the listctrl selection and the edit controls are allowed to be out of sync - the program handles it fine, the average user does not). If there is data, especially unsaved data, in the edit controls, the matching listctrl entry must be selected.

The problem occurs when the user selects a new listctrl entry, is reminded that the current entry is modified, and decides to finish working with the current entry. It seems if you de-select the new selection and re-select the old selection in the EVT_LIST_ITEM_SELECTED handler, wx will send another selection event (for a total of 4*). This only happens with mouse selection, if the user uses the keyboard to navigate the listctrl, events are sent just once. The end effect being the user is sometimes asked multiple times if they want to save/discard their changes or cancel the new selection.

* When a new item is selected by mouse:
1 event when the selection changes to the item the user clicked on
1 event when the handler changes the selection back to the old selection
1 event when the selection changes to the item the user clicked on
1 event when the handler changes the selection back to the old selection

* When a new item is selected by keyboard:
1 event when the selection changes by keyboard
1 event when the handler changes the selection back to the old selection

I have found a really bad work-around that usually works, but it contains a race condition that it sometimes loses. I'm hoping for a for better idea. The attached snippet shows the problem, and the almost always functional work-around.

listctrl.py (3 KB)

Zach Heilig wrote:

The problem occurs when the user selects a new listctrl entry, is reminded that the current entry is modified, and decides to finish working with the current entry. It seems if you de-select the new selection and re-select the old selection in the EVT_LIST_ITEM_SELECTED handler, wx will send another selection event (for a total of 4*). This only happens with mouse selection, if the user uses the keyboard to navigate the listctrl, events are sent just once. The end effect being the user is sometimes asked multiple times if they want to save/discard their changes or cancel the new selection.

I have found a really bad work-around that usually works, but it contains a race condition that it sometimes loses. I'm hoping for a for better idea. The attached snippet shows the problem, and the almost always functional work-around.

You may have better luck using the deselection event, something like this:

     def evt_deselected(self, event):
         if event.m_itemIndex == self.cur_select:
             dlg = wx.MessageDialog(
                 self,
                 'Allow selection to change?',
                 'Some Dialog',
                 wx.YES|wx.NO|wx.CENTRE|wx.ICON_WARNING)

             answer = dlg.ShowModal()
             dlg.Destroy()
             if answer == wx.ID_NO:
                 self.allowSelection = False
                 wx.CallAfter(
                     self.list_ctrl_1.SetItemState,
                     self.cur_select,
                     wx.LIST_STATE_FOCUSED|wx.LIST_STATE_SELECTED,
                     )
             else:
                 self.allowSelection = True

     def evt_selected(self, event):
         if self.allowSelection:
             new_select = event.m_itemIndex
             self.cur_select = new_select
             self.list_ctrl_1.Focus(new_select)

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn wrote:

Zach Heilig wrote:

The problem occurs when the user selects a new listctrl entry, is reminded that the current entry is modified, and decides to finish working with the current entry. It seems if you de-select the new selection and re-select the old selection in the EVT_LIST_ITEM_SELECTED handler, wx will send another selection event (for a total of 4*). This only happens with mouse selection, if the user uses the keyboard to navigate the listctrl, events are sent just once. The end effect being the user is sometimes asked multiple times if they want to save/discard their changes or cancel the new selection.

You may have better luck using the deselection event, something like this:

    def evt_deselected(self, event):

After testing this, it does not change the behavior. The user is still asked twice if they cancel the selection change. Using wx.CallLater to reset the selection just feels wrong, as the delay needs to be longer on highly loaded or slower systems. Selecting a delay large enough to work, yet small enough to not appear sluggish seems impossible.

Thanks for the deselected suggesting anyway though, determining whether or not to pop up a dialog in the real app is significantly cleaner in the EVT_LIST_ITEM_DESLECTED handler.

Zach Heilig wrote:

Robin Dunn wrote:

Zach Heilig wrote:

The problem occurs when the user selects a new listctrl entry, is reminded that the current entry is modified, and decides to finish working with the current entry. It seems if you de-select the new selection and re-select the old selection in the EVT_LIST_ITEM_SELECTED handler, wx will send another selection event (for a total of 4*). This only happens with mouse selection, if the user uses the keyboard to navigate the listctrl, events are sent just once. The end effect being the user is sometimes asked multiple times if they want to save/discard their changes or cancel the new selection.

You may have better luck using the deselection event, something like this:

    def evt_deselected(self, event):

After testing this, it does not change the behavior. The user is still asked twice if they cancel the selection change.

It worked in your sample app, did you try that or just your real app?

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!