Home Software Hardware Misc About

metamenus

vs. 0.13 (15/09/2020)

metamenus is a wxPython library that provides classes which handles menubars, context menus and taskbar menus and greatly simplifies menu creation and maintenance -- you essentially type the menu text and forget about Appends, completely avoid Ids and have all of the menu event binding operations automatically handled for you. metamenus also helps you with internationalization and localization of your application.

The goals I had in mind when I wrote it were to make dealing with menus something intuitive and extremely simple yet fast and powerful and making code less susceptible to errors.

metamenus provides MenuEx, a class that handle context or taskbar menus, and MenuBarEx, that handles menu bars. Keep reading to see and download some examples, or click the class names do go directly to their class references.

Making wxPython menus the easy way

There are plenty of places that help you writing wxPython menus the hard normal way. And they are great. But now I will show you the simplest way:

our first (meta) menu
code result

menu =  [
            ['File'],
            ['  New'],
            ['  Open'],
            ['  Close'],
            ['  Save'],
            ['  Save as...'],
            ['  -'],
            ['  Exit'],
        ]


testing metamenus 1

There. It's done.


Submenus

The normal way to build a submenu is: make a first menu; make a second menu; make this second menu a submenu of the first menu under the item x we are creating now. Not hard to grasp eventually but not terribly intuitive either. With metamenus it just flows:

creating submenus
code result

menu =  [
            ['File'],
            ['  New'],
            ['    Browser Window'],
            ['    Message'],
            ['  Open'],
            ['  Close'],
            ['  Save'],
            ['  Save as...'],
            ['  -'],
            ['  Exit'],
        ]


testing metamenus 2

metamenus use string indentation to set menu 'levels', i. e., submenus have higher indentation levels than its parent menus.


Accelerators and shortcuts

The wxPython docs call accelerators both the underlined character on the menu label and the actual shortcut for the menu item. In fact, both are meant to allow users to control menu options with the keyboard. metamenus surely does accelerators and uses the same syntax the wxPython docs describe to generate them:

adding accelerators & shortcuts
code result

menu =  [
            ['&File'],
            ['  &New\tCtrl+N'],
            ['  &Open\tCtrl+O'],
            ['  &Close\tCtrl+W'],
            ['  &Save\tCtrl+S'],
            ['  Save &as...'],
            ['  -'],
            ['  E&xit\tCtrl+X'],
        ]

testing metamenus 3

Radio items, check items, normal items

Menu items may be either normal items, check items or radio items. The examples shown above deal only with normal items which don't have any special properties. The check items have a boolean flag associated to them and they show a checkmark in the menu when the flag is set. The radio items are similar to the check items except that all the other items in the same radio group are unchecked when a radio item is checked. The radio group is formed by a contiguous range of radio items.

radio and check menu items
code result

menu =  [
            ['Options'],
            ['  Foo',    "radio"],
            ['  Bar',    "radio"],
            ['  -'],
            ['  Spam',   "check"],
            ['  Eggs',   "check"],
        ]

testing metamenus 4

It's pretty self-explanatory. And if you are already used to wx syntax you can also use wx.ItemKind types (wx.ITEM_RADIO or wx.ITEM_CHECK) instead of "radio" and "check" strings in the example above as metamenus makes no distinction between them.


Separators

A separator is a single horizontal line that, well, separates your menus so that you group the items for clarity into functional groups. All of the examples above show what we need to do to produce a separator: you just hit the minus sign on your keyboard.


Icons

Icons on menus help your users navigate the program. With metamenus you may virtually add any bitmap to a menu as well as easily get the platform native icons as provided by wx.ArtProvider.

You will have to pass a dictionary to metamenus with "bmp" as keyword and a value that may be a bitmap, a string or a callable and metamenus will use its magic to fetch the required image.

The next example shows metamenus using ArtProvider to get the required icons:

using wx.ArtProvider
code result

menu =  [
            ['File'],
            ['  New',        {"bmp": "new"}],
            ['  Open',       {"bmp": "file_open"}],
            ['  Close',      {"bmp": "close"}],
            ['  Save',       {"bmp": "file_save"}],
            ['  Save as...', {"bmp": "file_save_as"}],
            ['  -'],
            ['  Exit',       {"bmp": "quit"}]
        ]

