Welcome, guest | Sign In | My Account | Store | Cart

XMLMenuLoader uses XML definitions to create a wx.MenuBar. It is based on the code in this post:

http://mail.python.org/pipermail/python-list/2001-June/046912.html

and adds checkable menus and submenus.

Python, 189 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
"""
XMLMenuLoader: Class to handle loading a wxMenuBar with menu items
defined in XML

author:   Tom Jenkins <tjenkins@devis.com>
          based on code by Bjorn Pettersen <BPettersen@NAREX.com>

          20041010: Egor Zindy <ezindy@gmail.com> Code now uses pulldom, supports
          submenus and checkable items.

          #*COULDDO*# Add new XML definitions (menu images, greyed menus...)

          #*TODO*: refactor load into base class that handles loading the menu
          in another way so we can have ListMenuLoader, StreamMenuLoader, etc...
 
usage:
    self.menubar=wx.MenuBar()
    loader = XMLMenuLoader(MenuBar = self.menubar, controller=self)
    loader.load("menu.xml")
    self.SetMenuBar(self.menubar)
"""

import wx
from xml.dom import pulldom
  
class XMLMenuLoader:
    
    def __init__(self, MenuBar = None, controller = None):
        """Load the menubar with the menu items stored in XML format in
           the instance"s filename property
           wxMenuBar -> the root menubar of the frame; if given will become
           the instances new menubar property
           controller -> the class instance that will receive any callbacks
           stored in the menu items" callback attribute
        """
        self.MenuBar=MenuBar
        self.controller=controller

    def load(self,filename="menu.xml"):
        events = pulldom.parse(filename)
        self.parse(events)

    def loadString(self,xml_string):
        events = pulldom.parseString(xml_string)
        self.parse(events)

    def parse(self,events):
        #menu_list is used as a stack: the last element
        #is the parent menu to which items are appended.

        menu_list=[]
        menu_list.append(self.MenuBar)

        for (event,node) in events:
            if event=="START_ELEMENT" and node.nodeName=="menu":
                menu = wx.Menu()
                parent_menu=menu_list[-1]
                menu_name=node.getAttribute("name").replace("_", "&")

                if len(menu_list)==1:
                    parent_menu.Append(menu, menu_name)
                else:
                    parent_menu.AppendMenu(-1,menu_name,menu)

                menu_list.append(menu)

            elif event=="END_ELEMENT" and node.nodeName=="menu":
                #Encountered the end of a menu definition.
                #Remove the wx.menu from the stack.
                menu_list.pop(-1)

            elif event=="START_ELEMENT" and node.nodeName=="separator":
                #separator definition
                menu=menu_list[-1]
                menu.AppendSeparator()

            elif event=="START_ELEMENT" and node.nodeName=="menuitem":
                #the current menu
                menu=menu_list[-1]

                #checking all the attributes.

                #the menuitem name
                name=node.getAttribute("name").replace("_", "&")

                #if id is not defined, check for one available
                if node.hasAttribute("id"):
                    id=int(node.getAttribute("id"))
                else:
                    id = wx.NewId()

                #menuitem info attribute
                if node.hasAttribute("info"):
                    info=node.getAttribute("info")
                else:
                    info=""

                #callback
                if self.controller and node.hasAttribute("callback"):
                    callback=node.getAttribute("callback")
                    handler = getattr(self.controller, callback, None)
                    if handler:
                        wx.EVT_MENU(self.controller, id, handler)

                #checkable menu?
                if node.hasAttribute("chk"):
                    chk=int(node.getAttribute("chk"))
                    menu.AppendCheckItem(id,name,info)
                    menu.Check(id,chk)
                else:
                    menu.Append(id,name,info)

#----------------------------------------------------------------------
class MyFrame(wx.Frame):

    def __init__(self, parent, id, title,
        pos, size, style = wx.DEFAULT_FRAME_STYLE ):
                
        wx.Frame.__init__(self, parent, id, title, pos, size, style)

        self.CreateMyMenuBar()
        self.CreateStatusBar(1)

    def CreateMyMenuBar(self):
        self.mainmenu=wx.MenuBar()
        loader = XMLMenuLoader(MenuBar = self.mainmenu, controller=self)
        loader.loadString(xml_string)
        self.SetMenuBar(self.mainmenu)

    def OnChoice(self, event):
        id=event.GetId()
        menu_item=self.mainmenu.FindItemById(id)
        self.SetStatusText("item name: %s" % menu_item.GetLabel())


    def OnOptions(self, event):
        id=event.GetId()
        menu_item=self.mainmenu.FindItemById(id)
        is_checked=menu_item.IsChecked()

        if id==15000:
            my_str="apple"
        elif id==15001:
            my_str="pear"
        elif id==15002:
            my_str="orange"

        self.SetStatusText("item: %s | checked: %d" % (my_str,is_checked))

    def OnCloseWindow(self, event):
        self.Destroy()

