Introduction

One of the biggest hurdles I have had to overcome as a newbie in the whacky world of codeslinging, python and wxPython, is how to wrap my head around writing effective code that is easy to manage and extend. It's hard enough to get stuff chugging along in a satisfactory manner without the flashy GUI glitter, but when you add OOP with classes, selfs and import statements galore to the cocktail, you have a recipe for disaster for us newcomers.

Take my first project. I hashed out an interface in wxGlade, then sprinkled a whole bunch of ID_* identifiers with a generous hand over the stew, seasoned with EVENTS for all the widgets, tossed in functions for handling popups and toolbars and other GUI mojo, and mixed in the whole stuffed enchilada consisting of networking, configuration parser and database functions and other code to get the App From Hell to actually do something interesting. The result was 1000 lines of spaghetti code that more looked like something the cat dragged in than anything else. That's when I discovered my life just wouldn't be complete without an extra box of widgets crammed in there as well as a couple of more pages to the wxNotebook, but needless to say it was impossible to make heads or tails of the mess.

Let's see if we can remedy this binary horror.

The General Idea

First we clean things up a tad, by dividing everything into neat little packages. The application I'll use for demonstration purposes is a wxFrame with a wxMenu and wxstatusbar slapped on for good measure. The menu has two functions, one calling a standard onExit() function, and the other triggers an event which calls a function to read in the sourcecode and launches a popup in the form of a wxmessageDialog to display it. Not exactly a marvel of an application, but at least it's on a level where even granny can follow along.

Here's an outline of the files we'll be using:

GUI.PY

   1 #!/usr/bin/env python
   2 # -*- coding: iso-8859-15 -*-
   3 # generated by wxGlade 0.6.2 on Sun Sep 21 16:28:55 2008 from "OrganizeYourCode.wxg"
   4 
   5 import wx
   6 
   7 # begin wxGlade: extracode
   8 # end wxGlade
   9 
  10 
  11 
  12 class myNotebook(wx.Notebook):
  13     def __init__(self, *args, **kwds):
  14         # begin wxGlade: myNotebook.__init__
  15         kwds["style"] = 0
  16         wx.Notebook.__init__(self, *args, **kwds)
  17         self.notebook_1_pane_1 = wx.Panel(self, -1)
  18         self.button_1 = wx.Button(self.notebook_1_pane_1, -1, "button_1")
  19         self.button_2 = wx.Button(self.notebook_1_pane_1, -1, "button_2")
  20         self.text_ctrl_1 = wx.TextCtrl(self.notebook_1_pane_1, -1, "", style=wx.TE_MULTILINE)
  21         self.notebook_1_pane_2 = wx.Panel(self, -1)
  22 
  23         self.__set_properties()
  24         self.__do_layout()
  25         # end wxGlade
  26 
  27     def __set_properties(self):
  28         # begin wxGlade: myNotebook.__set_properties
  29         self.AddPage(self.notebook_1_pane_1, "tab1")
  30         self.AddPage(self.notebook_1_pane_2, "tab2")
  31         # end wxGlade
  32 
  33     def __do_layout(self):
  34         # begin wxGlade: myNotebook.__do_layout
  35         sizer_2 = wx.BoxSizer(wx.VERTICAL)
  36         sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
  37         sizer_3.Add(self.button_1, 0, 0, 0)
  38         sizer_3.Add((20, 20), 0, 0, 0)
  39         sizer_3.Add(self.button_2, 0, 0, 0)
  40         sizer_2.Add(sizer_3, 0, wx.TOP|wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 4)
  41         sizer_2.Add(self.text_ctrl_1, 1, wx.EXPAND, 0)
  42         self.notebook_1_pane_1.SetSizer(sizer_2)
  43         # end wxGlade
  44 
  45 # end of class myNotebook
  46 
  47 
  48 class MyFrame1(wx.Frame):
  49     def __init__(self, *args, **kwds):
  50         # begin wxGlade: MyFrame1.__init__
  51         kwds["style"] = wx.DEFAULT_FRAME_STYLE
  52         wx.Frame.__init__(self, *args, **kwds)
  53 
  54         # Menu Bar
  55         self.frame1_menubar = wx.MenuBar()
  56         global ID_EXIT; ID_EXIT = wx.NewId()
  57         global ID_BLURB; ID_BLURB = wx.NewId()
  58         wxglade_tmp_menu = wx.Menu()
  59         wxglade_tmp_menu.Append(ID_EXIT, "exit", "", wx.ITEM_NORMAL)
  60         wxglade_tmp_menu.Append(ID_BLURB, "blurb", "", wx.ITEM_NORMAL)
  61         self.frame1_menubar.Append(wxglade_tmp_menu, "File")
  62         self.SetMenuBar(self.frame1_menubar)
  63         # Menu Bar end
  64         self.frame1_statusbar = self.CreateStatusBar(1, 0)
  65         self.notebook_1 = myNotebook(self, -1)
  66 
  67         self.__set_properties()
  68         self.__do_layout()
  69         # end wxGlade
  70 
  71     def __set_properties(self):
  72         # begin wxGlade: MyFrame1.__set_properties
  73         self.SetTitle("OrganizeYourCode")
  74         self.SetSize((402, 524))
  75         self.frame1_statusbar.SetStatusWidths([-1])
  76         # statusbar fields
  77         frame1_statusbar_fields = ["Created with wxGlade!"]
  78         for x, item in enumerate(frame1_statusbar_fields):
  79             self.frame1_statusbar.SetStatusText(item, x)
  80         # end wxGlade
  81 
  82     def __do_layout(self):
  83         # begin wxGlade: MyFrame1.__do_layout
  84         sizer_1 = wx.BoxSizer(wx.VERTICAL)
  85         sizer_1.Add(self.notebook_1, 1, wx.EXPAND, 0)
  86         self.SetSizer(sizer_1)
  87         self.Layout()
  88         # end wxGlade
  89 
  90 # end of class MyFrame1
  91 
  92 
  93 if __name__ == "__main__":
  94     app = wx.PySimpleApp(0)
  95     wx.InitAllImageHandlers()
  96     frame1 = MyFrame1(None, -1, "")
  97     app.SetTopWindow(frame1)
  98     frame1.Show()
  99     app.MainLoop()

