This is a tutorial about TkDND, and extension that adds drag and drop capabilities 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 transferring text. * DND_Files: this type can be used for transferring a list of file/directory names, or URLs. Note that transferring file/directory names or URLs is always made as a set, thus TkDND always expects and returns a list, even for transferring a single item (file/directory name or URL). * DND_Color: this type can be used for transferring 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 virtual events that will be delivered to a drop target, are: * <>: this event will be delivered when the mouse enters the widget during a drop. * <>: this event will be delivered when the mouse moves over the widget during a drop. * <>: this event will be delivered when the mouse leaves the widget (signaling that a drop will not occur). * <>: 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 <> 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 <> {puts %D; return default} ====== 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: * <>: this event will be delivered only during a text drop. * <>: this event will be delivered only during a drop of file/directory names or URLs. * <>: 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 <> {puts "Generic data drop: \"%D\""; return default} bind .drop_target <> {puts "Dropped text: \"%D\""; return default} bind .drop_target <> {puts "Dropped files: \"[join %D {, }]\""; return default} bind .drop_target <> {puts "Dropped color: \"%D\""; return default} ====== 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 <> event, if a more specialised binding, like <>, 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 <> & <> events: ====== bind .drop_target <> {%W state active} bind .drop_target <> {%W state !active} ====== But this is not enough, as during drops, <> will not be delivered, so we need to reset the widget appearance in the <> 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 <> {%W state active} bind .drop_target <> {%W state !active} ## Drop callbacks: bind .drop_target <> { puts "Generic data drop: \"%D\"" %W state !active return default } bind .drop_target <> { puts "Dropped text: \"%D\"" %W state !active } bind .drop_target <> { puts "Dropped files: \"[join %D {, }]\"" %W state !active return default } bind .drop_target <> { puts "Dropped color: \"%D\"" %W state !active return default } ====== *** Accepting only specific types *** Our example so far accepts everything, as we have specified the type "*" when we register our drop target. We can specify a list of types, if we want to accept specific types: ====== ## Accept only text: tkdnd::drop_target register .drop_target1 DND_Text ## Accept only text & files: tkdnd::drop_target register .drop_target2 {DND_Text DND_Files} ====== *** Actions *** During a drop and drop operation there are two types of negotiations that take place between drag sources and drop targets. One is the negotiations about types, so as the types offered by the drag source match those accepted by the drop target (else no drop can occur). But besides types, there is also a negotiation about actions. Actions specify what will happen to the data after it is dropped. It can be copied, or it can be moved, meaning that the drag source should delete the dropped data. The actions supported by TkDND are: * copy: the data will be copied. * move: the data will be moved. * link: the data will be linked. * ask: a dialog will be displayed to the user, in order to select an action (not currently implemented). * private: a private action will be performed by the drop target. * refuse_drop: a drop cannot occur. * default: select the preferred action of the drag source. In general, it is required by TkDND that all callbacks must return an action, despite the fact that "reasonable" assumptions will be made by TkDND if the callback does not return an action. It is important that callbacks registered to <> and <> events must return an action (as the default is "refuse_drop"). The action return must be one of the actions supported by the drag source. This action list can be retrieved through "%a", and the currently agreed action with "%A". It is common practice that the action must be changed according to the keys pressed. For example, under Windows a pressed "Shift" key suggest a move actions, and a pressed "Alt" key suggests a link action. The pressed keys can be retrieved with "%b". In the following example, we show how this behavior can be implemented: ====== proc handle_position {widget mouse_x mouse_y drag_source_actions buttons} { if {"alt" in $buttons && "link" in $drag_source_actions} { return link } elseif {"ctrl" in $buttons && "move" in $drag_source_actions} { return move } elseif {"copy" in $drag_source_actions} { return copy } else { return refuse_drop } };# handle_position bind .drop_target <> [list handle_position %W %X %Y %a %b] ====== *** The action refuse_drop *** The action "refuse_drop" is quite handy if we want to refuse drops to specific parts of the widget, which can be found from the mouse coordinates %X and %Y during <> events (which are in root coordinates). Suppose we wanted drops to be allowed only on the left half of the widget: ====== proc handle_position {widget mouse_x mouse_y drag_source_actions buttons} { ## Limit drops to the left half part of the window... set x [winfo rootx $widget] set w [winfo width $widget] set middle [expr {$x + $w / 2.}] if {$mouse_x > $middle} {return refuse_drop} if {"alt" in $buttons && "link" in $drag_source_actions} { return link } elseif {"ctrl" in $buttons && "move" in $drag_source_actions} { return move } elseif {"copy" in $drag_source_actions} { return copy } else { return refuse_drop } };# handle_position bind .drop_target <> [list handle_position %W %X %Y %a %b] ====== *** The complete example *** ====== 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 <> {%W state active} bind .drop_target <> {%W state !active} ## Position events: proc handle_position {widget mouse_x mouse_y drag_source_actions buttons} { ## Limit drops to the left half part of the window... set x [winfo rootx $widget] set w [winfo width $widget] set middle [expr {$x + $w / 2.}] if {$mouse_x > $middle} {return refuse_drop} if {"alt" in $buttons && "link" in $drag_source_actions} { return link } elseif {"ctrl" in $buttons && "move" in $drag_source_actions} { return move } elseif {"copy" in $drag_source_actions} { return copy } else { return refuse_drop } };# handle_position bind .drop_target <> [list handle_position %W %X %Y %a %b] ## Drop callbacks: bind .drop_target <> { puts "Generic data drop: \"%D\"" %W state !active return default } bind .drop_target <> { puts "Dropped text: \"%D\"" %W state !active return default } bind .drop_target <> { puts "Dropped files: \"[join %D {, }]\"" %W state !active return default } bind .drop_target <> { puts "Dropped color: \"%D\"" %W state !active return default } ====== ** Creating a drag source ** A "drag source" is a widget which can initiate a drag and drop operation. <> Tutorial | GUI | Example