def main(argv=None):
    app = wx.PySimpleApp()
    f = MyFrame(None, -1, "XMLMenuLoader: XML menu creation", wx.Point(20,20), wx.Size(400,300) )
    f.Show()
    app.MainLoop()

#----------------------------------------------------------------------
if __name__ == "__main__":

    global xml_string
    xml_string="""
    <menubar>
        <menu name='_File'>
            <menuitem name='_New' callback='OnChoice'/>
            <menuitem name='_Open...' callback='OnChoice'/>
            <menu name='_Export to'>
                <menuitem name='Jpeg...' callback='OnChoice'/>
                <menuitem name='Png...' callback='OnChoice'/>
            </menu>
            <separator/>
            <menuitem name='E_xit' callback='OnCloseWindow'/>
        </menu>
        <menu name='_Edit'>
            <menuitem name='Undo' callback='OnChoice'/>
            <menuitem name='Redo' callback='OnChoice'/>
        </menu>
        <menu name='_Checkable'>
            <menuitem id='15000' name='Item 1' callback='OnOptions' chk='1'/>
            <menuitem id='15001' name='Item 2' callback='OnOptions' chk='0'/>
            <menuitem id='15002' name='Item 3' callback='OnOptions' chk='0'/>
        </menu>
        <menu name='_Help'>
            <menuitem name='About...' info='Read more about it' callback='OnAbout'/>
        </menu>
    </menubar>"""
    
    main()
    

To try it out, just launch the code.

usage: self.menubar=wx.MenuBar() loader = XMLMenuLoader(MenuBar = self.menubar, controller=self) loader.load("menu.xml") self.SetMenuBar(self.menubar)

if you call the file XMLMenuLoader.py then you will need a

from XMLMenuLoader import XMLMenuLoader

at the top of your code.

Do the submenus work in Linux?

3 comments

Gregory Baker 16 years, 4 months ago  # | flag

Resource Editor. How is this different to or better than using XRCed resource editor and the xrc module that comes with wxPython?

Egor Zindy (author) 16 years, 4 months ago  # | flag

Re: Resource Editor. The problem is that the xrc module is not well documented (well, there's an example in the demo and that's about it).

I found a wiki with a reasonably well explained example here: http://wiki.wxpython.org/index.cgi/UsingXmlResources

The difference between the two XML structures is that XMLMenuLoader focusses on the menu structure only, rather than the whole application.

From the example again, this is how the menu xml looks like

&lt;object class="wxMenuBar" name="MenuBar"&gt;
  &lt;object class="wxMenu" name="OperationMeu"&gt;
    &lt;label&gt;Operations&lt;/label&gt;
    &lt;object class="wxMenuItem" name="AddMenuItem"&gt;
      &lt;label&gt;&amp;Add&lt;/label&gt;
      &lt;accel&gt;Ctrl-A&lt;/accel&gt;
      &lt;help&gt;Add second arg to the first arg.&lt;/help&gt;
    &lt;/object&gt;
    &lt;object class="wxMenuItem" name="SubstractMenuItem"&gt;
      &lt;label&gt;&amp;Substract&lt;/label&gt;
      &lt;accel&gt;Ctrl-S&lt;/accel&gt;
      &lt;help&gt;Substract second arg from the first arg.&lt;/help&gt;
    &lt;/object&gt;
    &lt;object class="separator"/&gt;
    &lt;object class="wxMenuItem" name="MultiplyMenuItem"&gt;
      &lt;label&gt;&amp;Multiply&lt;/label&gt;
      &lt;accel&gt;Ctrl-M&lt;/accel&gt;
      &lt;help&gt;Multiply first arg by second arg&lt;/help&gt;
    &lt;/object&gt;
    &lt;object class="wxMenuItem" name="DivideMenuItem"&gt;
      &lt;label&gt;&amp;Divide&lt;/label&gt;
      &lt;accel&gt;Ctrl-D&lt;/accel&gt;
      &lt;help&gt;Divide first arg by second arg.&lt;/help&gt;
    &lt;/object&gt;
  &lt;/object&gt;
&lt;/object&gt;

I think the structure in XMLMenuLoader is a bit lighter (easy enough to be edited by hand if needed):

&lt;menubar&gt;
    &lt;menu name='_File'&gt;
        &lt;menuitem name='_New' callback='OnChoice'/&gt;
        &lt;menuitem name='_Open...' callback='OnChoice'/&gt;
        &lt;menu name='_Export to'&gt;
            &lt;menuitem name='Jpeg...' callback='OnChoice'/&gt;
            &lt;menuitem name='Png...' callback='OnChoice'/&gt;
        &lt;/menu&gt;
        &lt;separator/&gt;
        &lt;menuitem name='E_xit' callback='OnCloseWindow'/&gt;
    &lt;/menu&gt;
&lt;/menubar&gt;

XMLMenuLoader also connects elements in the menu with function names and handles non-existing functions gracefully.

Gregory Baker 16 years, 4 months ago  # | flag

Resource Editor. Thank you for responding to my question Egor. I think you raise valid issues. I'll have to look a little more closely.