Message and Entry

The Message and Entry widgets allow for display and input of simple text. Both are essentially functional subsets of the Text widget we'll meet later -- Text can do everything Message and Entry can, but not vice versa.

7.6.1 Message

The Message widget is simply a place to display text. Although the standard showinfo dialog we met earlier is perhaps a better way to display pop-up messages, Message splits up long strings automatically and flexibly, and can be embedded inside container widgets anytime you need to add some read-only text to a display. Moreover, this widget sports over a dozen configuration options that let you customize its appearance. Example 7-16 and Figure 7-21 illustrate Message basics; see a Tk or Tkinter reference for other options it supports.

Example 7-16. PP2EGui ourmessage.py

from Tkinter import *
msg = Message(text="Oh by the way, which one's Pink?")
msg.config(bg='pink', font=('times', 16, 'italic'))
msg.pack()
mainloop()

Figure 7-21. A Message widget at work

figs/ppy2_0721.gif

7.6.2 Entry

The Entry widget is a simple, single-line text input field. It is typically used for input fields in form-like dialogs, and anywhere else you need the user to type a value into a field of a larger display. Entry also supports advanced concepts such as scrolling, key bindings for editing, and text selections, but it's simple to use in practice. Example 7-17 builds the input window shown in Figure 7-22.

Example 7-17. PP2EGui ourentry1.py

from Tkinter import *
from quitter import Quitter

def fetch():
 print 'Input => "%s"' % ent.get() # get text

root = Tk()
ent = Entry(root)
ent.insert(0, 'Type words here') # set text
ent.pack(side=TOP, fill=X) # grow horiz

ent.focus() # save a click
ent.bind('', (lambda event: fetch())) # on enter key
btn = Button(root, text='Fetch', command=fetch) # and on button 
btn.pack(side=LEFT)
Quitter(root).pack(side=RIGHT)
root.mainloop()

Figure 7-22. entry1 caught in the act

figs/ppy2_0722.gif

On startup, the entry1 script fills the input field in this GUI with the text "Type words here" by calling the widget's insert method. Because both the Fetch button and the Enter key are set to trigger the script's fetch callback function, either user event gets and displays the current text in the input field, using the widget's get method:

C:...PP2EGuiTour>python entry1.py
Input => "Type words here"
Input => "Have a cigar"

We met the event earlier when we studied bind; unlike button presses, these lower-level callbacks get an event argument, so the script uses a lambda wrapper to ignore it. This script also packs the entry field with fill=X to make it expand horizontally with the window (try it out), and calls the widget focus method to give the entry field input focus when the window first appears. Manually setting the focus like this saves the user from having to click the input field before typing.

7.6.2.1 Programming Entry widgets

Generally speaking, the values typed into and displayed by Entry widgets are set and fetched with either tied "variable" objects (described later in this chapter), or with Entry widget method calls like this:

ent.insert(0, 'some text') # set value
value = ent.get() # fetch value (a string)

The first parameter to the insert method gives the position where the text is to be inserted. Here, "0" means the front because offsets start at zero, and integer 0 and string '0' mean the same thing (Tkinter method arguments are always converted to strings if needed). If the Entry widget might already contain text, you also generally need to delete its contents before setting it to a new value, or else new text will be simply added to the text already present:

ent.delete(0, END) # first, delete from start to end
ent.insert(0, 'some text') # then set value

The name END here is a preassigned Tkinter constant denoting the end of the widget; we'll revisit it in Chapter 8 when we meet the full-blown and multiple-line Text widget (Entry's more powerful cousin). Since the widget is empty after the deletion, this statement sequence is equivalent to the prior:

ent.delete('0', END) # delete from start to end
ent.insert(END, 'some text') # add at end of empty text

Either way, if you don't delete the text first, new text inserted is simply added. If you want to see how, try changing the fetch function to look like this -- an "x" is added at the front and end of the input field on each button or key press:

def fetch():
 print 'Input => "%s"' % ent.get() # get text
 ent.insert(END, 'x') # to clear: ent.delete('0', END)
 ent.insert(0, 'x') # new text simply added

In later examples, we'll also see the Entry widget's state='disabled' option, which makes it read-only, as well as its show='*' option, which makes it display each character as a * (useful for password-type inputs). Try this out on your own by changing and running this script, for a quick look. Entry supports other options we'll skip here too; see later examples and other resources for additional details.

7.6.2.2 Laying out input forms

As mentioned, Entry widgets are often used to get field values in form-like displays. We're going to create such displays often in this book, but to show you how this works in simpler terms, Example 7-18 combines labels and entries to achieve the multiple-input display captured in Figure 7-23.