testing metamenus 5

The value passed as a string will always need to be a case-insensitive string representation of the ArtProvider id minus the 'ART_' prefix. For example, if you want the ArtProvider bitmap for wx.ART_CLOSE, the value must be "CLOSE" (or "close").

And as for using any arbitrary bitmap other than the ones ArtProvider provides, the process is similar except that you will then pass directly a bitmap or a callable that returns a bitmap when called. This is useful, for example, when you have images and want to pass them like my_image.ConvertToBitmap(), but that could raise an error if the menu is being parsed when the wx.App object have not yet been created. You may pass then my_image.ConvertToBitmap and metamenus will call it later.


Events (or: how to to something when a menu item is clicked)

In the previous examples our menu is attached to a wx.MenuBar. metamenus will automatically call a method on your parent frame using the menu "path" itself as method name prefixed with "OnMB_". For example, if you click on "File" > "New", metamenus will call a OnMB_FileNew method on your frame. Thus, you can focus on writing the methods for your program without worring about menu event bindings or ids.


    def OnMB_FileNew(self):
        """Creates a new file"""

        # do something here...


MenuEx methods

__init__(parent, menu, margin=wx.DEFAULT, font=wx.NullFont, show_title=True, custfunc={}, i18n=True, style=0)

Constructor. Parameters:

menu: A python list of menu items where each menu item is a python list that needs to be in one of the following formats:

