wxPython 4.0.4 Released

"What? It's 2019 already?"

wxPython 4.0.4 is now available at PyPI, with some additional files at Extras

Changes in this release include the following:

  • Fixed an issue where wx.lib.intctrl would erroneously attempt to use long on Python3. (#898)

  • Include the MSVC runtime DLLs for Python 3.7 builds too.

  • Clear LIBPATH_PYEXT and LIB_PYEXT for linux builds too. (#904)

  • Added a dependency on the Pillow package since it's used in some wx.lib.agw modules. (PR#908)

  • Add flag to hide page in wx.lib.agw.aui.notebook. (#895)

  • Switch wx.lib.plot to issue deprecation warnings with PlotPendingDeprecation so it doesn't have to enable all warnings to get them to be shown by default. (#902)

  • Added a Python 3.7 builder on Fedora 28. (#925)

  • Fix the object ownership transfer for wx.Menu.Insert() (#931)

  • Added wx.Treebook.GetTreeCtrl, wx.Listbook.GetListView and wx.Choicebook.GetChoiceCtrl. (#918)

  • Removed the wx.BookCtrlBase.RemovePage workaround as it was causing problems and doesn't seem to be necessary any more. The existing wxWidgets assertions are catching the out of range error just fine, however if wxWidgets was built without the debug helpers turned on then it could still cause a crash. (#888)

  • Reverted the changes which removed the content of the wx.lib.pubsub package and encouraged users to switch to the real PyPubSub package instead. Removing it caused more issues than were expected so it has been restored and the code updated to PyPubSub v3.3.0. Version 4.0.0 is available upstream, but it is not compatible with Python 2.7. Now, wx.lib.pubsub is actually deprecated instead of just trying to pass control over to the upstream PyPubSub library. (#932)

  • Improve calltip stability in pyshell. (#941)

  • Fix TypeError in wx.lib.throbber. (#924)

  • Fix missing parameter tool_id in wx.lib.agw.ribbon.toolbar.RibbonToolBar.AddToggleTool. (#947)

  • Add a step to wx.Config.ReadInt to attempt converting from long to int under python2. (#384)

  • Add virtual behavior for wx.RichTextCtrl and wx.TextCtrl's Copy/Cut/Paste methods and their Can* counterparts. (#954)

  • Fix IO type in wx.lib.agw.thumbnailctrl (#959)

  • Fix type error that would occur using pycolourchooser. (#957)

  • Optimize line drawing in HyperTreeList. (#973)

  • Add wrapper for wx.StaticBox.GetBordersForSizer and use it in the demo to do platform-specific layout of the items in the StaticBox. (#974)

  • Update wx.Point, wx.RealPoint, and wx.Size to use floating point arithmetic when conducting scalar multiplication (#971)

  • Fix load/save bugs in PySlices (PR#978)

  • Replace deprecated PIL.Image.tostring (PR#1005)

  • Fix rendering and mouse sensitivity in UltimateListCtrl when adding HyperText items. (#1010)

  • Added a parameter to lib.agw.CustomTreeCtrl.SetItemWindow(), to allow positioning the Window (a small image) on the left of text in a CustomTreeItem. (#PR886).

  • Declared DeleteAllPages in the notebook subclasses, so the proper C++ implementation will be called. (#972)

  • Removed wx.lib.floatbar, which has been deprecated forever and probably hasn't been working in nearly as long. (#976)

  • Updated SIP to version 4.19.13.

  • Fix an issue in wx.lib.agw.aui.AuiManager where the orientation of an AuiToolBar would not be updated when calling LoadPerspective. (#917)

  • Fixed a bug in wx.FileSystemHandler.OpenFile where the object ownership was not being transferred correctly, causing a crash after a premature object deletion. (#926)

  • Fixed wx.ListCtrl.Append when wx.LC_SORT style is used, so appending items out of order does not lose the data for the remaining columns. (#906)

  • Add wx.Accessible, it's Windows-only, will raise a NotImplementedError exception on the other platforms. (#958)

  • Added the ability to generate stub classes for use when optional wxWidgets features are not part of the build. So far, stubs are available for wx.Accessible, wx.FileSystemWatcher, wx.glcanvas, wx.media and wx.html2.

  • Moved the wxpy_api.h file into the wx package at wx/include/wxPython so it will be included in the wheel file. (#961)

  • Fixed how string data is added to a virtual file-like object in wx.MemoryFSHandler. All strings are now added to the file as utf-8 encoded data, in both Python2 and Python3, and will be read from the virtual file the same way. If you need to use some other encoding for some reason you can first convert the text to a bytesarray or other buffer protocol compatible object and then create the virtual file from that data. (#969)

  • Performance update for wx.lib.agw.customtreectrl (#1049)

  • Ensure that colours set in wx.lib.agw.customtreectrl.TreeItemAttr are instances of wx.Colour. (#1032)

  • Fix drawing of ticks in wx.lib.agw.speedmeter when there are negative bounds values. (#1013)

  • wxWidgets for Mac includes the wxJoystick class now, also update the demo. (#997)

  • Fix wx.html.HtmlPrintout to not be seen as an abstract class, so it can be instantiated. (#1060)

  • Fix wx.aui.AuiNotbook.SetArtProvider to properly transfer ownership of the art object from Python to C++. This possible double-deletion and related crashing problems. (#1061)

  • Fixed the wrappers for wx.html.HtmlWindow.OnOpeningURL to properly handle the redirect output parameter. (#1068) This is a backwards-incompatible change, please see the Migration Guide for details.

  • TabNavigatorWindow works similarly to other programs now. It's resizable and draggable so if user has tons of files with long names, it isn't an irritation anymore plastered right in the middle of the screen and can't be worked with easily and ESC now cancels the popup with a proper returnId. (#1096)

  • Added missing methods in wx.ListBox, SetItemForegroundColour, SetItemBackgroundColour and SetItemFont. (#1095)

  • Backported a fix in wxWidgets that avoids crashing in hhctrl.ocx when using context sensitive help in 64-bit builds on Windows. (#1104)

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.

  1. 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)
    
  2. 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)
    
  3. 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)
    
  4. 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)
    
  5. 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
    
  6. 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.

wxPython 4.0.1 Released

"Lemonade"

wxPython 4.0.1 is now available at PyPI, with some additional files at the Extras archive.

This release is a quick hot-fix of some issues discovered in 4.0.0 just after the release, plus a bit of low-hanging fruit that was easy to squeeze in too. Changes in this release include the following:

Read more…