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
Contents
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:
- Some native controls on some platforms don't support changing colours or other attributes, at least not without extra work. [If you are having this trouble] there is a set of generic buttons available in the library in wxPython.lib.buttons that are more flexible and will let you change whatever you want.
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:
menu = self.GetMenuBar().FindItemById(event.GetId()).GetMenu()
wxPopupWindow
Using Static Text controls as Transient Popups
{{{On Tue Nov 13 06:23:02 2001, Vadim Zeitlin wrote:
>On Mon, 12 Nov 2001, Robin Dunn wrote: >> On wxGTK if the first child in a PopupTransientWindow is a wxStaticText >> then the mouse will never be released since the wxStaticText can't get the >> mouse events.
I don't know what to do about it The only answer I see is to always create a filler window as the child of wxPopupWindow and create wxStaticText as its child - this is ugly but should work.
}}}
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:
- "It just specifies that a different version of the native control should be used on win32. It's ignored on other platforms."
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
word = wx.TextCtrl.GetStringSelection().strip()
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:
- If you are using absolute positions for your tools when using a
wxToolBarSimple object, do not call this function. You must call it at all other times.
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