Automating a wxPython main menu setup?
Question: I am trying to find a way to condense and automate the construction of a main
menu (underneath the title bar, with _file_ , _edit_ , _help_ , etc.) in
wxPython.

Writing out each and every menu item is direct, but I notice I repeat myself a
lot, between Appending, sorting IDs, etc. Followed by other unique pits like
if I want to add an icon to a specific menu, or if I have submenus and they
may have submenus, etc. Without one consistent way to itemize everything,
simply by adding information to maybe a list or dictionary, or a combo of the
two, my wx.Frame object will get very dense.

I can't see a clean an organized way of doing that, short of a 3-dimensional
array. And even then, I don't know how to organize that 3D array uniformly so
every item is ready to go.

Here is what I have so far (pardon any indentation errors; it works fine on
me):

    
    
    class frameMain(wx.Frame):
        """The main application frame."""
        def __init__(self,
                     parent=None,
                     id=-1,
                     title='TITLE',
                     pos=wx.DefaultPosition,
                     size=wx.Size(550, 400),
                     style=wx.DEFAULT_FRAME_STYLE):
            """Initialize the Main frame structure."""
            wx.Frame.__init__(self, parent, id, title, pos, size, style)
            self.Center()
            self.CreateStatusBar()
    
            self.buildMainMenu()
    
        def buildMainMenu(self):
            """Creates the main menu at the top of the screen."""
            MainMenu = wx.MenuBar()
    
            # Establish menu item IDs.
            menuID_File = ['exit']
            menuID_Help = ['about']
            menuID_ALL = [menuID_File,
                          menuID_Help]
    
            # Make a dictionary of the menu item IDs.
            self.menuID = {}
            for eachmenu in menuID_ALL:
                for eachitem in eachmenu:
                    self.menuID[eachitem] = wx.NewId()
    
            # Create the menus.
            MM_File = wx.Menu()
            FILE = {}
            MM_File.AppendSeparator()
            FILE['exit'] = MM_File.Append(self.menuID['exit'],
                                          'Exit',
                                          'Exit application.')
            self.Bind(wx.EVT_MENU, self.onExit, FILE['exit'])
            MainMenu.Append(MM_File, 'File')
    
            MM_Help = wx.Menu()
            HELP = {}
            MM_Help.AppendSeparator()
            HELP['about'] = MM_Help.Append(self.menuID['about'],
                                           'About',
                                           'About the application.')
            self.Bind(wx.EVT_MENU, self.onAbout, HELP['about'])
            MainMenu.Append(MM_Help, 'Help')
    
            # Install the Main Menu.
            self.SetMenuBar(MainMenu)
    

I tried using the list-to-dictionary thing to make it so I don't need a
specific index number when referring to an ID, just write in a keyword and it
gets the ID. I write it once, and it's applied across the rest of the
function.

Notice how I have to make a whole new variable and repeat itself, like
MM_File, MM_Edit, MM_Help, and each time I do I put in similar information to
append and bind. And keep in mind, some of the menus may need Separators, or
have menus in menus, or I may want to use a sprite next to any of these menu
items, so I'm trying to figure how to organize my arrays to do that.

What is the appropriate way to organize this into a concise system so it
doesn't bloat this class?


Answer: There are several approaches you can take with this. You can put the menu
generation code into a helper function if you like. Something like this should
work:

    
    
    def menu_helper(self, menu, menu_id, name, help, handler, sep=True):
        menu_obj = wx.Menu()
        if sep:
            menu_obj.AppendSeparator()
        menu_item = menu_obj.Append(menu_id, name, help)
        self.Bind(wx.EVT_MENU, handler, menu_item)
        self.MainMenu.Append(menu_obj, menu)
    

Here's a complete example:

    
    
    import wx
    
    class frameMain(wx.Frame):
        """The main application frame."""
        def __init__(self,
                     parent=None,
                     id=-1,
                     title='TITLE',
                     pos=wx.DefaultPosition,
                     size=wx.Size(550, 400),
                     style=wx.DEFAULT_FRAME_STYLE):
            """Initialize the Main frame structure."""
            wx.Frame.__init__(self, parent, id, title, pos, size, style)
            self.Center()
            self.CreateStatusBar()
    
            self.buildMainMenu()
    
        def buildMainMenu(self):
            """Creates the main menu at the top of the screen."""
            self.MainMenu = wx.MenuBar()
    
            # Establish menu item IDs.
            menuID_File = 'exit'
            menuID_Help = 'about'
            menuID_ALL = [menuID_File,
                          menuID_Help]
    
            # Make a dictionary of the menu item IDs.
            self.menuID = {item: wx.NewId() for item in menuID_ALL}
    
    
            # Create the menus.
    
            self.menu_helper('File', self.menuID['exit'], 'Exit',
                             'Exit application', self.onExit)
    
    
            self.menu_helper('Help', self.menuID['about'], 'About',
                             'About the application.', self.onAbout)
    
            # Install the Main Menu.
            self.SetMenuBar(self.MainMenu)
    
        def menu_helper(self, menu, menu_id, name, help, handler, sep=True):
            """
            """
            menu_obj = wx.Menu()
            if sep:
                menu_obj.AppendSeparator()
            menu_item = menu_obj.Append(menu_id, name, help)
            self.Bind(wx.EVT_MENU, handler, menu_item)
            self.MainMenu.Append(menu_obj, menu)
    
        #----------------------------------------------------------------------
        def onExit(self, event):
            pass
    
        def onAbout(self, event):
            pass
    
    if __name__ == '__main__':
        app = wx.App(False)
        frame = frameMain()
        frame.Show()
        app.MainLoop()
    

Or you could create a class that handles all the menu creation. You could also
create a config file that has all this information in it that you read to
create your menu. Another alternative would be to use XRC, although I
personally find that a bit limiting.

