Python DevCenter
oreilly.comSafari Books Online.Conferences.

advertisement


Python Programming on Win32 using WxPython
Pages: 1, 2, 3, 4, 5

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.



MDI Frame

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 OnInit method:

class DoubleTalkBrowserApp(wxApp):
    def OnInit(self):
        frame = MainFrame(NULL)
        frame.Show(true)
        self.SetTopWindow(frame)
        return true
 
app = DoubleTalkBrowserApp(0)
app.MainLoop()

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:

class MainFrame(wxMDIParentFrame):
    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)
            self.SetIcon(self.icon)
 
        # create a statusbar that shows the time and date on the right
        sb = self.CreateStatusBar(2)
        sb.SetStatusWidths([-1, 150])
        self.timer = wxPyTimer(self.Notify)
        self.timer.Start(1000)
        self.Notify()
 
        menu = self.MakeMenu(false)
        self.SetMenuBar(menu)
        menu.EnableTop(1, 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_SAVEAS,self.OnMenuSaveAs)
        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)
        EVT_CLOSE(self, self.OnCloseWindow)

Figure 20-9 shows the state of the application so far.

Figure 20-9. The first MDI wxPython application

 

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.

Icons

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 SetIcon method.

Timers

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 Notify method:

    # Time-out handler
    def Notify(self):
        t = time.localtime(time.time())
        st = time.strftime(" %d-%b-%Y   %I:%M:%S", t)
        self.SetStatusText(st, 1)

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.

Main menu

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.AppendSeparator()
        fmenu.Append(ID_EXIT, "E&xit",   "Terminate the program")
 
        dtmenu = wxMenu()
        dtmenu.Append(ID_ADD, "&Add Transaction",
                      "Add a new transaction")
        if withEdit:
            dtmenu.Append(ID_EDIT, "&Edit Transaction",
                          "Edit selected transaction in current view")
        dtmenu.Append(ID_JRNL, "&Journal view",
                      "Open or raise the journal view")
        dtmenu.Append(ID_DTAIL,"&Detail view",
                      "Open or raise the detail view")
 
        hmenu = wxMenu()
        hmenu.Append(ID_ABOUT, "&About",
                     "More information about this program")
 
        main = wxMenuBar()
        main.Append(fmenu, "&File")
        main.Append(dtmenu,"&Bookset")
        main.Append(hmenu, "&Help")
 
        return main

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):
        self.Close()
 
    def OnCloseWindow(self, event):
        self.timer.Stop()
        del self.timer
        del self.icon
        self.Destroy()
 
    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)
        dlg.ShowModal()
        dlg.Destroy()

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 event.Veto(true).

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.

wxFileDialog

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)
        dlg.SetStyle(wxOPEN)
        dlg.SetWildcard("*.dtj")
        if dlg.ShowModal() == wxID_OK:
            self.path = dlg.GetPath()
            self.SetTitle(self.title + ' - ' + self.path)
            self.bookset = BookSet()
            self.bookset.load(self.path)
            self.GetMenuBar().EnableTop(1, true)
            
            win = JournalView(self, self.bookset, ID_EDIT)
            self.views.append((win, ID_JRNL))
            
        dlg.Destroy()
Figure 20-10. wxPython browsing for a Doubletalk transaction file

 

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_OK and 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.

 

Pages: 1, 2, 3, 4, 5

Next Pagearrow





Sponsored by: