Disconnecting Menu events

I'm not finding any specific examples of what I'm wanting to do. Can anyone confirm this for me?

I've set up a menu on my wxFrame, like so:

EVT_MENU(self, ID_SPAM, self.panel.OnSpam)
EVT_MENU(self, ID_EGGS, self.panel.OnEggs)

Now, I need to change this panel for a new one, and changing the menu to match the new panel. I'm presuming that I should disconnect the events when I do this. (Or is it safe to leave them, and presume that a new menu won't send a command with that ID? What if I'm re-using the ID in the new menu -- will the new event binding replace the old one, or will it be appended to a list?)

To disconnect the above events, I believe that I'd need to specify the command IDs and the event type, like so:

self.Disconnect(ID_SPAM, -1, wxEVT_COMMAND_MENU_SELECTED)
self.Disconnect(ID_EGGS, -1, wxEVT_COMMAND_MENU_SELECTED)

Is that the right syntax? The examples I've seen haven't needed to specify an ID, so I'm not sure I'm getting it in the right position. Alternatively, if I don't specify an ID at all, like this

self.Disconnect(-1, -1, wxEVT_COMMAND_MENU_SELECTED)

will that disconnect *all* menu events for the frame? That could be handy, as it's probably easier for me to just wipe the slate clean and rebuild the menu and event table from scratch, rather than trying to keep track of which menu items stay the same and which need to go...

Jeff Shannon
Technician/Programmer
Credit International

It gets appended to a list. If the first handler calls event.Skip()
then the next handler will be called. I doubt this is what you want.

What I'd suggest (without trying, of course <wink>) is to simply have
unique id's for all your menu items. That way you don't have to muck
around with binding/rebinding event handlers.

As an aside, since your menu system is so complex, you might consider
setting your menus and handlers up in a list, something like:

import wx

import menus

class DynamicMenuFrame(wx.Frame):
    def __init__(self, parent, id, title, menubars):
        wx.Frame.__init__(self, parent, id, title)

        self.menubars = {}

        for mb in menus.menubars:
            self.menubars[mb] = wx.MenuBar()

            for menuLabel, menuItems in menus.menubars[mb]:
                menu = wx.Menu()
                for item in menuItems:
                    if item is None:
                        menu.AppendSeparator()
                        continue
                    itemLabel, itemHandler = item
                    id = wx.NewId()
                    menu.Append(id, itemLabel)
                    wx.EVT_MENU(self, id, getattr(self, itemHandler))
                self.menubars[mb].Append(menu, menuLabel)

    def SwitchMenus(self, mb):
        self.SetMenuBar(self.menubars[mb])
                        
class MyFrame(DynamicMenuFrame):
    def __init__(self, parent, id):
        DynamicMenuFrame.__init__(self, parent, id, "Menu Test",
                                  menubars = ['eggs', 'spam'])
        
        self.SwitchMenus('spam') # our default menu

    # handlers
    def OnEggsMenu(self, event):
        print "switching to eggs menu"
        self.SwitchMenus('eggs')
        
    def OnSpamMenu(self, event):
        print "switching to spam menu"
        self.SwitchMenus('spam')
        
    def OnCopyEgg(self, event):
        print "OnCopyEgg"
        
    def OnCopySpam(self, event):
        print "OnCopySpam"

    def OnPasteEgg(self, event):
        print "OnPasteEgg"
        
    def OnPasteSpam(self, event):
        print "OnPasteSpam"

    def OnExit(self, event):
        print "Exit"
        self.Close()
        
app = wx.PySimpleApp()
f = MyFrame(None, -1)
f.Show(True)
app.MainLoop()

Regards,
Cliff

···

On Thu, 2003-08-07 at 19:00, Jeff Shannon wrote:

I'm not finding any specific examples of what I'm wanting to do. Can
anyone confirm this for me?

I've set up a menu on my wxFrame, like so:

EVT_MENU(self, ID_SPAM, self.panel.OnSpam)
EVT_MENU(self, ID_EGGS, self.panel.OnEggs)

Now, I need to change this panel for a new one, and changing the menu to
match the new panel. I'm presuming that I should disconnect the events
when I do this. (Or is it safe to leave them, and presume that a new
menu won't send a command with that ID? What if I'm re-using the ID in
the new menu -- will the new event binding replace the old one, or will
it be appended to a list?)

--
Heads on fire and drunken lights
Days devoured by hungry nights
                           -Coil

Jeff Shannon wrote:

