www.gibmonks.com

  Previous section   Next section

Practical Programming in Tcl & Tk, Third Edition
By Brent B. Welch

Table of Contents
Chapter 36.  Focus, Grabs, and Dialogs


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 36-1 on page 519 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 36-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 36-4. The focus command.
focusReturns the widget that currently has the focus on the display of the application's main window.
focus ?-force? windowSets the focus to window. The -force option ignores the window manger, so use it sparingly.
focus -displayof winReturns the focus widget on the same display as win.
focus -lastfor winReturns 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. 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. Table 36-5 summarizes the grab command.

Table 36-5. The grab command.
grab ?-global? windowSets 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 windowReleases a grab on window.
grab set ?-global? winSets a grab to a particular window.
grab status windowReturns none, local, or global.

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. 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 36-6 summarizes the tkwait command.

Table 36-6. The tkwait command.
tkwait variable varnameWaits for the global variable varname to be set. This is just like the vwait command.
tkwait visibility winWaits for the window win to become visible.
tkwait window winWaits for the window win to be destroyed.

graphics/tip_icon.gif

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 41 describes a protocol on page 572 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 had focus before the dialog is created so that focus can be restored 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 41 describes the window manager commands used to map and unmap windows on page 572. Example 36-1 shows Dialog_Create, Dialog_Wait, and Dialog_Dismiss that capture all of these tricks:

Example 36-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.

graphics/tip_icon.gif

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 visibility 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 36-2 A simple dialog.

graphics/36fig01.gif


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"

The Dialog_Prompt dialog 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.


      Previous section   Next section
    Top