Chapter 39. Focus, Grabs, and Dialogs

Dialog boxes are a standard part of any user interface. Several dialog boxes are built into Tk. This chapter also describes how to build dialogs from scratch, which involves keyboard focus and grabs. Input focus directs keyboard events to different widgets. The grab mechanism lets a widget capture the input focus. This chapter describes the focus, grab, tk_dialog, and tkwait commands. Tk 4.2 adds tk_getOpenFile, tk_getSaveFile, tk_chooseColor, and tk_messageBox. Tk 8.3 adds tk_chooseDirectory.

Dialog boxes are a common feature in a user interface. The application needs some user response before it can continue. A dialog box displays some information and some controls, and the user must interact with it before the application can continue. To implement this, the application grabs the input focus so that the user can only interact with the dialog box. Tk has several built-in dialog boxes, including standard dialogs for finding files and selecting colors. A standard dialog has the same Tcl interface on all platforms, but it is implemented with platform-specific library routines to provide native look and feel. This chapter describes the dialogs built into Tk and then goes into the details of focus and grabs.

Standard Dialogs

The tk_dialog command presents a choice of buttons and returns a number indicating which one was clicked by the user. The general form of the command is:

tk_dialog win title text bitmap default ?label? ?label? ...

The title appears in the title bar, and the text appears in the dialog. The bitmap appears to the left of the text. Specify {} for the bitmap if you do not want one. The set of built-in bitmaps is given on page 627. The label arguments give labels that appear on buttons along the bottom of the dialog. The default argument gives the index of the default button, counting from zero. If there is no default, specify {} or -1.

Message Box

The tk_messageBox dialog is a limited form of tk_dialog that has native implementations on the different platforms. Like tk_dialog, it allows for a message, bitmap, and a set of buttons. However, the button sets are predefined, and the bitmaps are limited. The yesno button set, for example, displays a Yes and a No button. The abortretryignore button set displays Abort, Retry, and Ignore buttons. The tk_messageBox command returns the symbolic name of the selected button (e.g., yes or retry.) The yesnocancel message box could be used when trying to quit with unsaved changes:

set choice [tk_messageBox -type yesnocancel -default yes 
    -message "Save changes before quitting?" 
    -icon question]

The complete set of options to tk_messageBox is listed in Table 39-1:

Table 39-1. Options to tk_messageBox

-default name

Default button name (e.g., yes)

-icon name

Nameerror, info, question, or warning.

-message string

Message to display.

-parent window

Embeds dialog in window.

-title title

Dialog title (UNIX and Windows)

-type type

Typeabortretrycancel, ok, okcancel, retrycancel, yesno, or yesnocancel

File and Directory Dialogs

There are two standard file dialogs, tk_getOpenFile and tk_getSaveFile, and one standard directory dialog, tk_chooseDirectory. The tk_getOpenFile dialog is used to find an existing file, while tk_getSaveFile can be used to find a new file. The tk_chooseDirectory dialog, added in Tk 8.3, allows the user to select a directory, rather than a file. These procedures return the selected file or directory name, or the empty string if the user cancels the operation. These procedures take several options that are listed in Table 39-2:

Table 39-2. Options to the standard file and directory dialogs

-defaultextension ext

Appends ext if an extension is not specified. tk_getOpenFile and tk_getSaveFile only.

-filetypes typelist

typelist defines a set of file types that the user can select to limit the files displayed in the dialog. tk_getOpenFile and tk_getSaveFile only.

-initialdir dir

Lists contents of dir in the initial display. If not provided, then the current working directory is displayed.

-initialfile file

Default file, for tk_getSaveFile only.

-message string

A message to include in the client area of the dialog. (Macintosh, only when Navigation Services are installed.) tk_getOpenFile and tk_getSaveFile only. (Tk 8.3.1)

-multiple

Allows the user to select multiple files, returned as a list. tk_getOpenFile only. (Tk 8.4)

-mustexist boolean

If False (default), the user may specify non-existent directories. tk_chooseDirectory only.

-parent window

Creates the dialog as a child of window. The dialog is displayed on top of its parent window.

-title string

Displays string in the title (UNIX and Windows).

The file dialogs can include a listbox that lists different file types. The file types are used to limit the directory listing to match only those types. The typelist option specifies a set of file extensions and Macintosh file types that correspond to a named file type. If you do not specify a typelist, users just see all the files in a directory. Each item in typelist is itself a list of three values:

name extensions ?mactypes?

The name is displayed in the list of file types. The extensions is a list of file extensions corresponding to that type. The empty extension "" matches files without an extension, and the extension * matches all files. The mactypes is an optional list of four-character Macintosh file types, which are ignored on other platforms. On the Macintosh, if you give both extensions and mactypes, the files must match both. If the extensions is an empty list, only the mactypes are considered. However, you can repeat name in the typelist and give extensions in one set and mactypes in another set. If you do this, then files that match either the extensions or mactypes are listed.

The following typelist matches Framemaker Interchange Files that have both a .mif extension and a MIF type:

set typelist {
   {"Maker Interchange Files" {".mif"} {"MIF "}}
}

The following typelist matches GIF image files that have either a .gif extension or the GIFF file type. Note that the mactypes are optional:

set typelist {
   {"GIF Image" {".gif"}}
   {"GIF Image" {} {"GIFF"}}}
}

The following typelist puts all these together, along with an entry for all files. The entry that comes first is displayed first:

set typelist {
   {"All Files" {*}}
   {"GIF Image" {".gif"}}
   {"GIF Image" {} {"GIFF"}}
   {"Maker Interchange Files" {".mif"} {"MIF "}}
}

Color Dialog

The tk_chooseColor dialog displays a color selection dialog. It returns a color, or the empty string if the user cancels the operation. The options to tk_chooseColor are listed in Table 39-3:

Table 39-3. Options to tk_chooseColor

-initialcolor color

Initial color to display.

-parent window

Creates the dialog as an embedded child of window.

-title string

Displays string in the title (UNIX and Windows).

Custom Dialogs

When you create your own dialogs, you need to understand keyboard focus, focus grabs, and how to wait for the user to finish with a dialog. Here is the general structure of your code when creating a dialog:

# Create widgets, then
focus $toplevel
grab $toplevel
tkwait window $toplevel

This sequence of commands directs keyboard focus to the toplevel containing your dialog. The grab forces the user to interact with the dialog before using other windows in your application. The tkwait command returns when the toplevel window is destroyed, and this automatically releases the grab. This assumes that the button commands in the dialog destroy the toplevel. The following sections explain these steps in more detail, and Example 39-1 on page 606 illustrates a more robust sequence.

Input Focus

The window system directs keyboard events to the toplevel window that currently has the input focus. The application, in turn, directs the keyboard events to one of the widgets within that toplevel window. The focus command sets focus to a particular widget, and it is used by the default bindings for Tk widgets. Tk remembers what widget has focus within a toplevel window and automatically gives focus to that widget when the system gives focus to a toplevel window.

On Windows and Macintosh, the focus is given to an application when you click in its window. On UNIX, the window manager application gives focus to different windows, and window managers allow different conventions to shift focus. The click-to-type model is similar to Windows and Macintosh. There is also focus-follows-mouse, which gives focus to the window under the mouse. One thing to note about click-to-type is that the application does not see the mouse click that gives the window focus.

Once the application has focus, you can manage the focus changes among your widgets any way you like. By default, Tk uses a click-to-type model. Text and entry widgets set focus to themselves when you click on them with the left mouse button. You can get the focus-follows-mouse model within your widgets by calling the tk_focusFollowsMouse procedure. However, in many cases you will find that an explicit focus model is actually more convenient for users. Carefully positioning the mouse over a small widget can be tedious.

The focus Command

Table 39-4 summarizes the focus command. The focus implementation supports multiple displays with a separate focus window on each display. This is useful on UNIX where X supports multiple displays. The -displayof option can be used to query the focus on a particular display. The -lastfor option finds out what widget last had the focus within the same toplevel as another window. Tk will restore focus to that window if the widget that has the focus is destroyed. The toplevel widget gets the focus if no widget claims it.

Table 39-4. The focus command

focus

Returns the widget that currently has the focus on the display of the application's main window.

focus ?-force? window

Sets the focus to window. The -force option ignores the window manger, so use it sparingly.

focus -displayof win

Returns the focus widget on the same display as win.

focus -lastfor win

Returns the name of the last widget to have the focus in the same toplevel as win.

Keyboard Focus Traversal

Users can change focus among widgets with <Tab> and <Shift-Tab>. The creation order of widgets determines a traversal order for focus that is used by the tk_focusNext and tk_focusPrev procedures. There are global bindings for <Tab> and <Shift-Tab> that call these procedures:

bind all <Tab> {tk_focusNext %W}
bind all <Shift-Tab> {tk_focusPrev %W}

The Tk widgets highlight themselves when they have the focus. The highlight size is controlled with the highlightThickness attribute, and the color of the highlight is set with the highlightColor attribute. The Tk widgets, even buttons and scrollbars, have bindings that support keyboard interaction. A <space> invokes the command associated with a button, if the button has the input focus.

