TkDND(n)

πŸ“¦ version 2.9.5, in fedora 43, fedora 44

✨ NAME ✨

okay bestie, welcome to TkDND(n) β€” it's the Tk Built-In Commands page for TkDND, aka the Tk Drag and Drop Interface πŸ’… this is part 1 of 3 so buckle up, we're going on a journey πŸ¦‹


🌸 SYNOPSIS 🌸

here's the whole vibe in one code block, no cap:

tkdnd::drop_target register window ?type-list?
tkdnd::drop_target unregister window

tkdnd::drag_source register window ?type-list? ?mouse-buttons?
tkdnd::drag_source unregister window

tkdnd::platform_specific_types type-list
tkdnd::platform_independent_types type-list

tkdnd::GetDropFileTempDirectory
tkdnd::SetDropFileTempDirectory directory

πŸ’– INTRODUCTION πŸ’–

so the TkDND family of commands gives you a Tcl interface to the native, platform specific drag and drop mechanism β€” like, the real one, not a knockoff ✨ here's the tea per platform:

  • on Unix, the drag & drop protocol in play is the XDND protocol version 5 β€” which is also what the Qt toolkit uses, plus the KDE and GNOME desktops πŸ‘‘
  • on Windows, it's the OLE2 drag & drop interfaces πŸͺŸ
  • on Macintosh, it's the Cocoa drag and drop interfaces 🍎

you can build the TkDND family straight from source code using the standard procedure:

configure; make; make install

Cmake is also supported, periodt. and some binary packages are available from the SourceForge download site, from ActiveState's ActiveTcl distribution, and elsewhere 🌈

with these commands you can register existing Tk widgets as drag sources or drop targets, and those widgets can send or receive data during drag and drop operations, encoded in specific types πŸ”₯ these types can be platform independent types like DND_Text and DND_Files, or platform specific types like CF_UNICODETEXT or text/plain πŸ’…

the legal forms for the TkDND commands are, bestie, as follows πŸ‘‡

  • tkdnd::drop_target register window ?type-list? β€” this command will register window as a drop target. a drop target is a widget than can accept a drop action. window must exist when this command is executed, and this command can be executed multiple times on a widget.

when window is registered as a drop target, and optional type-list can be provided. this type list can contain one or more types that window will accept during a drop action, and it can contain platform independent or platform specific types. platform independent are DND_Text for dropping text portions and DND_Files for dropping a list of files (which can contain one or multiple files) on window. if type-list is not specified, it defaults to the empty list 🫢

  • tkdnd::drop_target unregister window β€” this command will stop window from being a drop target. thus, window will stop receiving events related to drop operations. it is an error to use this command for a window that has not been registered as a drop target with tkdnd::drop_target register ⚠️
fr fr don't try to unregister something that was never registered, it will simply not slay.
  • tkdnd::drag_source register window ?type-list? ?mouse-buttons? β€” this command will register window as a drag source. a drag source is a widget than can start a drag action. window must exist when this command is executed, and this command can be executed multiple times on a widget.

when window is registered as a drag source, and optional type-list can be provided. this type list can contain one or more types that window will provide during a drag action, and it can contain platform independent or platform specific types. platform independent are DND_Text for dropping text portions and DND_Files for dropping a list of files (which can contain one or multiple files) on window. however, this type list is indicative/informative β€” window can initiate a drag action with even a different type list. if type-list is not specified, it defaults to the empty list πŸ’«

finally, mouse-buttons is one or more mouse buttons that will be used for starting the drag action. it can have any of these values:

  • "1" β€” left mouse button πŸ–±οΈ
  • "2" β€” middle mouse button (wheel) πŸ–±οΈ
  • "3" β€” right mouse button πŸ–±οΈ

if mouse-buttons is not specified, it defaults to "1" (left mouse button). multiple mouse buttons can be specified as a list of values, i.e. {1 3} 😌

  • tkdnd::drag_source unregister window β€” this command will stop window from being a drag source. thus, window will stop receiving events related to drag operations. it is an error to use this command for a window that has not been registered as a drag source with tkdnd::drag_source register πŸ’€
  • tkdnd::platform_specific_types type-list β€” this command will accept a list of types that can contain platform independent or platform specific types. a new list will be returned, where each platform independent type in type-list will be substituted by one or more platform specific types. thus, the returned list may have more elements than type-list 🌱
  • tkdnd::platform_independent_types type-list β€” this command will accept a list of types that can contain platform independent or platform specific types. a new list will be returned, where each platform specific type in type-list will be substituted by one or more platform independent types. thus, the returned list may have more elements than type-list πŸ“
  • tkdnd::GetDropFileTempDirectory β€” this command will return the temporary directory used by TkDND for storing temporary files. when the package is loaded, this temporary directory will be initialised to a proper directory according to the operating system. this default initial value can be changed to be the value of the following environmental variables: TKDND_TEMP_DIR, TEMP, TMP πŸŽ€
  • tkdnd::SetDropFileTempDirectory directory β€” this command will change the temporary directory used by TkDND for storing temporary files to directory πŸ“

πŸ¦‹ SUPPORTED TYPES πŸ¦‹

in order to declare the format that the data that will transferred during a drag and drop operation, all drag and drop protocols use the notion of types. unfortunately, each protocol defines its own, usually platform specific, types 😭 TkDND, trying to maintain portability among different platforms, offers some predefined types for some basic kinds of data, like text, and filenames.

currently, the following predefined cross-platform values are available ✨:

  • DND_Text: β€” this type can be used for transferring textual data. internally, it is translated to the following platform specific formats:
    • Windows: CF_UNICODETEXT, CF_TEXT.
    • Unix: text/plain;charset=UTF-8, text/plain.
    • Mac: NSStringPboardType.
  • DND_Files: β€” this type can be used for transferring a list of file names. internally, it is translated to the following platform specific formats:
    • Windows: CF_HDROP.
    • Unix: text/uri-list.
    • Mac: NSFilenamesPboardType.

additionally to the platform independent types, TkDND supports the following platform specific types πŸ’…:

πŸͺŸ Windows:

  • CF_UNICODETEXT: Text transfer encoded in Unicode.
  • CF_TEXT: Text transfer with application dependent encoding. If an encoding locale is specified through CF_LOCALE it is used, else the system encoding is used for the conversion.
  • FileGroupDescriptor - FileContents: These two types are used for transferring a set of files that do not appear physically on disk, like files from compressed folders or Outlook e-mail messages. File names are transferred as in the CF_TEXT type, while file contents are transferred in binary. TkDND retrieves both the file names and the file contents, and saves then in a temporary directory. When the transfer is complete, the file names of the saved files in the temporary folder are returned. Note that TkDND support this type pair only as drop targets and not as drag sources.
  • FileGroupDescriptorW - FileContents: These two types are used for transferring a set of files that do not appear physically on disk, like files from compressed folders or Outlook e-mail messages. File names are transferred as in the CF_UNICODETEXT type, while file contents are transferred in binary. TkDND retrieves both the file names and the file contents, and saves then in a temporary directory. When the transfer is complete, the file names of the saved files in the temporary folder are returned. Note that TkDND support this type pair only as drop targets and not as drag sources.
  • CF_HDROP: Files transfer encoded in UTF-8.

🐧 Unix:

okay so this section is literally just the heading β€” nothing is listed under it in the original, bestie, so there's nothing to report πŸ’€ moving on!


finally, format types used for drop types can have wildcards, following the same rules as "string match" 🌟 for example, registering a drop target with the type "*" will accept any drop, no matter what the drop format is πŸ‘‘


πŸ’œ EVENTS πŸ’œ

windows registered as either drop targets or drag sources will receive certain events during drag and drop operations πŸ“¬ as a result, the windows are expected to have bindings for some of these events. some events are mandatory (in the sense that a drag or drop operation can be stopped if the bindings do not exist), while others are not. however, it is a good practice to define bindings for all events, so as the application will behave as expected during drag and drop operations πŸ™ˆ

the TkDND package defines a set of virtual events, that correspond to various phases of a drag and drop operation. all windows that are either a drop target or a drag source are expected to have bindings for (all) these events, created with the bind Tk command πŸŽ€

while these event bindings are regular Tk events, they have a small difference from plain Tk events: most of them are expected to return a value 😳 since Tk bindings cannot return a value, TkDND does not actually generate these events (i.e. through "event generate"). instead, TkDND locates the script that has been bound to the virtual event, and directly executes it, as a script. apart from this small difference in how the events are triggered, the events required by TkDND can be viewed as regular events πŸ’«

in the following two sections all virtual events defined by the TkDND package are presented πŸ‘€

πŸ’– DROP TARGET EVENTS πŸ’–

okay so if your window is registered as a drop target, you're expected to have bindings for these virtual events, bestie:

  • <<DropEnter>> β€” this fires when the mouse enters the window during a drop action. its whole purpose is to change the visual state of the window so the user knows whether the drop will be accepted or not. the binding script is expected to return a single value that will define the drop action. this returned action can be one of copy, move, link, ask, private and refuse_drop. this event is not mandatory, but if it is defined, it has to return an action. in case an action is not returned, the drop is refused for this window. <em>no pressure tho</em> πŸ’…
  • <<DropPosition>> β€” this one fires when the mouse moves inside the window during a drop action. the point is to let the window decide if it will accept the drop and the action of the drop, if a drop is going to happen at the specific mouse coordinates. so the script binding for this event can get the mouse coordinates and the pressed modifier buttons (like ctrl, shift or alt), and is expected to return the drop action, which again must be one of copy, move, link, ask, private and refuse_drop. this event is not mandatory, but if it is defined, it has to return an action. in case an action is not returned, the drop is refused for this window. lowkey important if you want fine control ✨
  • <<DropLeave>> β€” this fires when the mouse leaves the area covered by the window, without a drop happening. the binding of this event is expected to restore the visual state of the window to normal (i.e. the visual state the window was in before the <<DropEnter>> event was triggered). the binding for this event is not expected to return a value. just put it back how you found it, bestie πŸŽ€
  • <<Drop>> β€” this is the main character event. it's triggered by a drop action, and it's expected to handle the dropped data and reset the visual state of the window. the binding script is expected to return a value, which will be the action that has been performed to the data and must be one of copy, move, link, ask, private and refuse_drop. this event is not mandatory, but if it is defined, it has to return an action. in case an action is not returned, the drop is refused for this window. this is where the magic happens πŸ”₯
  • <<Drop:type>> β€” this is a specialisation of the generic <<Drop>> event, augmented with a type. if such a binding exists and the drop type matches type, this event binding will be executed, instead of the generic <<Drop>> event binding. these events allow for easy specialisation of drop bindings, according to the type of the dropped data. type can be either a platform independent or a platform specific type. the binding script of such an event is expected to return a value, which will be the action that has been performed to the data and must be one of copy, move, link, ask, private and refuse_drop. this event is not mandatory, but if it is defined, it has to return an action. in case an action is not returned, the drop is refused for this window. so fancy, so specific πŸ’–

🌈 DRAG SOURCE EVENTS 🌈

if your window is registered as a drag source, you're expected to have bindings for these virtual events, queen:

  • <<DragInitCmd>> β€” this fires when a drag action is about to start. it's a mandatory event (and if you don't have it, the drag action gets cancelled, no cap). it's responsible for providing three things: the list of actions and format types supported by the drag source, and of course the data to be dropped. so the binding script for this event must return a list of three elements: the drop actions supported by the drag source (which can be any of copy, move, link, ask, and private), the format type list that the data can be dropped as (which can be any platform independent or platform specific type), and finally the data. a simple example of such a binding is:
