Building a Doubletalk Browser with wxPython
Okay, now let's build something that's actually useful and learn more about the
wxPython framework along the way. As has been shown with the other GUI toolkits, we'll build a small application around the Doubletalk class library that allows browsing and editing of transactions.
We're going to implement a Multiple Document Interface, where the child frames are different views of the transactional data, rather than separate "documents." Just as with previous samples, the first thing to do is create an application class and have it create a main frame in its
frame = MainFrame(NULL)
app = DoubleTalkBrowserApp(0)
Since we are using MDI, there is a special class to use for the frame's base class. Here is the code for the initialization method of the main application frame:
title = "Doubletalk Browser - wxPython Edition"
def _ _init_ _(self, parent):
wxMDIParentFrame._ _init_ _(self, parent, -1, self.title)
self.bookset = None
self.views = 
if wxPlatform == '_ _WXMSW_ _':
self.icon = wxIcon('chart7.ico', wxBITMAP_TYPE_ICO)
# create a statusbar that shows the time and date on the right
sb = self.CreateStatusBar(2)
self.timer = wxPyTimer(self.Notify)
menu = self.MakeMenu(false)
EVT_MENU(self, ID_OPEN, self.OnMenuOpen)
EVT_MENU(self, ID_CLOSE, self.OnMenuClose)
EVT_MENU(self, ID_SAVE, self.OnMenuSave)
EVT_MENU(self, ID_EXIT, self.OnMenuExit)
EVT_MENU(self, ID_ABOUT, self.OnMenuAbout)
EVT_MENU(self, ID_ADD, self.OnAddTrans)
EVT_MENU(self, ID_JRNL, self.OnViewJournal)
EVT_MENU(self, ID_DTAIL, self.OnViewDetail)
Figure 20-9 shows the state of the application so far.
Obviously, we're not showing all the code yet, but we'll get to it all eventually as we go through piece by piece.
Notice the use of
wxMDIParentFrame as the base class of
MainFrame. By using this class you automatically get everything needed to implement MDI for the application without having to worry about what's really happening behind the scenes. The
wxMDIParentFrame class has the same interface as the
wxFrame class, with only a few additional methods. Often changing a single document interface program to a MDI program is as easy as changing the base classes the application's classes are derived from. There is a corresponding
wxMDIChildFrame to be used for the document windows, as we'll see later. If you ever need to have access to the client area (or the background area) of the MDI parent, you can use the
wxMDIClientWindow class. You might use this for placing a background image behind all the child windows.
The next thing the previous code does is create an icon and associate it with the frame. Normally Windows applications load items such as icons from a resource file that is linked with the executable. Since
wxPython programs have no binary executable file, you create the icon by specifying the full path to a .ico file. Assigning the icon to the frame only requires calling the frame's
You may have noticed from Figure 20-9 that the status bar has two sections, with the date and time displayed in the second one. The next bit of code in the initialization method handles that functionality. The frame's
CreateStatusBar method takes an optional parameter specifying the number of sections to create, and
SetStatusWidths can be given a list of integers to specify how many pixels to reserve for each section. The -1 means that the first section should take all the remaining space.
In order to update the date and time, you create a
wxPyTimer object. There are two types of timer classes in
wxPython. The first is the
wxPyTimer used here, which accepts a function or method to use as a callback. The other is the
wxTimer class, which is intended to be derived from and will call a required method in the derived class when the timer expires. In the example you specify that when the timer expires, the
Notify method should be called. Then start the timer, telling it to expire every 1000 milliseconds (i.e., every second). Here is the code for the
# Time-out handler
t = time.localtime(time.time())
st = time.strftime(" %d-%b-%Y %I:%M:%S", t)
You first use Python's
time module to get the current time and format it in to a nice, human-readable formatted string. Then by calling the frame's
SetStatus-Text method, you can put that string into the status bar, in this case in slot 1.
As you can see in the next bit of code, we have moved the building of the menu to a separate method. This is mainly for two reasons. The first is to help reduce clutter in the
_ _init_ _ method and better organize the functionality of the class. The second reason has to do with MDI. As with all MDI applications, each child frame can have its own menu bar, automatically updated as the frame is selected.
The approach taken by our sample is to either add or remove a single item from the
BookSet menu based on whether a view can select transactions for editing. Here's the code for the
MakeMenu method. Notice how the parameter controls whether the Edit Transaction item is added to the menu. It might have made better sense to just enable or disable this item as needed, but then you wouldn't be able to see how
wxPython changes the menus automatically when the active window changes. Also notice that you don't create the Window menu. The
wxMDIParentFrame takes care of that for you:
def MakeMenu(self, withEdit):
fmenu = wxMenu()
fmenu.Append(ID_OPEN, "&Open BookSet", "Open a BookSet file")
fmenu.Append(ID_CLOSE, "&Close BookSet",
"Close the current BookSet")
fmenu.Append(ID_SAVE, "&Save", "Save the current BookSet")
fmenu.Append(ID_SAVEAS, "Save &As", "Save the current BookSet")
fmenu.Append(ID_EXIT, "E&xit", "Terminate the program")
dtmenu = wxMenu()
dtmenu.Append(ID_ADD, "&Add Transaction",
"Add a new transaction")
dtmenu.Append(ID_EDIT, "&Edit Transaction",
"Edit selected transaction in current view")
dtmenu.Append(ID_JRNL, "&Journal view",
"Open or raise the journal view")
"Open or raise the detail view")
hmenu = wxMenu()
"More information about this program")
main = wxMenuBar()
If you skip back to the
_ _init_ _ method, notice that after you create the menu and attach it to the window, the
EnableTop method of the menubar is called. This is how to disable the entire
BookSet submenu. (Since there is no
BookSet file open, you can't really do anything with it yet.) There is also an
Enable method that allows you to enable or disable individual menu items by ID.
The last bit of the
_ _init_ _ method attaches event handlers to the various menu items. We'll be going through them one by one as we explore the functionality behind those options. But first, here are some of the simpler ones:
def OnMenuExit(self, event):
def OnCloseWindow(self, event):
def OnMenuAbout(self, event):
dlg = wxMessageDialog(self,
"This program uses the doubletalk package to\n"
"demonstrate the wxPython toolkit.\n\n"
"by Robin Dunn",
"About", wxOK | wxICON_INFORMATION)
The user selects Exit from the File menu, then the
OnMenuExit method is called, which asks the window to close itself. Whenever the window wants to close, whether it's because its
Close method was called or because the user clicks on the Close button in the titlebar, the
OnCloseWindow method is called. If you want to prompt the user with an "Are you sure you want to exit?" type of message, do it here. If he decides not to quit, just call the method
Most programs will want to have a fancier About box than the
wxMessageDialog provides, but for our purposes here it works out just fine. Don't forget to call the dialog's
Destroy method, or you may leak memory.
Before doing anything with a
BookSet, you have to have one opened. For this, use the common dialog
wxFileDialog. This is the same File Open dialog you see in all your other Windows applications, all wrapped in a nice
wxPython-compatible class interface.
Here's the event handler that catches the File Open menu event, and Figure 20-10 shows the dialog in action:
def OnMenuOpen(self, event):
# This should be checking if another is already open,
# but is left as an exercise for the reader...
dlg = wxFileDialog(self)
if dlg.ShowModal() == wxID_OK:
self.path = dlg.GetPath()
self.SetTitle(self.title + ' - ' + self.path)
self.bookset = BookSet()
win = JournalView(self, self.bookset, ID_EDIT)
Start off by creating the file dialog and tell it how to behave. Next show the dialog and give the user a chance to select a
BookSet file. Notice that this time you're checking the return value of the
ShowModal method. This is how the dialog says what the result was. By default, dialogs understand the
wxID_CANCEL IDs assigned to buttons in the dialog and do the right thing when they are clicked. For dialogs you create, you can also specify other values to return if you wish.