Print

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

The first thing to do after a successful completion of the file dialog is ask the dialog what the selected pathname was, and then use this to modify the frame's title and to open a BookSet file.



Take a look at the next line. It reenables the BookSet menu since there is now a file open. It's really two statements in one and is equivalent to these two lines:

            menu = self.GetMenuBar()
            menu.EnableTop(1, true)

Since it makes sense to actually let the user see something when they ask to open a file, you should create and show one of the views in the last bits of the OnMenuOpen handler above. We'll take a look at that next.

wxListCtrl

The Journal view consists of a wxListCtrl with a single-line summary for each transaction. It's placed inside a wxMDIChildFrame and since it's the only thing in the frame, don't worry about setting or maintaining the size, the frame does it automatically. (Unfortunately, since some platforms send the first resize event at different times, sometimes the window shows up without its child sized properly.) Here's a simple workaround:

class JournalView(wxMDIChildFrame):
    def _    _init_    _(self, parent, bookset, editID):
        wxMDIChildFrame._    _init_    _(self, parent, -1, "")
        self.bookset = bookset
        self.parent = parent
 
        tID = wxNewId()
        self.lc = wxListCtrl(self, tID, wxDefaultPosition, 
                             wxDefaultSize, wxLC_REPORT)
        ## Forces a resize event to get around a minor bug...
        self.SetSize(self.GetSize())
 
        self.lc.InsertColumn(0, "Date")
        self.lc.InsertColumn(1, "Comment")
        self.lc.InsertColumn(2, "Amount")
 
        self.currentItem = 0
        EVT_LIST_ITEM_SELECTED(self, tID, self.OnItemSelected)
        EVT_LEFT_DCLICK(self.lc, self.OnDoubleClick)
 
        menu = parent.MakeMenu(true)
        self.SetMenuBar(menu)
        EVT_MENU(self, editID, self.OnEdit)
        EVT_CLOSE(self, self.OnCloseWindow)
 
        self.UpdateView()

Figure 20-11 shows the application is progressing nicely and starting to look like a serious Windows application.

Figure 20-11. The list of Doubletalk transactions

 

The wxListCtrl has many personalities, but they should all be familiar to you. Underneath its wxPython wrappers, it's the same control used in Windows Explorer in the right side panel. All the same options are available: large icons, small icons, list mode, and the report mode used here. You define the columns with their headers and then set some events for the list control. You want to be able to edit the transactions when they are double-clicked, so why are both event handlers needed? The list control sends an event when an item is selected, but it doesn't keep track of double-clicks. The base wxWindow class, on the other hand, reports double-clicks, but it knows nothing about the list control. So by catching both events you can easily implement the functionality you need. Here is the code for the event handlers:

    def OnItemSelected(self, event):
        self.currentItem = event.m_itemIndex
 
    def OnDoubleClick(self, event):
        self.OnEdit()

After creating and setting up the list control, you create a menubar for this frame. Here you call the menu-making method in the parent, asking it to add the Edit Transaction menu item.

The last thing the _ _init_ _ method does is call a method to fill the list control from the BookSet. We've split this into a separate method so it can be called independently whenever the BookSet data changes. Here's the UpdateView method:

    def UpdateView(self):
        self.lc.DeleteAllItems()
        for x in range(len(self.bookset)):
            trans = self.bookset[x]
            self.lc.InsertStringItem(x, trans.getDateString())
            self.lc.SetStringItem(x, 1, trans.comment)
            self.lc.SetStringItem(x, 2, str(trans.magnitude()))
 
        self.lc.SetColumnWidth(0, wxLIST_AUTOSIZE)
        self.lc.SetColumnWidth(1, wxLIST_AUTOSIZE)
        self.lc.SetColumnWidth(2, wxLIST_AUTOSIZE)
 
        self.SetTitle("Journal view - %d transactions" %
                      len(self.bookset))

Putting data in a list control is fairly easy; just insert each item. For the report mode, you insert an item for the first column and then set values for the remaining columns. For each column in the example, just fetch some data from the transaction and send it to the list control. If you were using icons or combination of icons and text, there are different methods to handle that.

Now that there's data in the list control, you should resize the columns. You can either specify actual pixel widths or have the list auto-size the columns based on the widths of the data.

The last thing the JournalView class needs to do is to enable the editing of the transactions. We saw previously that when an item is double-clicked, a method named OnEdit is invoked. Here it is:

    def OnEdit(self, *event):
        if self.currentItem:
            trans = self.bookset[self.currentItem]
            dlg = EditTransDlg(self, trans,
                               self.bookset.getAccountList())
            if dlg.ShowModal() == wxID_OK:
                trans = dlg.GetTrans()
                self.bookset.edit(self.currentItem, trans)
                self.parent.UpdateViews()
            dlg.Destroy()

This looks like what we did with the file dialog in the main frame, and indeed you will find yourself using this pattern quite often when using dialogs. The one item to notice here is the call to UpdateViews() in the parent window. This is how to manage keeping all the views of the BookSet up to date. Whenever a transaction is updated, this method is called and then loops through all open views, telling the views to update themselves with their UpdateView() method.

