This page is for those of us concerned with making sure their code works properly on all platforms, and/or that wish to make contributions to the wxPython library.

In my relatively short experience with deriving custom controls using wxPython, I have found that there are a few things to watch out for when trying to make your code platform independent. If you're like me, you probably don't have one of every platform lying around to test on, so I thought it would be a good idea to record such tidbits and what, if anything, you can do about them, so others don't have to rediscover them the hard way.

(Many, if not all, of these are really wxWindows issues, but as a wxPython developer I think they're useful to record in the wiki for our native tongue, as it were... ;) )

I thought that the best way to organize this is by base control type.
I encourage others to add to this page with other things they've found as well.

-- Will Sadkin

Miscellany

My experience so far has been that while there are differences between the platforms, usually the is one way to solve a given problem that works on all of them. I have yet to have an platform specific code in my stuff. It's often the case that the documented way works, and it's just that you don't have to do everything quite right on some platforms, in some cases, so READ THE DOCS.

I don't think any one platform is more lower common denominator than any other...

-Chris Barker

Changing Control Properties Dynamically

According to Robin Dunn:

wxMenu

Handling EVT_MENU: event.GetEventObject() inconsistent

   1 ''' menuItemEventObjectDifferentAmongPlatforms.py
   2     2003-12-04 by Paul McNett (p@ulmcnett.com)
   3 
   4     This example shows a menu item EVT_MENU event being handled by the 
   5     main frame, and shows that on Linux, event.GetEventObject() returns
   6     a reference to the menu item, while on Windows, event.GetEventObject()
   7     returns a reference to the main frame. I haven't tested on OSX yet.
   8     
   9 '''
  10 import wx
  11 
  12 class MainMenuBar(wx.MenuBar):
  13     def __init__(self, mainFrame):
  14         wx.MenuBar.__init__(self)
  15         self.Append(FileMenu(mainFrame), "&File")
  16 
  17 class FileMenu(wx.Menu):
  18     def __init__(self, mainFrame):
  19         wx.Menu.__init__(self)
  20         submenu = FileOpenMenu(mainFrame)
  21         self.AppendMenu(wx.NewId(), "&Open\tCtrl+O", submenu)
  22 
  23 class FileOpenMenu(wx.Menu):
  24     def __init__(self, mainFrame):
  25         wx.Menu.__init__(self)
  26         menuId = wx.NewId()
  27         self.Append(menuId, "&Test", "Runs the test")
  28         wx.EVT_MENU(mainFrame, menuId, mainFrame.OnFileOpen)
  29 
  30 class MainFrame(wx.Frame):
  31     def __init__(self):
  32         wx.Frame.__init__(self, None, -1, "Select menu File|Open|Test...")
  33         self.SetMenuBar(MainMenuBar(self))
  34 
  35     def OnFileOpen(self, event):
  36         print ( "\n         Your platform: %s\n"
  37                 "event.GetEventObject(): %s\n\n"
  38                 "Run this demo on various platforms \n"
  39                 "and note the differences. Particulary,\n"
  40                 "I've noted that on Linux I get the\n"
  41                 "expected menu item reference, while\n"
  42                 "on Windows I get the mainFrame reference.\n") \
  43             % (wx.Platform, event.GetEventObject()) 
  44         
  45 if __name__ == "__main__":
  46         # instantiate a simple app object:
  47         app = wx.PySimpleApp()
  48         frame = MainFrame()
  49         frame.SetSize((400,200))
  50         frame.Show(1)
  51         app.MainLoop()

Robin posted the following as the solution to this issue:

I'm not sure if it has been done yet, but correcting this difference has been discussed. In the meantime you can use something like the following to get the menu on all platforms:

wxPopupWindow

Using Static Text controls as Transient Popups

{{{On Tue Nov 13 06:23:02 2001, Vadim Zeitlin wrote:

}}}

wxTextCtrl

Fixed Width Fonts

Although the wxWindows documentation says that wxMODERN is available as a fixed width font, it is reported not to work under MSW. However, the undocumented font family wxTELETYPE works on all platforms; it is mapped to an appropriate font family for that platform, (eg. "Courier New" under MSW.)

However, I've found that wxTELETYPE still displays my wxTextCtrl in Arial on Windows NT4. (PaulMcNett, 2003-12-04)

Rich Text

The wxTE_RICH style does not work properly under all versions of MSW. Among other issues, GetInsertionPoint() on a multiline text control reports is off by the number of newlines that precede the cursor position. Robin Dunn's recommendation was to use wxTE_RICH2, which solves this particular problem. When asked what this style was, Robin replied:

However, On Fri, 31 Jan 2003 , Vadim Zeitlin wrote:

On Fri, 31 Jan 2003 13:27:25 Robert Roebling wrote:
RR> as the subject says: what is the difference between
RR> wxTE_RICH and wxTE_RICH2 under MSW 2.4.0 ?

 From the implementation point of view, wxTE_RICH2 uses RichEdit 2.0 (or
3.0 masquerading as 2.0) while wxTE_RICH uses RichEdit 1.0 (or, you guessed
it, 3.0 masquerading as 1.0).

 From the users point of view the difference is that only wxTE_RICH2 allows
having characters in different encodings in the same control (i.e. this is
basicly why I added it: I use it in Mahogany for viewing the mail messages
which may contain text in different languages/encodings). It wasn't made
default however because RichEdit 2.0 is *very* buggy and it's almost always
better to use wxTE_RICH if you don't need the multiple languages support.

 Of course, on Win2k/Win98 and later, we don't have neither RichEdit 1.0
nor 2.0 but RichEdit 3.0 which can emulate [with some of, but not all, the
bugs present in the "real" thing] either 1.0 or 2.0. So there the
difference between wxTE_RICH and wxTE_RICH2 doesn't make much sense. But
the trouble is that I have no idea about how to distinguish RichEdit 3.0
from 1.0/2.0...

 So it's a mess. I have no idea about what we can do about it short of
writing our own richedit replacement.

(So: Caveat Emptor.)

wxTextCtrl.SetSelection()

Under the Microsoft Windows implementation, the function .SetSelection() also sets the insertion point, but on other platforms, notably GTK, it does not. So, to ensure consistency, you should always call .SetInsertionPoint() before setting the selection.

wxTextCtrl.SetValue() from within Event Handlers

Under one or more platforms, (notably wxGTK), if you replace the contents of the control from within an event handler, two EVT_TEXT events are generated: one for the intermediate value of an empty string, and one for the resulting text. (This is notably not the case for the Microsoft Windows port.) If your derived control is trying to prevent an empty string as the value, then to make it platform-independent you need to account for this. (eg. wxIntCtrl.)

Two events can also be generated in response to a single .SetValue() within an EVT_CHAR event handler, one text event a duplicate of the other. (This *does* happen under MSW, but is apparently not consistent with all versions.) If you're doing this sort of thing, and want to prevent this duplicate event problem reliably, you'll have to add logic in your own EVT_TEXT handler for detecting no change in actual value and swallowing the second event rather than 'skipping' it. (eg. wxMaskedTextCtrl.)

wxTextCtrl.GetStringSelection()

If the string to be returned results from the user having double-clicked a "word," then the return is slightly different under Windows and under Mac OSX. The Windows token may include a trailing space and the Mac token does not. To guard against inconsistencies, something like this

yields the same string on both platforms.

wxToolbar

wxToolbar.Realize()

This function should be called after you have added tools. On GTK it does not appear to be necessary (it won't hurt either), but on MSW, not calling it will result in the controls all being drawn on top of each other.

Note from the docs:

wxToolBar.SetToolBitmapSize()

Sets the default size of each tool bitmap. The default bitmap size is 16 by 15 pixels. On GTK, the toolbar will automatically re-size itself to fit the bitmaps you add, but on MSW, this call is required if you use bitmaps bigger that the standard. It won't hurt anything to call it on GTK.

wxDC

wxDC.SetClippingRegion()

On GTK, wxDC.SetClippingRegion only works properly with positive coordinates. For example, wxDC.SetClippingRegion(10,100,50,-50) will work on MSW, but not on GTK. You need to use: wxDC.SetClippingRegion(10,50,50,50). This will work on all platforms

wxDC.DrawBitmap()

On MSW, wxDC.DrawBitmap will fail silently if the Bitmap is currently selected into another wxMemoryDC. Before drawing, the Bitmap must be deselected out of the wxMemoryDC, or the wxMemoryDC must be deleted. This is not required on GTK, so it can be a strange bug that only shows up on MSW

wxMDIParentFrame()

GetChildren()

Using GetChildren() from the parent frame has the following behavior for the platforms listed.

- MSW, instances of wxMDIChildFrame() will show-up with all other top level children
  within the parent frame, where other top level children could be a status bar, sash
  windows, etc...

- Linux, no instances of wxMDIChildFrame() will show-up. Instead, a reference to a
  container, wxMDIClientFrame, will appear. The client frame will then hold all 
  instances of wxMDIChildFrame.

MSW trace: 
References to wxMDIChildFrame follow those of the sash windows and a status bar:
[
 <wxPython.windows3.wxSashLayoutWindow instance;proxy of C++ wxSashLayoutWindow instance at _96b008_wxSashLayoutWindow_p>, 
 <wxPython.windows3.wxSashLayoutWindo w instance;proxy of C++ wxSashLayoutWindow instance at _fbed30_wxSashLayoutWindow_p>, 
 <wxPython.windows3.wxSashLayoutWindow instance;proxy of C++ wxSashLayout Window instance at _139a978_wxSashLayoutWindow_p>, 
 <wxPython.stattool.wxStatusBarPtr instance;proxy of C++ wxStatusBar instance at _120a430_wxStatusBar_p>, 
 <m_Child.m_Child instance;proxy of C++ wxMDIChildFrame instance at _14f49d0_wxMDIChildFrame_p>, 
 <m_Child.m_Child instance;proxy of C++ wxMDIChildFrame instance a t _1513cd8_wxMDIChildFrame_p>
]

Linux trace: 
There are no references to wxMDIChildFrame, instead there's now a reference to 
wxMDIClientWindow. The wxMDIClientWindow() will hold all references to any wxMDIChildFrame:
[
 <wxPython.mdi.wxMDIClientWindowPtr instance;proxy of C++ wxMDIClientWindow instance at _8458808_wxMDIClientWindow_p>, 
 <wxPython.windows3.wxSashLayoutWindow instance;proxy of C++ wxSashLayoutWindow instance at _8459798_wxSashLayoutWindow_p>, 
 <wxPython.windows3.wxSashLayoutWindow instance;proxy of C++ wxSashLayoutWindow instance at _845ac40_wxSashLayoutWindow_p>, 
 <wxPython.windows3.wxSashLayoutWindow instance;proxy of C++ wxSashLayoutWindow instance at _84659c0_wxSashLayoutWindow_p>
]

Sorry, I have no information or traces for the Mac...but it would be nice if someone could add one. :-)

wxFrame

New frames not showing widgets correctly under MS Windows

Symptoms: Widgets don't show up in a frame until you resize the frame with your mouse.

Solution: Send a SizeEvent to the frame after you've created your widgets.

e = wx.SizeEvent(self.GetSize())
self.ProcessEvent(e)

wxNotebook

Switching pages messing up rendering under MS Windows

Symptoms: When switching pages in a wxNotebook control subclass in GTK, everything works fine. When running the same code in MS Windows, the rendering and visibility of widgets is erroneous.

Solution: You are probably catching EVT_NOTEBOOK_PAGE_CHANGED, but in your event handler you aren't passing the event along to the next handler in the chain. You must do so in order to make sure the notebook control stays in a consistent state.

class MyNotebook(wx.Notebook):
    def __init__(self, ...):
        ...
        wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), self.OnPageChanged)
    def OnPageChanged(self, event):
        ...
        event.Skip()

