So far, all I’ve shown you are the read-only widgets such as labels and frames. This is the first of several chapters that discusses Tk’s widgets that permit entering text. This chapter introduces the entry
and spinbox
widgets. Tk’s entry
widget is a specialized type of text-entry field best-suited to high-speed, head-down data entry but applicable for many types of data entry in which you want to control or validate the data that is input. It displays one line of text that you can edit (or not), subject to restrictions you set using widget-specific attributes and a validation routine that you write. The spinbox
widget, often referred to as a spinner in other GUI toolkits, is based on the entry widget. In addition to enabling text entry in the manner of entry
widgets, spinbox
es can also scroll, or spin, through a fixed set of values from which you select the desired value (hence their name). The reason I’m covering these two special-purpose text widgets before the all-purpose text
widget (see Chapter 14) is that the entry
and spinbox
widgets support a subset of the features and functionality of the more general text widget. As a result, when you get to Chapter 14, you can build on what you already know.
Mad Libs Revista rewrites the Mad Libs program from Chapter 4 to use entry
widgets to create a mad lib silly sentence. To start the game, execute the script g_mad_lib.tcl in this chapter’s code directory. Figures 12.1, 12.2, and 12.3 show the game in progress.
The entry
and spinbox
widgets are very similar. They share many of the same features, attributes, and options. In fact, the spinbox
widget is derived from the entry
widget. Obviously, they have different behaviors and appearances. In the following discussion, I lump entry
and spinbox
widgets together to avoid having to repeat myself, but I’ll point out where the two widgets differ when necessary.
The entry
widget displays a one-line, editable text string. By default, an entry
’s string is empty. You can select all or part of an entry
widget’s contents using the mouse, keyboard, or pro-grammatically using widget attributes and commands. If the text in an entry widget is too long to fit entirely within the widget’s window, only part of the text will be displayed. This much probably doesn’t surprise you. What you might not realize, though, is that you can change the view to display different portions of the text. A spinbox
widget has all of the features of an entry
widget plus the ability to allow users to spin through a fixed set of values (such as times or dates).
In addition to the standard options and attributes entry
and spinbox
widgets support, they have specific characteristics that I want to highlight (see Table 12.1).
Table 12.1. entry
and spinbox
Attributes
Attribute | Widget | Description |
---|---|---|
S: | ||
|
| Background color of the spin buttons themselves. |
|
| Cursor displayed when the mouse pointer hovers over the spin buttons themselves. |
|
| Relief used for a depressed spin button. |
|
| Relief used for a raised spin button. |
|
| Specifies the Tcl command to execute when the spinbutton is invoked. |
|
| If true, selected text also becomes the X selection. |
|
| Defines the format string used when setting the string value (used with |
|
| Sets the lowest value for a spinbox (in floating point format); used with |
|
| Background color of the insertion area. |
|
| Width of the border of the insertion area. |
|
| If non-zero, the cursor blinks, and this value defines the length of the off portion of the blink cycle in milliseconds. |
|
| If non-zero, the cursor blinks, and this value defines the length of the on portion of the blink cycle in milliseconds. |
|
| Specifies the width in pixels of the insertion cursor. |
|
| Specifies the script to execute if |
|
| Sets the increment interval between |
|
| Defines the background color when the widget is read-only; defaults to the normal background color. |
|
| The background color of selected text. |
|
| The width of the border around selected text. |
|
| The foreground color of selected text. |
|
| Masks the contents of the entry widget with the specified character, such as *. |
|
| Controls whether or not the widget accepts focus when using keyboard traversal (using Tab and Shift-Tab). A value of 0 skips the widget, a value of 1 includes the widget, and empty string leaves the decision up to the traversal scripts. |
|
| The name of a variable whose value is linked to the -text attribute; updates to this variable are reflected immediately in the -text attribute. |
|
| Sets the highest value for a spinbox; used with |
|
| Determines the mode in which validation should operate; must be one of none (the default), |
|
| Specifies the script to execute for validating input in the widget. |
|
| Defines list of valid values for the widget’s string; overrides |
|
| If true, values in a spinbox will roll over (wrap) at the bottom and top of the defined range. |
|
| Connects the widget to a procedure to use for scrolling the widget’s viewable area horizontally. |
Many commands for the entry and spinbox widgets accept index arguments. An index argument defines a particular character in the widget’s string value. These values are usually used in the context of selecting text or to refer to a portion of a widget’s text value that is already selected. You can use one of the following index arguments (out-of-range indices round to the nearest legal value):
anchor
—. Sets the anchor point for the selection, which is set with the select from
and select adjust
widget commands.
end
—. Represents the character immediately following the last one in the widget’s string.
insert
—. Represents the character immediately after the insertion point.
sel.first
—. Represents the first selected character.
sel.last
—. Represents the last selected character.
number
—. Specifies the character as a zero-based numeric index.
@
number
—. Specifies number
as an x-coordinate in the widget’s window. The character that spans the specified coordinate will be used. For example, @0 corresponds to the left-most character.
To use the input validation capabilities of entry
and spinbox
widgets, set the -validate
attribute to one of the values shown in Table 12.2 and set the -validatecommand
attribute to a script or procedure that validates the text (by default, validation is disabled). If the validation script returns 1
, true
, or another valid Tcl Boolean value, the changes to the widget’s text will be accepted. Otherwise, the changes will be ignored, and the widget will not accept or show the change.
Table 12.2. Options for the validate
Attribute
Option | Description |
---|---|
| Executes the validation script for all conditions. |
| Executes the validation script when the widget receives or loses focus. |
| Executes the validation script when the widget receives focus. |
| Executes the validation script when the widget loses focus. |
| Executes the validation script when the widget is edited. |
| No validation occurs (default). |
It is possible to perform percent substitutions on the -validatecommand
and -invalidcommand
, just as you would in a bind script. Tk recognizes substitutions shown in Table 12.3.
Table 12.3. Percent Substitutions for Validation Scripts
Substitution | Description |
---|---|
| Validation type (0 for |
| Index of the character inserted/deleted, if any;, -1 otherwise. |
| The widget’s value after the edit. |
| The widget’s value before the edit. |
| The text string being inserted/deleted, if any;, otherwise an empty string. |
| The current validation mode (see Table 12.3). |
| The validation mode that triggered the callback ( |
| The widget’s name. |
Mixing -textvariable
and -validatecommand
might cause unpleasant results. If you use -textvariable
to set the value of entry and spinbox widgets for read-only purposes, there should be no problems. Problems occur when you try to set -textvariable
to a value that your validation command (which is controlled by the -validatecommand
). The issue is that setting -textvariable
to an invalid value causes the -validate
attribute to be reset to none, which means that your -invalidcommand
script will not be triggered. Why does validate get set to none? To prevent an infinite loop: setting -textvariable
to an invalid value causes -invalidcommand
to execute, which might set -textvariable
to an invalid value, which causes -invalidcommand
to execute. Disabling validation avoids the possibility of the infinite loop.
Likewise, if an error occurs in the -validatecommand
or -invalidcommand
while evaluating their respective scripts (or if -validatecommand
does not return a valid Tcl boolean value), then validation will be disabled. In addition, if you edit the widget’s value inside either the -validatecommand
or -invalidcommand scripts
, validation will be disabled. Again, one reason this occurs is to prevent infinite loops. In addition, editing the widget’s value during validation overrides the edit that was being validated.
If you absolutely must edit a widget’s value during validation and want to ensure that the -validate
option remains set, include the following command in your validation script:
%W config -validate %v
This command reenables validation. However, your best course of action is to avoid edits during validation and code your scripts to edit widgets after validation.
Finally, avoid setting an associated -textVariable
during validation, because doing so causes the widget to get out of sync with its associated variable.
In Chapter 11, I introduced the toplevel
widget and used it to create a crude but effective message box. However, Tk comes with a very nice and easy-to-use message box, created with the tk_messageBox
command. Its general syntax is:
tk_messageBox ?opt val? ?...?
The icons, text, and buttons displayed in the message box depend on the options you specify in opt
and val
. Table 12.4 shows the options that tk_messageBox
supports.
Table 12.4. Options for tk_messageBox
Option | Description |
---|---|
| Defines the icon type to display in the message box. |
| Specifies the message text to display. |
| Sets the message box’s parent window to win; the message box appears over |
| Defines the title that appears in the title bar. |
| Specifies the set of buttons to display in the message box. |
When you click one of the buttons in the window, the window closes and returns a text string corresponding to the clicked button. The -type
attribute determines the buttons that appear in the window. You can use one of the following values for -type
:
abortretryignore
—. Displays Abort, Retry, and Ignore buttons, which return abort
, retry
, or ignore
, respectively, when pressed.
ok
—. Displays an OK button, which returns ok
when pressed.
okcancel
—. Displays OK and Cancel buttons, which return ok
or cancel
, respectively, when pressed.
retrycancel
—. Displays Retry and Cancel buttons, which return retry
or cancel
, respectively, when pressed.
yesno
—. Displays Yes and No buttons, which return yes
or no
, respectively, when pressed.
yesnocancel
—. Displays Yes, No, and Cancel buttons, which return yes
, no
, or cancel
, respectively, when pressed.
This chapter’s game uses tk_messageBox
, so please refer to g_mad_lib.tcl for an example of using this very useful command.
g_mad_lib.tcl is a classic demonstration that turning a simple, text-based program into a graphical program is rarely a simple “rewrite” at all. Rather, it is a completely new program. In this case, the only element of my original Mad Libs program that survived the rewrite is the source sentence and the code for extracting the text that needed to be replaced.
#!/usr/bin/wish # g_mad_lib.tcl # Demonstrate the entry widget # Block 1 # Validation command proc NotEmpty {val} { if {$val != {}} { return true } else { tk_messageBox -icon error -type ok -parent . -message "Replacement word or phrase cannot be empty!" return false } } # Block 2 # Find all of the prompts in the text proc FindPrompts {source} { global prompts inputs set i 0 set j 0 while {[string first "?" $source $j] != -1} { set start [string first "?" $source $j] set end [string first "?" $source [expr $start + 1]] set j [expr $end + 1]; set prompt [string range $source [expr $start + 1] [expr $end - 1]] lappend prompts [label .l$i -text [string totitle $prompt]] lappend inputs [entry .e$i -bg white -validate focusout -vcmd {NotEmpty %P}] incr i } } # Block 3 # Draw the game prompts and entry boxes proc DisplayPrompts {prompts inputs} { set len [llength $prompts] for {set i 0} {$i < $len} {incr i} { grid [lindex $prompts $i] [lindex $inputs $i] -sticky w } button .bShow -text "Replace" -command ShowMadLib button .bExit -text "Exit" -command exit grid .bShow .bExit -padx 5 -pady {20 5} -sticky e } # Block 4 # Create a list of replacement phrases from the player's input proc GetFields {} { global inputs foreach input $inputs { lappend fields [$input get] } return $fields } # Block 5 # Build and display the completed mad lib proc ShowMadLib {} { global line foreach field [GetFields] { set start [string first "?" $line] set end [string first "?" $line [expr $start + 1]] set line [string replace $line $start $end $field] } toplevel .w message .w.m -text $line grid .w.m } # Block 6 # The source sentence set line "One day while I was ?verb ending in -ing? in my living room, " append line "a ?adjective? ?mythical creature? fell through the roof. " append line "It jumped on the ?piece of furniture? and knocked over the " append line "?noun?. Then it ran into the dining room and ?past tense verb? " append line "a ?noun?. After ?number? minutes of chasing it through the " append line "house I finally caught it and put it outside. It quickly " append line "flew away." # List variables to contain the prompts and the player's input set prompts {} set inputs {} # Parse the source sentence FindPrompts $line # Display the game window DisplayPrompts $prompts $inputs
As usual, g_mad_lib.tcl begins with procedure definitions. Block 1 defines my validation routine, NotEmpty
. It accepts a single argument, the text string the player types in an entry
widget. If the text is not empty, NotEmpty
returns true
. Otherwise, I display a message box complaining that the field cannot be empty and then return false.
Block 2 defines the FindPrompts
procedure, which accepts a string argument, $source
, that stores the sentence to parse. First, I declare two global variables, $prompts
and $
inputs. $prompts
is a list variable containing labels (actually, variable references to label
widgets) that display the prompts for the items the player must provide. $inputs
stores variable references to the entry widgets in which the player types.
Next, I create two counter variables, $i
and $j
. $i
increments by one each iteration of the loop; I use it to create sequentially numbered label
and entry
widgets. $j
helps me keep track of my current position in the $source
string, as I will explain shortly.
As with the original Mad Lib program, I use a while
loop to iterate through the string, using the string first
command to locate text situated between pairs of question marks. The algorithm I use in g_mad_lib.tcl is slightly different. Whereas in mad_lib.tcl I replaced text onthe-fly as I parsed the source sentence, in g_mad_lib.tcl all I want to do is extract the prompts; I perform the replacements in a separate statement. As a result, I need to keep track of the position of the second or closing question mark so I can start the search for the next prompt at that position. The method I use is the following:
Search for the first or opening ?, starting from the index stored in $j
, which is 0 for the first iteration of the loop. Store this value in the variable $start
.
Search for the second or closing ?, starting at the character index immediately following the index stored in $start
. Store this value in the variable $end
.
Set $j
to the character index immediately following the index stored in $end
. The next search will start from this index in the string.
Extract the text between the two ?s and store it in the variable $prompt
.
Create a label
widget using the value of $prompt
and append a variable reference to the newly created label widget to the list variable $prompts
. I could have combined Steps 4 and 5, but I didn’t want a line of code extending over several lines. Breaking it up into two operations also makes the code easier to read. Otherwise, it would have looked something like the following:
lappend prompts [label .l$i -text [string totitle [string range $source [expr $start + 1] [expr $end • 1]]]]
Create an entry
widget corresponding to the label
widget created in Step 5 and append a variable reference to the newly created widget to the list variable $inputs
. I set the -validate
attribute to focusout
and -vcmd
to NotEmpty %P
. This means that when input focus leaves the entry
widget, the NotEmpty
procedure will be called with the value of the edited text in the widget.
Increment the value of $i
.
Repeat Steps 1–7 until no more ?s are found in $source
, at which point the loop and the procedure terminate.
Block 3 consists of the DisplayPrompts
procedure. It creates the game window shown in Figures 12.1, 12.2, and 12.3 at the beginning of this chapter. DisplayPrompts
accepts two arguments, the $prompts
and $inputs
variables populated by the FindPrompts
procedure. After determining the number of elements in the $prompts
list, I use a for
loop to iterate through both $prompts
and $inputs
, laying out rows that contain a label widget and its associated entry widget and sticking them to the west or left side of the parent (root) window. Next, DisplayPrompts
creates two buttons, .bShow
, which invokes the ShowMadLib
procedure, and .bExit
, which exits the program. These buttons appear on their own row.
The GetFields
procedure in Block 4 iterates through the entry
widgets, extracting their text strings and appending the extracted values to the $fields
list variable. It returns the completed list of fields to the calling procedure, which is ShowMadLib
, defined in Block 5. ShowMadLib
, in turn, iterates through the source sentence a second time, this time replacing text between ?s with the player-provided input. After completing the string replacements, I create a new toplevel
window, embed a message
widget in it containing the modified sentence, and display the completed Mad Lib.
Here are some exercises you can try to practice what you learned in this chapter:
As it is, you can click the Replace button in g_mad_lib.tcl without providing any input in the entry widgets. Modify g_mad_lib.tcl to require all entry widgets to have valid text.
Modify g_lib_mad.tcl to read its source sentence from a file rather than using a hard-coded sentence.
The validation scheme in g_mad_lib.tcl has the annoying side effect of not allowing the player to click in one entry
widget and then click in a second one without entering text. If you change your mind this way, the validation routine nags you with a message box telling you that the first entry is still empty. Fix the validation routine.