In preparation for this presentation I took another look at the tkinter module documentation, and I'm embarassed to admit that I don't see anything (at a glance) that tkinter can't do that we needed. So maybe personal experience played into the decision more than it should have. I think that some of the issues we're going to talk about are applicable regardless of toolkit, especially the packaging issues.
Of course, wxPython isn't without it's problems. It has a tendancy to show it's C roots, although this is becoming less of a problem. I recommend using wxPython 2.5; it's technically the development branch, but lots of things are mapped in a more Python-ic manner.
wxPython will also occasionally it will let you do things the "wrong" way. And they'll work -- on some platforms. These bugs are the hardest to track down and I'll try to point out possible problem areas to you that I repeatedly ran into.
I've also taken a look at PyObjC for OS X, and while it definitly produces applications that behave better on OS X (in subtle ways), we don't have the resources to manage two code bases. It's possible that the ccPublisher 2 code base will support different UI front ends, but it's not a driving need.
In the first iteration of ccPublisher we (I) totally ignored the Model, rolling all the logic into the top-level window and application classes. While I could attempt to justify that decision, I've since begun working on a major refactoring effort which attempts to enforce a stronger separation between UI and logic. While this has resulted in some tougher "engineering" decisions, it's also resulted in cleaner, more maintainable code.
This leads me to the corrolarry to one of Jim Fulton's Laws of Engineering. One of Jim's Laws states "You can't solve a problem until you know the answer." My corrolary is "You can solve a problem if you know what the answer isn't."
wxPython provides a passable tool called XRCED for editting XRC files. It's far from perfect, but does allow you to do most editting tasks. Note that on OS X, you'll need to use the most recent version. Older versions had problems with properly displaying the widget properties.
# create the frame's skeleton (** phase 1 **)
pre = wx.PreFrame()
# load the actual definition from the XRC (** phase 2 **)
xrc.LoadOnFrame(pre, parent, "FRM_MAIN")
# finish creation (** phase 3 **)
self.PostCreate(pre)
The GridBagSizer is a tempting widget, since it allows lots of flexibility in organizing widgets into columns/rows, and also supports spanning rows and columns. However, my experience has been that it has layout problems on OS X, especially when nested within another sizer. Therefore, it's often easier to use nested FlexGridSizers to accomplish the same task.
When inserting a static bitmap, the XRC specification requires you specify the explicit path to the image. Since the images and resources may be stored in different places depending on platform, I've found it easier to use a placeholder Panel in the XRC, and then load the image at run time. See my wiki for sample code.
It seems to make sense to perform application-level "janitorial" tasks in the Application setup and tear-down code. Things such as loading/saving preferences, configuring platform specific paths and loading translation catalogs. For this reason we're starting to use XpApp, a specialized App class that just provides more hooks for customizing specific parts of the startup process.
class ConverterApp(wx.App):
def OnInit(self):
"""Perform application set up. Return True on success."""
self.mainWindow = ConverterWindow(None, "converter.xrc")
self.SetTopWindow(self.mainWindow)
self.mainWindow.Show()
return True
def OnExit(self):
"""Perform application tear down."""
if __name__ == '__main__':
app = ConverterApp()
app.MainLoop()
self.Bind(wx.EVT_BUTTON, self.onCalculate, XRCCTRL(self, "CMD_CONVERT"))
def onCalculate(self, event):
dollars = XRCCTRL(self, "TXT_DOLLARS").GetValue()
rate = XRCCTRL(self, "TXT_RATE").GetValue()
euros = float(dollars) * float(rate)
XRCCTRL(self, "TXT_EUROS").SetValue(str(euros))
xrc_pot.py to generate the template file.msgfmt.py to compile .po to .mo files before this will work.
def initI18n(self):
# initialize the wxPython translation interface
i18n_path = os.path.join(os.getcwd(), 'locale')
wx.Locale.AddCatalogLookupPathPrefix(i18n_path)
self.i18n = wx.Locale(wx.LANGUAGE_DEFAULT)
self.i18n.AddCatalog('converter')
# initialize the gettext interface
gettext.install('converter', os.path.join(os.getcwd(), 'locale'), unicode=True)
self.tbicon = wx.TaskBarIcon()
self.tbicon.SetIcon(wx.Icon('app.ico', wxBITMAP_TYPE_ICO), 'tooltip')
The taskbar icon for both Windows and Linux is handled using the wxTaskBarIcon class. This class has a SetIcon method, which takes a wxIcon instance. This class can also manage the tooltip and popup menu. Note that on Linux, this only works with window managers which support the freedesktop.org System Tray Protocol. Gnome and KDE both do.
The Desktop Icon is set using the setup.py, and should contain multiple images (16x16, 32x32, 48x48). The png2ico utility is very useful for creating the appropriate file.
The Application Icon is actually specified in the setup.py call to py2app. We'll cover that later. If you install the Apple Developer Tools, you get the IcnsEditor application, which allows you to construct icon resource files from PNGs, etc.
In order to specify the About Box and other Application menu items, we create a menubar with the appropriate IDs. These IDs are fairly static, but can be retrieved dynamically from the wx.App class. For example, wx.App.GetMacAboutMenuItemId() will return the Menu ID which wxPython will dynamicaly use for the About entry on the Application Menu.
To establish the top of screen menu, simply create a menu and use the top window's SetMenuBar method. This will become the top of screen menu.
When dropping files on the application icon for Windows or Linux, the file names come in as sys.argv. For OS X, this only occurs if argv_emulation is enabled. Finally, to enable dropping files on the dock icon when the app is running, we need to implement the MacOpenFile method on the App class.
Solutions do exist for freezing applications on Linux, but we have made the internal decision that users of Linux are probably savvy enough to use the command line. We are, however, exploring the possibility of writing a "proper" installation script, complete with desktop entries and the like.
windows and consoles options to specify the primary scripts and (optionally) icons.encodings package.setup.py py2exe results in the files being copied into the dist directoryYou can download Py2Exe from http://starship.python.net/crew/theller/py2exe/.
WiX is one of Microsoft's first open source projects, and is licensed under the CPL. It depends on the .NET framework.
bundlebuilderAs anyone who's developed for OS X knows, .app "files" are really bundles; that is, directories with a set structure that contain the entire application and it's dependencies.
hdiutil create -srcfolder $SOURCE $DESTNAME
# check for win32 support
if sys.platform == 'win32':
# win32 allows building of executables
import py2exe
# check for OS X support
if sys.platform == 'darwin':
import py2app
pkg_data.append( ('../Frameworks',
['/usr/local/lib/wxPython-unicode-2.5.3.1/lib/libwx_macud-2.5.3.rsrc'])
)
setup(name='CurrencyConverter',
version='1.0',
description = "",
long_description="",
url='http://creativecommons.org',
author='Nathan R. Yergler',
author_email='nathan@creativecommons.org',
windows=[{'script':'converter_4.py',
}],
app = ['converter_4.py'],
scripts=['converter_4.py',],
options={"py2exe": {"packages": ["encodings", 'rdflib'], },
"py2app": {"argv_emulation": True,
},
},
)