This is a tutorial about TkDND, and extension that adds drag and drop cababilities to Tk.
The first step is of course to load the TkDND package:
package require tkdnd
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:
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:
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:
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.
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 }