Listbox navigation by keyboard

Richard Suchenwirth 2001-03-13 - Here's a quick sketch of running code (only does first letter, is case sensitive, etc., but may easily be extended):

 # make sure listbox can take keyboard focus 
 # (also enables cursor, PgUp/Dn etc.)
    bind $listbox <1> [list focus $listbox]
 # bind keyboard events to a handler proc
    bind $listbox <Any-Key> {listbox:match %W %A}

 proc listbox:match {w key} {
    if [regexp {[-A-Za-z0-9]} $key] {
                        set n 0   
                        foreach i [$w get 0 end] {
                                # pity I'm still on 8.0.5, else I'd say -nocase
                                if [string match $key* $i] {
                                        $w see $n
                                        $w selection clear 0 end
                                        $w selection set $n
                                break
                                } else {
                                        incr n
                                }
                        }               
                }
 }

Left-Click once on a listbox to give it focus (should get a highlighted border). Keys from the set specified in regexp make selection and view jump to the first occurence of the key.

--

Here's a version I use, which handles multiple characters. It requires something like this first:

    bind $w <KeyPress> "dialog::a_key $w.frame.list %A"

Then the following procedure handles everything -Vince.

 ## 
  # -------------------------------------------------------------------------
  # 
  # "dialog::a_key" --
  # 
  #  When the user presses a straightforward key in a dialog, this procedure
  #  is called.  If the first item in the dialog is suitable for keyboard
  #  matching (e.g. it is a popup menu or a listbox), we try to match
  #  the item the user is spelling out.
  # -------------------------------------------------------------------------
  ##
 proc dialog::a_key {w key} {
    variable string_so_far
    #puts stdout "$w $key [winfo class $w]"
    if {[lsearch -exact [list Menubutton Listbox] [winfo class $w]] == -1} {
        return
    }
    append string_so_far $key
    #puts stderr $string_so_far
    after cancel [list set dialog::string_so_far ""]
    after 1000 [list set dialog::string_so_far ""]
    switch -- [winfo class $w] {
        "Menubutton" {
            set m [$w cget -menu]
            set last [$m index end]
            for {set i 0} {$i < $last} {incr i} {
                regsub -all " " [string tolower [$m entrycget $i -label]] "" item
                if {[string first $string_so_far $item] == 0} {
                    $m invoke $i
                    return
                }
            }
        }
        "Listbox" {
            set last [$w index end]
            for {set i 0} {$i < $last} {incr i} {
                regsub -all " " [string tolower [$w get $i]] "" item
                if {[string first $string_so_far $item] == 0} {
                    set cur [$w curselection]
                    if {[llength $cur]} {
                        eval $w selection clear $cur
                    }
                    $w selection set $i
                    $w see $i
                    return
                }
            }
        }
    }

}


I did a quick and dirty version of this with bindings. It's case sensitive and only works well with a sorted listbox. Very simple to make it work with letters other than the first.

 set l .listbox
 foreach letter {a b c d e f g h i j k l m n o p q r s t u v w x y z} {
     bind $l <KeyPress-$letter> "set a \[lsearch -glob \[$l get 0 end\] $letter*] \;\
     $l selection clear 0 end \;\
     $l selection set \$a \; $l see \$a"
 }

MHo 2008-10-03: I wonder if it's possible to highlight the 'hotkey' in every listbox item. I fear the answer is no...


Tk examples - Arts and crafts of Tcl-Tk programming - Category GUI