Python DevCenter
oreilly.comSafari Books Online.Conferences.

advertisement


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

If you review the sidebar "wxPython Window Layout," you'll see a number of choices available, but we have chosen to use the brute-force mechanism for the Edit Transaction dialog:

    # Create some controls
    wxStaticText(self, -1, "Date:", wxDLG_PNT(self, 5,5))
    self.date = wxTextCtrl(self, ID_DATE, "",
                      wxDLG_PNT(self, 35,5), wxDLG_SZE(self, 50,-1))
 
    wxStaticText(self, -1, "Comment:", wxDLG_PNT(self, 5,21))
    self.comment = wxTextCtrl(self, ID_COMMENT, "",
                      wxDLG_PNT(self, 35, 21), wxDLG_SZE(self, 195,-1)

The code shows how to create the labels and the text fields at the top of the dialog. Notice the use of wxDLG_PNT and wxDLG_SZE to convert dialog units to a wxPoint and a wxSize, respectively. (The -1's used above mean that the default size should be used for the height.) Using dialog units instead of pixels to define the dialog means you are somewhat insulated from changes in the font used for the dialog, so you use dialog units wherever possible. The wxPoint and wxSize are always defined in terms of pixels, but these conversion functions allow the actual number of pixels used to vary automatically from machine to machine with different fonts. This makes it easy to move programs between platforms that have completely different window managers. Figure 20-13 shows this same program running on RedHat Linux 6.0, and you can see that for the most part, the controls are still spaced appropriately even though a completely different font is used on the form. It looks like the wxTextCtrl is a few dialog units taller on this platform, so perhaps there should be a bit more space between the rows. We leave this as an exercise for you.

Figure 20-13. The wxPython Doubletalk editor running on Redhat Linux 6.0

 

The next control to be defined is the wxListCtrl that displays the account and amount lines:

    self.lc = wxListCtrl(self, ID_LIST,
                         wxDLG_PNT(self, 5,34), wxDLG_SZE(self, 225,60),
                         wxLC_REPORT)
 
    self.lc.InsertColumn(0, "Account")
    self.lc.InsertColumn(1, "Amount")
    self.lc.SetColumnWidth(0, wxDLG_SZE(self, 180,-1).width)
    self.lc.SetColumnWidth(1, wxDLG_SZE(self,  40,-1).width)

It's important to note that the width of this control is 225 dialog units. Since this control spans the entire width of the dialog, you know the space you have to work with. You can use this value when deciding where to place or how to size the other controls.

Instead of auto-sizing the width of the list columns, let's now use explicit sizes. But you can still use dialog units to do it by extracting the width attribute from the wxSize object returned from wxDLG_SZE. We should mention the following points:

  • The balance field is disabled, as you only want to use it to display a value.
  • Use a wxStaticLine control for drawing the line across the dialog.
  • A wxComboBox is used for selecting existing account names from a list.
  • Use the standard IDs wxID_OK and wxID_CANCEL for OK and Cancel buttons, respectively, and force the OK button as the default button.
  • Call the base class Fit() method to determine the initial size of the dialog window. This function calculates the total required size based on the size information specified in each of the children.

Here's the rest of the code for creating the controls:

    wxStaticText(self, -1, "Balance:", wxDLG_PNT(self, 165,100))
    self.balance = wxTextCtrl(self, ID_BAL, "",
                              wxDLG_PNT(self, 190,100), 
                              wxDLG_SZE(self, 40, -1))
    self.balance.Enable(false)
 
    wxStaticLine(self, -1, wxDLG_PNT(self, 5,115), 
                           wxDLG_SZE(self, 225,-1))
 
    wxStaticText(self, -1, "Account:", wxDLG_PNT(self, 5,122))
    self.account = wxComboBox(self, ID_ACCT, "",
                       wxDLG_PNT(self, 30,122), wxDLG_SZE(self, 130,-1),
                       accountList, wxCB_DROPDOWN | wxCB_SORT)
 
    wxStaticText(self, -1, "Amount:", wxDLG_PNT(self, 165,122))
    self.amount = wxTextCtrl(self, ID_AMT, "",
                         wxDLG_PNT(self, 190,122), 
                         wxDLG_SZE(self, 40, -1))
 
    btnSz = wxDLG_SZE(self, 40,12)
    wxButton(self, ID_ADD, "&Add Line", wxDLG_PNT(self, 52,140), btnSz)
    wxButton(self, ID_UPDT, "&Update Line", wxDLG_PNT(self, 97,140),
             btnSz)
    wxButton(self, ID_DEL, "&Delete Line", wxDLG_PNT(self, 142,140),
             btnSz)
 
    self.ok = wxButton(self, wxID_OK, "OK", wxDLG_PNT(self, 145,5),
                       btnSz)
    self.ok.SetDefault()
    wxButton(self, wxID_CANCEL, "Cancel", wxDLG_PNT(self, 190,5), btnSz)
 
    # Resize the window to fit the controls
    self.Fit()

The last thing to do is set up some event handlers and load the dialog controls with data. The event handling for the controls is almost identical to the menu handling discussed previously, so there shouldn't be any surprises:

    # Set some event handlers
    EVT_BUTTON(self, ID_ADD,  self.OnAddBtn)
    EVT_BUTTON(self, ID_UPDT, self.OnUpdtBtn)
    EVT_BUTTON(self, ID_DEL,  self.OnDelBtn)
    EVT_LIST_ITEM_SELECTED(self,   ID_LIST, self.OnListSelect)
    EVT_LIST_ITEM_DESELECTED(self, ID_LIST, self.OnListDeselect)
    EVT_TEXT(self, ID_DATE, self.Validate)
 
    # Initialize the controls with current values
    self.date.SetValue(self.trans.getDateString())
    self.comment.SetValue(self.trans.comment)
    for x in range(len(self.trans.lines)):
        account, amount, dict = self.trans.lines[x]
        self.lc.InsertStringItem(x, account)
        self.lc.SetStringItem(x, 1, str(amount))
 
    self.Validate()

The last thing the code snippet does is call a Validate() method, which as you can probably guess, is responsible for validating the dialog data; in this case, validating the date and that all transaction lines sum to zero. Check the date when the field is updated (via the EVT_TEXT() call shown in the code) and check the balance any time a line is added or updated. If anything doesn't stack up, disable the OK button. Here is Validate:

def Validate(self, *ignore):
    bal = self.trans.balance()
    self.balance.SetValue(str(bal))
    date = self.date.GetValue()
    try:
        dateOK = (date == dates.testasc(date))
    except:
        dateOK = 0
 
    if bal == 0 and dateOK:
        self.ok.Enable(true)
    else:
        self.ok.Enable(false)

Notice that the balance field is updated. The next thing we demonstrate is the Add Line functionality. To do this, you need to take whatever is in the account and amount fields, add them to the transaction, and also add them to the list control:

def OnAddBtn(self, event):
    account = self.account.GetValue()
    amount = string.atof(self.amount.GetValue())
    self.trans.addLine(account, amount)
 
    # update the list control
    idx = len(self.trans.lines)
    self.lc.InsertStringItem(idx-1, account)
    self.lc.SetStringItem(idx-1, 1, str(amount))
 
    self.Validate()
    self.account.SetValue("")
    self.amount.SetValue("")

You call Validate again to check if the transaction's lines are in balance. The event handlers for the Update and Delete buttons are similar and not shown here.

That's about all there is to it! wxPython takes care of the tab-traversal between fields, auto-completion on the Enter key, auto-cancel on Esc, and all the rest.

wxPython Conclusion

This small section has barely touched the surface of what wxPython is capable of. There are many more window and control types than what have been shown here, and the advanced features lend themselves to highly flexible and dynamic GUI applications across many platforms. Combined with the flexibility of Python, you end up with a powerful tool for quickly creating world-class applications.

For more information on wxPython, including extensive documentation and sample code, see the wxPython home page at http://alldunn.com/wxPython/.

For more information on the underlying wxWindows framework, please visit its home page at http://www.wxwindows.org/.


1. When getting started, you should probably avoid using PythonWin or IDLE for running wxPython programs, because the interactions between the various toolkits may have unexpected consequences.

Mark Hammond is an independent Microsoft Windows consultant working out of Melbourne, Australia.

Andy Robinson is a London-based consultant specializing in business analysis, object-oriented design, and Windows development.


Discuss this article in the O'Reilly Network Python Forum.

Return to the Python DevCenter.





Sponsored by: