Bindings and variable substitution

aricb Newcomers to Tcl/Tk frequently puzzle over the issue of variable substitution in conjunction with [bind] and the -command option of various widgets. The problem is that sometimes you want a variable to be substituted when a binding is declared, and other times you want the variable to be substituted each time a binding fires. This changes how you write the binding!

Example: Binding the current, define-time value

Imagine you have a loop that creates a bunch of similar widgets, and you want to bind all of them to the same command, but you want the command to distinguish which widget triggered it, so you use the loop variable as an identifier. You want that loop variable substituted when the binding is created, rather than when the binding fires. In this case, don't use curly braces to define the binding. Instead, build your binding script with the [list] command:

    for {set i 1} {$i < 5} {incr i} {
        set label [label .l$i -text $i]
        grid $label
        bind $label <Button-1> [list labelclick $i]
    }

    proc labelclick {num} {
        puts "you clicked on label $num"
    }

Example: Binding the call-time value

Now imagine that you are writing a fancy text editor. You want the editor to save the current file using the current filename when the user presses <Ctrl-s>. Filenames can change, so you don't want to hard-wire the filename into your script. Instead, you rely on a global variable to contain the filename, and you refer to that variable in your binding, which you put in curly braces to prevent substitution at the time the binding is created:

    set text [text .text]
    grid $text
    bind $text <Control-s> {saveFile %W $filename}

    proc saveFile {window filename} {
        set file [open $filename w]
        puts $file [$window get 1.0 end]
        close $file
    }

    set filename [file join [pwd] temp.txt]

Of course, if you are really writing an editor, you'll need a lot more code than that (for one thing, you'll need to ensure that $filename exists at all times). In fact, you will probably want to take an entirely different approach, but for present purposes, hopefully you understand how and why the example works.

General Comments

A lot of issues with substitution and binding can be solved by taking advantage of binding substitutions, which are enumerated on the bind man page [L1 ]. Many other issues can be resolved by reducing your binding script to a procedure call and letting the procedure take care of the ugly stuff, as Bryan Oakley explains at [L2 ]. See also [L3 ] for more on when to use curly braces.

Also, most of the time you just use the first, define-time bindings in your scripts. Why? Because you can always use [global] inside a procedure to access the other kind; it is even easier if you do it that way. If your callback is complex enough to need anything more than a single command, it is best done with a helper procedure. It's less brain-bending and easier to test.