All widgets have a takeFocus attribute that the tk_focusNext and tk_focusPrev procedures use to determine if a widget will take the focus during keyboard traversal. There are four possible values to the attribute:

  • 0 indicates the widget should not take focus.

  • 1 indicates the widget should always take focus.

  • An empty string means the traversal procedures tk_focusNext and tk_focusPrev should decide based on the widget's state and bindings.

  • Otherwise the value is a Tcl command prefix. The command is called with the widget name as an argument, and it should return either 0, 1, or the empty string.

Grabbing the Focus

An input grab overrides the normal focus mechanism. For example, a dialog box can grab the focus so that the user cannot interact with other windows in the application. The typical scenario is that the application is performing some task but it needs user input. The grab restricts the user's actions so it cannot drive the application into an inconsistent state. In most cases you only need to use the grab and grab release commands. Note that the grab set command is equivalent to the grab command. Table 39-5 summarizes the grab command.

Table 39-5. The grab command

grab ?-global? window

Sets a grab to a particular window.

grab current ?window?

Queries the grabs on the display of window, or on all displays if window is omitted.

grab release window

Releases a grab on window.

grab set ?-global? win

Sets a grab to a particular window.

grab status window

Returns none, local, or global.

A global grab prevents the user from interacting with other applications, too, even the window manager. Tk menus use a global grab, for example, which is how they unpost themselves no matter where you click the mouse. When an application prompts for a password, a global grab is also a good idea. This prevents the user from accidentally typing their password into a random window. The next section includes examples that use the grab command.

The tkwait Command

You wait for the user to interact with the dialog by using the tkwait command. The tkwait waits for something to happen, and while waiting it allows events to be processed. Like vwait, you can use tkwait to wait for a Tcl variable to change value. You can also wait for a window to become visible, or wait for a window to be destroyed. Table 39-6 summarizes the tkwait command.

Table 39-6. The tkwait command

tkwait variable varname

Waits for the global variable varname to be set.

This is just like the vwait command.

tkwait visibility win

Waits for the window win to become visible.

tkwait window win

Waits for the window win to be destroyed.

Note

The tkwait command

Use tkwait with global variables.

The variable specified in the tkwait variable command must be a global variable. Remember this if you use procedures to modify the variable. They must declare it global or the tkwait command will not notice the assignments.

The tkwait visibility waits for the visibility state of the window to change. Most commonly this is used to wait for a newly created window to become visible. For example, if you have any sort of animation in a complex dialog, you could wait until the dialog is displayed before starting the animation.

Destroying Widgets

The destroy command deletes one or more widgets. If the widget has children, all the children are destroyed, too. Chapter 44 describes a protocol on page 661 to handle destroy events that come from the window manager. You wait for a window to be deleted with the tkwait window command.

The focus, grab, tkwait sequence

In practice, I use a slightly more complex command sequence than just focus, grab, and tkwait. You can remember what widget used to have the focus and then restore it after the dialog completes. When you do this, it is more reliable to restore focus before destroying the dialog. This prevents a tug of war between your application and the window manager. This sequence looks like:

set old [focus]
focus $toplevel
grab $toplevel
tkwait variable doneVar
grab release $toplevel
focus $old
destroy $toplevel

This sequence supports another trick I use, which is to unmap dialogs instead of destroying them. This way the dialogs appear more quickly the next time they are used. This makes creating the dialogs a little more complex because you need to see if the toplevel already exists. Chapter 44 describes the window manager commands used to map and unmap windows on page 661. Example 39-1 shows Dialog_Create, Dialog_Wait, and Dialog_Dismiss that capture all of these tricks:

Example 39-1. Procedures to help build dialogs

proc Dialog_Create {top title args} {
   global dialog
   if [winfo exists $top] {
      switch -- [wm state $top] {
         normal {
            # Raise a buried window
            raise $top
         }
         withdrawn -
         iconic {
            # Open and restore geometry
            wm deiconify $top
            catch {wm geometry $top $dialog(geo,$top)}
         }
      }
      return 0
   } else {
      eval {toplevel $top} $args
      wm title $top $title
      return 1
   }
}
proc Dialog_Wait {top varName {focus {}}} {
   upvar $varName var

   # Poke the variable if the user nukes the window
   bind $top <Destroy> [list set $varName cancel]

   # Grab focus for the dialog
   if {[string length $focus] == 0} {
      set focus $top
   }
   set old [focus -displayof $top]
   focus $focus
   catch {tkwait visibility $top}
   catch {grab $top}

   # Wait for the dialog to complete
   tkwait variable $varName
   catch {grab release $top}
   focus $old
}
proc Dialog_Dismiss {top} {
   global dialog
   # Save current size and position
   catch {
      # window may have been deleted
      set dialog(geo,$top) [wm geometry $top]
      wm withdraw $top
   }
}

The Dialog_Wait procedure allows a different focus widget than the toplevel. The idea is that you can start the focus out in the appropriate widget within the dialog, such as the first entry widget. Otherwise, the user has to click in the dialog first.

