Drag and Drop Explorer Files to Tkinter Entry Widget

tkinter inter-windows drag and drop support

Just answering for the case someone else got stuck on it for weeks like I did.

You can see dist repo that pmgagne created here:
https://github.com/pmgagne/tkinterdnd2

The package is not yet published on pypi but you can build a wheel and install it following the instructions on this issue I opened: https://github.com/pmgagne/tkinterdnd2/issues/5

Hopefully, the author will build and publish it soon, so you could simply do python -m pip install tkinterdnd2 and enjoy tkinter inter-windows drag and drop support!

If you don't want to build it your self here's a wheel download: tkinterdnd2-0.3.0-py2.py3-none-any.zip
extract zip and then you can do python -m pip install tkinterdnd2-0.3.0-py2.py3-none-any.whl and then you will be able to import tkinterdnd2 in your python project.

UPDATE

the author did not respond so I forked it published it myself.
you can now install simply using

pip install tkinterdnd2

enjoy!

Drag and drop a url from a browser into a text field (tkinter or kivy)

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.

Drag and Drop in Tkinter?

Tkinter has no built-in support for dragging and dropping between applications. Tkdnd is, AFAIK, the only way to accomplish it without writing the low level code yourself.

Make Tkinter Notebook be Draggable to Another View

I don't know of any native properties for the ttk.Notebook widget but you could try Tkdnd.

There is some discussion of it here:
How to Install and Use TkDnD with Python 2.7 Tkinter on OSX?
Which also links to:
python drag and drop explorer files to tkinter entry widget

tkinter entry allows blank submissions and having to manually erase

Made some changes to the code (explanation in code comments):

import tkinter as tk

# no need to use flag because just change win to None
# and check if it is None rather than using an
# additional variable
win = None

# this would set win variable to None
def set_win_none(event=None):
global win
win = None

def entry_function(event=None):
global win
name = entry.get()

# check if the string is empty, if so
# stop the function
if not name:
return
# clear the entry
entry.delete('0', 'end')

# as mentioned win can be used, it just shortens the code
if win is None:
win = tk.Toplevel()
# set geometry as needed, this
# will place the window
# on the right of the main window
win.geometry(f'100x100+{window.winfo_x() + window.winfo_width()}+{window.winfo_y()}')
win.bind('<Destroy>', set_win_none)
tk.Label(win, text=name).pack()
else:
tk.Label(win, text=name).pack()

window = tk.Tk()

label = tk.Label(window, text="Manual:", bg="dark grey", fg="white")
entry = tk.Entry(window)
button = tk.Button(window, text="submit", command=entry_function)

# better bind the return key to the entry since it is
# directly involved
entry.bind('<Return>', entry_function)

# very rarely do you need to use .place
# better use .grid or .pack
label.grid(row=0, column=0, padx=5)
entry.grid(row=0, column=1, padx=5)
button.grid(row=0, column=2, padx=5)

window.mainloop()

There is rarely a need to use .place, better use .grid or .pack method for placing widgets, they are far more dynamic and adjust to how the window changes.

Also:

I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but have space around = if it is used for assigning a value (variable = 'some value'). Have space around operators (+-/ etc.: value = x + y(except here value += x + y)). Have two blank lines around function and class declarations.



Related Topics



Leave a reply



Submit