Previous section   Next section

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

Table of Contents
Chapter 42.  Managing User Preferences

The Preferences User Interface

The figure shows the interface for the items added with the Pref_Add command given in the previous section. The pop-up window with the extended help text appears after you click on "Scrollbar placement." The user interface to the preference settings is table-driven. As a result of all the Pref_Add calls, a single list of all the preference items is built. The interface is constructed by looping through this list and creating a user interface item for each:

Example 42-5 A user interface to the preference items.


proc Pref_Dialog {} {
   global pref
   if [catch {toplevel .pref}] {
      raise .pref
   } else {
      wm title .pref "Preferences"
      set buttons [frame .pref.but -bd 5]
      pack .pref.but -side top -fill x
      button $buttons.quit -text Dismiss \
         -command {PrefDismiss}
      button $buttons.save -text Save \
         -command {PrefSave}
      button $buttons.reset -text Reset \
         -command {PrefReset ; PrefDismiss}
      label $buttons.label \
         -text "Click labels for info on each item"
      pack $buttons.label -side left -fill x
      pack $buttons.quit $buttons.save $buttons.reset \
         -side right -padx 4

      frame .pref.b -borderwidth 2 -relief raised
      pack .pref.b -fill both
      set body [frame .pref.b.b -bd 10]
      pack .pref.b.b -fill both

      set maxWidth 0
      foreach item $pref(items) {
         set len [string length [PrefComment $item]]
         if {$len > $maxWidth} {
            set maxWidth $len
      set pref(uid) 0
      foreach item $pref(items) {
         PrefDialogItem $body $item $maxWidth

The interface supports three different types of preference items: boolean, choice, and general value. A boolean is implemented with a checkbutton that is tied to the Tcl variable, which will get a value of either 0 or 1. A boolean is identified by a default value that is either ON or OFF. A choice item is implemented as a set of radiobuttons, one for each choice. A choice item is identified by a default value that is a list with the first element equal to CHOICE. The remaining list items are the choices, with the first one being the default choice. A regexp is used to check for CHOICE instead of using list operations. This is because Tcl 8.0 will complain if the value is not a proper list, which could happen with arbitrary values. If neither of these cases, boolean or choice, are detected, then an entry widget is created to hold the general value of the preference item:

Example 42-6 Interface objects for different preference types.
proc PrefDialogItem { frame item width } {
   global pref
   incr pref(uid)
   set f [frame $frame.p$pref(uid) -borderwidth 2]
   pack $f -fill x
   label $f.label -text [PrefComment $item] -width $width
   bind $f.label <1> \
      [list PrefItemHelp %X %Y [PrefHelp $item]]
   pack $f.label -side left
   set default [PrefDefault $item]
   if {[regexp "^CHOICE " $default]} {
      foreach choice [lreplace $default 0 0] {
         incr pref(uid)
         radiobutton $f.c$pref(uid) -text $choice \
            -variable [PrefVar $item] -value $choice
         pack $f.c$pref(uid) -side left
   } else {
      if {$default == "OFF" || $default == "ON"} {
         # This is a boolean
         set varName [PrefVar $item]
         checkbutton $f.check -variable $varName \
             -command [list PrefFixupBoolean $f.check $varName]
         PrefFixupBoolean $f.check $varName
         pack $f.check -side left
      } else {
         # This is a string or numeric
         entry $f.entry -width 10 -relief sunken
         pack $f.entry -side left -fill x -expand true
         set pref(entry,[PrefVar $item]) $f.entry
         set varName [PrefVar $item]
         $f.entry insert 0 [uplevel #0 [list set $varName]]
         bind $f.entry <Return> "PrefEntrySet %W $varName"
proc PrefFixupBoolean {check varname} {
   upvar #0 $varname var
   # Update the checkbutton text each time it changes
   if {$var} {
      $check config -text On
   } else {
      $check config -text Off
proc PrefEntrySet { entry varName } {
   PrefValueSet $varName [$entry get]


In this interface, when the user clicks a radiobutton or a checkbutton, the Tcl variable is set immediately. To obtain a similar effect with the general preference item, the <Return> key is bound to a procedure that sets the associated Tcl variable to the value from the entry widget. PrefEntrySet is a one-line procedure that saves us from using the more awkward binding shown below. Grouping with double quotes allows substitution of $varName, but then we must quote the square brackets to postpone command substitution:

bind $f.entry <Return> "PrefValueSet $varName \[%W get\]"

The binding on <Return> is done as opposed to using the -textvariable option because it interacts with traces on the variable a bit better. With trace you can arrange for a Tcl command to be executed when a variable is changed, as in Example 42-10 on page 592. For a general preference item it is better to wait until the complete value is entered before responding to its new value.

The other aspect of the user interface is the display of additional help information for each item. If there are lots of preference items, then there isn't enough room to display this information directly. Instead, clicking on the short description for each item brings up a toplevel with the help text for that item. The toplevel is marked transient so that the window manager does not decorate it:

Example 42-7 Displaying the help text for an item.
proc PrefItemHelp { x y text } {
   catch {destroy .prefitemhelp}
   if {$text == {}} {
   set self [toplevel .prefitemhelp -class Itemhelp]
   wm title $self "Item help"
   wm geometry $self +[expr $x+10]+[expr $y+10]
   wm transient $self .pref
   message $self.msg -text $text -aspect 1500
   pack $self.msg
   bind $self.msg <1> {PrefNukeItemHelp .prefitemhelp}
   .pref.but.label configure -text \
      "Click on pop-up or another label"
proc PrefNukeItemHelp { t } {
   .pref.but.label configure -text \
       "Click labels for info on each item"
   destroy $t

      Previous section   Next section