bind .drag_source <<DragInitCmd>> \
     {list copy DND_Text {Hellow world!}}
  • <<DragEndCmd>> β€” this fires when the drag action has finished (whether the drop was successful or not). its main purpose is to process the dropped data according to the drop action returned by the drop target. binding for this event is not mandatory, and the binding is not expected to return a value. chill, no return needed ✨

🧚 BINDING SCRIPTS AND SUBSTITUTIONS 🧚

all bindings scripts defined for any of the virtual events above will be executed in the same interpreter that was used for creating the window the event is bound to, and the binding script will be executed at the global level (i.e. only global variables will be accessible). so no hiding in local scopes, bestie

if the binding script contains any % characters, then substitutions will be made, like in normal Tk binding scripts. valid % specifiers are:

  • %% β€” replaced with a single percent. classic escape
  • %A β€” the current action of the drag/drop operation. what's happening right now
  • %a β€” the action list supported by the drag source. all the actions it can do
  • %b β€” the mouse buttons that are pressed during a drag/drop operation. note that typically a single mouse button is reported as pressed, even if more than one mouse buttons are actually pressed. under Unix though, all pressed mouse buttons will be reported (if the XKeyboard extension is available β€” else only "1" will always be reported). so extra if you're on Unix
  • %c β€” the codes of the list of types supported by the drag source. all codes are in octal format and have the same order as the list of types obtained through the %t substitution. octal, fancy
  • %C β€” the code (in octal format) of the current type of the drag and drop operation. current type, octal style
  • %CTT β€” the list of types from the drop target type list that are common to the drag source type list. the overlap, the intersection
  • %CST β€” the list of types from the drag source type list that are common to the drop target type list. the other overlap
  • %D β€” the data that has been dropped. under some platforms the data will be available before the drop has occurred. the format of the data is the current type of the drop operation. the actual goods πŸ“¦
  • %e β€” the name of the current virtual event. one of <<DropEnter>>, <<DropPosition>>, <<DropLeave>>, <<Drop:type>>, <<Drop>>, <<DragInitCmd>>, <<DragEndCmd>>. which event is this?
  • %L β€” the list of types supported by the drag source. types, types, types
  • %m β€” the list of modifier keyboard keys that are pressed. modifier keys are some special keys, like Shift, Control or Alt. valid modifiers are "shift", "ctrl" and "alt" (under all operating systems), and "mod1" to "mod5" under Unix. please note that the usefulness of modifiers may differ across operating systems. for example, under Windows the drop target must examine the modifiers, and decide upon the drop action that must be performed (by selecting an action from the list of actions supported by the drag source). however, under Unix, the drag source is expected to decide on the drop action, not the drop target. the drop target is expected to either accept the action selected by the drag source (and return it back), or select "copy", "default", or "ask". so, under Unix examining the pressed modifier keys fulfills only informative purposes. modifier tea β˜•
  • %ST β€” the list of types supported by the drag source. again? yes, same vibe
  • %t β€” the list of types supported by the drag source. and again
  • %T β€” the current type of the drag and drop operation. current type
  • %TT β€” the list of types supported by the drop target. target's types
  • %W β€” the window that the event is delivered to. the window in question
  • %X β€” the mouse pointer x coordinate, relative to the root window. root x
  • %Y β€” the mouse pointer y coordinate, relative to the root window. root y
  • %x β€” the mouse pointer x coordinate, relative to the drop target window. target x
  • %y β€” the mouse pointer y coordinate, relative to the drop target window. target y

🎯 SPECIFYING DROP TARGETS 🎯

creating drop targets is easy, bestie: we just have to register a window as a drop target with the list of format types it can accept, and add a few bindings. for example, a window that accepts textual drops can be as follows:

