Search code examples
pythonwxpython

wxPython: PopupMenu with dynamic data


I have the code below. What the final goal is, is that the content of the PopupMenu can defer from were you click. The content will be loaded from a file and put in options. That works. I limited the problem to the code below. What is expected is that when you right-click in the frame you get a PopupMenu with 2 options "menu1" and "menu2". That works. And if you select "menu 1", than "Command 1" should be printed and for "menu2", "Command 2" But both options will print "Command 2".

Any help?

import wx


def on_right_click(event):
    menu = wx.Menu()
    options = [{'msg': 'menu1', 'cmd': 'command 1'},
               {'msg': 'menu2', 'cmd': 'command 2'}]
    for option in options:
        menu_item = menu.Append(wx.ID_ANY, option['msg'])
        menu.Bind(
            event=wx.EVT_MENU,
            handler=lambda x: print(option['cmd']),
            source=menu_item
        )
    frame.PopupMenu(menu, event.GetPosition())
    menu.Destroy()


app = wx.App()
frame = wx.Frame(parent=None, title='Hello World')
frame.Bind(wx.EVT_RIGHT_DOWN, on_right_click)

frame.Show()
app.MainLoop()

Solution

  • The variable value in the lambda is evaluated on execution, thus the returned value is always the result of the last value.
    You can test this by using an index and increasing it to an invalid index value, all lambdas will fail with an index error.
    The answer is to extend your lambda arguments to fix the value.
    e.g.

    import wx
    
    def on_right_click(event):
        menu = wx.Menu()
        options = [{'msg': 'menu1', 'cmd': 'command 1'},
                   {'msg': 'menu2', 'cmd': 'command 2'},
                   {'msg': 'menu3', 'cmd': "I can't believe"},
                   {'msg': 'menu4', 'cmd': "it's not butter"}]
        for option in options:
            menu_item = menu.Append(wx.ID_ANY, option['msg'])
            menu.Bind(
                event=wx.EVT_MENU,
                handler=lambda x, current_cmd = option['cmd']: print(current_cmd),
                source=menu_item
            )
        frame.PopupMenu(menu, event.GetPosition())
        menu.Destroy()
    
    
    app = wx.App()
    frame = wx.Frame(parent=None, title='Right Click to Test')
    frame.Bind(wx.EVT_RIGHT_DOWN, on_right_click)
    
    frame.Show()
    app.MainLoop()
    

    See this related post: position in a list with bind and wxpython