Example 7-18. PP2EGuiTourentry2.py

# use Entry widgets directly and layout by rows

from Tkinter import *
from quitter import Quitter
fields = 'Name', 'Job', 'Pay'

def fetch(entries):
 for entry in entries:
 print 'Input => "%s"' % entry.get() # get text

def makeform(root, fields):
 entries = []
 for field in fields:
 row = Frame(root) # make a new row
 lab = Label(row, width=5, text=field) # add label, entry
 ent = Entry(row)
 row.pack(side=TOP, fill=X) # pack row on top
 lab.pack(side=LEFT)
 ent.pack(side=RIGHT, expand=YES, fill=X) # grow horizontal
 entries.append(ent)
 return entries

if __name__ == '__main__':
 root = Tk()
 ents = makeform(root, fields)
 root.bind('', (lambda event, e=ents: fetch(e))) 
 Button(root, text='Fetch',
 command=(lambda e=ents: fetch(e))).pack(side=LEFT)
 Quitter(root).pack(side=RIGHT)
 root.mainloop()

Figure 7-23. entry2 (and entry3) form displays

figs/ppy2_0723.gif

The input fields here are just simple Entry widgets. The script builds an explicit list of these widgets to be used to fetch their values later. Every time you press this window's Fetch button, it grabs the current values in all the input fields and prints them to the standard output stream:

C:...PP2EGuiTour>python entry2.py
Input => "Bob"
Input => "Technical Writer"
Input => "Jack"

You get the same field dump if you press the Enter key any time this window has the focus on your screen -- this event has been bound to the whole root window this time, not to a single input field.

Most of the art in form layout has to do with arranging widgets in a hierarchy. This script builds each label/entry row as a new Frame attached to the window's current TOP; labels are attached to the LEFT of their row, and entries to its RIGHT. Because each row is a distinct Frame, its contents are insulated from other packing going on in this window. The script also arranges for just the entry fields to grow vertically on a resize, as in Figure 7-24.

Figure 7-24. entry2 (and entry3) expansion at work

figs/ppy2_0724.gif

7.6.2.3 Going modal again

Later on this tour, we'll see how to make similar form layouts with the grid geometry manager too. But now that we have a handle on form layout, let's see how to apply the modal dialog techniques we met earlier to a more complex input display like this.

Example 7-19 uses the prior's makeform and fetch functions to generate a form and prints its contents much as before. Here, though, the input fields are attached to a new Toplevel pop-up window created on demand, and an OK button is added to the pop-up window to trigger a window destroy event. As we learned earlier, the wait_window call pauses until the destroy happens.

Example 7-19. PP2EGuiTourentry2-modal.py

# must fetch before destroy with entries

from Tkinter import *
from entry2 import makeform, fetch, fields

def show(entries):
 fetch(entries) # must fetch before window destroyed!
 popup.destroy() # fails with msgs if stmt order is reversed

def ask():
 global popup
 popup = Toplevel() # show form in modal dialog window
 ents = makeform(popup, fields)
 Button(popup, text='OK', command=(lambda e=ents: show(e)) ).pack()
 popup.grab_set()
 popup.focus_set()
 popup.wait_window() # wait for destroy here

root = Tk()
Button(root, text='Dialog', command=ask).pack()
root.mainloop()

When run, pressing the button in this program's main window creates the blocking form input dialog in Figure 7-25, as expected.

Figure 7-25. entry2-modal (and entry3-modal) displays

figs/ppy2_0725.gif

But there is a subtle danger lurking in this modal dialog code: because it fetches user inputs from Entry widgets embedded in the popped-up display, it must fetch those inputs before destroying the pop-up window in the OK press callback handler. It turns out that a destroy call really does destroy all the child widgets of the window destroyed; trying to fetch values from a destroyed Entry not only doesn't work, but generates a host of error messages in the console window -- try reversing the statement order in the show function to see for yourself.

To avoid this problem, we can either be careful to fetch before destroying, or we can use Tkinter variables, the subject of the next section.

7.6.2.4 Tkinter "variables"

Entry widgets (among others) support the notion of an associated variable; changing the associated variable changes the text displayed in the Entry, and changing the text in the Entry changes the value of the variable. These aren't normal Python variable names, though -- variables tied to widgets are instances of variable classes in the Tkinter module library. These classes are named StringVar, IntVar, DoubleVar, and BooleanVar; you pick one based on the context in which it is to be used. For example, a StringVar class instance can be associated with an Entry field, as demonstrated by Example 7-20.

Example 7-20. PP2EGuiTourentry3.py

# use StringVar variables and layout by columns

