subclassing wxMenuBar... 
Author Message
 subclassing wxMenuBar...

I was stepping through the wxPython tutorial, and came across the part
where they're creating the menus. It seemed like a good idea to put the
menu definition in a datafile instead of specifying it programatically,
so I though it would be a good chance to get some xml experience too...

I have the following xml:

  <menu name="_File">
    <menuitem id="101" name="_About" info="More information about this
program"/>
    <separator/>
    <menuitem id="102" name="E_xit" info="Terminate the program"/>
  </menu>

(using the '_' instead of '&' to indicate shortcuts) and the following
code:

  from xml.dom import minidom
  from wxPython.wx import *

  def filterText(nodelist):
     return [ x for x in nodelist if x.nodeType != x.TEXT_NODE ]

  class XMLMenuBar(wxMenuBar):
     def __init__(self, filename):
        menubar = minidom.parse(filename)
        for menu in menubar.childNodes:
           menuName = menu.attributes['name'].nodeValue.replace('_',
'&')
           wxmenu = wxMenu()

           for menuItem in filterText(menu.childNodes):
              if menuItem.tagName == 'menuitem':
                 attrs = menuItem.attributes
                 idTag = int( attrs['id'].nodeValue )
                 name = attrs['name'].nodeValue.replace('_', '&')
                 info = attrs['info'].nodeValue
                 wxmenu.Append(idTag, name, info)
              elif menuItem.tagName == 'separator':
                 wxmenu.AppendSeparator()

           self.Append(wxmenu, menuName)

(I'm relatively new to dom programming, so any hints about that would be
well received...) however, when I instantiate this class from my wxFrame
subclass, I get the following traceback from the last line above:

Traceback (most recent call last):
  File "wXMLMenu.py", line 65, in ?
    app = MyApp(0)
  File "d:\python21\wxPython\wx.py", line 1613, in __init__
    _wxStart(self.OnInit)
  File "wXMLMenu.py", line 60, in OnInit
    frame = MyFrame(NULL, -1, "Hello from wxPython")
  File "wXMLMenu.py", line 38, in __init__
    menu = XMLMenuBar('menu.xml')
  File "wXMLMenu.py", line 25, in __init__
    self.Append(wxmenu, menuName)
  File "d:\python21\wxPython\windows.py", line 807, in Append
    val = apply(windowsc.wxMenuBar_Append,(self,) + _args, _kwargs)
TypeError: Type error in argument 1 of wxMenuBar_Append. Expected
_wxMenuBar_p.

which I'm assuming means that the SWIGd code is expecting a wxMenuBar
pointer to be the first argument to Append and can't deal with a
subclass...

Does anyone know a way around this?

-- bjorn



Wed, 19 Nov 2003 07:46:17 GMT  
 subclassing wxMenuBar...

Quote:

> I was stepping through the wxPython tutorial, and came across the part
> where they're creating the menus. It seemed like a good idea to put the
> menu definition in a datafile instead of specifying it programatically,
> so I though it would be a good chance to get some xml experience too...

Yep, that makes sense...

[snip]

Quote:
> which I'm assuming means that the SWIGd code is expecting a wxMenuBar
> pointer to be the first argument to Append and can't deal with a
> subclass...

> Does anyone know a way around this?

> -- bjorn

Well I wouldn't subclass wxMenuBar; instead I'd create a helper class
that would handle reading the xml and loading it into the wxMenuBar.  I
took your code and changed it to work as a helper.  Along the way I
added some functionality.

First I changed the format of the xml by wrapping it in a menubar tag.
<menubar>
<menu name="_Test">
        <menuitem id="101" name="_About"... />
        <menuitem id="103" name="_Change" info="Change title?"/>
        <separator/>
        <menuitem id="102" name="E_xit".../>
</menu>
<menu name="_Edit">
        <menuitem id="201" name="Copy" info="Copy" callback='onCopy'/>
        <menuitem id="202" name="Cut" info="Cut" callback='onCut'/>
</menu>
</menubar>

This allows us to have one or more menu elements. To handle this in the
code, I changed the for loop to loop over each "menu" element in the
menubar:
   menubar = minidom.parse(self._filename)
   # loop over and process all of the menu elements in the menubar
   for menu in menubar.getElementsByTagName('menu'):

