Introduction to bindtags

When you read the tcl manpage on bind [1] you will notice that the synopsis of the command doesn't mention windows or widgets, but rather tags (also known as bindtags or binding tags.

bind tag ?sequence? ?+??script?

Bindings are associated with tags, and tags are associated with widgets. This bit of indirection makes for a very powerful and flexible way to associate bindings with a specific widget or any logical grouping of widgets.

For example, most default bindings are associated with a tag named after the widget class so that all widgets of the same type share the same behavior. In addition, there are also bindtags associated with the logical grouping of all widgets inside a toplevel (e.g. ".") and all widgets (bindtag of "all").

Why is this important? In many cases you can go a long way with Tk without ever needing to know about bindtags. You can associate bindings with widgets, and since the default bindtags for a widget include a tag that is the same as the widget name it all just works. The importance of bindtags comes into play when you want to either apply the same binding to a logical group of widgets, or have your bindings override or work in conjuction with existing bindings, or even if you want to completely disable the default bindings altogether.

You may not ever need to do any of those things. If you do, however, you need to know a little bit about how bindtags work

Looking under the hood

You can use the bindtags command [2] to set or view the bindtags of any widget. For example, here is what you get when you view the bindtags for a few different widgets:

$ wish
% frame .f
.f
% bindtags .f
.f Frame . all
% canvas .c
.c
% bindtags .c
.c Canvas . all
% button .b
.b
% bindtags .b
.b Button . all

Notice that the default bindtags are ordered from most specific (the widget itself) to least specific (all widgets). This order is important because it is the order in which the bindings are processed when an event is received by a widget.

To confirm the relationship between the bindtags and the order that they are processed, let's create a new toplevel and a frame within that toplevel to see how it affects the bindtags. We'll assign the frame a custom class* as well, to illustrate the effect that has on the bindtags:

* The Frame and Toplevel widgets are the only widgets as of tcl 8.4 which can accept a ‑class argument

$ wish
% toplevel .t
.t
% frame .t.x -class "XFrame"
.t.x
% bindtags .t.x
.t.x XFrame .t all

Notice the difference between the bindtags for this frame and the frame in the first example. In this example the bindtag "XFrame" takes the place of "Frame" for the class and ".t" takes the place of "." in the bindtag representing the toplevel widget.

Experimenting with bindings

Because frames don't typically have any bindings associated with them they make a good place to experiment with bindings since we don't have to worry that default behaviors will get in our way.

First we'll need a frame that we can click on; to make the frame easy to see we'll give it an explicit size and borderwidth, though they have no bearing on the behavior of the bindings. Once we have the frame we need to assign bindings to each of the default bindtags:

% frame .f -width 50 -height 50 -borderwidth 1 -relief raised
.f
% bindtags .f
.f Frame . all
% pack .f
% bind .      <ButtonPress-1> {puts "bindtag: ."}
% bind all    <ButtonPress-1> {puts "bindtag: all"}
% bind Frame  <ButtonPress-1> {puts "bindtag: Frame"}
% bind .f     <ButtonPress-1> {puts "bindtag: .f"}

After entering the above code in a wish console, click on the frame and you will see the following output:

bindtag: .f
bindtag: Frame
bindtag: .
bindtag: all

As you can see, the the bindings fire in the order of the bindtags for the widget. If we change the order of the bindtags, we change the order that the event is processed:

% bindtags .f [list all . Frame .f]

Again, after entering the above code in the wish console, click on the frame and you will see the following output. Notice that the order in which the bindings are handled exactly mirror the relative order of the bindtags:

bindtag: all
bindtag: .
bindtag: Frame
bindtag: .f

To illustrate that bindtags can be any arbitrary string, let's add a custom bindtag after the class bindtag:

% bindtags .f [list .f Frame MyTag . all]
% bind MyTag <ButtonPress-1> {puts "bindtag: MyTag"}

The output from the above should look like this:

bindtag: .f
bindtag: Frame
bindtag: MyTag
bindtag: .
bindtag: all

Default bindings

As I stated earlier, bindtags are used for the default behavior of all widgets. While it may seem that default bindings for a widget are somehow "built in" to the widget itself, they are simply attached to one of the default bindtags. Mostly these bindings are associated with a tag named after the widget class, though a few are associated with the tag "all".

To see the bindings for a bindtag we can use the bind command. Like many tk commands, it can be used both for setting values and well as for introspection. The following example shows what events are bound to the tag Button, which is one of the default bindtags for button widgets:

% bind Button
<ButtonRelease-1> <Button-1> <Leave> <Enter> <Key-space>

Using the same technique we can see what bindings are associated with the tag "all":

% bind all
<<PrevWindow>> <Key-Tab> <Key-F10> <Alt-Key>

Practical uses

Most of the time I use bindtags it is to solve one of two problems: to change the behavior of a widget, or to change the order in which events are handled.

Changing the behavior of a widget

Suppose, for example, you are creating a text editor and want to let the user choose bindings that emulates another well-known editor that they may already be familiar with. You may want to give them the choice to have the text widget work more like emacs, more like vi, or more like bbedit. Additionally, you may want to give them the ability to create all their own keyboard shortcuts.

In such a scenario you could associate vi-like bindings with a tag named "Vi", emacs-like bindings to a tag named "Emacs", and let the user specify bindings for a tag named "Custom". When the user wants to switch to a different set you can simply change the bindtags (e.g. ".editor Text . all", ".editor Vi . all", ".editor Emacs . all", ".editor Custom . all", etc.) Although you could write code to completely change all the bindings to the widget itself, it's easier to maintain complete sets of bindings and just swap one bindtag for another.

Changing the order of binding execution

Another situation where bindtags are useful is to make sure that an event is handled in a specific order. For example, by default bindings on widgets occur before the class bindings since, as we've seen, the class bindtag follows the widget-specific bindtag.

A classic example is where you want to create a binding on a mouse click for a listbox, and in that binding you want to grab the selection*. Since the selection is set by the class binding and the class binding is executed after the widget specific binding, when you query the listbox for the current selection you'll always get whatever was selected before the click.

Hopefully the solution is now clear: you can add a bindtag after the class binding (e.g. ".listbox Listbox PostListbox . all") and add your binding to that tag. Your binding is then guaranteed to be executed after the class binding, and after the selection has been set.

* In modern versions of Tk you can bind to <<ListboxSelect>> [3] if all you want is the selection, and avoid the bindtag issue altogether. The technique can apply to any widget, however. For example, you may want to add a binding to <Any-KeyPress> for an entry widget. Unless you arrange for that binding to fire after the class binding you'll discover that when your binding fires the entry widget has not yet been updated to include the character the user just typed.

Summary

Bindtags are a remarkably flexible mechanism for associating bindings with widgets. As you can see, a widget can have multiple bindtags, and the order determines the order in which bindings on those bindtags are called when a widget receives an event. The default ordering of bindtags is the widget name, the widget class, the name of the toplevel in which the widget exists, and the special tag "all".

You can write very large GUI applications with Tk and never need to manipulate the bindtags. Once in a while, however, you may find a situation where you need to do something a little out of the ordinary, and knowing how to manipulate the bindtags may save you a lot of headaches.

For more on bindtags, read the man page [2] and see the Tcler's Wiki page on bindtags [4]

References

  1. http://www.tcl.tk/man/tcl8.4/TkCmd/bind.htm
  2. http://www.tcl.tk/man/tcl8.4/TkCmd/bindtag.htm
  3. http://www.tcl.tk/man/tcl8.4/TkCmd/listbox.htm#M60
  4. http://mini.net/tcl/bindtags