A bit of experimentation (which I probably should've done first) shows me that

self.Disconnect(ID_SPAM, -1, wxEVT_COMMAND_MENU_SELECTED)
self.Disconnect(ID_EGGS, -1, wxEVT_COMMAND_MENU_SELECTED)

seems to work. I disconnected the events and created a new menubar; selecting the same commands on the new menubar does nothing.

Yep, this is a correct way to do it.

On the other hand,

self.Disconnect(-1, -1, wxEVT_COMMAND_MENU_SELECTED)

does not seem to disconnect anything -- the commands duplicated from the first menu still work. Well, I'd hoped to save a bit of typing, but so be it.

I'm not sure why, but the search is not checking for a wildcard (-1) for the first ID value. Everything else is allowed to be wild so I guess that whoever wrote it just decided that it made more sense to allow the eventType to be wild and require the ID. The good news is that you can save a bit of typing by doing it like this:

self.Disconnect(ID_SPAM)
self.Disconnect(ID_EGGS)

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Cliff Wells wrote:

What I'd suggest (without trying, of course <wink>) is to simply have
unique id's for all your menu items. That way you don't have to muck
around with binding/rebinding event handlers.

Now I remember why I wasn't setting up my entire event table beforehand.

EVT_MENU(self, ID_OPTIONS, self.panel.OnOptions)

needs to be able to resolve self.panel.OnOptions as it's run. If that method only applies to the second panel, rather than the first, I get an AttributeError. This can be resolved with an extra level of indirection, though --

    EVT_MENU(self, ID_OPTIONS, self.OnOptions)

def OnOptions(self, evt):
    return self.panel.OnOptions(evt)

That way, the panel method doesn't need to exist until the event is triggered.

Jeff Shannon
Technician/Programmer
Credit International

Nothing wrong with that, but an alternative would be to lazy-load the
menus. Building on my previous example:

# ------------- menus.py -----------------------
eggmenu = [
    # file
    ('File',[
    ('Spam Menu', 'OnSpamMenu'),
    None, # separator
    ('Exit', 'OnExit'),
    ]),

    # edit
    ('Edit', [
    ('Copy Egg', 'OnCopyEgg'),
    ('Paste Egg', 'OnPasteEgg'),
    ]),
]

spammenu = [
    # file
    ('File',[
    ('Eggs Menu', 'OnEggsMenu'),
    None, # separator
    ('Exit', 'OnExit'),
    ]),

    # edit
    ('Edit', [
    ('Copy Spam', 'OnCopySpam'),
    ('Paste Spam', 'OnPasteSpam'),
    ]),
]

menubars = { 'eggs': eggmenu,
             'spam': spammenu,
             }

# --------------- main.py -----------------------
import wx

import menus

class DynamicMenuFrame(wx.Frame):
    def __init__(self, parent, id, title, menubars, menudefs):
        wx.Frame.__init__(self, parent, id, title)

        self.menubars = {}

        for mb in menubars:
            self.LoadMenu(mb, menudefs)

    def SwitchMenus(self, mb):
        self.SetMenuBar(self.menubars[mb])
                        
    def LoadMenu(self, mb, menudefs):
        self.menubars[mb] = wx.MenuBar()

        for menuLabel, menuItems in menudefs.menubars[mb]:
            menu = wx.Menu()
            for item in menuItems:
                if item is None:
                    menu.AppendSeparator()
                    continue
                itemLabel, itemHandler = item
                id = wx.NewId()
                menu.Append(id, itemLabel)
                wx.EVT_MENU(self, id, getattr(self, itemHandler))
            self.menubars[mb].Append(menu, menuLabel)

class MyFrame(DynamicMenuFrame):
    def __init__(self, parent, id):
        DynamicMenuFrame.__init__(self, parent, id, "Menu Test",
                                  menubars = ['spam'],
                                  menudefs = menus)
        
        self.SwitchMenus('spam') # our default menu

    # handlers
    def OnEggsMenu(self, event):
        print "switching to eggs menu"
        if 'eggs' not in self.menubars:
            self.LoadMenu('eggs', menus)
        self.SwitchMenus('eggs')
        
    def OnSpamMenu(self, event):
        print "switching to spam menu"
        if 'spam' not in self.menubars:
            self.LoadMenu('spam', menus)
        self.SwitchMenus('spam')
        
    def OnCopyEgg(self, event):
        print "OnCopyEgg"
        
    def OnCopySpam(self, event):
        print "OnCopySpam"

    def OnPasteEgg(self, event):
        print "OnPasteEgg"
        
    def OnPasteSpam(self, event):
        print "OnPasteSpam"

    def OnExit(self, event):
        print "Exit"
        self.Close()
        
app = wx.PySimpleApp()
f = MyFrame(None, -1)
f.Show(True)
app.MainLoop()

Regards,
Cliff

···

On Fri, 2003-08-08 at 17:39, Jeff Shannon wrote:

Now I remember why I wasn't setting up my entire event table beforehand.

EVT_MENU(self, ID_OPTIONS, self.panel.OnOptions)

needs to be able to resolve self.panel.OnOptions as it's run. If that
method only applies to the second panel, rather than the first, I get an
AttributeError. This can be resolved with an extra level of
indirection, though --

    EVT_MENU(self, ID_OPTIONS, self.OnOptions)

def OnOptions(self, evt):
    return self.panel.OnOptions(evt)

That way, the panel method doesn't need to exist until the event is
triggered.

--
They suffocate in an alarming embrace
                     -Dead Can Dance