To add more wizardry, just insert more ID_*. I keep mine in a separate file for now, and then simply use C-X-i in Emacs to insert it. That way I can trash the interface if I get sick of it or find out it demands pulling off an anatomic impossibility just to navigate. See Sept 21, 2003 note at the bottom for an alternate solution to these wxNewId() edits

For items which don't have a ID_* value, just do as follows:

   1 # In GUI.PY
   2 self.b = wxButton(self, -1, "Test")
   3 # In MAIN.PY
   4 EVT_BUTTON(self, self.frame.b.GetId(), self.SomeFunction)

FUNCTIONS.PY

   1 #! /usr/bin/env python
   2 from __future__ import with_statement # needed for Python before version 2.6
   3 
   4 def openfile(fileobj, mode = "", list=None):
   5     """Generic file opener, adds newlines to list and returns lines or string"""
   6     try:
   7         with open(fileobj, mode or "r") as file:
   8 
   9             # Add newline if we write a list to a file
  10             if mode in ('w', 'a'):
  11                 file.writelines(line + '\n' for line in list)
  12             
  13             # Read and return a list if we read a file
  14             elif mode == 'r':
  15                 return file.read().splitlines()
  16 
  17             # If we want a single, big-ass string instead
  18             else:
  19                 return file.read()
  20 
  21     except IOError:
  22         print "No such file: %s" % fileobj
  23 
  24 
  25 def stringify(list):
  26     """Turn list into string object and return"""
  27     return "".join(map(str, list))
  28 
  29 def test_openfile():
  30     """
  31     Test function for trying out our code shenanigans before using them in
  32     the actual application.
  33     """
  34 
  35     lines = openfile("functions.py")
  36     string = stringify(lines)
  37     print string
  38 
  39 if __name__ == '__main__':
  40         test_openfile()

Not the kinkiest example ever deviced, but it gets the job done. You can call it as a stand-alone file like this: % python functions.py and ka-pow, it reads its own sourcecode. It's a rather spiffy way of testing your code without getting bogged down with the fancy GUI gizmos. (If I do say so myself)

POPUPS.PY

   1 #! /usr/bin/env python
   2 
   3 import wx
   4 import wx.lib.dialogs
   5 
   6 class Popup:
   7     """Class for handling all our fancy GUI stuff"""
   8     def msg(self, frame, string, title):
   9         """Display STRING in a wx.MessageDialog"""
  10         dlg = wx.lib.dialogs.ScrolledMessageDialog(frame, string, title)
  11         dlg.ShowModal()
  12         dlg.Destroy()

The frame is the parent of the widget, and should show you how easy it is to pass an entire object. You could also add from functions import * and have msg() call openfile() and stringify() if you're into that sorta thing.