label .drop_target -text {Text Drop Target!} -bg white
tkdnd::drop_target register .drop_target DND_Text
bind .drop_target <<DropEnter>> {%W configure -bg yellow; list copy}
bind .drop_target <<DropPosition>> {list copy}
bind .drop_target <<DropLeave>> {%W configure -bg white}
bind .drop_target <<Drop>> {%W configure -text %D; %W configure -bg white}

from the above bindings, none is obligatory. however, we usually want to receive dropped data (thus the <<Drop>> event must be handled) and we want to give visual feedback to the users through the <<DropEnter>> and <<DropLeave>> events. finally, <<DropPosition>> is only necessary if we want to only accept drops on specific areas of the window, or we want to change the drop action according to the pressed modifiers. so basically, do the most for the best experience ✨

now, if we want to also add the ability to receive file drops, we could add:

tkdnd::drop_target register .drop_target DND_Files
bind .drop_target <<Drop:DND_Files>> \
   {puts %D; %W configure -bg white}

note that we have added a "specialised" drop binding, for the event <<Drop:DND_Files>>: this means that when a text portion is dropped over the window, the <<Drop>> event binding will be executed. but when a list of files is dropped onto the window, the <<Drop:DND_Files>> event binding will be executed. if we proceed and define a binding for the <<Drop:DND_Text>> event, the binding of the "general" <<Drop>> event will never be executed. so specific, so extra πŸ’…


πŸš€ SPECIFYING DRAG SOURCES πŸš€

in order to specify a drag source, we need to register a window as a drag source:

tkdnd::drag_source register .text_drag_source

the above command defines a drag source with an empty type list (and which will be declared in the <<DragInitCmd>> event binding) and arranges mouse bindings such as a drag will be started with the left mouse button. then, it is absolutely necessary to define a binding for the <<DragInitCmd>>: this event binding must return the list of actions, the list of format types and the actual data to be dropped:

bind .text_drag_source <<DragInitCmd>> \
     {list {copy move} DND_Text {Hello from Tk!}}

please note that all specified format types must be compatible to each other, as they all characterise the same data. consistency is key, bestie πŸ‘‘

✨ SPECIFYING DRAG SOURCES WITH MULTIPLE DATA TYPES ✨

okay so bestie, plot twist πŸŽ€ β€” sometimes your drag source isn't serving just one vibe. Sometimes it wants to send a whole little set of data types that don't even match each other (incompatible, like, they don't text back compatible πŸ’€). And when that happens, the result of your <<DragInitCmd>> event binding has to switch up a tiny bit, because now it's returning two items instead of the three we described in the previous paragraph. ngl, it's giving less work, not more.

The first element is, once again, a list of the allowable actions. Same as before, no glow-up there. But the second item? That one's a list of "format type" and "data" pairs β€” like little buddy-system duos 🫢.

bind .text_drag_source <<DragInitCmd>> \
     {list {copy move} {DND_Text {Hello from Tk!} DND_Files {/tmp}}}

And that was all! 😌✨ iconic, honestly. that's the whole thing.


πŸ’€ BUGS πŸ’€

okay so the one (1) bug note, and it's not even that scandalous, it's just a compatibility tea β˜•:

  • OS X: TkDND under OS X is built on top of the Cocoa framework. Which means it is compatible with Tk version >= 8.6. periodt. if you're on something older than 8.6 on OS X, this is not the moment for you πŸ™ˆ

πŸ”‘ KEYWORDS πŸ”‘

  • dnd
  • drag and drop

and that's a wrap on the whole TkDND(n) bestie!! πŸŽ€βœ¨ you now know how to drag, how to drop, how to target, how to source, how to flex multiple data types at once, and where the one OS X caveat lives. slay πŸ’…

this page comes to you from Tk (version 8.4), and it's dated the same β€” so yeah, it's been around a minute, but it still eats πŸ”₯. mwah, go forth and drag responsibly πŸ¦‹

πŸ“¦ where this page comes from