A Style Guide for wxPython
  --Chris Barker (Chris.Barker@noaa.gov)

This is a little style guide for using wxPython. It's not the be-all and
end-all of how wxPython code should be written, but I what I've tried to
capture is away to write wxPython code that is clear and Pythonic. It
was vetted on the wxPython-users mailing list, with very little
disagreement.

Pythonic is partly defined by:

http://www.python.org/doc/Humor.html#zen


1) Use "import wx" NEVER use "from wxPython.wx import *".
   Don't use "import *" for other libs either.

   BECAUSE: Namespaces are one honking great idea

   For modules that are buried deep in a package, you can use:

   from wx.lib.SomeLib import SomeModule

   As no one wants to type:

   AnObject = wx.lib.SomeLib.SomeModule.SomeClass()


2) Use keyword arguments in constructors:

   MainFrame = wx.Frame(None, title="A Title", size=(500, 400))

   This lets you avoid putting in a bunch of unneeded defaults, like:
   wx.DefaultSize, wx.DefaultPosition, wx.ID_ANY, etc.

   BECAUSE: Explicit is better than implicit.

3) Don't use IDs. There is very rarely a good reason to use them.
  
   BECAUSE: Simple is better than complex.
  
   a) Most Widget constructors will fill in a default ID for you, so you
   don't have to specify one at all. Other arguments can be specified as
   key word arguments (see above):

   MyFrame = wx.Frame(None, title="A Title", size=(400,400))

   AButton = wx.Button(self, label="Push Me")

   b) If the id is a required argument, use wx.ID_ANY. wx.ID_ANY == -1,
   but I like to use it because it makes the code very clear. And who
   knows, maybe that magic value will change one day.

   EXCEPTION: (there's always an exception!)  Use standard IDs for
   standard menus, buttons, etc.It is useful to use the standard IDs
   because they may turn on standard functionality, such as menu item
   remapping for wxMac, automatic Dialog completion or cancellation,
   using stock button labels and images, etc. a list of these standard
   IDs can be found in the "Stock items" section of the wxWidgets
   Reference manual. Example:

   item = FileMenu.Append(wx.ID_ANY, "&Quit")

   This will put the Quit menu where it should be on OS-X, for instance.


4) Use the Bind() method to bind events:

   a)
   AButton = wx.Button(self, label="Push Me")
   AButton.Bind(wx.EVT_BUTTON, self.OnButtonPush)

   b)
   You can use Bind() for menus too, even though they don't have a
   Bind() method, in this way:

   FileMenu = wx.Menu()
        
   item = FileMenu.append(wx.ID_ANY, "&Quit")
   self.Bind(wx.EVT_MENU, self.OnQuit, item)

   (where self is a wx.Frame)
  
5) Use Sizers!

   If you use Sizers rather than absolute positioning, you get code
   that:

   - Works better across platforms: different platforms have different
   size widgets.

   - Easily adapts to different languages: different languages have
   different length labels, etc.

   - Works better even on one platform is the user uses a different
   default font, different theme.

   - Is more maintainable: If you need to change, remove or add a
   widget, the rest of your dialog of panel can re-arrange itself.

6) wx.App() now has the same built in functionality as wx.PySimpleApp(),
   so there is no need for the later.

7) Use separate, custom classes rather than nesting lots of wx.Panels in
   one class. If you find yourself doing this in an __init__:

   self.MainPanel = wx.Panel(self, ...
   self.SubPanel1 = wx.Panel(self.MainPanel, ..)
   self.SubPanel2 = wx.Panel(self.SubPanel1, ...)
   MyButton = wx.Button(self.SubPanel2, ....)

   Then you are creating an ugly, hard to maintain, mess! Instead, create
   custom classes for the stuff that all is working together in a panel:

   class MainPanel(wx.Panel):
       ....

   class SubPanel1(wx.Panel):
       ....

   etc.   

   You'll also find that by doing this, you're less likely to break the
   "Don't Repeat Yourself" (DRY) principle. Often you'll find that you
   have group of widgets that are common to various parts of your
   app. If you put them on a custom panel, you'll be able to re-use that
   code. Try to make each of these widgets autonomous, so they can be
   plugged in elsewhere in your app, or even another application.

   If your widget has to communicate with other controls define your own
   custom events and use them to communicate with other controls. Almost
   as a unit test it is nice to write a little demo app, which only uses
   that widget and demonstrates all its functionality.

8) Use native Python stuff rather than wx stuff were possible:
 
   BECAUSE: Simple is better than complex.

   For example, use  size=(500, 400) rather than size=wx.Size(500, 400)


-------------------
Here's a very small sample demonstrating this style:

#!/usr/bin/env python2.4

# I like to put the python version on the #! line,
# so that I can have multiple versions installed.

"""

This is a small wxPython app developed to demonstrate how to write
Pythonic wxPython code.

"""

import wx

class DemoPanel(wx.Panel):
    def __init__(self, Parent, *args, **kwargs):
        wx.Panel.__init__(self, Parent, *args, **kwargs)

        self.Parent = Parent

        NothingBtn = wx.Button(self, label="Do Nothing with a long label")
        NothingBtn.Bind(wx.EVT_BUTTON, self.DoNothing )

        MsgBtn = wx.Button(self, label="Send Message")
        MsgBtn.Bind(wx.EVT_BUTTON, self.OnMsgBtn )

        Sizer = wx.BoxSizer(wx.VERTICAL)
        Sizer.Add(NothingBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5)
        Sizer.Add(MsgBtn, 0, wx.ALIGN_CENTER | wx.ALL, 5)

        self.SetSizerAndFit(Sizer)

    def DoNothing(self, event=None):
        pass
    
    def OnMsgBtn(self, event=None):
        dlg = wx.MessageDialog(self,
                               message='A completely useless message',
                               caption='A Message Box',
                               style=(wx.OK | wx.ICON_INFORMATION)
                               )
        dlg.ShowModal()
        dlg.Destroy()

class DemoFrame(wx.Frame):
    """ This window displays a button """
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        # Build the menu bar
        MenuBar = wx.MenuBar()

        FileMenu = wx.Menu()
        
        item = FileMenu.Append(wx.ID_EXIT, text="&Quit")
        self.Bind(wx.EVT_MENU, self.OnQuit, item)

        MenuBar.Append(FileMenu, "&File")
        self.SetMenuBar(MenuBar)

        # Add the Widget Panel
        self.Panel = DemoPanel(self)

        self.Fit()

    def OnQuit(self, event=None):
        self.Close()

app = wx.App()
frame = DemoFrame(None, title="Micro App")
frame.Show()
app.MainLoop()

 LocalWords:  wxPython Pythonic wx Namespaces SomeModule AnObject MainFrame
 LocalWords:  DefaultSize DefaultPosition MyFrame AButton wxMac wxWidgets init
 LocalWords:  OnButtonPush FileMenu OnQuit PySimpleApp MainPanel SubPanel
 LocalWords:  MyButton DemoPanel
