Data entry dialogs
A dialog can be used to request information from the user. Let's take a quick look at how we query the user for data using the tkSimpleDialog module. Unlike many of our examples, this one is short and to the point:
Example_8_2.py from Tkinter import *
from tkSimpleDialog import askinteger import Pmw class App:
self.result = Pmw.EntryField(master, entry_width= value='', label_text='Returned value: labelpos=W, labelmargin=1) self.result.pack(padx=15, pady=15)
retVal = askinteger("The Larch",
"What is the number of The Larch?", minvalue=0, maxvalue=50) 2
askquestion

showwarning
showerror
askokcancel
Figure 8.1 Standard dialogs askquestion showinfo showwarning showerror askokcancel askyesno
- askretrycancel
Figure 8.1 Standard dialogs display.result.setentry(retVal) root.mainloop()
Code comments
1 askinteger can be used with just two arguments: title and prompt.
2 In this case, a minimum and maximum value have been added. If the user types a value outside this range, a dialog box is displayed to indicate an error (see figure 8.1).
tl -f Avoid popping up dialogs whenever additional information is required from the user. If you find that the current form that is displayed frequently requires the user to supply additional information, it's very possible that your original form design is inadequate. Reserve popup dialogs for situations which occur infrequently or for near-boundary conditions.
Running Example_8_2.py displays screens similar to those shown in figure 8.2.
- Figure 8.2 tkSimpleDialog: askinteger
Despite the warning in the note above, if you have just a few fields to collect from the user, you can use dialog windows. This is especially true if the application doesn't require the information every time it is run; adding the information to screens in the application adds complexity and clutters the screen. Using a dialog saves quite a bit of work, but it may not be particularly attractive, especially if you need to have more than two or three entry fields or if you need several widget types. However, this example is quite short and to the point.
Example_8_3.py from Tkinter import *
from tkSimpleDialog import Dialog import tkMessageBox
|
import Pmw | ||
|
class GetPassword(Dialog): def body(self, master): self.title("Enter New Password") | ||
|
Label(master, text='Old Password:').grid(row=0, sticky=W) 1 jA Label(master, text='New Password:').grid(row=1, sticky=W) | Label(master, text='Enter New Password Again:').grid(row=2, sticky=W) | ||
|
self.oldpw = Entry(master, width = 16, show='*') self.newpw1 = Entry(master, width = 16, show='*') self.newpw2 = Entry(master, width = 16, show='*') |
f* | |
|
self.oldpw.grid(row=0, column=1, sticky=W) self.newpw1.grid(row=1, column=1, sticky=W) self.newpw2.grid(row=2, column=1, sticky=W) return self.oldpw |
opw = self.oldpw.get() npw1 = self.newpw1.get() npw2 = self.newpw2.get() |
/O Validate |
|
tkMessageBox.showerror('Bad Password1, 'New Passwords do not match') else: # This is where we would set the new password, pass |
dialog = GetPassword(root) | |
|
Code comments A This example uses the grid geometry manager. The sticky attribute is used to make sure that the labels line up at the left of their grid cells (the default is to center the text in the cell). See "Grid" on page 86 for more details. Label(master, text='Old Password:').grid(row=0, sticky=W) 2 Since we are collecting passwords from the user, we do not echo the characters that are typed. Instead, we use the show attribute to display an asterisk for each character. self.oldpw = Entry(master, width = 16, show='*') O When the user clicks the OK button, the apply callback gets the current data from the widgets. In a full implementation, the original password would be checked first. In our case we're just checking that the user typed the same new password twice and if the passwords do not match we pop up an error dialog, using showerror. tkMessageBox.showerror('Bad Password', 'New Passwords do not match') | ||
|
Figure 8.3 illustrates the output of Example_8_3.py. | ||
|
DIALOGS |
145 | |
Figure 8.3 A tkSimpleDialog that is used to collect passwords. The error dialog is displayed for bad entries.
- MBad Password
Figure 8.3 A tkSimpleDialog that is used to collect passwords. The error dialog is displayed for bad entries.
8.1.3 Single-shot forms
If your application has simple data requirements, you may need only simple forms. Many user interfaces implement a simple model:
1 Display some fields, maybe with default values.
2 Allow the user to fill out or modify the fields.
3 Collect the values from the screen.
4 Do something with the data.
5 Display the results obtained with the values collected.
If you think about the applications you're familiar with, you'll see that many use pretty simple, repetitive patterns. As a result, building forms has often been viewed as a rather tedious part of developing GUIs; I hope that I can make the task a little more interesting.
There is a problem in designing screens for applications that do not need many separate screens; developers tend to write a lot more code than they need to satisfy the needs of the application. In fact, code that supports forms often consumes more lines of code than we might prefer. Later, we will look at some techniques to reduce the amount of code that has to be written, but for now let's write the code in full.
This example collects basic information about a user and displays some of it. The example uses Pmw widgets and is a little bit longer than it needs to be, so that we can cover the basic framework now; we will leave those components out in subsequent examples.
Example_8_4.py from Tkinter import * import Pmw import string class Shell:
Pmw.initialise(self.root)
self.root.title(title)
def doBaseForm(self, master): # Create the Balloon, self.balloon = Pmw.Balloon(master)
self.menuBar = Pmw.MenuBar(master, hull_borderwidth=1, hull_relief = RAISED, hotkeys=1, balloon = self.balloon) self.menuBar.pack(fill=X)
self.menuBar. self.menuBar.
self.menuBar self.menuBar self.menuBar self.menuBar addmenu('File', 'Exit') addmenuitem('File', 'command1, 'Exit the application', label='Exit', command=self.exit) addmenu('View', 'View status') addmenuitem('View', 'command', 'Get user status', label='Get status', command=self.getStatus) addmenu('Help', 'About Example 8-4', side=RIGHT) addmenuitem('Help', 'command',
'Get information on application', label='About...', command=self.help)
A J4
self.dataFrame = Frame(master) self.dataFrame.pack(fill=BOTH, expand=1)
self.infoFrame = Frame(self.root,bd=1, relief='groove') self.infoFrame.pack(fill=BOTH, expand=1, padx = 10)
self.statusBar = Pmw.MessageBar(master, entry_width = 40, entry_relief='groove', labelpos = W, label_text = '') self.statusBar.pack(fill = X, padx = 10, pady = 10)
# Add balloon text to statusBar self.balloon.configure(statuscommand o self.statusBar.helpmessage)
# Create about dialog. Pmw.aboutversion('8.1')
Pmw.aboutcopyright('Copyright My Company 1999'
'\nAll rights reserved')
Pmw.aboutcontact(
'For information about this application contact:\n' + ' My Help Desk\n' 1 Phone: 800 555-1212\n' ' email: help@my.company.com'
self.about = Pmw.AboutDialog(master, applicationname = self.about.withdraw()
def exit(self): import sys sys.exit(0)
Code comments
O The constructor initializes both Tk and Pmw: self.root = Tk() Pmw.initialise(self.root)
Note that Pmw.initialise is not a typo; Pmw comes from Australia!
2 We create an instance of the Pmw.Balloon to implement Balloon Help. Naturally, this bit could have been left out, but it is easy to implement, so we might as well include it.
self.balloon = Pmw.Balloon(master) Actions are bound later.
3 The next few points illustrate how to construct a simple menu using Pmw components. First we create the MenuBar, associating the balloon and defining hotkey as true (this creates mnemonics for menu selections).
self.menuBar = Pmw.MenuBar(master, hull_borderwidth=1, hull_relief = RAISED, hotkeys=1, balloon = self.balloon) self.menuBar.pack(fill=X)
Note
It is important to pack each form component in the order that they are to be displayed—having a menu at the bottom of a form might be considered a little strange!
4 The File menu button is created with an addmenu call:
self.menuBar.addmenu('File', 1 Exit') The second argument to addmenu is the balloon help to be displayed for the menu button. We then add an item to the button using addmenuitem: self.menuBar.addmenuitem('File1, 'command', 'Exit the application', label='Exit', command=self.exit)
addmenuitem creates an entry within the specified menu. The third argument is the help to be displayed.
5 We create a Frame to contain the data-entry widgets and a second frame to contain some display widgets:
self.dataFrame = Frame(master) self.dataFrame.pack(fill=BOTH, expand=1)
6 At the bottom of the form, we create a statusBar to display help messages and other information:
self.statusBar = Pmw.MessageBar(master, entry_width = 40, entry_relief=GROOVE, labelpos = W, label_text = '') self.statusBar.pack(fill = X, padx = 10, pady = 10)
7 We bind the balloon's statuscommand to the MessageBar widget:
self.balloon.configure(statuscommand = self.statusBar.helpmessage)
8 We create an About... dialog for the application. This is definitely something we could have left out, but now that you have seen it done once, I won't need to cover it again. First, we define the data to be displayed by the dialog:
Pmw.aboutversion('8.1')
Pmw.aboutcopyright('Copyright My Company 1999'
'\nAll rights reserved1)
Pmw.aboutcontact(
'For information about this application contact:\n' + ' My Help Desk\n' + 1 Phone: 800 555-1212\n' + ' email: help@my.company.com')
9 Then the dialog is created and withdrawn (unmapped) so that it remains invisible until required:
self.about = Pmw.AboutDialog(master, applicationname = 'Example 8-1')
self.about.withdraw()
Example_8_4.py (continued)
def getStatus(self):
username = self.userName.get() cardnumber = self.cardNumber.get()
self.img = PhotoImage(file='%s.gif' % username) self.pictureID['image'] = self.img self.userInfo.importfile('%s.txt' % username) self.userInfo.configure(label_text = username)
def help(self):
self.about.show()
def doDataForm(self):
self.userName=Pmw.EntryField(self.dataFrame, entry_width=8, value='', modifiedcommand=self.upd_username, label_text='User name:', labelpos=W, labelmargin=1) self.userName.place(relx=.20, rely=.325, anchor=W)
self.cardNumber = Pmw.EntryField(self.dataFrame, entry_width=8, value='', modifiedcommand=self.upd_cardnumber, label_text='Card number: ', labelpos=W, labelmargin=1) self.cardNumber.place(relx=.20, rely=.70, anchor=W)
def doInfoForm(self):
self.pictureID=Label(self.infoFrame, bd=0) self.pictureID.pack(side=LEFT, expand=1)
self.userInfo = Pmw.ScrolledText(self.infoFrame, borderframe=1, labelpos=N, usehullsize=1, y!
|
hull_width=270, | ||
|
hull_height=100, | ||
|
text_padx=10, | ||
|
text_pady=10, | ||
|
text_wrap=NONE) | ||
|
self.userInfo.configure(text_font = ('verdana1 |
, 8)) | |
|
self.userInfo.pack(fill=BOTH, expand=1) | ||
|
def upd_username(self): | ||
|
upname = string.upper(self.userName.get()) | ||
|
if upname: | ||
|
self.userName.setentry(upname) | ||
|
def upd_cardnumber(self): | ||
|
valid = self.cardNumber.get() | ||
|
if valid: | ||
|
self.cardNumber.setentry(valid) | ||
|
if _name_ == 1_main_': | ||
|
shell=Shell(title='Example 8-4') | ||
|
shell.root.geometry(M%dx%d" % (400,350)) | ||
|
shell.doBaseForm(shell.root) | ||
|
shell.doDataForm() | ||
|
shell.doInfoForm() | ||
|
shell.root.mainloop() | ||
|
Code comments (continued) | ||
|
— |
getStatus is a placeholder for a more realistic function that can |
be applied to the collected |
|
data. First, we use the get methods of the Pmw widgets to obtain the content of the widgets: | ||
|
username = self.userName.get() | ||
|
cardnumber = self.cardNumber.get() | ||
|
! |
Using username, we retrieve an image and load it into the label widget we created earlier: | |
|
self.img = PhotoImage(file='%s.gif' % username) | ||
|
self.pictureID['image'] = self.img | ||
|
@ |
Then we load the contents of a file into the ScrolledText widget and update its title: | |
|
self.userInfo.importfile('%s.txt' % username) | ||
|
self.userInfo.configure(label_text = username) | ||
|
# |
Using the About dialog is simply a matter of binding the widget's show method to the menu | |
|
item: | ||
|
def help(self): | ||
|
self.about.show() | ||
|
$ |
The form itself uses two Pmw EntryField widgets to collect data: | |
|
self.userName=Pmw.EntryField(self.dataFrame, entry_width=8, | ||
|
value=11, | ||
|
modifiedcommand=self. |
upd_username, | |
|
label_text=1 User name:', | ||
|
labelpos=W, labelmargin=1) | ||
|
self.userName.place(relx=.20, rely=.325, anchor=W) | ||
|
150 |
CHAPTER 8 DIALOGS AND FORMS | |
% The modifiedcommand in the previous code fragment binds a function to the widget to be called whenever the content of the widget changes (a valuechanged callback). This allows us to implement one form of validation or, in this case, to change each character to upper case: upname = string.upper(self.userName.get()) if upname:
self.userName.setentry(upname)
© Finally, we create the root shell and populate it with the subcomponents of the form:
shell=Shell(title='Example 8-4')
shell.doBaseForm(shell.root)
shell.doDataForm()
shell.doInfoForm()
shell.root.mainloop()
Note that we delay calling the doBaseForm, doDataForm and doInfoForm methods to allow us flexibility in exactly how the form is created from the base classes.
If you run Example_8_4.py, you will see screens similar to the one in figure 8.4. Notice how the ScrolledText widget automatically adds scroll bars as necessary. In fact, the overall layout changes slightly to accommodate several dimension changes. The title to the ScrolledText widget, for example, adds a few pixels to its containing frame; this has a slight effect on the layout of the entry fields. This is one reason why user interfaces need to be completely tested.
t I Automatic scroll bars can introduce some bothersome side effects. In figure 8.4, the vertical scroll bar was added because the number of lines exceeded the height of the widget. The horizontal scroll bar was added because the vertical scroll bar used space needed to display the longest line. If I had resized the window about 10 pixels wider, the horizontal scroll bar would not have been displayed.
|
Example B-4 |
■ -|n|x | |
|
File Vi^w |
Help | |
|
[view user information | ||
|
user name: | | ||
|
Card number: | | ||
View user information
Figure 8.4 Single-shot form
Post a comment