Note

Procedures to help build dialogsdialogbuiding with procedureprocedureto build dialogs

Grab can fail.

The catch statements in Dialog_Wait come from my experiences on different platforms. The tkwait visibility is sometimes required because grab can fail if the dialog is not yet visible. However, on other systems, the tkwait visi bility itself can fail in some circumstances. Tk reflects these errors, but in this case all that can go wrong is no grab. The user can still interact with the dialog without a grab, so I just ignore these errors.

Prompter Dialog

Example 39-2. A simple dialog

A simple dialogdialogsimple example
proc Dialog_Prompt { string } {
   global prompt
   set f .prompt
   if [Dialog_Create $f "Prompt" -borderwidth 10] {
      message $f.msg -text $string -aspect 1000
      entry $f.entry -textvariable prompt(result)
      set b [frame $f.buttons]
      pack $f.msg $f.entry $f.buttons -side top -fill x
      pack $f.entry -pady 5
      button $b.ok -text OK -command {set prompt(ok) 1}
      button $b.cancel -text Cancel 
         -command {set prompt(ok) 0}
      pack $b.ok -side left
      pack $b.cancel -side right
      bind $f.entry <Return> {set prompt(ok) 1 ; break}
      bind $f.entry <Control-c> {set prompt(ok) 0 ; break}
   }
   set prompt(ok) 0
   Dialog_Wait $f prompt(ok) $f.entry
   Dialog_Dismiss $f
   if {$prompt(ok)} {
      return $prompt(result)
   } else {
      return {}
   }
}
Dialog_Prompt "Please enter a name"

Example 39-2 shows Dialog_Prompt, which gets a value from the user, returning the value entered, or the empty string if the user cancels the operation. Dialog_Prompt uses the Tcl variable prompt(ok) to indicate the dialog is complete. The variable is set if the user presses the OK or Cancel buttons, or if the user presses <Return> or <Control-c> in the entry widget. The Dialog_Wait procedure waits on prompt(ok), and it grabs and restores focus. If the Dialog_Create procedure returns 1, then the dialog is built: otherwise, it already existed.

Keyboard Shortcuts and Focus

Focus is set on the entry widget in the dialog with Dialog_Wait, and it is convenient if users can use special key bindings to complete the dialog. Otherwise, they need to take their hands off the keyboard and use the mouse. The example defines bindings for <Return> and <Control-c> that invoke the OK and Cancel buttons, respectively. The bindings override all other bindings by including a break command. Otherwise, the Entry class bindings insert the short-cut keystroke into the entry widget.

Animation with the update Command

Suppose you want to entertain your user while your application is busy. By default, the user interface hangs until your processing completes. Even if you change a label or entry widget in the middle of processing, the updates to that widget are deferred until an idle moment. The user does not see your feedback, and the window is not refreshed if it gets obscured and uncovered. The solution is to use the update command that forces Tk to go through its event loop and update the display.

The next example shows a Feedback procedure that displays status messages. A read-only entry widget displays the messages, and the update command ensures that the user sees each new message. An entry widget is used because it won't change size based on the message length, and it can be scrolled by dragging with the middle mouse button. Entry widgets also work better with update idletasks as described later:

Example 39-3. A feedback procedure

proc Feedback { message } {
   global feedback
   set e $feedback(entry)
   $e config -state normal
   $e delete 0 end
   $e insert 0 $message
   # Leave the entry in a read-only state
   $e config -state disabled
   # Force a display update
   update idletasks
}

The Tk widgets update their display at idle moments, which basically means after everything else is taken care of. This lets them collapse updates into one interaction with the window system. On UNIX, this improves the batching effects that are part of the X protocol. A call to update idletasks causes any pending display updates to be processed. Chapter 16 describes the Tk event loop in more detail.

Note

A feedback procedureuser feedbackfeedback, to user

Use update idletasks if possible.

The safest way to use update is with its idletasks option. If you use the update command with no options, then all events are processed. In particular, user input events are processed. If you are not careful, it can have unexpected effects because another thread of execution is launched into your Tcl interpreter. The current thread is suspended and any callbacks that result from input events are executed. It is usually better to use the tkwait command if you need to process input because it pauses the main application at a well-defined point.

One drawback of update idletasks is that in some cases a widget's redisplay is triggered by window system events. In particular, when you change the text of a label, it can cause the size of the label to change. The widget is too clever for us in this case. Instead of scheduling a redisplay at idle time, it requests a different size and then waits for the <Configure> event from the window system. The <Configure> event indicates a size has been chosen by the geometry manager, and it is at that point that the label schedules its redisplay. So, changing the label's text and doing update idletasks does not work as expected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.216.249