Due to some changes upstream in wxWidgets the wxNewId function has been
deprecated. This deprecation first took effect in wxPython in the recent 4.0.2 release, and has triggered some
discussions in the wxPython-users group about window IDs. This post will give a
little more context and background, and also list some examples and advice I
gave in wxPython-users so it can be more easily found than just being buried in
a message archive.
Background
About 20 years ago wxWidgets was rearchitected to use event tables to dispatch
events to event handlers. Prior to this change, when events arrived in the
application they were handled by calling the C++ virtual method intended to handle
that kind of event. In other words, C++'s virtual method dispatch mechanism
handled it. This worked well, but it was very inflexible and cumbersome.
Switching to event tables simplified many things, increased the flexibility, and
made it easier to add new kinds of events to the library as needed without
redesigning base classes, etc.
The items in the event tables consisted of an event type ID, the source widget
ID, a pointer to the member function to be called when the event arrives, and
some additional housekeeping items. Originally the tables were all constructed
at compile time and are a member of the class whose members are the event
handler methods. Since they are built at compile-time then these tables are
static (unchangeable) at runtime. A little later on dynamic event tables were
added and that allowed wxPython to be born, but even then it was a very long
time before it was common practice for wxWidgets C++ program to use dynamic
event tables to any great extent.
Since the event tables were defined statically at compile time that meant that
the table items which needed window IDs to properly route the event would also
need to have the ID defined at compile time as well. That led to the common
practice for wxWidgets users to create a bunch of ID_FOO constants and
manually managing their values and worrying about whether there were overlaps
between sets of IDs. This works fine, but can be cumbersome at times.
In the early days of wxPython it quickly became obvious that following the same
C++ patterns was not a very fun way to work with the library. Once wxWidgets
adopted the practice that widgets would generate their own ID if -1 or
wxID_ANY was passed to the constructor then several Python-only enhancements
naturally fell into place as well, such as the wx.PyEventBinder objects and
the wx.EvtHandler.Bind method. I'll show some of the ways that wxPython can
be used without IDs in the next section.
Writing ID-free wxPython
There are a few things that have been done to minimize the need to have
preallocated IDs in wxPython. In fact, back in the early days of this project
there were a number of advocates for eliminating, or at least hiding the ID
parameters throughout all of the wxPython API. The reasoning was that they
aren't really necessary in most cases, and the remaining cases could likely be
worked around easily. When you boil it all down to the nitty-gritty, the primary
purpose of window IDs is for identifying the source of events when searching for
matching handlers, but there are many cases where it simply doesn't matter.
-
Many consider it more pythonic to Bind() an event handler directly to the
widget that produces the event, such as binding EVT_BUTTON to the button
object itself. In this case, the button's ID does not matter since there can
only be one source of button events seen at that point in the hierarchy. So
the following works and no specific IDs need to be dealt with at all:
obj = SomeWidget(self, wx.ID_ANY, ...)
obj.Bind(wx.EVT_SOMETHING, self.myHandler)
-
Building on that concept a little more, if there is only ever one source of a
particular kind event in some containment hierarchy (up to the point of the
object the handler is bound to) then again, the ID of that source doesn't
matter, at least for the purpose of binding and finding event handlers for
that event type. And it definitely doesn't matter if the same ID is used in
another containment hierarchy because that other one will never be seen here.
Again, the specific ID of the widget is not needed.
obj = SomeWidget(self, wx.ID_ANY, ...)
self.Bind(wx.EVT_SOMETHING, self.myHandler)
-
When there can be multiple sources of the same event type within a
containment hierarchy which need to be routed to different handlers then the
third parameter of Bind() (named 'source') can be used to accomplish that,
and still there is no need to use and know specific ID values.
objA = SomeWidget(self, wx.ID_ANY, ...)
objB = SomeWidget(self, wx.ID_ANY, ...)
self.Bind(wx.EVT_SOMETHING, self.myAHandler, objA)
self.Bind(wx.EVT_SOMETHING, self.myBHandler, objB)
-
Thanks to the magic of Python the source parameter of the Bind() method can
take any kind of object which has a GetId() method. That includes all
windows/widgets, menu and toolbar items (which are returned by the methods which
add the items to the menu or toolbar), timers, and so forth. If there is
anything that can be the source of an event that does not have a GetId() method
then I would consider that to be a bug and it should be reported. Since it's
Python then it can also be non-UI objects that have a GetId() method, if that
makes sense for the design of your application.
item = menu.Append(wx.ID_ANY, "&Foo", "Do some foo stuff")
self.Bind(wx.EVT_MENU, self.onDoFoo, item)
-
If you ever do need to know the ID of an item then you can just call it's
GetId() instead of creating a preset constant.
item = menu.Append(wx.ID_ANY, "&Foo", "Do some foo stuff")
toolbar.AddTool(item.GetId(), "Foo", fooBmp, "Do Some Foo stuff")
self.Bind(wx.EVT_MENU, self.onDoFoo, item) # works for both items
-
One other common place where I've seen people wanting to use specific IDs is
in event handlers to differentiate behavior based on the source of the event.
For example:
def OnHandleButtons(self, evt):
if evt.GetId() == ID_A:
self.doSomething()
elif evt.GetId() == ID_B:
self.doSomethingElse()
...
However it's just as easy to compare the event's source object itself, and is
also a stronger way to dispatch based on source in the rare case where there
might be a duplicate ID conflict.
def OnHandleButtons(self, evt):
if evt.GetEventObject() is self.objA:
self.doSomething()
elif evt.GetEventObject() is self.objB:
self.doSomethingElse()
...
On the other hand, it is also simple to bind a separate handler for each of
the cases above and that would also be a better OOP design.
This obviously doesn't cover all use cases now and forever, but it should show
that the need to use and reuse preset ID values is lower than some people may
think, and that the desire is that they should not be needed at all.