python drag and drop explorer files to tkinter entry widget
Question: I'm fairly new to Python. I'm trying to input a file name (complete with full
path) to a TKinter entry widget. Since the path to the file name can be very
long I would like to be able to drag and drop the file directly from Windows
Explorer. In Perl I have seen the following:

    
    
    use Tk::DropSite;
    .
    .
    my $mw = new MainWindow;
    $top = $mw->Toplevel;
    $label_entry = $top->Entry(-width => '45',. -background => 'ivory2')->pack();
    $label_entry->DropSite(-dropcommand => \&drop,-droptypes => 'Win32',);
    

Is there something similar I can do using TKinter in Python?


Answer: Tk does not have any command to handle that, and Python doesn't include any
extra Tk extension to perform drag & drop inter-applications, therefore you
need an extension to perform the operation. Tkdnd (the Tk extension at
<http://sourceforge.net/projects/tkdnd>, not the Tkdnd.py module) works for
me. To use it from Python, a wrapper is required. Quickly searching for one,
it seems <http://mail.python.org/pipermail/tkinter-
discuss/2005-July/000476.html> contains such code. I did another one because I
didn't like that other one. The problem with my wrapper is that it is highly
untested, in fact I only used the function `bindtarget` and only for 10
seconds or so.

With the wrapper below, you can create some widget and announce that it
supports receiving dragged files. Here is one example:

    
    
    # The next two lines are not necessary if you installed TkDnd
    # in a proper place.
    import os
    os.environ['TKDND_LIBRARY'] = DIRECTORYTOTHETKDNDBINARY
    
    import Tkinter
    from untested_tkdnd_wrapper import TkDND
    
    root = Tkinter.Tk()
    
    dnd = TkDND(root)
    
    entry = Tkinter.Entry()
    entry.pack()
    
    def handle(event):
        event.widget.insert(0, event.data)
    
    dnd.bindtarget(entry, handle, 'text/uri-list')
    
    root.mainloop()
    

And here is the code for `untested_tkdnd_wrapper.py`:

    
    
    import os
    import Tkinter
    
    def _load_tkdnd(master):
        tkdndlib = os.environ.get('TKDND_LIBRARY')
        if tkdndlib:
            master.tk.eval('global auto_path; lappend auto_path {%s}' % tkdndlib)
        master.tk.eval('package require tkdnd')
        master._tkdnd_loaded = True
    
    
    class TkDND(object):
        def __init__(self, master):
            if not getattr(master, '_tkdnd_loaded', False):
                _load_tkdnd(master)
            self.master = master
            self.tk = master.tk
    
        # Available pre-defined values for the 'dndtype' parameter:
        #   text/plain
        #   text/plain;charset=UTF-8
        #   text/uri-list
    
        def bindtarget(self, window, callback, dndtype, event='<Drop>', priority=50):
            cmd = self._prepare_tkdnd_func(callback)
            return self.tk.call('dnd', 'bindtarget', window, dndtype, event,
                    cmd, priority)
    
        def bindtarget_query(self, window, dndtype=None, event='<Drop>'):
            return self.tk.call('dnd', 'bindtarget', window, dndtype, event)
    
        def cleartarget(self, window):
            self.tk.call('dnd', 'cleartarget', window)
    
    
        def bindsource(self, window, callback, dndtype, priority=50):
            cmd = self._prepare_tkdnd_func(callback)
            self.tk.call('dnd', 'bindsource', window, dndtype, cmd, priority)
    
        def bindsource_query(self, window, dndtype=None):
            return self.tk.call('dnd', 'bindsource', window, dndtype)
    
        def clearsource(self, window):
            self.tk.call('dnd', 'clearsource', window)
    
    
        def drag(self, window, actions=None, descriptions=None,
                cursorwin=None, callback=None):
            cmd = None
            if cursorwin is not None:
                if callback is not None:
                    cmd = self._prepare_tkdnd_func(callback)
            self.tk.call('dnd', 'drag', window, actions, descriptions,
                    cursorwin, cmd)
    
    
        _subst_format = ('%A', '%a', '%b', '%D', '%d', '%m', '%T',
                '%W', '%X', '%Y', '%x', '%y')
        _subst_format_str = " ".join(_subst_format)
    
        def _prepare_tkdnd_func(self, callback):
            funcid = self.master.register(callback, self._dndsubstitute)
            cmd = ('%s %s' % (funcid, self._subst_format_str))
            return cmd
    
        def _dndsubstitute(self, *args):
            if len(args) != len(self._subst_format):
                return args
    
            def try_int(x):
                x = str(x)
                try:
                    return int(x)
                except ValueError:
                    return x
    
            A, a, b, D, d, m, T, W, X, Y, x, y = args
    
            event = Tkinter.Event()
            event.action = A       # Current action of the drag and drop operation.
            event.action_list = a  # Action list supported by the drag source.
            event.mouse_button = b # Mouse button pressed during the drag and drop.
            event.data = D         # The data that has been dropped.
            event.descr = d        # The list of descriptions.
            event.modifier = m     # The list of modifier keyboard keys pressed.
            event.dndtype = T
            event.widget = self.master.nametowidget(W)
            event.x_root = X       # Mouse pointer x coord, relative to the root win.
            event.y_root = Y
            event.x = x            # Mouse pointer x coord, relative to the widget.
            event.y = y
    
            event.action_list = str(event.action_list).split()
            for name in ('mouse_button', 'x', 'y', 'x_root', 'y_root'):
                setattr(event, name, try_int(getattr(event, name)))
    
            return (event, )
    

Together with Tkdnd, you will find a `tkdnd.tcl` program which is a higher
level over the own C extension it provides. I didn't wrap this higher level
code, but it could be more interesting to replicate it in Python than to use
this lower level wrapper.