MAIN.PY

   1 #! /usr/bin/env python
   2 
   3 import wx
   4 import gui
   5 import popups
   6 import functions
   7 
   8 
   9 ---- /!\ '''Edit conflict - other version:''' ----
  10 # You'll find images.py [OrganizingYourCodeImages|here] - just copy it to your
  11 
  12 ---- /!\ '''Edit conflict - your version:''' ----
  13 # You'll find images.py [[OrganizingYourCodeImages|here]] - just copy it to your
  14 
  15 ---- /!\ '''End of edit conflict''' ----
  16 # path/current directory - it's for the toolbar.
  17 import images
  18 
  19 ID_COMBO = wx.NewId()
  20 
  21 class Toolbar:
  22     def __init__(self, frame, list):
  23         # Now we can call ID_COMBO from outside the class...
  24         self.ID_COMBO = ID_COMBO
  25 
  26         self.tb = frame.CreateToolBar(wx.TB_HORIZONTAL|wx.NO_BORDER|wx.TB_FLAT)
  27 
  28         # Change the wx.NewId() as in gui.py when you're ready to play
  29         # with them.
  30         self.tb.AddSimpleTool(wx.NewId(), images.getOpenBitmap(),
  31                                                   "Open","file")
  32         self.tb.AddSimpleTool(wx.NewId(),images.getPasteBitmap(),
  33                                                   "Save","Save file")
  34         self.tb.AddSeparator()
  35         self.tb.AddSimpleTool(wx.NewId(), images.getCopyBitmap(),
  36                                                   "Slice", "Cut out slice of out of a file")
  37         self.tb.AddSimpleTool(wx.NewId(), images.getTestBitmap(),
  38                                                   "Edit", "Edit word")
  39         self.tb.AddSimpleTool(wx.NewId(), images.getNewBitmap(),
  40                                                   "Reset", "Reset list")
  41         self.tb.AddSeparator()
  42 
  43         # Notice ID_COMBO. It's declared exactly the way we did in gui.py
  44         self.cb = wx.ComboBox(self.tb, ID_COMBO, "", choices=list,size=(150,-1),
  45                              style=wx.CB_DROPDOWN | wx.CB_READONLY)
  46         self.tb.AddControl(self.cb)
  47 
  48         self.tb.Realize()
  49 
  50 
  51 ##class MyApp(wx.App):
  52 class MyApp(wx.PySimpleApp):
  53     def OnInit(self):
  54         wx.InitAllImageHandlers()
  55         frame = gui.MyFrame1(None, -1, "")
  56         frame.Show(True)
  57         self.SetTopWindow(frame)
  58         self.frame = frame
  59 
  60         # This is how you can pass the frame around and add more glitz
  61         # at liberty!
  62         self.list = ["one", "two", "three", "four", "five"]
  63         self.toolbar = Toolbar(self.frame, self.list)
  64 
  65         self.popup = popups.Popup()
  66 
  67         # Menu Events
  68         wx.EVT_MENU(self, gui.ID_EXIT, self.MenuExit)
  69         wx.EVT_MENU(self, gui.ID_BLURB, self.MenuBlurb)
  70         self.frame.SetAcceleratorTable(wx.AcceleratorTable([
  71                         (wx.ACCEL_NORMAL, wx.WXK_F1, gui.ID_EXIT),
  72                         (wx.ACCEL_NORMAL, wx.WXK_F2, gui.ID_BLURB),
  73                         ]))
  74         return True
  75 
  76     def MenuExit(self, event):
  77         self.frame.Close(true)
  78 
  79     def MenuBlurb(self, event):
  80         list = functions.openfile("main.py")
  81         string = functions.stringify(list)
  82         self.popup.msg(self.frame, string, "Blurb Title")
  83 
  84 def main():
  85 #    app = wx.PySimpleApp(0)
  86     app = MyApp(0);
  87     app.MainLoop()
  88 
  89 if __name__ == '__main__':
  90     main()

Ah, *so* much nicer than a single, everything-but-the-kitchen-sink file, don't you think so? You can of course put class Toolbar in yet another file.

TODO LIST

This should give other newbies in the same boat a leg up, but I still need to figure out the follow to achieve perfect Appness. Help out if you can!

CHANGE LOG

April 21, 2003:

Sept 21, 2003:

September 21, 2008:

May 2010, split images onto its own page


/!\ Edit conflict - other version:


May 2010, split images onto its own page


/!\ Edit conflict - your version:



/!\ End of edit conflict


KUDOS

My heartfelt thanks and appreciation goes out to Alberto Griggio and Chris Munchenberg from the imitable wxPython mailing list for wetnursing me through the above examples - I wouldn't have been able to catch another Z without their support on the list. (Please direct your lawsuits and other legal shenanigans due to this page to them too, hehehe). Peace out, you Pyrates out there!


/!\ Edit conflict - other version:



/!\ Edit conflict - your version:



/!\ End of edit conflict


OrganizingYourCode (last edited 2010-05-09 23:01:44 by 80-42-177-48)

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