Updated 2008-04-13 05:34:42 by Teo

Richard Suchenwirth 2006-12-22 - The tk_optionMenu offers a handy alternative to a combobox. But for a larger number of structured choices, it is sometimes better to have cascading menus instead of one. Here's a quick hack for that. It follows the same simple API as tk_optionMenu, but if an item is a list of more than one elements, it is turned into a submenu. (Add an extra layer of braces around labels that contain spaces - see example below.) Oh, and the special item "--" makes a separator.
 proc tk_optionCascade {w varName args} {
     set dn [image create bitmap -data "#define i_width 7\n\#define i_height 5
	static char i_bits[] = {\n0,127,62,28,8\n}"]
     set it [lindex $args 0]
     while {[llength $it]>1} {set it [lindex $it 0]}
     set ::$varName $it
     menubutton $w -menu $w.m -text $it -relief raised \
	 -image $dn -compound right
     menu $w.m -tearoff 0
     tk_optionCascade_add $w.m $varName $args
     trace var ::$varName w "$w config -text \${$varName} ;\#"
     return $w.m
 }
 proc tk_optionCascade_add {w varName argl} {
     set n 0
     set colbreak 0
     foreach arg $argl {
	 if {$arg eq "--"} {
	     $w add separator
	 } elseif {$arg eq "|"} {
	     set colbreak 1; continue
	 } elseif {[llength $arg] == 1} {
	     $w add radiobutton -label [join $arg] -variable $varName
	 } else {
	     set child [menu $w.[incr n] -tearoff 0]
	     $w add cascade -label [lindex $arg 0] -menu $child
	     tk_optionCascade_add $child $varName [lrange $arg 1 end]
	 }
	 if $colbreak {
	     $w entryconfigure end -columnbreak 1
	     set colbreak 0
	 }
     }
 }

# Demo and testing:
 package require Tk
 tk_optionCascade .o myvar \
	   {color red green blue -- {other yellow magenta cyan}} \
	   {hue   dark medium light} \
	   -- {"multi word example"} ok
 pack .o

RS 2007-02-05: added "|" to be treated as column-break, for data-driven multicolumn menus, such that e.g.
 tk_optionCascade .x myvar northwest southwest | northeast southeast

shows a menu of two columns, with directions in the plausible place :)

[Teo] 2008-04-13: A fairly common case when tk_optionMenu fails to work properly is a big list of unstructured items (e.g. a list of font families). The following tk_optionCascade2 adds cascading menus for items which don't fit the screen height.
 proc tk_optionCascade2 {path varName value args} {
     upvar #0 $varName v
     if {![info exists v]} {
 	set v $value
     }
     set m [tk_optionMenu $path $varName $v]
     tk_optionCascade2_menu $m $varName [linsert $args 0 $value]
     return $m
 }

 proc tk_optionCascade2_menu {m varName argl} {
     $m delete 0 end
     set next [menu $m.n -tearoff 0]
     $m add cascade -label "More" -menu $next
     set index 0
     set len [llength $argl]
     set height [winfo screenheight $m]
     foreach arg $argl {
 	$m insert $index radiobutton -label $arg -variable $varName
 	incr index
 	update idletasks
 	if {[winfo reqheight $m] > $height && $index < $len} {
 	    incr index -1
 	    $m delete $index
 	    tk_optionCascade2_menu $next $varName [lrange $argl $index end]
 	    return
 	}
     }
     $m delete end
 }

 # demo
 eval tk_optionCascade2 .q1 myvar2 [lsort [font families]]
 pack .q1

Category Example