ListCtrl.GetSelectedItemCount() giving wrong result after DeleteItem()?

I have an application with a wx.ListCtrl configured for report mode and multiple selections.

I have noticed that after calling the DeleteItem() method, the value returned by GetSelectedItemCount() doesn’t match the actual number of items selected in the listctrl.

Here is a simplified example that demonstrates the issue:

import wx

class MyFrame(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((400, 300))
        self.SetTitle("Test ListCtrl")

        self.panel = wx.Panel(self, wx.ID_ANY)

        main_sizer = wx.BoxSizer(wx.VERTICAL)

        self.list_ctrl = wx.ListCtrl(self.panel, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        self.list_ctrl.AppendColumn("A", format=wx.LIST_FORMAT_LEFT, width=-1)
        main_sizer.Add(self.list_ctrl, 1, wx.EXPAND, 0)

        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        main_sizer.Add(button_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.TOP, 8)

        self.reload_button = wx.Button(self.panel, wx.ID_ANY, "Reload Items")
        button_sizer.Add(self.reload_button, 0, wx.RIGHT, 16)

        self.delete_button = wx.Button(self.panel, wx.ID_ANY, "Delete Selected")
        button_sizer.Add(self.delete_button, 0, 0, 0)

        self.panel.SetSizer(main_sizer)

        self.Layout()

        self.Bind(wx.EVT_BUTTON, self.OnDeleteSelected, self.delete_button)
        self.Bind(wx.EVT_BUTTON, self.OnReloadItems, self.reload_button)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelected, self.list_ctrl)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnDeselected, self.list_ctrl)

        self.insertItems()


    def insertItems(self):
        for i in range(0, 6):
            self.list_ctrl.InsertItem(i, "Item %d" % i)


    def OnDeleteSelected(self, event):
        indices = []
        index = self.list_ctrl.GetFirstSelected()
        while index != wx.NOT_FOUND:
            indices.append(index)
            index = self.list_ctrl.GetNextSelected(index)
        for index in reversed(indices):
            self.list_ctrl.DeleteItem(index)
            print("Deleted item %d" % index)


    def OnDeselected(self, event):
        print("Deselected index %d,  num selected = %d" % (event.GetIndex(), self.list_ctrl.GetSelectedItemCount()))


    def OnReloadItems(self, _event):
        self.list_ctrl.DeleteAllItems()
        self.insertItems()


    def OnSelected(self, event):
        print("Selected index %d,  num selected = %d" % (event.GetIndex(), self.list_ctrl.GetSelectedItemCount()))


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

If I run this code and then do the following steps:
Click on Item 0
Click on Item 1
Click on “Delete Selected” button
Click on Item 3
Click on Item 4

It produces the following output:

Selected index 0,  num selected = 1
Deselected index 0,  num selected = 0
Selected index 1,  num selected = 1
Deleted item 1
Selected index 2,  num selected = 2
Deselected index 2,  num selected = 1
Selected index 3,  num selected = 2

Notice that after calling DeleteItem(), the values returned by GetSelectedItemCount() are wrong. It seems to be treating the item(s) that have been deleted as still being part of the selected items?

If I then click on the “Reload Items” button, GetSelectedItemCount() starts returning the correct values once more.

I am using Python 3.10.6 + wxPython 4.2.0 gtk3 (phoenix) wxWidgets 3.2.0 on Linux Mint 21.1.

Is this a bug, or am I missing something?

it’s not a bug :wink:

Could you expand on the assertion that it is not a bug, because it looks like a bug and smells like bug, usually that indicates a bug.
Indeed if you add an extra function to the OnSelected print

print("Selected index %d,  num selected = %d, of %d" % (event.GetIndex(), self.list_ctrl.GetSelectedItemCount(), self.list_ctrl.GetItemCount()))

You get the ridiculous position after deleting 3 items of:

Selected index 1,  num selected = 1, of 6
Selected index 2,  num selected = 2, of 6
Selected index 3,  num selected = 3, of 6
Deleted item 3
Deleted item 2
Deleted item 1
Selected index 1,  num selected = 4, of 3

So the number of selected items is 4 but only 3 items are in the listctrl.
It reeks of insect! :wink:

1 Like

I have found that if I explicitly call self.list_ctrl.Select(index, False) before calling self.list_ctrl.DeleteItem(index) then GetSelectedItemCount() will return the correct values.

This suggests that GetSelectedItemCount() is not actually counting the selected items each time it is called, but the ListCtrl is holding a running total that it updates each time Select() is called. However (on GTK at least) it’s not updating the total when DeleteItem() is called on its own.

I did find an ancient report on wxWidgets for a similar issue on GTK, but it was closed because it couldn’t be replicated at the time:

Tested on Windows 10.
Python 3.10.11 wx version 4.2.1a1.dev5545
Python 3.8.10 wx version 4.2.0

Following your steps, I got the following results:

Selected index 0,  num selected = 1
Deselected index 0,  num selected = 0
Selected index 1,  num selected = 1
Deselected index 1,  num selected = 0
Deleted item 1
Selected index 2,  num selected = 1
Deselected index 2,  num selected = 0
Selected index 3,  num selected = 1

It seems no bug on Windows (EVT_LIST_ITEM_DESELECTED event comes before deletion; see Line 4).

1 Like

