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_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
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.
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),
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
wxStaticLinecontrol for drawing the line across the dialog.
wxComboBoxis used for selecting existing account names from a list.
- Use the standard IDs
wxID_CANCELfor 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_SZE(self, 40, -1))
wxStaticLine(self, -1, wxDLG_PNT(self, 5,115),
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_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),
wxButton(self, ID_DEL, "&Delete Line", wxDLG_PNT(self, 142,140),
self.ok = wxButton(self, wxID_OK, "OK", wxDLG_PNT(self, 145,5),
wxButton(self, wxID_CANCEL, "Cancel", wxDLG_PNT(self, 190,5), btnSz)
# Resize the window to fit the controls
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
for x in range(len(self.trans.lines)):
account, amount, dict = self.trans.lines[x]
self.lc.SetStringItem(x, 1, str(amount))
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
def Validate(self, *ignore):
bal = self.trans.balance()
date = self.date.GetValue()
dateOK = (date == dates.testasc(date))
dateOK = 0
if bal == 0 and dateOK:
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())
# update the list control
idx = len(self.trans.lines)
self.lc.SetStringItem(idx-1, 1, str(amount))
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.
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/.
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.