Unbind `EVT_MENU` all at once

Hi there

I have an application in which Menubar and context menus are created and destroyed many times at run-time. I recently noticed that Unbind(wx.EVT_MENU) always fails, where many ids are bound to it. Is it possible to unbind all handlers bound to wx.EVT_MENU all at once?

To demonstrate it,

import wx
from wx.py.shell import Shell

app = wx.App()
frm = wx.Frame(None)
if 1: # Add some local vars to the shell.
    frm.self = frm
    frm.this = __import__('__main__')
    frm.shell = Shell(frm, locals=frm.__dict__)
frm.Show()
app.MainLoop()

Note the shell has a context-menu for copy, paste, etc.
If the id is given explicitly, it works and the handler for the ID_COPY menu is removed.

>>> self.shell.Unbind(wx.EVT_MENU, id=self.shell.ID_COPY)
True

But,

>>> self.shell.Unbind(wx.EVT_MENU)
False

I’ve not used it, but I wonder if the AppEventHandlerMixin class in wx.lib.eventStack might be applicable?

The wxWidgets documentation for wxEvtHandler::Unbind describes the id parameter as “The first ID of the identifier range associated with the event handler”. It does not say anything about wx.ID_ANY being a wildcard.

The feature you are trying to use does not exist.

Hi @RichardT,

Thank you for the information.
Unfortunately, it doesn’t seem to fit my purpose. But, the idea in wx.lib.eventStack is interesting and it may be useful for another chance.

Hi @AndersMunch,

You are right.
I misunderstood that specifying only the event argument removes all handlers. Actually, I found that it removes the last bound handler. I think this is one of the “search criteria” described in the document for Unbind:

wxWidgets: wxEvtHandler Class Reference

Unbinds the given function, functor or method dynamically from the event handler, using the specified parameters as search criteria and returning true if a matching function has been found and removed.

Not explicitly stated, but other “criteria” would be “If you bind an event handler using id, you must specify the id to unbind”.

A code snippet to demonstrate it:

import wx

class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        btn = wx.Button(self, label='test')

        ## btn.Bind(wx.EVT_BUTTON, self.OnButton1)
        ## btn.Bind(wx.EVT_BUTTON, self.OnButton2)
        ## print(btn.Unbind(wx.EVT_BUTTON)) #-> True: Unbind the last handler

        self.Bind(wx.EVT_BUTTON, self.OnButton1, btn)
        print(self.Unbind(wx.EVT_BUTTON)) #-> False
        print(self.Unbind(wx.EVT_BUTTON, btn)) #-> True: `source` or `id` must be specified.

    def OnButton1(self, evt):
        print("OnButton1:evt.Id =", evt.Id)
        evt.Skip()

    def OnButton2(self, evt):
        print("OnButton2:evt.Id =", evt.Id)
        evt.Skip()

if __name__ == "__main__":
    app = wx.App()
    frm = Frame(None)
    frm.Show()
    app.MainLoop()

Solved.

def _unbind(menu):
    """Unbind all handlers from the menu."""
    for item in menu.MenuItems:
        if item.Id != wx.ID_SEPARATOR:
            self.Unbind(wx.EVT_MENU, item)
            self.Unbind(wx.EVT_UPDATE_UI, item)
            self.Unbind(wx.EVT_MENU_HIGHLIGHT, item)
        if item.SubMenu:
            _unbind(item.SubMenu)

Thanks all!