Another addition I made was to add a callback attribute.  Then if you
pass a class instance to the load method, the load method will attempt
to hookup the method name set in the callback attribute in the
controller class instance.

To use this new class you would do the following in the frame:
   loader = XMLMenuLoader('menu.xml')
   loader.load(wxMenuBar = self.mainmenu, controller=self)
   loader.setMenuBar(None)

By changing your original code from a subclass of a wxMenuBar to an
helper class, we can expand the usefulness.  Perhaps instead of xml we
decide to use nested lists?  We can refactor the XMLMenuLoader class
into a base MenuLoader class and various subclasses.

Thanks for the initial post Bjorn, I had fun working on this...

--
Tom Jenkins
devIS - Development Infostructure
http://www.devis.com

[ menu.py 2K ]
'''
XMLMenuLoader: Class to handle loading a wxMenuBar with menu items defined in XML



#*TODO*: refactor load into base class that handles loading the menu in another way
so we can have ListMenuLoader, StreamMenuLoader, etc...

'''

from xml.dom import minidom
from wxPython.wx import *

def filterText(nodelist):
   return [ x for x in nodelist if x.nodeType != x.TEXT_NODE ]

class XMLMenuLoader:
   def __init__(self, filename, wxMenuBar = None):
      '''Save the filename and menubar to use'''
      self._filename = filename
      self.setMenuBar(wxMenuBar)

   def setMenuBar(self, wxMenuBar = None):
      ''' set the menubar property to the given menubar
      wxMenuBar -> the root menubar of the frame'''
      self._wxMenuBar = wxMenuBar

   def menuBar(self):
      '''Return the stored menubar'''
      return self._wxMenuBar

   def load(self, wxMenuBar = 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
                Example XML:
                <menubar>
                        <menu name="_Test">
                                <menuitem id="101" name="_About" info="More information about this program" callback='onTestAbout'/>
                                <menuitem id="103" name="_NoCallback" info="No Callback Example"/>
                                <separator/>
                                <menuitem id="102" name="E_xit" info="Terminate the program" callback='onTestExit'/>
                        </menu>
                        <menu name="_Edit">
                                <menuitem id="201" name="Copy" info="Copy the selection" callback='onCopy'/>
                                <menuitem id="202" name="Cut" info="Cut the selection" callback='onCut'/>
                        </menu>
                </menubar>
                '''
                if wxMenuBar != None:
                        self.setMenuBar(wxMenuBar)

                menubar = minidom.parse(self._filename)
                # loop over and process all of the menu elements in the menubar
        for menu in menubar.getElementsByTagName('menu'):
                        menuName = menu.attributes['name'].nodeValue.replace('_', '&')
                        wxmenu = wxMenu()

                        for menuItem in filterText(menu.childNodes):
                                if menuItem.tagName == 'menuitem':
                                                attrs = menuItem.attributes
                                                idTag = int( attrs['id'].nodeValue )
                                                name = attrs['name'].nodeValue.replace('_', '&')
                                                info = attrs['info'].nodeValue
                                                wxmenu.Append(idTag, name, info)
                                # only load the callbacks if there is a controller
                                if controller:
                                        try:
                                                handler = attrs['callback'].nodeValue
                                                EVT_MENU(controller, idTag, eval('controller.%s' % handler))
                                        except KeyError, ex:
                                                # didn't have a callback attribute - skip
                                                #*TODO*: check if there is a better way; maybe a has_key test?
                                                pass

                                elif menuItem.tagName == 'separator':
                                                wxmenu.AppendSeparator()

                        self.menuBar().Append(wxmenu, menuName)



Fri, 21 Nov 2003 21:26:28 GMT  
 subclassing wxMenuBar...

Quote:


> > I was stepping through the wxPython tutorial, and came across the part
> > where they're creating the menus. It seemed like a good idea to put the
> > menu definition in a datafile instead of specifying it programatically,
> > so I though it would be a good chance to get some xml experience too...

[snip]

> Well I wouldn't subclass wxMenuBar; instead I'd create a helper class
> that would handle reading the xml and loading it into the wxMenuBar.  I
> took your code and changed it to work as a helper.  Along the way I
> added some functionality.

[snip]
Err... well it looks like the mailing list -> newsgroup bridge
stripped out the attachment that contained the code for the helper
class.  So let me follow up by putting it directly in the message
<grin>

#----------------------------------
'''
XMLMenuLoader: Class to handle loading a wxMenuBar with menu items
defined in XML



#*TODO*: refactor load into base class that handles loading the menu
in another way so we can have ListMenuLoader, StreamMenuLoader, etc...

'''

from xml.dom import minidom
from wxPython.wx import *

def filterText(nodelist):
   return [ x for x in nodelist if x.nodeType != x.TEXT_NODE ]

class XMLMenuLoader:
   def __init__(self, filename, wxMenuBar = None):
                '''Save the filename and menubar to use'''
                self._filename = filename
                self.setMenuBar(wxMenuBar)

   def setMenuBar(self, wxMenuBar = None):
                ''' set the menubar property to the given menubar
                wxMenuBar -> the root menubar of the frame'''
                self._wxMenuBar = wxMenuBar

   def menuBar(self):
                '''Return the stored menubar'''
                return self._wxMenuBar

   def load(self, wxMenuBar = 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
                '''
                if wxMenuBar != None:
                        self.setMenuBar(wxMenuBar)

                menubar = minidom.parse(self._filename)
                # loop over and process all of the menu elements in the menubar
                for menu in menubar.getElementsByTagName('menu'):
                        menuName = menu.attributes['name'].nodeValue.replace('_', '&')
                        wxmenu = wxMenu()

                        for menuItem in filterText(menu.childNodes):
                                if menuItem.tagName == 'menuitem':
                                                attrs = menuItem.attributes
                                                idTag = int( attrs['id'].nodeValue )
                                                name = attrs['name'].nodeValue.replace('_', '&')
                                                info = attrs['info'].nodeValue
                                                wxmenu.Append(idTag, name, info)
                                # only load the callbacks if there is a controller
                                if controller:
                                        try:
                                                callback = attrs['callback'].nodeValue
                                                handler = getattr(controller, callback, None)
                                                if handler:
                                                        EVT_MENU(controller, idTag, handler)
                                        except KeyError, ex:
                                                # didn't have a callback attribute - skip
                                                #*TODO*: check if there is a better way; maybe a has_key test?
                                                pass

                                elif menuItem.tagName == 'separator':
                                                wxmenu.AppendSeparator()

                        self.menuBar().Append(wxmenu, menuName)
#----------------------------------

Quote:

> Thanks for the initial post Bjorn, I had fun working on this...

Didn't-have-fun-getting-the-code-out-to-you-though-ly, yours
Tom


Sat, 22 Nov 2003 21:27:57 GMT  
 subclassing wxMenuBar...

Quote:


> > I was stepping through the wxPython tutorial, and came
> across the part
> > where they're creating the menus. It seemed like a good
> idea to put the
> > menu definition in a datafile instead of specifying it
> programatically,
> > so I though it would be a good chance to get some xml
> experience too...

> Yep, that makes sense...

> [snip]

> > which I'm assuming means that the SWIGd code is expecting a
> wxMenuBar
> > pointer to be the first argument to Append and can't deal with a
> > subclass...

> > Does anyone know a way around this?

> > -- bjorn

> Well I wouldn't subclass wxMenuBar; instead I'd create a helper class
> that would handle reading the xml and loading it into the
> wxMenuBar.  I
> took your code and changed it to work as a helper.  Along the way I
> added some functionality.

Cool. Thanks! I was wondering how I would easily add callback
definitions :-)

-- bjorn



Sat, 22 Nov 2003 23:42:00 GMT  
 
 [ 4 post ] 

 Relevant Pages 

1. wxMenuBar

2. Perfect example of why subclassing can be dangerous...

3. Subclassing CompiledMethod

4. Win98SE subclassing problem

5. Subclassing Binarystream and folderitems

6. Subclassing Object3D

7. Subclassing in 32bit applications...

8. Subclassing in 32bit apps...

9. Subclassing to get WM_HELP msg. ,HELPINFO

10. Subclassing and WM_CHAR, WM_KEYDOWN

11. Subclassing

12. explain a subclassing question please

 

 
Powered by phpBB® Forum Software