9.6. 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.
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 any time you need to add some read-only text to a display. Moreover, this widget sports more than a dozen configuration options that let you customize its appearance. Example 9-16 and Figure 9-21 illustrate Message basics; see a Tk or Tkinter reference for other options it supports.
Figure 9-21. A Message widget at work
Example 9-16. PP3E\Gui\tour\message.py
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 9-17 builds the input window shown in Figure 9-22.
Figure 9-22. entry1 caught in the act
Example 9-17. PP3E\Gui\tour\entry1.py
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:\...\PP3E\Gui\Tour>python entry1.py Input => "Type words here" Input => "Have a cigar"
We met the <Return> 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 it 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.
188.8.131.52. 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 Entry widget method calls such as this one:
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 simply be 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 10 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 one:
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 that is 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 beginning 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.
184.108.40.206. 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 9-18 combines labels and entries to achieve the multiple-input display captured in Figure 9-23.
Figure 9-23. entry2 (and entry3) form displays
Example 9-18. PP3E\Gui\Tour\entry2.py
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:\...\PP3E\Gui\Tour>python entry2.py Input => "Bob" Input => "Technical Writer" Input => "Jack"
You get the same field dump if you press the Enter key anytime 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 the 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 9-24.
Figure 9-24. entry2 (and entry3) expansion at work
220.127.116.11. Going modal again
Later on this tour, we'll see how to make similar form layouts with the grid geometry manager. 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.
Example 9-19 uses the prior example'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 9-19. PP3E\Gui\Tour\entry2-modal.py
When you run this code, pressing the button in this program's main window creates the blocking form input dialog in Figure 9-25, as expected.
Figure 9-25. entry2-modal (and entry3-modal) displays
But a subtle danger is 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 also 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 use Tkinter variables, the subject of the next section.
18.104.22.168. 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 in Example 9-20.
Example 9-20. PP3E\Gui\Tour\entry3.py
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 the script entry2 (see Figure 9-23). For illustration purposes, the window is laid out differentlyas a Frame containing two nested subframes used to build the left and right columns of the form areabut the end result is the same when it is displayed on screen.
The main thing to notice here, though, is the use of StringVar variables. Instead of using 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 in this way, 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.[*] The variable object get method returns as a string for StringVar, an integer for IntVar, and a floating-point number for DoubleVar.
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 OK to fetch input values from them long after a modal dialog has been dismissed, as shown in Example 9-21.
Example 9-21. PP3E\Gui\Tour\entry3-modal.py
This version is the same as the original (shown in Example 9-19 and Figure 9-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 check buttons, radio boxes, and scales in order to provide access to current settings and link multiple widgets together. Almost coincidentally, that's the topic of the next section.