from Tkinter import *
from quitter import Quitter
fields = 'Name', 'Job', 'Pay'

def fetch(variables):
 for variable in variables:
 print 'Input => "%s"' % variable.get() # get from var

def makeform(root, fields):
 form = Frame(root) # make outer frame
 left = Frame(form) # make two columns
 rite = Frame(form)
 form.pack(fill=X) 
 left.pack(side=LEFT)
 rite.pack(side=RIGHT, expand=YES, fill=X) # grow horizontal

 variables = []
 for field in fields:
 lab = Label(left, width=5, text=field) # add to columns
 ent = Entry(rite)
 lab.pack(side=TOP)
 ent.pack(side=TOP, fill=X) # grow horizontal
 var = StringVar()
 ent.config(textvariable=var) # link field to var
 var.set('enter here')
 variables.append(var)
 return variables

if __name__ == '__main__':
 root = Tk()
 vars = makeform(root, fields)
 Button(root, text='Fetch', 
 command=(lambda v=vars: fetch(v))).pack(side=LEFT)
 Quitter(root).pack(side=RIGHT)
 root.bind('', (lambda event, v=vars: fetch(v))) 
 root.mainloop()

Except for the fact that this script initializes input fields with the string "enter here", it makes a window identical in appearance and function to that created by script entry2 (see Figure 7-23). For illustration purposes, the window is laid out differently -- as a Frame containing two nested subframes used to build the left and right columns of the form area -- but the end result is the same when displayed on screen.

The main thing to notice here, though, is the use of StringVar variables. Rather than use a list of Entry widgets to fetch input values, this version keeps a list of StringVar objects that have been associated with the Entry widgets like this:

ent = Entry(rite)
var = StringVar()
ent.config(textvariable=var) # link field to var

Once you've tied variables like this, changing and fetching the variable's value:

var.set('text here')
value = var.get()

will really change and fetch the corresponding display's input field value.[1] The variable object get method returns as a string for StringVar, an integer for IntVar, and a floating-point number for DoubleVar.

[1] In a now-defunct Tkinter release shipped with Python 1.3, you could also set and fetch variable values by calling them like functions, with and without an argument (e.g., var(value) and var()). Today, you should call variable set and get methods instead. For unknown reasons, the function call form stopped working years ago, but you may still see it in older Python code (and first editions of at least one O'Reilly Python book).

Of course, we've already seen that it's easy to set and fetch text in Entry fields directly, without adding extra code to use variables. So why the bother about variable objects? For one thing, it clears up that nasty fetch-after-destroy peril we met in the prior section. Because StringVars live on after the Entry widgets they are tied to have been destroyed, it's okay to fetch input values from them long after a modal dialog has been dismissed, as shown in Example 7-21.

Example 7-21. PP2EGuiTourentry3-modal.py

# can fetch values after destroy with stringvars

from Tkinter import *
from entry3 import makeform, fetch, fields

def show(variables):
 popup.destroy() # order doesn't matter here
 fetch(variables) # variables live on after window destroyed

def ask():
 global popup
 popup = Toplevel() # show form in modal dialog window
 vars = makeform(popup, fields)
 Button(popup, text='OK', command=(lambda v=vars: show(v)) ).pack()
 popup.grab_set()
 popup.focus_set()
 popup.wait_window() # wait for destroy here

root = Tk()
Button(root, text='Dialog', command=ask).pack()
root.mainloop()

This version is the same as the original (shown in Example 7-19 and Figure 7-25), but show now destroys the pop-up before inputs are fetched through StringVars in the list created by makeform. In other words, variables are a bit more robust in some contexts because they are not part of a real display tree. For example, they are also associated with checkboxes, radioboxes, and scales, to provide access to current settings and link multiple widgets together. Almost coincidentally, that's the topic of the next section.

Introducing Python

Part I: System Interfaces

System Tools

Parallel System Tools

Larger System Examples I

Larger System Examples II

Part II: GUI Programming

Graphical User Interfaces

A Tkinter Tour, Part 1

A Tkinter Tour, Part 2

Larger GUI Examples

Part III: Internet Scripting

Network Scripting

Client-Side Scripting

Server-Side Scripting

Larger Web Site Examples I

Larger Web Site Examples II

Advanced Internet Topics

Part IV: Assorted Topics

Databases and Persistence

Data Structures

Text and Language

Part V: Integration

Extending Python

Embedding Python

VI: The End

Conclusion Python and the Development Cycle



Programming Python
Python Programming for the Absolute Beginner, 3rd Edition
ISBN: 1435455002
EAN: 2147483647
Year: 2000
Pages: 245

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net