Version 47 of flexmenu

Updated 2019-09-17 18:12:58 by bll

Last update: 2019-9-16 version 1.28


bll 2018-9-20: An alternative menu system.

2018-9-21: There seems to be a little interest in this. I wrote it for a couple of rather minor reasons (one of which I'm not sure is valid). Though I do really like having real checkbuttons and radiobuttons in the menu, and I like the -keepopen option. I have not tried using it in my production code as yet.



 Version History
   1.28  2019-9-16
       Fix gap on delete.
       Fix reconfigure on insert or delete.
   1.27  2019-9-16
       Fix column weight issues.
       MacOS: toplevels are ok for 10.14+
       MacOS: revert to old dark mode test for 10.14.
   1.26 2019-9-10
       Internal cleanup.
       Fix sizing issues.
       Change color defaults for Mac OS.
       Mac OS dark mode colors.
       Fixed cascade unpost.
       Handle change of item selection on insert/add/delete.
   1.21 2018-12-03
       Fix grab handling issue.
       Fix close check handling.
   1.20 2018-12-03
       Resolve issues with menu display and configuration.
   1.19 2018-12-02
       Added -bindaccelerators option.
       Added -stickycascade option.
       Fixed display when moving off of active item in certain cases.
       Fixed initial display of menu.
   1.18 2018-12-02
       Mac OS X: Force -mode frame, as toplevels cannot be supported
         at this time.
   1.17 2018-11-29
       Change manual invoke processing to work when the menu is inactive.
         This matches the original menu function.  May create some odd
         states if run on a sub-cascade.
       Fixed overlapping menu handling.
   1.16 2018-11-29
       Allow vertical separators in menubar.
       Add new -gap option for items.
   1.15 2018-10-25
       Fix mac os x: enter/leave processing does not work properly.
   1.14 2018-10-22
       Fix various bugs with user 'invoke' method.
   1.13 2018-10-22
       Turn off mouse activation of menus in the wait state so that
         overlapping menus work (changed in 1.12).
       -xalign and -yalign can have tag[+-]offset
       Fix menutop/menuleft display (call postprocess).
   1.12 2018-10-21
       Initialize styles once only.
       Fix x and y offsets.
       Fix borderwidth and relief on menubar/menuleft menus.
       Fix placement of active frame in scrolling window.
       Fix enter/leave handling.
       Fix accelerator keys.
       Fix F10.
       Fix left arrow item positioning.
   1.11 2018-10-1
       Fix mistake.
   1.10 2018-10-1
       Rewrite styling and active state handling (pass 1).
       Some option cleanup.
   1.9 2018-10-1
       Fix incorrect default for -borderwidth.
       Fix bad copy/paste in enterLeaveHandler.
       Fix column spans with -hidemargin.
       Fix non-global grab.
       Fix enter/leave handling.
       Fix press handler/menu.enter/grab.
       Fix column weighting with -hidemargin.
   1.8 2018-10-1
       Fix crash when window closed.
   1.7 2018-10-1
       Remove tailcall.
       Rewrite grab handling.
       Rewrite enter/leave handling.
       Fix invoke of item when -keepopen is false.
   1.6 2018-9-30
       Set menu colors from the current theme.
       Fix -font handling.
       Fix menus not closing on checkbutton select.
       Button press will activate the menu and item.
   1.5 2018-9-30
       Fixed -borderwidth and -relief options for items.
       Added -mode {toplevel|frame} option.
       Cleaned up some stacking order issues
         (fixes the activeframe covering menu items).
   1.4 2018-9-29
       Added -precommand for cascades.
       Added -yalign for cascades.
       Added -xalign for cascades.
       Fixed bug in entrycget.
       Fixed 'delete' operation to match 'menu'.
       Fixed bug where the width was lost on a redisplay.
       Fixed display when initial number of items did not exceed -maxheight.
       Fixed various bugs with empty menus.
   1.3 2018-9-22
       Fixed initialization to only execute on mac os x.
       Fixed incorrect state for menus after invoking an item.
       Brought state table documentation up to date.
   1.2 2018-9-21  (broken for non mac os x)
       Fixed -padx/-pady so they will work for the main menubar
       Set the -acceleratorprefix to \u2318 for Mac OS X.
         This is converted to 'Meta' (hope that's right).
       Changed default -acceleratorprefix to 'Alt-' (due to mac os x changes).
       Fixed the colors on mac os x.  Dark mode and graphite themes
         are supported.
   1.1 2018-9-20
       Fixed active highlight for scrolled menus
   1.0 2018-9-20
       initial release


  • Does not work with .toplevel configure -menu .mymenu. Tk uses the internal menu API to attach menus. flexmenu cannot work with the -menu option.
  • Must use pack or grid (or place) to attach the menu. Converting an existing program to use flexmenu could be quite painful.
  • The main menu must be created with either -type menubar or -type menuleft.
  • It has not been tested much.


  • Checkbuttons and Radiobuttons are ttk widgets.
  • Supports left side menus (-type menuleft).
  • Scrolling menus (-maxheight).
  • Configure -columnbreak <value> at the menu level to automatically break every <value> items.
  • -keepopen option will leave the menu open after invoking an item.
  • -acceleratorfont, -acceleratorforeground and -acceleratoractiveforeground options.
  • -activerelief option.
  • -hidearrows option.
  • -hideaccelerators option.
  • -acceleratorprefix option to set the default accelerator prefix.
  • -padx, -pady options to change the padding for menu items.
  • -bindaccelerators option.
  • -stickycascade option.
  • -mode option to select the use of frames or toplevels. Mac OS X is forced to use -mode frame.
  • Is a little more dynamic than the standard menu. Many things can be reconfigured and the changes will be picked up.

Item Features:

  • Any widget can be put into the menu (.mymenu add widget -widget .mymenu.mycombobox).
  • Margin images (-marginimage). It is quite common nowadays to use small icons on the left margin of the menu as an aid for the user.
  • Accelerator labels are automatically generated based on either an & prefix in the label, the -underline option, or the -accelerator option.
  • Accelerator bindings are automatically generated.
  • -activerelief option.
  • -gap option.
  • Cascades: -yalign, -xalign options to change cascade alignment.
  • Cascades: -precommand option. Useful for dynamic menu generation.


  • Ignored: -bitmap, -tearoff, -tearoffcommand, -title, -selectcolor.
  • -hidemargin works properly on a per-entry basis. menu seems to treat it as a menu option even though it is specified per entry. I think -hidemargin would be better off as a menu option rather than an item option, but backwards compatibility is an issue.
  • Mac OS X: -mode toplevel is not supported. Mac OS X is forced into -mode frame.


  • May be overeager in generating accelerator labels. The user may not want accelerator labels displayed for every item.
  • Has not been tested much.
  • At this time, flexmenu does not check to see if the entire menu is visible, and does not do any relocation of the menu.
  • Could use better documentation.

Known Issues:

Fixed in 1.18: Mac OS X is not working at this time (2018-10-22). Mac OS X works, but only with -mode frame. -mode frame is forced for Mac OS X (2018-11-02).

Fixed in 1.1: Active item highlighting for scrolling menus is not working right.

Fixed in 1.2: The accelerator key prefix is set to Alt. This needs to be set correctly for Mac OS X, but I do not recall which meta key the Mac sends.

The clone command has only a very basic implementation.

Flicker with -selectimage. Currently, the menu layout is reapplied when a -image is changed to a -selectimage. This is just in case the -selectimage is a different size. If the assumption can be made that the image sizes are identical, this redraw could be removed.


 Example 1
package require Tk
source flexmenu.tcl

# standard checkbutton widgets
# -keepopen set
flexmenu .mcb -keepopen true
.mcb add checkbutton -variable ::x -text Check -onvalue 0 -offvalue 1 \
    -accelerator Ctrl-4
.mcb add checkbutton -variable ::x -text Check -onvalue 0 -offvalue 1 \
    -accelerator Ctrl-5 -indicatoron false
.mcb add checkbutton -variable ::x -text Check -onvalue 0 -offvalue 1 \
    -state disabled

# standard radiobutton widgets
flexmenu .mrb -keepopen true
.mrb add radiobutton -variable ::x -value 0 -text Radio-0 \
    -accelerator Ctrl-2
.mrb add radiobutton -variable ::x -value 1 -text Radio-1 \
    -accelerator Ctrl-3
.mrb add radiobutton -variable ::x -value 2 -text Radio-2 \
    -accelerator Ctrl-4 -state disabled

# automatic column break every fifth item.
# frame widgets in the menu
set clist {#000000 #ff0000 #00ff00 #0000ff #ff8000 #ff0080 #80ff00
    #00ff80 #8000ff #0080ff #ffff00 #ff00ff #00ffff #ffff80 #ff80ff
    #80ffff #ffffff #ff4040 #40ff40 #4040ff #404040 #808080 #ffff40
    #ff40ff #40ffff}
set col [flexmenu .col -columnbreak 5]
set count 0
foreach {c} $clist {
  set tf [frame .col.x$c \
      -background $c -relief raised -borderwidth 2 \
      -width 20 -height 20 ]
  .col add widget -widget $tf -hidemargin 1
  incr count

# scrolling menu
# frame widgets in the menu
set clist {#000000 #ff0000 #00ff00 #0000ff #ff8000 #ff0080 #80ff00
    #00ff80 #8000ff #0080ff #ffff00 #ff00ff #00ffff #ffff80 #ff80ff
    #80ffff #ffffff #ff4040 #40ff40 #4040ff #404040 #808080 #ffff40
    #ff40ff #40ffff}
set colsc [flexmenu .colsc -keepopen true -maxheight 5]
foreach {c} $clist {
  set tf [frame .colsc.bbb$c \
      -background $c -relief raised -borderwidth 2 \
      -width 20 -height 20 ]
  .colsc add widget -widget $tf -hidemargin 1

# combobox widget
flexmenu .mw 
ttk::combobox .mw.cb -values {aa bb cc dd ee ff gg} \
    -textvariable ::y -state readonly -width 5
.mw add widget -widget .mw.cb

# main cascade
flexmenu .m
.m add cascade -menu .col -label {Colors}
.m add cascade -menu .colsc -label {Scrolling Colors}
.m add cascade -menu .mcb -label {Check Buttons}
.m add cascade -menu .mrb -label {Radio Buttons}
.m add cascade -menu .mw -label {Widgets}
.m add command -label E&xit -command exit

flexmenu .mtop -type menubar
.mtop add cascade -label Test -menu .m
.mtop add command -label E&xit -command exit
pack .mtop -side top -fill x -expand 1 -anchor nw
frame .f -width 200 -height 200 
pack .f

 Example 2 : -type menuleft

# This has not been tested on windows.

package require Tk
source flexmenu.tcl

set ::lsortcmd [list ::lsort]
try {
  package require collate
  set ::lsortcmd [list ::lsort -command collate]
} on error {err res} {

variable vars

proc attachmenu { pm dir } {
  if { [catch {$pm entrycget active -menu}] } {
    set m [mkmenu $dir]
    $pm entryconfigure active -menu $m
  } else {
    set m [$pm entrycget active -menu]
    refreshmenu $m $dir

proc refreshmenu { nm dir } {
  variable vars

  set flist {}
  set tm [clock milliseconds]
  if { ! [info exists vars(cache.$dir)] ||
      ($tm - $vars(cache.ts.$dir)) > 60000 } {
    try {
      set flist [{*}$::lsortcmd [glob -directory $dir -tails *]]
    } on error {err res} {
    set vars(cache.$dir) $flist
    set vars(cache.ts.$dir) [clock milliseconds]

    $nm delete 0 end

    set count 0
    foreach {f} $flist {
      set ff [file join $dir $f]
      if { [file isdirectory $ff] } {
        $nm add cascade \
            -label $f \
            -yalign menutop \
            -precommand [list ::attachmenu $nm $ff]
      } else {
        $nm add command -label $f
      incr count
      if { $count > 20 } {

proc mkmenu { dir args } {
  variable vars

  set nm [flexmenu .m$vars(menu.counter) \
      -keepopen true \
      -padx 4p \
      -pady 4p \
      -maxheight 10 \
  refreshmenu $nm $dir
  set vars(dir.$nm) $dir
  incr vars(menu.counter)
  return $nm

proc main { } {
  variable vars

  set dir [lindex $::argv 0]
  if { $dir eq {} } {
    set dir /
  set vars(menu.counter) 1

  set mm [mkmenu $dir -type menuleft]
  $mm configure
  grid $mm -sticky news
  grid rowconfigure . 0 -weight 1
  grid columnconfigure . 0 -weight 1

  ttk::frame .f -width 400 -height 300
  grid .f -sticky se -row 0 -column 1
  lower .f
  . configure -background [ttk::style lookup . -background]
  after 100 update


 Example 3 : menubar with clock
package require Tk
source ../code/flexmenu.tcl

set clock {}

proc doclock { } {
  global clock
  set clock [clock format [clock seconds] \
      -format "%Y-%M-%d %r" -locale system]
  after 500 ::doclock

flexmenu .mtop -type menubar
.mtop add command -label E&xit -command exit
.mtop add separator
.mtop add command -label Exit -underline 0 -command exit
.mtop add command -label Help -underline 0 -command exit -gap true
ttk::style map Clock.TLabel -foreground [list disabled darkblue]
ttk::label .clock -textvariable clock -style Clock.TLabel
.mtop add widget -widget .clock -hidemargin true -state disabled
pack .mtop -side top -fill x -expand 1 -anchor nw

ttk::frame .f -width 400 -height 300
pack .f
lower .f
. configure -background [ttk::style lookup . -background]