EVT_NOTEBOOK_PAGE_CHANGED not created on wxNotebook.DeletePage using wxPython with GTK 1.2

Symptoms: When using AddPage or DeletePage in a wxNotebook, an EVT_NOTEBOOK_PAGE_CHANGED event is generated on the MS Windows platform. However, it is *not* generated on the GTK 1.2 platform. (This behavior exists in 2.5.1.5 -- haven't tried other versions or other platforms.)

Solution: Use the following class to fix the problem until a new version of wxPython that fixes this bug is released :-)

import wx

class NotebookFixer(wx.Notebook):
    """Fixes bugs in wx.Notebook when running with GTK 1.2"""

    def __init__(self, *args, **kwargs):
        wx.Notebook.__init__(self, *args, **kwargs)
        self.__initPageChanged()
        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.__catchPageChangedEvent,
                  self)

    def __catchPageChangedEvent(self, event):
        """Catch a page change event and record that it happened."""
        if not self.__sendingPageChange:
            self.__pageChangedEventFlag = True

    def __initPageChanged(self):
        self.__sendingPageChange = False
        self.__pageChangedEventFlag = False

    def __resetPageChangedEvent(self):
        """Resets internal flag for page change events."""
        if not self.__sendingPageChange:
            self.__pageChangedEventFlag = False

    def __sendPageChangedEvent(self):
        """Send a page change event for current page if not already sent."""
        if wx.Platform == '__WXGTK__' and not self.__pageChangedEventFlag:
            pos = self.GetSelection()
            event = wx.NotebookEvent(wx.EVT_NOTEBOOK_PAGE_CHANGED.evtType[0],
                                     self.GetId(), pos, -1)
            self.__sendingPageChange = True
            self.ProcessEvent(event)
            self.__sendingPageChange = False

    def AddPage(self, *args, **kwargs):
        self.__resetPageChangedEvent()
        result = wx.Notebook.AddPage(self, *args, **kwargs)
        self.__sendPageChangedEvent()
        return result

    def DeletePage(self, *args, **kwargs):
        self.__resetPageChangedEvent()
        result = wx.Notebook.DeletePage(self, *args, **kwargs)
        self.__sendPageChangedEvent()
        return result

wxPython Platform Inconsistencies (last edited 2008-03-11 10:50:27 by localhost)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.