Updated 2015-02-19 10:29:13 by PeterLewerin

Peter Lewerin (content disclaimer) (2014-02-19)

Many years ago on the page A User's Guide to Tcl/Tk someone mentioned the steps one would take using bindtags and bind to find out the bindings that apply to a window / widget and what action a particular binding indicates. LV asked for a tool that would automate investigating bindings, and I put together a basic browser as a suggestion. It was a bit of a kludge, with everything in global scope and so on, but it seemed to work. Today I decided to rewrite it and put it on a page of its own. My snit is quite rusty from being away from Tcl for so many years, so I may have made some mistakes. It does work however, and now everything is well-structured and encapsulated.

Using the browser

If you have a window / widget that you want to look into the bindings for, say a button (as in the original example):
pack [button .b -text {Example widget}]

Then you can instantiate a bindingdisplay Snit widget like this:
pack [bindingdisplay .bd -widget .b]

(In this case, the button widget and the bindingdisplay widget live side by side on ., but they do not have to, the bindingdisplay widget could have its own toplevel for instance.)

This gives you a set of two scrolled listboxes (one listing bindtags, and one listing bindings) and a text label. The first listbox will have the items .b, Button, ., and all. Selecting Button fills the second listbox with six bindings, from <ButtonRelease-1> to <Key-space>, that apply to the button via the Button class. Selecting <Leave> sets the text label to display the text "tk::ButtonLeave %W".

The bindingdisplay Snit widget

You will of course have required the Snit package:
package require snit

This is how the bindingdisplay Snit widget is defined. It uses regular and tiled widgets, and another Snit widget that creates scrolled listboxes, which is defined under the next heading. It pre-fills the first listbox with bindtags information for the widget in question. It contains the methods to propagate the user's selections from bindtags to bindings to actions, and sets up the listboxes to perform them when a selection is made.
snit::widget bindingdisplay {
    option -widget {}

    component bindtags
    component bindings
    component actionframe
    component actionlabel

    delegate method setaction to actionlabel as {configure -text}

    constructor args {
        install bindtags using lister $win.c -text Bindtags
        install bindings using lister $win.b -text Bindings
        install actionframe using ttk::labelframe $win.f -text Action
        install actionlabel using ttk::label $win.f.a

        $self configurelist $args

        pack $actionlabel

        grid $bindtags $bindings
        grid $actionframe - -sticky w

        $bindtags fill {*}[bindtags [$self cget -widget]]
        $bindtags onselect [mymethod bindtagsToBindings]
        $bindings onselect [mymethod bindingsToAction]

    method bindtagsToBindings {} {
        $self setaction {}
        $bindings fill {*}[bind [$bindtags get]]

    method bindingsToAction {} {
        $self setaction [bind [$bindtags get] [$bindings get]]

The lister Snit widget

This is a very basic scrolled listbox. It knows how to replace its contents with a new list of items, how to register actions to perform when an item is selected, and how to retrieve the latest selection made.
snit::widget lister {
    hulltype ttk::labelframe
    component listbox
    component scrollbar
    variable selection {}

    delegate option -text to hull

    constructor args {
        install listbox using tk::listbox $win.lbx -selectmode single -yscrollcommand [list $win.scr set]
        install scrollbar using ttk::scrollbar $win.scr -command [list $win.lbx yview]

        pack $listbox $scrollbar -side left -fill y

        $self configurelist $args

    method fill args {
        $listbox delete 0 end
        $listbox insert end {*}$args

    method onselect cmd {
        bind $listbox <<ListboxSelect>> $cmd

    method get {} {
        if {[$listbox curselection] ne {}} {
            set selection [$listbox get [$listbox curselection]]
        set selection

The original code

This was the code I posted back then. There are a lot of mistakes in it: I wrote it in a hurry. For one thing, it refers to the returned items from bindtags as "classes", even though only (in this case) Button is actually a class.
package require Tk

set w [button .b]

frame .classes
label .classes.label -text {Classes}
listbox .classes.box -selectmode single -yscrollcommand {.classes.scroller set}
scrollbar .classes.scroller -command {.classes.box yview}
pack .classes.label
pack .classes.box .classes.scroller -side left -fill y

frame .bindings
label .bindings.label -text {Bindings}
listbox .bindings.box -selectmode single -yscrollcommand {.bindings.scroller set}
scrollbar .bindings.scroller -command {.bindings.box yview}
pack .bindings.label
pack .bindings.box .bindings.scroller -side left -fill y

label .label -text {Action}
label .action -textvariable action

grid $w                 -sticky w
grid .classes .bindings
grid .label   x         -sticky w
grid .action  -         -sticky w

eval .classes.box insert end [bindtags $w]

bind .classes.box <<ListboxSelect>> {
    set ::class [.classes.box get [.classes.box curselection]]
    .bindings.box delete 0 end
    eval .bindings.box insert end [bind $::class]

bind .bindings.box <<ListboxSelect>> {
    set ::action [string trim [bind $::class [.bindings.box get [.bindings.box curselection]]]]