Version 1 of TkDND Tutorial

Updated 2012-07-14 11:22:02 by GeorgePetasis

This is a tutorial about TkDND, and extension that adds drag and drop cababilities to Tk.

Loading the package

The first step is of course to load the TkDND package:

package require tkdnd

Creating a drop target

A "drop target" is a widget that can accept drops. What we must specify when creating a drop target, is a list of "types" that our drop target can accept. Lets create a simple Tk widget (in this case a ttk::button):

pack [ttk::button .drop_target -text " Drop Target (I can accept anything!) "] \
      -fill x -padx 20 -pady 20

In order to register widget ".drop_target" as a drop target, we need to call the "tkdnd::drop_target" command, which needs 3 arguments: "register" or "unregister" (according to whether we create a drop target or we delete an existing one), the path of the Tk widget to be registered/unregistered, and a list of types, the drop target will accept:

tkdnd::drop_target register .drop_target *

The type "*" is a special type, which matches all types the TkDND package can handle. Other available types are:

  • DND_Text: this type can be used for transfering text.
  • DND_Files: this type can be used for transfering a list of file/directory names, or URLs. Note that transfering file/directory names or URLs is always made as a set, thus TkDND always axpects and returns a list, even for transfering a single item (file/directory name or URL).
  • DND_Color: this type can be used for transfering colors (R G B A). Currently works only under Unix.

So, we have created a single drop target, that can accept anything, which we can try:

package require tkdnd

pack [ttk::button .drop_target -text " Drop Target (I can accept anything!) "] \
      -fill x -padx 20 -pady 20

tkdnd::drop_target register .drop_target *

We can see that we can drop from other application data on our drop target, but nothing actually happens if we perform the drop.

During the drop, the TkDND package delivered many Tk virtual events to our widget (".drop_target"), but we haven't registered or handled any of them. The virual events that will be delivered to a drop target, are:

  • <<DropEnter>>: this event will be delivered when the mouse enters the widget during a drop.
  • <<DropPosition>>: this event will be delivered when the mouse moves over the widget during a drop.
  • <<DropLeave>>: this event will be delivered when the mouse leaves the widget (signaling that a drop will not occur).
  • <<Drop>>: this event will be delivered when a drop happened over the drop target widget.

All of these events are optional, but usually we want to implement at least a binding for the <<Drop>> event, so as to get the dropped data. So, lets define a callback for this event, that simply prints the received data:

bind .drop_target <<Drop>> {puts %D}

If we test our demo script now, we can see that dropped data are print to the console/terminal. However, there is no way we can tell if the dropped data is text, or a set of file/directory names, as our drop target accepts anything. An easy way to do this, is to define a callback for one of the "specialised" virtual events, that also contain a type:

  • <<Drop:DND_Text>>: this event will be delivered only during a text drop.
  • <<Drop:DND_Files>>: this event will be delivered only during a drop of file/directory names or URLs.
  • <<Drop:DND_Color>>: this event will be delivered only during a color drop.

So, lets add some more callbacks:

package require tkdnd
catch {console show}

pack [ttk::button .drop_target -text " Drop Target (I can accept anything!) "] \
      -fill x -padx 20 -pady 20

tkdnd::drop_target register .drop_target *
bind .drop_target <<Drop>>           {puts "Generic data drop: \"%D\""}
bind .drop_target <<Drop:DND_Text>>  {puts "Dropped text:  \"%D\""}
bind .drop_target <<Drop:DND_Files>> {puts "Dropped files: \"[join %D {, }]\""}
bind .drop_target <<Drop:DND_Color>> {puts "Dropped color: \"%D\""}

TkDND provides various pieces of information through "%*" codes, which get substituted with actual values, just like Tk. "%D" means dropped data. Testing with this script, we can see that TkDND will never deliver a generic <<Drop>> event, if a more specialised binding, like <<Drop:DND_Text>>, exists.

Providing visual feedback

Although not required, usually widgets provide visual feedback when a drop is above them, specifying whether they will accept the drop, or not. In order to provide some simple feedback, we need to add callbacks to the <<DropEnter>> & <<DropLeave>> events:

bind .drop_target <<DropEnter>> {%W state  active}
bind .drop_target <<DropLeave>> {%W state !active}

But this is not enough, as during drops, <<DropLeave>> will not be delivered, so we need to reset the widget appearence in the <<Drop>> callbacks:

package require tkdnd
catch {console show}

pack [ttk::button .drop_target -text " Drop Target (I can accept anything!) "] \
      -fill x -padx 20 -pady 20

tkdnd::drop_target register .drop_target *

## Visual feedback:
bind .drop_target <<DropEnter>> {%W state  active}
bind .drop_target <<DropLeave>> {%W state !active}

## Drop callbacks:
bind .drop_target <<Drop>>           {
  puts "Generic data drop: \"%D\""
  %W state !active
}
bind .drop_target <<Drop:DND_Text>>  {
  puts "Dropped text:  \"%D\""
  %W state !active
}
bind .drop_target <<Drop:DND_Files>> {
  puts "Dropped files: \"[join %D {, }]\""
  %W state !active
}
bind .drop_target <<Drop:DND_Color>> {
  puts "Dropped color: \"%D\""
  %W state !active
}