EVT_LISTBOX handler not called after first item in Listbox is deleted

Using the example below:

  1. Click the Add button twice. Two items are added to the listbox. Neither item is selected and the Delete button is disabled.
  2. Click on the first item. The item is selected, the OnListbox() method is called and the Delete button is enabled.
  3. Click the Delete button. The first item is deleted from the listbox and the Delete button is disabled.
  4. Click on the remaining item in the listbox. The item is selected, however the OnListbox() method is not called and the Delete button remains disabled.

If I rerun the program and repeat the above steps, but this time select and delete the last item in the listbox, the remaining item is automatically selected and the Delete button is enabled.

As a workaround, uncomment the code in OnDelete() to explicitly reselect an item following the deletion and the Delete button will then be enabled.

The behaviour when deleting the first item without the workaround seems weird and inconsistent. Is this a known problem?

I am using Python 3.8.5, wxPython 4.1.1 gtk3 (phoenix) wxWidgets 3.1.5 on Linux Mint 20.

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 1.0.0 on Fri Dec 25 20:42:38 2020
#

import wx

# begin wxGlade: dependencies
# end wxGlade

# begin wxGlade: extracode
# end wxGlade


class MyDialog(wx.Dialog):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyDialog.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_DIALOG_STYLE
        wx.Dialog.__init__(self, *args, **kwds)
        self.SetSize((250, 220))
        self.SetTitle("Listbox Test")

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        self.list_box_1 = wx.ListBox(self, wx.ID_ANY, choices=[])
        self.list_box_1.SetMinSize((88, 60))
        sizer_1.Add(self.list_box_1, 1, wx.EXPAND, 0)

        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
        sizer_1.Add(sizer_2, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.TOP, 4)

        self.add_button = wx.Button(self, wx.ID_ANY, "Add")
        sizer_2.Add(self.add_button, 0, 0, 0)

        self.delete_button = wx.Button(self, wx.ID_ANY, "Delete")
        sizer_2.Add(self.delete_button, 0, 0, 0)

        self.SetSizer(sizer_1)

        self.Layout()
        # end wxGlade

        self.Bind(wx.EVT_BUTTON,  self.OnAdd,     self.add_button)
        self.Bind(wx.EVT_BUTTON,  self.OnDelete,  self.delete_button)
        self.Bind(wx.EVT_LISTBOX, self.OnListbox, self.list_box_1)
        self.configureButton()

    def configureButton(self):
        has_selection = self.list_box_1.GetSelection() != wx.NOT_FOUND
        self.delete_button.Enable(has_selection)

    def OnAdd(self, _event):
        self.list_box_1.Append("item")
        self.configureButton()

    def OnDelete(self, _event):
        index = self.list_box_1.GetSelection()
        if index != wx.NOT_FOUND:
            self.list_box_1.Delete(index)
            # Reselect an item
            # num_items = self.list_box_1.GetCount()
            # if num_items > 0:
            #     index = min(index, num_items-1)
            #     self.list_box_1.Select(index)
            self.configureButton()

    def OnListbox(self, _event):
        print("OnListbox")
        self.configureButton()

# end of class MyDialog

class MyApp(wx.App):
    def OnInit(self):
        self.dialog = MyDialog(None, wx.ID_ANY, "")
        self.SetTopWindow(self.dialog)
        self.dialog.ShowModal()
        self.dialog.Destroy()
        return True

# end of class MyApp

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

    import platform
    python_version = platform.python_version()
    wxPython_version = wx.version()
    print("Python %s, wxPython %s" % (python_version, wxPython_version))

I’m not sure if it’s known. Please create a ticket about it at https://trac.wxwidgets.org/ if there isn’t one there already. There are some differences between platforms when deleting the selected item, which is to be expected when using native widgets. It may be that wxGTK is missing some interaction with the native listbox…

Actually, now that I think about it a bit more it may actually be due to an expected behavior… Other than a few exceptions the general rule in wxWidgets is to only send events for changes directly caused by a user action, and that those coming from programmatic actions would not cause an event to be sent. In this case one could argue that deleting an item is a programmatic action and so the subsequent automatic selection of another item (for the platforms that do that) would not cause an event to be sent…

So with that in mind, another possible workaround that may work well for you would be to call configureButton from a timer rather than explicitly calling it in response to the other events. That way the state of the delete button doesn’t depend on there being a selection event when an item is deleted. You could also try using wx.EVT_UPDATE_UI events to manage the state of the button.

Thanks for your reply, Robin. I take your point about programmatic actions not causing an event to be sent. I will investigate your alternative workarounds and see if they work better in my actual application than what I’m currently doing.