[label]
or [label, args]
or [label, kwargs]
or [label, args, kwargs]
or [label, kwargs, args]  (but please don't do this one as several bits may be harmed during the process).

Its options are:

label: (string) The text for the menu item. Leading whitespaces are used to compute the indentation level of the item, which in turn is used to determine the grouping of items. MenuEx and MenuBarEx determine one indentation level for every group of two whitespaces. Menubars top-level items must have no indentation. Separators are items labeled with a "-" and may not have args and kwargs. Menu breaks are labeled with a "/" and may not have args and kwargs.

args: (tuple: helpString, wxItemKind or a single value): helpString is an optional string that will be shown on the parent's status bar (if it has one). wxItemKind may be one of wx.ITEM_CHECK, "check", wx.ITEM_RADIO or "radio". It is also optional; if you don't pass one, a default wx.ITEM_NORMAL will be used. Note that if you are to pass a single value you can do either:

args=("", wxItemKind)
or args=(helpString,)
or helpString
or wxItemKind
or (helpString) or (wxItemKind)

When you pass only one argument, metamenus will check if the thing passed can be translated as an item kind (either wx.RADIO, "radio", etc.) or not, and so will try to guess what to do with the thing. So that if you want a status bar showing something that could be interpreted as an item kind, say, "radio", you'll have to pass both arguments in the form ("radio",).

kwargs: (dict: "bmpChecked": wxBitmap , "bmpUnchecked": wxBitmap ,"bmp": wxBitmap, "font": wxFont, "width": int , "fgcolour": wxColour, "bgcolour": wxColour): These options internally access wx.MenuItem methods in order to change its appearance, and might not be present on all platforms. They are internally handled as follows:

key value (corresponding wx.MenuItem behaviour)
"bmpChecked" and "bmpUnchecked" SetBitmaps
"bmpChecked" or "bmp" several; see Icons example above
"font" SetFont
"margin" SetMarginWidth
"fgColour" SetTextColour
"bgColour" SetBackgroundColour

The "bmp", "bmpChecked" and "bmpUnchecked" options accept a bitmap or a callable that returns a bitmap when called. "bmp" also accepts a string. Please see the Icons example above for details. Please refer to the wxPython docs for wx.MenuItem for more information about the other methods.

margin: (int):  a value that will be used to do a SetMarginWidth for each menu item.

font: (wx.Font): a value that will be used to do a SetFont for each menu item.

show_title: (bool): Controls whether you want a title to be shown on a menu (platform-dependent).

custfunc: (dict): allows you to define explicitly what will be the parent's method called on a menu event. By default, all parent's methods have to start with "OnMB_" (for menubars) or "OnM_" (for menus) plus the full menu 'path'. For a 'Save' menu item inside a 'File' top menu, e.g., metamenus will try to call an OnMB_FileSave method on the parent frame. However, the custfunc arg allows you to pass a dict of {menupath: method, menupath: method, ...} pairs, so that if you want your File > Save menu triggering a 'onSave' method instead, you may pass:

{"FileSave": "onSave"}
or {"FileSave": self.onSave}

as custfunc. This way, your parent's method should look like this instead:

      def onSave(self):
        self.file.save()

Note that you don't have to put all menu items and methods inside custfunc. The menu paths not found there will still trigger automatically an OnMB_/OnM_-prefixed method.

i18n: (bool): Controls whether you want the items to be translated or not. Default is True. For more info on i18n, please see Internationalization (i18n) below.

style: Please refer to the wxPython docs for wx.MenuBar and wx.Menu for more information about menu styles.


Popup(evt)

Shows the menu. The parent must bind a wx.EVT_RIGHT_UP event to a handler. The handler then creates the menu and shows it using this method passing the wx.EVT_RIGHT_UP event.

Parameters:

evt: the wx.EVT_RIGHT_UP event triggered on the parent.

GetItemState(menu_string)

Returns True if a checkable menu item is checked.

Parameters:

menu_string: is a string that refers to a menu item. For a File > Save menu, e. g., it may be "OnMB_FileSave", "FileSave" or the string you passed via the custfunc parameter (i. e., if you passed {"FileSave": "onSave"} as custfunc, the string will be "onSave").

SetItemState(menu_string, check)

Sets the state of a checkable menu item.

Parameters:

menu_string: (string) See above.

check: (bool): True checks the item; False unchecks it.


EnableItem(menu_string, enable=True)

Enables or disables a menu item.

Parameters:

menu_string: (string) See above.

enable: (bool): True enables the item; False disables it.


EnableItems(menu_string_list, enable=True)

Enables or disables menu items via a list of menu_strings.

Parameters:

menu_string_list: (list) A list of menu_strings.

enable: (bool): True enables the items on the list; False disables them.


EnableAllItems(self, enable=True):

Enables or disables all menu items.

Parameters:

enable: (bool): True enables all the items; False disables them.

MenuBarEx methods

__init__(parent, menus, margin=wx.DEFAULT, font=wx.NullFont, custfunc={}, i18n=True, style=0)

Constructor. Parameters here are pretty much the same as MenuEx's, with only two differences: there is obviously not a show_title option and we may now deal with several menus:

menus: a python list of [menu, menu, ...] items..

GetItemState(menu_string)

Returns True if a checkable menu item is checked. See MenuEx GetItemState


SetItemState(menu_string, check)

Sets the state of a checkable menu item. See MenuEx SetItemState


EnableItem(menu_string, enable=True)

Enables or disables a menu item. See MenuEx EnableItem


EnableItems(menu_string_list, enable=True)

Enables or disables menu items. See MenuEx EnableItems


EnableTopMenu(self, menu_string, enable=True):

Enables or disables all of the menu items under the menu pointed by menu_string.


EnableTopMenus(self, menu_string_list, enable=True):

Enables or disables all of the menu items under the menus pointed by the list of menu_strings.


Internationalization (i18n)

metamenus integrates with wx.Locale to make menu items be translated on the fly, and may be even used to help creating gettext translation files.

It is a good idea to have your menus in a separate file as it keeps things organized and allows metamenus to help with the internationalization of your program. In this case, Run metamenus directly from the command line pointing it to the menu file:

python3 metamenus.py menu_file.py output_file

and it will extract the menu strings and generate a output_file with them. Then you will be able to produce the gettext catalog files used to translate you application. You will find a great and detailed explanation here.


Download

Click here to download a compressed file containing metamenus, the example files on this page and a demo to that shows further ways to integrate metamenus with your programs. To run the demo, save it along metamenus and type python3 Demo_metamenus.py.

Tested on wxPython 4.1.0 (gtk3) on Python 3.7.3 on Raspberry Pi OS and on wxPython 4.1.0 (gtk2) on Python 3.8.2 on Mint (Mate). metamenus, the metamenus demo and the example files available here are distributed under the BSD-3-Clause License.