wxPython Window Layout

wxPython includes a number of powerful techniques for controlling the layout of your windows and controls. There are several alternative mechanisms provided and potentially several ways to accomplish the same thing. This allows the programmer to use whichever mechanism works best in a particular situation or whichever they are most comfortable with.

Constraints
There is a class called wxLayoutConstraints that allows the specification of a window's position and size in relationship to its siblings and its parent. Each wxLayoutContraints object is composed of eight wxIndividualLayoutConstraint objects, which define different sorts of relationships, such as which window is above this window, what is the relative width of this window, etc. You usually have to specify four of the eight individual constraints in order for the window to be fully constrained. For example, this button will be positioned in the center of its parent and will always be 50% of the parent's width:

b = wxButton(self.panelA, 100, ' Panel A `)
lc = wxLayoutConstraints()
lc.centreX.SameAs (self.panelA, wxCentreX)
lc.centreY.SameAs (self.panelA, wxCentreY)
lc.height.AsIs ()
lc.width.PercentOf (self.panelA, wxWidth, 50)
b.SetConstraints(lc);

Layout algorithm
The class named wxLayoutAlgorithm implements layout of subwindows in MDI or SDI frames. It sends a wxCalculateLayoutEvent to children of the frame, asking them for information about their size. Because the event system is used this technique can be applied to any window, even those that aren't necessarily aware of the layout classes. However, you may wish to use wxSashLayoutWindow for your subwindows since this class provides handlers for the required events and accessors to specify the desired size of the window. The sash behavior in the base class can be used, optionally, to make the windows user-resizable. wxLayoutAlgorithm is typically used in IDE style of applications, where there are several resizable windows in addition to the MDI client window or other primary editing window. Resizable windows might include toolbars, a project window, and a window for displaying error and warning messages.

Sizers
In an effort to simplify the programming of simple layouts, a family of wxSizer classes has been added to the wxPython library. These are classes that are implemented in pure Python instead of wrapping C++ code from wxWindows. They are somewhat reminiscent of the layout managers from Java in that you select the type of sizer you want and then add windows or other sizers to it, and they all follow the same rules for layout. For example, this code fragment creates five buttons that are laid out horizontally in a box, and the last button is allowed to stretch to fill the remaining space allocated to the box:

box = wxBoxSizer(wxHORIZONTAL)
box.Add(wxButton(win, 1010, "one"), 0)
box.Add(wxButton(win, 1010, "two"), 0)
box.Add(wxButton(win, 1010, "three"), 0)
box.Add(wxButton(win, 1010, "four"), 0)
box.Add(wxButton(win, 1010, "five"), 1)

Resources
The wxWindows library has a simple dialog editor available that can assist with the layout of controls on a dialog and generates a portable cross-platform resource file. This file can be loaded into a program at runtime and transformed on the fly into a window with the specified controls on it. The only downfall with this approach is that you don't have the opportunity to subclass the windows that are generated, but if you can do everything you need with existing control types and event handlers, it should work out great. Eventually, there will be a wxPython-specific application builder tool that will generate either a resource type of file or actual Python source code for you.

Brute force
Finally, there is the brute-force mechanism of specifying the exact position of every component programmatically. Sometimes the layout needs of a window don't fit with any of the sizers or don't warrant the complexity of the constraints or the layout algorithm. For these situations, you can fall back on doing it "by hand," but you probably don't want to attempt it for anything much more complex than the Edit Transaction dialog.

wxDialog and friends

The next step is to build a dialog to edit a transaction. As you've seen, the transaction object is composed of a date, a comment, and a variable number of transaction lines each of which has an account name and an amount. We know that all the lines should add up to zero and that the date should be a valid date. In addition to editing the date and comment, you need to be able to add, edit, and delete lines. Figure 20-12 shows one possible layout for this dialog and the one used for this example.

Figure 20-12. The wxPython Doubletalk transaction editor

 

Since there's quite a bit going on here, let's go through the initialization of this class step by step. Here's the first bit:

class EditTransDlg(wxDialog):
    def _    _init_    _(self, parent, trans, accountList):
        wxDialog._    _init_    _(self, parent, -1, "")
        self.item = -1
        if trans:
            self.trans = copy.deepcopy(trans)
            self.SetTitle("Edit Transaction")
        else:
            self.trans = Transaction()
            self.trans.setDateString(dates.ddmmmyyyy(self.trans.date))
            self.SetTitle("Add Transaction")

This is fairly simple stuff. Just invoke the parent class's _ _init_ _ method, do some initialization, and determine if you're editing an existing transaction or creating a new one. If editing an existing transaction, use the Python copy module to make a copy of the object. You do this because you will be editing the transaction in-place and don't want to have any partially edited transactions stuck in the BookSet. If the dialog is being used to add a new transaction, create one, and then fix its date by truncating the time from it. The default date in the transaction includes the current time, but this dialog is equipped to deal only with the date portion.

 

Pages: 1, 2, 3, 4, 5

Next Pagearrow