so you need to jump through a few hoops, but what you want is possible.
using the information in this answer I've been able to get this working on windows 10 64 bit with 64 bit python 3.7.1
firstly I downloaded the relevant binaries from here which in my case was tkdnd2.8-win32-x86_64.tar.gz
I extracted this to get a folder called tkdnd2.8 which I put into the following location:
C:\Program Files\Python37\tcl
this means it can easily be found by the tk interpreter, you can put it elsewhere but you need to add the path to the environment variables in order for it to be found.
then I used the wrapper from that answer (but updated to python 3 names for tk):
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, )
then a simple test script:
import tkinter
from TkDND2 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/plain')
root.mainloop()
note that I called the wrapper TkDND2 so as not to get confused with the built in dnd.
by changing the last argument of bindtarget
you can change the type of input accepted, with text/plain
most textual input should be accepted from any program, with text/uri-list
I was only able to drag files from explorer into text entry and get the path of that file, URLs from IE did not work.
also note that the files for download on sourceforge are quite outdated but the only place I could fine to download a pre built binary distribution.
EDIT:
I wasn't happy with the wrapper I found elsewhere so I modified it so that it patches the Widget
class that everything in tkinter inherits from when its imported, this way you can use for example entry.bindtarget(callback, type)
instead.
import os
import tkinter
__all__ = ['Text', 'Files', 'URL', 'HTML', 'RTF']
# the types of drop supported content
Text = 'DND_Text'
Files = 'DND_Files'
URL = 'DND_URL'
HTML = 'DND_HTML'
RTF = 'DND_RTF'
def drop_entry(event):
event.widget.insert(0, event.data)
def drop_text(event):
event.widget.insert(1.0, event.data)
def _load_tkdnd(master):
if not getattr(master, '_tkdnd_loaded', False):
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
def bindtarget(self, callback=None, dndtype=Text, event='<Drop>', priority=50):
if callback == None:
classnames = [x.__name__ for x in self.__class__.__bases__]
classnames.append(self.__class__.__name__)
print(classnames)
if 'Entry' in classnames:
callback = drop_entry
elif 'Text' in classnames:
callback = drop_text
else:
raise ValueError('No default callback')
_load_tkdnd(self)
cmd = self._prepare_tkdnd_func(callback)
return self.tk.call('dnd', 'bindtarget', self, dndtype, event, cmd, priority)
def bindtarget_query(self, dndtype=None, event='<Drop>'):
_load_tkdnd(self)
return self.tk.call('dnd', 'bindtarget', self, dndtype, event)
def cleartarget(self):
_load_tkdnd(self)
return self.tk.call('dnd', 'cleartarget', self)
def bindsource(self, callback, dndtype, priority=50):
_load_tkdnd(self)
cmd = self._prepare_tkdnd_func(callback)
return self.tk.call('dnd', 'bindsource', self, dndtype, cmd, priority)
def bindsource_query(self, dndtype=None):
_load_tkdnd(self)
return self.tk.call('dnd', 'bindsource', self, dndtype)
def clearsource(self):
return self.tk.call('dnd', 'clearsource', self)
def drag(self, 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)
return self.tk.call('dnd', 'drag', self, 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, _subst_format_str))
return cmd
def _dndsubstitute(self, *args):
if len(args) != len(_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 # The widget that recieved the event
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, )
tkinter.Widget.bindtarget = bindtarget
tkinter.Widget.bindtarget_query = bindtarget_query
tkinter.Widget.cleartarget = cleartarget
tkinter.Widget.bindsource = bindsource
tkinter.Widget.bindsource_query = bindsource_query
tkinter.Widget.clearsource = clearsource
tkinter.Widget.drag = drag
tkinter.Widget._prepare_tkdnd_func = _prepare_tkdnd_func
tkinter.Widget._dndsubstitute = _dndsubstitute
example use:
import tkinter
import TkDND3
from tkinter.scrolledtext import ScrolledText
root = tkinter.Tk()
entry = tkinter.Entry(root)
entry.pack(fill='both', expand=True)
text = tkinter.Text(root)
text.pack(fill='both', expand=True)
entry.bindtarget()
text.bindtarget()
stext = ScrolledText(root)
stext.pack(fill='both', expand=True)
stext.bindtarget()
root.mainloop()
this also added some default callbacks for simple widgets (entry, text and descendants) so they could be called without arguments as the example shows.