raf wrote:
Hi,
windows-8.1, python-2.7.8, wxpython-2.8.12.1
I have an application with a large dynamic popup menu.
Every time I construct it, I use ID_ANY as the id
for all the menu items.
After calling frame.PopupMenu(menu), I call menu.Delete()
but all of the ids used in the menu remain effectively
claimed and the next time the menu is constructed,
the ids used by the new menu items are higher than the
last time.
I assume that's ok because Getting Started - wxPyWiki
says "You can make your own IDs, but there is no reason to do that."
However, occasionally I get reports from the users that when they
select a menu item, the application behaves as though some other
completely unrelated menu item had been selected.
I added some logging to see what was going on and asked them to
tell me when it next happened which it just has. It seems that the
problem happened the first time the user selected an item
after the menu item ids has exceeded 2**16. This might be an
unimportant coincidence. I only have one data point. But it
might be relevant.
It is. IIRC, Windows has a limit for menu IDs but a different limit for control IDs. In 2.8 and prior[1] the IDs generated for wx.ID_ANY (and those returned from wx.NewId()) are basically just an incrementing global variable with almost no smarts behind it. There is also wx.RegisterId(val) which will bump the auto-increment sequence to val+1 so you can reserve some IDs that way if you want, but you can still run into issues if the value wraps around and comes back to IDs that you may still be using somewhere. And then there are problems when the ID passes some artificial limit that the platform imposes.
When the problem occurs, the id of the selected menu item is a
much lower number than the ids of any of the menu items that
exist in the menu at the time. It looks as though it's using
a much earlier id from a previous incarnation of the menu.
Does anyone have any idea what might be going wrong?
If it could have something to do with the ever increasing ids,
is there a way to tell wx to reset its next-available-id to
be one more than the currently used id?
Would using application-defined ids be ok or might other uses
of ID_ANY reuse ids that I've chosen?
What I've tended to do in situations like this where there will be lots of items needing IDs being created, is to go ahead and use fixed ID values for the items, but use wx.NewId() to give me the ID values. That way they won't conflict with any other IDs that are still being generated with wx.ID_ANY (as long as they don't wrap around, but then that is what you are trying to prevent by doing this) and they will also not conflict with any stock IDs. For example, I'll have globals like this:
MY_ID_1 = wx.NewId()
MY_ID_2 = wx.NewId()
etc.
and then use those IDs where I create the popup menus. Presetting your menu IDs like that also means that you don't need to Bind/Unbind the event handlers each time you popup the menu, just do it once at the beginning of the program.
[1] There was a change made in the 2.9 series that puts some more intelligence behind the generated IDs, where it will try to track those that are reserved and reuse those that are not in use any more. I don't recall details at the moment on the implementation so I'm not sure how bulletproof it is, but it should be transparent to the application code.
···
--
Robin Dunn
Software Craftsman