PracTCL Programming Tips

This month, we inaugurate a bimonthly column for Tcl/Tk programmers. Stephen Uhler will cover some useful but perhaps poorly-known or poorly-understood features of the Tcl language and the Tk windowing toolkit.
What a Drag

For our second example, we'll use the placer to permit a user to interactively “drag and drop” a widget within a window. When the user selects a widget, by clicking on it with the mouse, we'll lift it up so it hovers over the window, and casts a shadow as we drag it around. Releasing the mouse drops the widget in its new location.

label .label -text "drag me"\
  -borderwidth 3 -relief raised
frame .shadow -bg black
lower .shadow .label
place .label -x 50 -y 50
set hover 5

We'll start by creating a widget to drag, .label, and its shadow .shadow. We'll use lower to make sure the shadow is always “below” the widget, and we'll start .label in an arbitrary location, 50 pixels from the top left corner of .. The variable hover controls how high we'll lift the widget above its window as we drag it.

bind .label <1> {
    array set data [place info .label]
    place .label -x [expr $data(-x) - $hover]\
                 -y [expr $data(-y) - $hover]
    place .shadow -in .label -x $hover -y $hover \
                  -relx 0 -rely 0 -relwidth 1 \
                  -relheight 1 -width -2 -height -2 \
                  -bordermode outside
    set Rootx [expr %X - [winfo x %W]]
    set Rooty [expr %Y - [winfo y %W]]

When we first click on .label, we need to lift it up, add its shadow, and compute where it is relative to the root window so we can figure out how to move it. The array set command (new in tcl7.4), takes name-value pairs and creates an array from them. Fortunately, the place info command happens to report the current place options in the form of name-value pairs, permitting us to access and modify individual place options using array accesses. The first place command simply moves the widget up and to the left $hover pixels as we first press the mouse. I think the second place command, which positions the shadow, uses every available place option!

The -in option, which would more accurately be described as “relative to”, causes all locations specified in .shadow to be relative to .label, instead of ., which would be the default. The -x and -y options, when added to -relx and -rely, position the shadow where .label was before we $hovered it. The -relwidth and -relheight options make .shadow the same size as .label, and then the -width and -height options make the shadow a little smaller, so it will appear farther away. Finally, the -bordermode option instructs the placer to include the border of .label when computing the sizes for -relwidth and -relheight.

Finally, we compute the location of the mouse cursor, in pixels, relative to the top left corner of the root window (Rootx, Rooty), so it will be easier to figure out how to track .label with the mouse.

bind .label <B1-Motion> {
    place .label -x [expr %X - $Rootx] \
                 -y [expr %Y - $Rooty]

As the mouse moves, we reposition the widget to follow along. Because we “placed” the shadow relative to the widget (using the -in option), it tags along all by itself.

bind .label <ButtonRelease-1> {
    array set data [place info .label]
    place .label -x [expr $data(-x) + $hover] \
                 -y [expr $data(-y) + $hover]
    place forget .shadow

When we release the mouse button, the same array set trick as before is used to “drop” the widget back on the window, then remove the shadow.

As you can hopefully see from these two simple examples, the “placer” can be a powerful tool for the dynamic placement of widgets in Tk.

Stephen Uhler is a researcher at Sun Microsystems Laboratories, where he works with John Ousterhout improving Tcl and Tk. Stephen is the author of the MGR window system and of PhoneStation, a TCL-based personal telephony environment. He may be reached via email at Stephen.Uhler@Eng.Sun.COM.