Avoiding Window IDs
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 bindingEVT_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 aGetId()
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 aGetId()
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 aGetId()
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.
Comments