This page is about code that is aimed to help a developer to deal with tk menu widgets.
FM, 2009-04-05. Menus are tree objects. Here is code to design their trees as nested list.
I chose to use a 4-length nested list (nl4tree) because for cascades entries, there are two kinds of data : the options for the uplevel menu itself and also the options to be set when adding the child menu.
Here is code to make a nl4tree. To use it you need the nl4 ensemble command, which can be found at nested list page of the wiki. The exemple below should look like this :

# package require nl4;# look at [nested list] page to get the nl4 ensemble command
namespace eval nl4tree {
proc append {tree ParentNode TreeToAppend} {
# nl4tree append ... Ok
upvar $tree Tree
::set Index [lreplace [nl4tree lindex $Tree $ParentNode] end end [nl4 rindice [nl4 type $Tree]] 0 0]
::set l [::lindex $Tree $Index]
lappend l $TreeToAppend
lset Tree $Index $l
}
proc children {tree parent} {
# nl4tree children ... Ok
::set Children [list]
lassign [nl4 index $tree] node data0 data1 children
foreach {child} [::lindex $children] {
if {$parent eq $node} {
lappend Children {*}[::lindex $child 0]
} else {
lappend Children {*}[nl4tree children $child $parent]
}
}
return $Children
}
proc delete {tree node} {
# nl4tree delete ... Ok
upvar $tree Tree
if {$node eq [nl4tree root $Tree]} {uplevel "unset $tree"; return}
::set TailIndex [::lindex [::set NodeIndex [nl4tree lindex $Tree $node]] end-1]
::set ListNodeIndex [lreplace $NodeIndex end-1 end]
::set L [lreplace [::lindex $Tree $ListNodeIndex] $TailIndex $TailIndex]
::set ParentIndex [nl4tree lindex $Tree [::set Parent [nl4tree parent $Tree $node]]]
::set SubTreeIndex [lreplace $ParentIndex end end]
lset Tree {*}$SubTreeIndex [nl4 rindice [nl4 type $Tree]] 0 0 $L
return
}
proc get {tree node numdict args} {
# nl4tree get .. Ok
::set index [lreplace [nl4tree lindex $tree $node] end end]
return [dict get [nl4 index [::lindex $tree $index] [expr {$numdict+1}]] {*}$args]
}
proc glob {tree pattern {index {}}} {
# nl4tree glob ... Ok
lassign [nl4 index $tree] node data0 data1 children
if {[string match $pattern $node]} {
lappend L $node
}
::set j 0
foreach {child} [::lindex $children] {
::set {childIndex} [list {*}$index [nl4 rindice [nl4 type $tree]] 0 0 $j]
lappend L {*}[nl4tree glob $child $pattern $childIndex]
incr j
}
if {[info exist L]} {
return $L
}
}
proc insert {tree Parent index TreeToInsert} {
# nl4tree insert ...Ok
upvar $tree Tree
::set ParentIndex [lreplace [nl4tree lindex $Tree $Parent] end end [nl4 rindice [nl4 type $Tree]] 0 0]
::set l [::lindex $Tree $ParentIndex]
if {$index < [nl4tree numchildren $Tree $Parent]} {
lset Tree $ParentIndex [linsert $l $index $TreeToInsert]
}
}
proc keys {tree node numdict args} {
# nl4tree keys ... Ok
::set index [lreplace [nl4tree lindex $tree $node] end end]
return [dict keys [nl4 index [::lindex $tree $index] [expr {$numdict+1}]] {*}$args]
}
proc lindex {tree parent {index {}}} {
# nl4tree lindex ... Ok
lassign [nl4 index $tree] node data0 data1 children
if {$parent eq $node} {
return [list {*}$index 0]
}
::set j 0
foreach {child} [::lindex $children] {
::set {childIndex} [list {*}$index [nl4 rindice [nl4 type $tree]] 0 0 $j]
if {[::set p [nl4tree lindex $child $parent $childIndex]] ne ""} {
return $p
}
incr j
}
return
}
proc node {tree args} {
# nl4tree node ... Ok
::set i 0
foreach i $args {
lappend Index [nl4 rindice [nl4 type $tree]] 0 0 $i
incr i
}
lappend Index 0
::lindex $tree {*}$Index
}
proc numchildren {tree node} {
# nl4tree numchildren ... Ok
::set Index [lreplace [nl4tree lindex $tree $node] end end]
llength [nl4 index [::lindex $tree $Index] 3]
}
proc parent {tree parent {Parent {}}} {
# nl4tree parent ... Ok
lassign [nl4 index $tree] node data0 data1 children
if {$parent eq $node} {
return $Parent
}
foreach {child} [::lindex $children] {
if {[::set p [nl4tree parent $child $parent $node]] ne ""} {
return $p
}
}
return
}
proc root {tree} {
# nl4tree root ... Ok
return [::lindex $tree 0]
}
proc set {tree node numdict args} {
# nl4tree set .. Ok
upvar $tree Tree
::set keys [lrange $args 0 end-1]
::set arg [::lindex $args end]
::set index [lreplace [nl4tree lindex $Tree $node] end end ]
::set DictIndex [::lindex [nl4 iorder [nl4 type [::lindex $Tree $index]]] [expr {$numdict+1}]]
::set Dict [nl4 index [::lindex $Tree $index] [expr {$numdict+1}]]
dict set Dict {*}$keys $arg
lset Tree {*}$index $DictIndex [list $Dict]
return $Dict
}
namespace export *
namespace ensemble create
}
package provide nl4tree 0.1# source nl4tree.tcl;# you'll find the nl4tree ensemble command just above
namespace eval treeMenu {
variable numMenu 0
variable numCommand 0
variable numSeparator 0
variable numCheckButton 0
variable numRadioButton 0
# create menu / cascade
proc cascade {D0 D1 args} {
variable numMenu
if {[llength $args] != 0} {
menu [::set N .menu[incr numMenu]] -tearoff false {*}$D0
return [nl4 south $N $D0 $D1 [list $args]]
} else {
return [nl4 south $N $D0 $D1]
}
}
# command entry
proc command {args} {
variable numCommand
::set N .command[incr numCommand]
return [nl4 south $N {} [dict create {*}$args]]
}
# separator entry
proc separator {args} {
variable numSeparator
::set N .separator[incr numSeparator]
return [nl4 south $N {} {}]
}
# checkbutton entry
proc checkbutton {args} {
variable numCheckButton
::set N .checkbutton[incr numCheckButton]
return [nl4 south $N {} [dict create {*}$args]]
}
# radiobutton entry
proc radiobutton {args} {
variable numRadioButton
::set N .radiobutton[incr numRadioButton]
return [nl4 south $N {} [dict create {*}$args]]
}
# Build the menu
variable TreeMenu
proc set {var tree {node {}}} {
variable TreeMenu
if {$node eq {}} {
::set TreeMenu($var,script) $tree
::set tree [uplevel $tree]
::set TreeMenu($var,tree) $tree
::set node [nl4tree root $tree]
}
foreach n [nl4tree children $tree $node] {
if {[string match .menu* $n]} {
$node add cascade -menu $n {*}[nl4tree get $tree $n 1]
} elseif {[string match .command* $n]} {
$node add command {*}[::set options [nl4tree get $tree $n 1]]
if {[dict exist $options -accelerator]} {
::set Accelerator [string map {Ctrl Control + -} [dict get $options -accelerator]]
if {! (([string index $Accelerator 0] eq "<") && ([string index $Accelerator 0] eq ">"))} {
::set Accelerator <$Accelerator>
}
bind . $Accelerator [subst {
$node invoke [$node index [dict get $options -label]]
}]
}
} elseif {[string match .separator* $n]} {
$node add separator {*}[nl4tree get $tree $n 1]
} elseif {[string match .checkbutton* $n]} {
$node add checkbutton {*}[nl4tree get $tree $n 1]
} elseif {[string match .radiobutton* $n]} {
$node add radiobutton {*}[nl4tree get $tree $n 1]
}
::treeMenu set $var $tree $n
}
return $node
}
proc update {var widget} {
# basic : destruction / reconstruction
variable TreeMenu
::set Reinit {{var pattern} {
expr {[regsub $pattern [lindex [lsort [nl4tree glob $::treeMenu::TreeMenu($var,tree) $pattern*]] 0] {}]-1}
}}
variable numMenu [apply $Reinit $var .menu]
variable numCommand [apply $Reinit $var .command]
variable numSeparator [apply $Reinit $var .separator]
variable numCheckButton [apply $Reinit $var .checkbutton]
variable numRadioButton [apply $Reinit $var .radiobutton]
destroy {*}[nl4tree glob $TreeMenu($var,tree) .menu*]
$widget conf -menu [uplevel [list ::treeMenu set $var $TreeMenu($var,script)]]
}
namespace export *
namespace ensemble create
}
# package provide treeMenu 0.1
# package require treeMenu (See above)
# using some shorthand
interp alias {} + {} treeMenu cascade
interp alias {} ! {} treeMenu command
interp alias {} - {} treeMenu separator
interp alias {} x {} treeMenu checkbutton
interp alias {} ° {} treeMenu radio
# Use some alternatives languages for menu label
proc translate {label lang} {
return [dict get [dict create \
Fichier [dict create en File fr Fichier] \
Ouvrir [dict create en Open fr Ouvrir] \
Nouveau [dict create en New fr Nouveau] \
Exporter [dict create en Export fr Exporter]\
Enregistrer [dict create en Save fr Enregistrer]\
"Enregistrer sous..." [dict create en "Save as..." fr "Enregistrer sous..."]\
Editer [dict create en Edit fr Editer] \
Fermer [dict create en Close fr Fermer] \
Quitter [dict create en Quit fr Quitter]\
Copier [dict create en Copy fr Copier]\
Coller [dict create en Paste fr Coller]\
Couper [dict create en Cut fr Couper]\
"en PDF" [dict create en "as PDF" fr "en PDF"]\
"en DVI" [dict create en "as DVI" fr "en DVI"]\
"en Postscript" [dict create en "as Postscript" fr "en Postscript"]\
"Cas" [dict create en Check fr "Cas"]\
"Options" [dict create en Options fr "Options"]\
"Anglais" [dict create en "English" fr "Anglais"]\
"Français" [dict create en "French" fr "Français"]\
"Comme nouveau" [dict create en "As new file" fr "Comme nouveau"]\
"Comme un calque" [dict create en "As new layer" fr "Comme un calque"]\
"Verrouiller" [dict create en "Press to lock file" fr "Cliquer pour verrouiller"]\
"Déverrouiller" [dict create en "Press to unlock file" fr "Cliquer pour déverrouiller"]\
] $label $lang]
}
# use state variables (fileopen : if there is a file open, lock : to protect the file of any change)
set fileopen 0
set lock 0
# variable to config the langage (you can change it in options menu)
set lang fr
# style of the menu
set Style [list -bg DarkOrange4 -fg white -activebackground DarkOliveGreen -activeforeground yellow -font \
[set font [font create -family Helvetica -size 12]]]
# Create the menu on the . Widget
. conf -menu [treeMenu set Menu {
+ $Style {} \
[+ $Style [list \
-label [translate "Fichier" $lang] \
-underline 0 ] \
[! -label [translate "Nouveau" $lang] \
-underline 0 \
-accelerator Ctrl+n \
-image [image create photo -data {
R0lGODlhEAAQAMZYAIiKg4iKhYqLg5ORe6CdeamkacW5TdPDO9HFYO3UAO3UAe3UAu3UBO3UBe3V
BO3VB+3VCe3VCujUJe7WCu7XEfDaIPPdIvPdI/ThP/fiNvTjW/XkV/rmRfrmRvblYPblYfrnR/Dk
iu7klvvoS+7lj/voTPvoTe/lj+bizezkqenjvOzkqujjw+fjye7mm+zlrOTj2eTj2uPj3ePj3uPj
4ePj4uPj4+fk2+Tk5OXk5OTl4eXl5e3nvunm1ubm5urn1Ofn5+ro3Ojo6Ovo5enp5+rp6erp6uvr
6+zs7Pjxr/32wvz55vz65vz65/365vz67P375/376v376/377f389P39/f7+/P7+/v//////////
////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////yH5
BAEKAH8ALAAAAAAQABAAAAergH9/AYQBAAMFBgcSF4KCAVeRV09JGxUKChQWjpBIR0M8EAsJU1MJ
Cg6PV0dFQS4NTU6yTkwOHYNXRkQ/IQxMs7QOILhCQD0nGAtQsksKGSO4QD43LxoMVkxMTQ4cJrg+
OzotJA9RDAxSDyWqOTg2MywiER8eEwicVzb67ygqKylKCODbp4+GjBgwqAgYSFBfjRpVAjBsqO+K
RFUU91nEJ6mjpIuDCokcKSgQADs=
}]\
-compound left \
-command {
set fileopen 1
treeMenu update Menu .
}] \
[! -label [translate "Ouvrir" $lang] \
-underline 0 \
-accelerator Ctrl-o \
-image [image create photo -data {
R0lGODlhEAAQAMZCAExOSk1PS05QTU9RTVBSTlVXU1dZVVhaVmJkYGRmXmZoY2lrZ2psaG1vZXt9
a4GEcIeJgYqNeIyPe4+Rfo+SfpCSg5KUgZiajJmbhpqch5qdiJydkJ2fkaKkk6OklaOllqeplqmq
mqqumq+wma6wnq+wn7CxmrGym7CyoLGxrbKzprO1o7m6rLu9qr6/rr6/r76+vL/Arr/Ar8DBr8DB
tcHDssPEs8PEt8PFtcnKu8rMvsvNvs/Qws/RwtDRw9DRxNHSxO7u7P//////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////yH5
BAEKAH8ALAAAAAAQABAAAAemgAwFg4MLf4eIiAU1OI0sBYmRBS0aGRglhJkFCn8FKxQUExYhHR4f
px8ckJ6gCZqDDaqdnxQFQre4QgWyBSgRErZBwsK6F6skERG2MMzMuhWrIA8PwcNBz6siDg62KQMA
4OABBwcFCAHoARs3QEA8Ozo5EIM+PPb2NiMm+/s/gz0nAgoceGIEjUEuZsRYOGOGjIcLX6jYRICA
gIsYBVS0aCCSR4+BAAA7
}] \
-compound left \
-command {
set fileopen 1
tk_getOpenFile
treeMenu update Menu .
}] \
[- ------------------------------------------ -]\
[+ $Style [list \
-label [translate "Exporter" $lang] \
-state [expr {$fileopen ? "normal" : "disable"}] \
-underline 0] \
[! -label [translate "en PDF" $lang] \
-command {
tk_getSaveFile
}] \
[! -label [translate "en DVI" $lang] \
-command {
tk_getSaveFile
}] \
[! -label [translate "en Postscript" $lang] \
-command {
tk_getSaveFile
}] \
]\
[- ----------------------------------------- -]\
[! -label [translate "Enregistrer" $lang] \
-state [expr {$fileopen==1 && $lock == 0 ? "normal" : "disable"}]\
-accelerator {Ctrl+s} \
-image [image create photo -data {
R0lGODlhEAAQAKUnACBKh1VXU85cAGF6kmF6k117mWJ7kmN7kmJ8lGN8lGB+nGmMsHCeznGeznKf
znKfz46pzJS227Kyspi43PyvPrm5ubm6urq6ur+/v8LCwsXFxb7S6tbW1sjZ7tfX1+Hh4eLi4uPj
4+bm5ufn5/T09Pn5+f7+/v//////////////////////////////////////////////////////
/////////////////////////////////////////////yH5BAEKAD8ALAAAAAAQABAAAAaAQIBQ
QCwahcJNh8JsOjeb5KNErVofUcBmCup6uyWs1EQqm81i7cPkabvbpPSWfD7L16a8Xn9/+P+AdwsI
hIUICxF3ChcaGh8jHhYFDBFZWwONAQEgGhUEDZVSBo6ajRifoQAQEwYfIqUZEqhZqgchHBe5FRUJ
DhNRP0Kaw8RIAEEAOw==
}]\
-compound left \
-command {
tk_getSaveFile
}] \
[! -label [translate "Enregistrer sous..." $lang] \
-state [expr {$fileopen ? "normal" : "disable"}] \
-image [image create photo -data {
R0lGODlhEAAQAKU7AAAAAExCJiBKh1VXU2tXNnBbOY9ZArdaAM5cAGF6kmF6k117mWJ7kmN7kmJ8
lGN8lGB+nKCDVWmMsKOFVaSHV3CeznGeznKfznKfz46pzMKritKsas+tcZS227KyssSwkZi43Pyv
Prm5ubm6urq6usi9rL+/v8LCwsXFxb7S6t/Vw9bW1sjZ7tfX1+PZx+Hh4eLi4uPj4+bm5ufn5+7q
xu/rx/Pz8/T09Pn5+fz8/P7+/v///////////////////yH5BAEKAD8ALAAAAAAQABAAAAadQIEQ
QSwaDAeEUJhihZ5PA41jCKVSTAxuizPUNhGDC4MVpLQwmHdDMXxwlvJZd7sVNBODqh7P6loGAQQG
JS0tNmR+NwB6dY6JZhg6k5SUOZBnGJqbnJgYEg6hog4SHZ4QJCgoLzMtIwsVHXIYCaoDAzAoIgoW
slkMq7eqJry+AhkgDC8ywicexWXHDTErJNYiIg8XIFg/Qrfg4UsCQQA7
}] -compound left \
-command {
tk_getSaveFile
}] \
[! -label [translate "Fermer" $lang] \
-state [expr {$fileopen ? "normal" : "disable"}] \
-command {
tk_messageBox -message "Closing"
set fileopen 0
set lock 0
treeMenu update Menu .
}] \
[- ---------------------------------------- -]\
[! -label [translate "Quitter" $lang] -accelerator {Ctrl+q} \
-image [image create photo -data {
R0lGODlhEAAQAKUzAAcPAA0cABMoAKQAABgtBrQAABgzAMwAAC40NiI+CCFBBitODSpPBy9WDTJZ
EDZbGDleGe8pKTpiFzhmDjtlGD5kHltbWjxsE0FuGT9wFUNuH0R1GUZ0I0t6Imxsakt+H05+JVB+
KlB/K3Z5enl5d/uCgqSkoq2tq7e3tbm5t729u8DAvcHBvtDQzdPT0NTU0d7e3ODg3uLi4P//////
/////////////////////////////////////////////yH5BAEKAD8ALAAAAAAQABAAAAaDwJ9w
SCwaEcik8ihzrU4yBECAIA4GiOYKFQ0YqsNBaQBzsVJRgqJ6vZbGrXM0wWC/34exyhRdNOwHgYFj
JBYIDhNsEQWMjBEDHlEPF4oRlgWPcggQGWxtlgMvmhIYYEIDjzKiMlEUG6Y/VwgxrCMIFRwfsEJZ
thodICEiuz9KxsRGRUEAOw==
}] -compound left -command {
tk_messageBox -message [translate "Quitter" $lang]
}] \
]\
[+ $Style [list \
-label [translate "Editer" $lang] \
-state [expr {$fileopen ? "normal" : "disable"}]] \
[! -label [translate "Couper" $lang] \
-accelerator Ctrl+x \
-image [image create photo -data {
R0lGODlhEAAQAIQZAFVXU2VnY3R1coGCf42Oi5iZlqipp66sp7Gup7+7sb6/vcfEu8jHwsrKyczK
xs/KwdHOxNXTytjW0dza0dzb1uPh3OPi3OTi3evp5f///////////////////////////yH5BAEK
AB8ALAAAAAAQABAAAAVJ4CeOZFkCpzmiJMCe1AtMb1sV4wDVq8MEHwAGwRsJJIQPIZJUBS8HgCVR
JAUWgEf15HKKXC4FwNBNBRufBou3Xn3Nsjd37q2HAAA7
}] \
-compound left \
-command {
tk_messageBox -message "Coupé"
}] \
[! -label [translate "Copier" $lang] \
-accelerator Ctrl+c \
-image [image create photo -data {
R0lGODlhEAAQAMIEAIiKhbu8uLq9tu7u7P///////////////yH5BAEKAAcALAAAAAAQABAAAANE
eLC891CRSSeIDxDBO7mYNozkh0mdB0biGDRNtnFaRV3toKWcudMEUglX+aVMikZQOECyZscVJDc8
GQUj59R2OyVhoAQAOw==
}] \
-compound left \
-command {
tk_messageBox -message "Copié"
}] \
[! -label [translate "Coller" $lang] \
-state [expr {$lock ? "disable" : "normal"}] \
-accelerator Ctrl+v \
-image [image create photo -data {
R0lGODlhEAAQAMZFAC0mGiwtLDstFy40No9ZAot2Unt5cIiDc4iKhZyTf6KbjaCinqGkoKOloaWm
o6aopKeopaippqmrp6qrqKusqausqq2uq7+8s+m5bs7Lwc/Mwc7Pys/RzdHTztbTyNLUz9TW0tXX
0tjY2NnY19fZ1dna19ra2Nrb2Nvb29vd2dze2t3e297f3N/h3d/h3uDi3u/gx+Hi3+Lj3+Pj4+Pk
4uTl4ubn5efo5efo5uvo3Ovo3enp5+jq6Orr6Ovr6evs6uzt6+7v7e/v7fDx7/Pz8f//////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////yH5
BAEKAH8ALAAAAAAQABAAAAeegH+CggOFg4eCBIoAKDMiAooEhwQwMAYBIxcmAQqVkokwGAM5HhoZ
OgMYMJ9/lBgFhbEFqqyuGBgJCLq7vLYYCEXBwsIIvsAbHB8gJCUpRcWht8AdCwwNDg8uz8ZFISQn
KiwxNNvRv0UlDhAREhQ45bfnKy0yNTY7PvDSRS8TFRY9gAjRd67GDR4/ggwhUq4SDGDDhiFoFekA
r4sIAgEAOw==
}] \
-compound left \
-command {
tk_messageBox -message "Collé"
}] \
\
[- --------------Separator--------------------------- -]\
[+ $Style [dict create \
-label [translate "Coller" $lang]... \
-state [expr {$lock ? "disable" : "normal"}]]\
\
[! -label [translate "Comme un calque" $lang] \
-command {
tk_messageBox -message "Collé comme un calque"
}]\
[! -label [translate "Comme nouveau" $lang]\
-command {
tk_messageBox -message "Coller comme nouveau"
}]\
]\
] \
[+ $Style [dict create \
-label [translate "Options" $lang] \
-underline 0] \
\
[x -label [translate [expr {$lock ? "Déverrouiller" : "Verrouiller"}] $lang ]\
-state [expr {$fileopen ? "normal" : "disable"}]\
-variable ::lock \
-onvalue 1 -offvalue 0 \
-command {
treeMenu update Menu .
}]\
[- ---------Separator----------------------------------- -]\
[° -label [translate Anglais $lang] -variable ::lang -value en -command {
treeMenu update Menu .
}]\
[° -label [translate Français $lang] -variable ::lang -value fr -command {
treeMenu update Menu .
}]\
]}]
Fully configurable, style control, language handling, state handling, accelerator handling, readable, tree aspect. It's marvelous, isn't it?