well, I only have a Windows, and there it ‘works’ (see further down) :rofl:

@RichardT, it would be interesting to know if the list on screen is correct (which I strongly assume) and if so simply your printouts played a trick on (not only) you :confounded: (maybe just wait or queue the GetSelectedItemCount())

@da-dada - the list on the screen does show the correct selection(s).

I have created a modified version of the test app. This one has an additional button “Get Selected Item Count”. Its event handler prints out the value returned by GetSelectedItemCount().

import wx

class MyFrame(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((420, 300))
        self.SetTitle("Test Selected Item Count")

        self.panel = wx.Panel(self, wx.ID_ANY)

        main_sizer = wx.BoxSizer(wx.VERTICAL)

        self.list_ctrl = wx.ListCtrl(self.panel, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        self.list_ctrl.AppendColumn("A", format=wx.LIST_FORMAT_LEFT, width=-1)
        main_sizer.Add(self.list_ctrl, 1, wx.EXPAND, 0)

        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        main_sizer.Add(button_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.TOP, 8)

        self.reload_button = wx.Button(self.panel, wx.ID_ANY, "Reload Items")
        button_sizer.Add(self.reload_button, 0, wx.RIGHT, 16)

        self.delete_button = wx.Button(self.panel, wx.ID_ANY, "Delete Selected")
        button_sizer.Add(self.delete_button, 0, wx.RIGHT, 16)

        self.get_selected_item_count_button = wx.Button(self.panel, wx.ID_ANY, "Get Selected Item Count")
        button_sizer.Add(self.get_selected_item_count_button, 0, 0, 0)

        self.panel.SetSizer(main_sizer)

        self.Layout()

        self.Bind(wx.EVT_BUTTON, self.OnDeleteSelected, self.delete_button)
        self.Bind(wx.EVT_BUTTON, self.OnGetSelItemCount, self.get_selected_item_count_button)
        self.Bind(wx.EVT_BUTTON, self.OnReloadItems, self.reload_button)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelected, self.list_ctrl)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnDeselected, self.list_ctrl)

        self.insertItems()


    def insertItems(self):
        for i in range(0, 6):
            self.list_ctrl.InsertItem(i, "Item %d" % i)


    def OnDeleteSelected(self, event):
        indices = []
        index = self.list_ctrl.GetFirstSelected()
        while index != wx.NOT_FOUND:
            indices.append(index)
            index = self.list_ctrl.GetNextSelected(index)
        for index in reversed(indices):
            # Uncomment this line to fix selected item count:
            # self.list_ctrl.Select(index, False)
            self.list_ctrl.DeleteItem(index)
            print("Deleted item %d" % index)


    def OnDeselected(self, event):
        print("Deselected index %d,  num selected = %d" % (event.GetIndex(), self.list_ctrl.GetSelectedItemCount()))


    def OnGetSelItemCount(self, _event):
        print("num selected = %d" % self.list_ctrl.GetSelectedItemCount())


    def OnReloadItems(self, _event):
        self.list_ctrl.DeleteAllItems()
        self.insertItems()


    def OnSelected(self, event):
        print("Selected index %d,  num selected = %d" % (event.GetIndex(), self.list_ctrl.GetSelectedItemCount()))


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

I then did the same steps as in my first post above and got the same output.

The frame ended up looking like this:

Screenshot at 2023-05-27 20-29-59

I then waited ~ 20 seconds and pressed the “Get Selected Item Count” button. This produced the following output:

num selected = 2

I also tried using wx.CallAfter()in the OnGetSelItemCount() method to make the GetSelectedItemCount() call, but it still returned the wrong value.

However, if I uncomment the self.list_ctrl.Select(index, False) call in the OnDeleteSelected() method, all subsequent calls to GetSelectedItemCount() return the correct values.

EDIT: I also made another version that included the following method:

    def getSelectedIndices(self):
        indices = []
        index = self.list_ctrl.GetFirstSelected()
        while index != wx.NOT_FOUND:
            indices.append(index)
            index = self.list_ctrl.GetNextSelected(index)
        return indices

That method always returned the same selections as shown on the screen after deleting an item, thus contradicting the values returned by GetSelectedItemCount().

1 Like

well, @RichardT, my thinking is pretty simple

since the table on screen looks correct the gap must be in trying to

  • understand the sequencing of events or

  • what may have the inventor driven

the first was a first shot & since all involved stuff operates on the same object it had a limited chance (my suggestion but with all that threading and changing, you know yourself)

the second idea points to what you have already been doing,

all I would say DeleteItem(index) already has an index so selecting that same index first would suggest the methods of wx.ListCtrl are in a mess (which would be more than a bug)

so I would rather assume a call of GetFirstSelected() may be required and according to the result information on the selected items is updated (at least that is how I would have done it, economical) :cowboy_hat_face:

At least on Linux, your solution Richard, appears to be the only one that works i.e. deselecting the item manually just prior to deletion.
I tried deleting via a while True loop using only self.list_ctrl.GetFirstSelected() rather than build an index list and reversing it and that doesn’t work either.
It seems that on Gtk, the update to the selected total, that should occur upon deletion, has been forgotten.
To reiterate, on Gtk your solution:

                self.list_ctrl.Select(index, False)
                self.list_ctrl.DeleteItem(index)

Seems the way to go.

1 Like