This question is about extended selection using the Shift key in a ListCtrl that is configured in Report view with multiple selection enabled.
If you left-click on an item in a ListCtrl with no modifier keys depressed, the item is selected and any items that were previously selected are deselected. If you left-click on an item with a Ctrl key depressed, you toggle its selection state without deselecting any other items that were previously selected. If you left-click on an item with a Shift key depressed, it will be selected, along with all the other items between it and the item on which the last left-click was made. Holding down a Shift key and pressing the Up or Down arrow keys also extends the selection.
In my application I have programmed a ListCtrl to display a context menu when you right click on an item. If the item was not previously selected then it gets set as the only selected item, then menu options are configured accordingly. The problem I have is that, if I then Shift + left-click on another item in the ListCtrl, the start point for the extended selection is not the item that was selected by the right-click, but whichever item that had been left-clicked before it.
I have been looking to see if there is a way to programmatically override the start point for the extended selection. I did wonder if setting the focus along with the selection, using the following statements, would work.
SEL_FOC = wx.LIST_STATE_SELECTED | wx.LIST_STATE_FOCUSED
self.list_ctrl.SetItemState(item, SEL_FOC, SEL_FOC)
However, that made no difference. I also tried using an UltimateListCtrl, but that behaved the same way as the ListCtrl.
Here is a simple example that can be used to demonstrate the issue:
import sys
import wx
DATA = [
("A", "Alpha"), ("B", "Bravo"), ("C", "Charlie"), ("D", "Delta"),
("E", "Echo"), ("F", "Foxtrot"), ("G", "Golf"), ("H", "Hotel"),
("I", "India"), ("J", "Juliet"), ("K", "Kilo"), ("L", "Lima"),
("M", "Mike"), ("N", "November"), ("O", "Oscar"), ("P", "Papa"),
("Q", "Quebec"), ("R", "Romeo"), ("S", "Sierra"), ("T", "Tango"),
("U", "Uniform"), ("V", "Victor"), ("W", "Whisky"), ("X", "X-ray"),
("Y", "Yankee"), ("Z", "Zulu")
]
class ListFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((210, 620))
self.SetTitle("Test ListCtrl selections")
self.main_panel = wx.Panel(self, wx.ID_ANY)
main_sizer = wx.BoxSizer(wx.VERTICAL)
self.list_ctrl = wx.ListCtrl(self.main_panel, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
self.list_ctrl.AppendColumn("Letter", format=wx.LIST_FORMAT_LEFT, width=-1)
self.list_ctrl.AppendColumn("Word", format=wx.LIST_FORMAT_LEFT, width=-1)
for letter, word in DATA:
index = self.list_ctrl.InsertItem(sys.maxsize, letter)
self.list_ctrl.SetItem(index, 1, word)
self.list_ctrl.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
self.list_ctrl.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
self._preparePopupMenu()
main_sizer.Add(self.list_ctrl, 1, wx.EXPAND, 0)
self.main_panel.SetSizer(main_sizer)
self.Layout()
def _preparePopupMenu(self):
"""Prepare settings for the list control's popup menu. """
self.popup_edit_id = wx.NewIdRef()
self.popup_print_id = wx.NewIdRef()
self.Bind(wx.EVT_MENU, self.OnPopupEdit, id=self.popup_edit_id)
self.Bind(wx.EVT_MENU, self.OnPopupPrint, id=self.popup_print_id)
def deselectAllRecords(self):
"""Deselect all records in the list ctrl. """
item = self.list_ctrl.GetFirstSelected()
while item != wx.NOT_FOUND:
self.list_ctrl.Select(item, on=False)
item = self.list_ctrl.GetNextSelected(item)
def popupContextMenu(self):
num_selected = self.list_ctrl.GetSelectedItemCount()
if num_selected == 0:
return
edit_enabled = num_selected == 1
popup_menu = wx.Menu()
popup_menu.Append(self.popup_edit_id, "Edit")
popup_menu.Enable(self.popup_edit_id, edit_enabled)
popup_menu.Append(self.popup_print_id, "Print selections")
self.PopupMenu(popup_menu)
popup_menu.Destroy()
def OnPopupPrint(self, _event):
words = []
index = self.list_ctrl.GetFirstSelected()
while index != wx.NOT_FOUND:
words.append(self.list_ctrl.GetItemText(index, 1))
index = self.list_ctrl.GetNextSelected(index)
print(", ".join(words))
def OnPopupEdit(self, _event):
if self.list_ctrl.GetSelectedItemCount() != 1:
return
index = self.list_ctrl.GetFirstSelected()
word = self.list_ctrl.GetItemText(index, 1)
dlg = wx.TextEntryDialog(self, "Edit selected word:", 'Edit')
dlg.SetValue(word)
result = dlg.ShowModal()
if result == wx.ID_OK:
new_word = dlg.GetValue()
self.list_ctrl.SetItem(index, 1, new_word)
def OnRightDown(self, event):
x = event.GetX()
y = event.GetY()
item, flags = self.list_ctrl.HitTest((x, y))
if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
if not self.list_ctrl.IsSelected(item):
# Make the item the only selected one
self.deselectAllRecords()
self.list_ctrl.Select(item)
def OnRightUp(self, _event):
self.popupContextMenu()
class MyApp(wx.App):
def OnInit(self):
self.frame = ListFrame(None, wx.ID_ANY, "")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
I am using Python 3.8.10 + wxPython 4.1.1 gtk3 (phoenix) wxWidgets 3.1.5 + Linux Mint 20.2
Does anyone know how to programmatically override the start point of an extended selection in a ListCtrl?