Empty event object for submenu

In my application, which has a menu in the tray, I’d like to handle an event to show a submenu, and I’ve seen some strange behaviour. I can’t get the submenu object from the event.
I wrote a test application with a main menu and a context popup menu, and the event handling works differently in them. In the main menu, events in the submenu return the object that triggered the event, but in the popup menu they don’t.

import wx

class MenuFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None)
        menubar = wx.MenuBar()
        menubar.Append(build_menu(), "menu")
        self.SetMenuBar(menubar)
        self.Bind(wx.EVT_MENU_OPEN, on_menu)
        self.Bind(wx.EVT_MENU_HIGHLIGHT, on_menu)
        self.Bind(wx.EVT_CONTEXT_MENU, self.popup_menu)

    def popup_menu(self, event):
        self.PopupMenu(build_menu())

def on_menu(event):
    print('--------')
    print(event)
    print(event.EventObject)

def build_menu():
    menu = wx.Menu()
    menuitem = menu.Append(wx.ID_ANY, 'MenuItem 1')
    menuitem = menu.Append(wx.ID_ANY, 'MenuItem 2')
    submenu = wx.Menu()
    submenu.Append(wx.ID_ANY, 'SubMenuItem 1')
    submenu.Append(wx.ID_ANY, 'SubMenuItem 2')
    menu.AppendSubMenu(submenu, 'Submenu')
    return menu

if __name__ == '__main__':
    app = wx.App()
    frame = MenuFrame()
    frame.Show()
    app.MainLoop()

For context submenu in console:

<wx._core.MenuEvent object at 0x0364A928>
None

Can anyone explain why this happens?

Hi & welcome to Discuss wxPython,

I ran your code using Python 3.10.12 + wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.2.1 on Linux Mint 21.2.

When I right-click, the context menu is displayed. When I place the mouse pointer over the submenu, all the events show an actual Menu object and do not show None.

I added a print("submenu =", submenu) statement in the build_menu() method.

When I ran the program it printed:

submenu = <wx._core.Menu object at 0x7fd15fc65090>

When I right-clicked it printed:

submenu = <wx._core.Menu object at 0x7fd15fc65240>

When I interacted with the submenu on the popup menu it printed:

<wx._core.MenuEvent object at 0x7fd15fc652d0>
<wx._core.Menu object at 0x7fd15fc65240>

It seems to be working on linux.

What version of wxPython are you using? What OS are you running on?

Thanks for testing. I am use Python 3.8.10, wxPython 4.2.1 on Windows 10
Seems only windows issue.

What happens if you use event.GetMenu() instead of event.EventObject ?

I test different event method early for get object, all return Null
And just test python-3.11.5-amd64.exe - the same as before.

there are many events going through the system (event loop) and the principle is you only Bind to those events you are interested in (and may have to Skip them for other interesting parties) :wink:

import wx

class EventFilter(wx.EventFilter):
    def __init__(self):
        super().__init__()
        self.internals = {
            wx.wxEVT_UPDATE_UI, wx.wxEVT_IDLE}
        wx.EvtHandler.AddFilter(self)

    def FilterEvent(self, evt):
        # if evt.GetEventType() not in self.internals:
        print(f'++++ {evt.GetId()} --> {evt}')
        return self.Event_Skip

    def __del__(self):
        wx.EvtHandler.RemoveFilter(self)

class MenuFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None)
        menubar = wx.MenuBar()
        menubar.Append(build_menu(), "menu")
        self.SetMenuBar(menubar)
        self.Bind(wx.EVT_MENU_OPEN, on_menu)
        self.Bind(wx.EVT_MENU_HIGHLIGHT, on_menu)
        self.Bind(wx.EVT_CONTEXT_MENU, self.popup_menu)
        self.evf = EventFilter()

    def popup_menu(self, event):
        self.PopupMenu(build_menu())

def on_menu(event):
    print('--------')
    print(event)
    print(event.EventObject)

def build_menu():
    menu = wx.Menu()
    menuitem = menu.Append(wx.ID_ANY, 'MenuItem 1')
    menuitem = menu.Append(wx.ID_ANY, 'MenuItem 2')
    submenu = wx.Menu()
    submenu.Append(wx.ID_ANY, 'SubMenuItem 1')
    submenu.Append(wx.ID_ANY, 'SubMenuItem 2')
    menu.AppendSubMenu(submenu, 'Submenu')
    return menu

if __name__ == '__main__':
    app = wx.App()
    frame = MenuFrame()
    frame.Show()
    app.MainLoop()

Thanks for the addition, but I meant why the event for submenu in context menu has an empty object?
And how in this case I can understand which submenu was opened in the context menu if there are several of them?

Please can you explain why you are trying to get the submenu?

What do you intend to do with the submenu afterwards?

I would like to implement lazy submenu loading for creating menu structure as a subfolder structure in a specific folder, with menu items corresponding to a given file type. In some cases there can be a lot of files and going through all the folders, filtering files and loading icons for them creates a nasty delay.

Does the following change make any difference?

    def __init__(self):
        wx.Frame.__init__(self, None)
        menubar = wx.MenuBar()
        menubar.Append(build_menu(), "menu")
        self.SetMenuBar(menubar)
        # self.Bind(wx.EVT_MENU_OPEN, on_menu)
        self.Bind(wx.EVT_MENU_HIGHLIGHT_ALL, on_menu)
        self.Bind(wx.EVT_CONTEXT_MENU, self.popup_menu)

I test this bind early too :pensive: With any HIGHLIGHT event inside submenu returned empty object.
For getting ID of highlighted item used GetMenuId method of wx.MenuEvent and for all item linked with submenu it return -3. Such item not have real ID, as I understand.

I seems to me that there is either a bug in wxPython/wxWidgets, or getting the submenu is not possible due to a limitation in Windows itself.

You could try raising an issue on either the wxPython or the wxWidgets GitHub repositories, explaining how it fails on Windows but works on Linux. However, even if they do agree it is a bug, it would take some time before any fix would appear in a new wxPython release.

A possible workaround would be to replace the submenus in the popup menu with menu items and then bind those items so they will display a popup window containing a ListCtrl populated with the filtered items.

1 Like

Thanks for help. I have already made a similar temporary workaround.

the docu says quite clearly that there need not be an object, so whatever you want to do it must be observed :sneezing_face:

You about this?

Returns the object (usually a window) associated with the event, if any.

But for main menu object not empty. And on linux.

yes! it’s about the highlighting change of the menubar items and that only if a menu is open
so its pretty useless (although None is the most used singleton AFAIK) :rofl: