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:
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 ==
{{{
#!python
''' menuItemEventObjectDifferentAmongPlatforms.py
2003-12-04 by Paul McNett (p@ulmcnett.com)
This example shows a menu item EVT_MENU event being handled by the
main frame, and shows that on Linux, event.GetEventObject() returns
a reference to the menu item, while on Windows, event.GetEventObject()
returns a reference to the main frame. I haven't tested on OSX yet.
'''
import wx
class MainMenuBar(wx.MenuBar):
def __init__(self, mainFrame):
wx.MenuBar.__init__(self)
self.Append(FileMenu(mainFrame), "&File")
class FileMenu(wx.Menu):
def __init__(self, mainFrame):
wx.Menu.__init__(self)
submenu = FileOpenMenu(mainFrame)
self.AppendMenu(wx.NewId(), "&Open\tCtrl+O", submenu)
class FileOpenMenu(wx.Menu):
def __init__(self, mainFrame):
wx.Menu.__init__(self)
menuId = wx.NewId()
self.Append(menuId, "&Test", "Runs the test")
wx.EVT_MENU(mainFrame, menuId, mainFrame.OnFileOpen)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Select menu File|Open|Test...")
self.SetMenuBar(MainMenuBar(self))
def OnFileOpen(self, event):
print ( "\n Your platform: %s\n"
"event.GetEventObject(): %s\n\n"
"Run this demo on various platforms \n"
"and note the differences. Particulary,\n"
"I've noted that on Linux I get the\n"
"expected menu item reference, while\n"
"on Windows I get the mainFrame reference.\n") \
% (wx.Platform, event.GetEventObject())
if __name__ == "__main__":
# instantiate a simple app object:
app = wx.PySimpleApp()
frame = MainFrame()
frame.SetSize((400,200))
frame.Show(1)
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 wxPopup``Window and create
wxStatic``Text 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. wxInt``Ctrl.)
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. wxMasked``Text``Ctrl.)
== 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:
[
,
,
,
,
,
]
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:
[
,
,
,
]
}}}
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 :-)
{{{
#!/usr/bin/env python
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
}}}