0

So I'm trying to drag the URL from a browser (chrome) into a text box.

Here is the image of the Tkinter Text box: Tkinter Text box image

It is the same way you can drag the URL into Word and it 'pastes' into the text area.

Picture for example:

print screen.

This is possible in wxPython with wx.TextDropTarget but how would it be done in Tkinter?

Is there a way to do this in Tkinter?

kit
  • 1,166
  • 5
  • 16
  • 23
Daniel Law
  • 23
  • 5
  • post what you have done so far, it possible – AD WAN Nov 28 '18 at 09:05
  • i havent done anything yet, i can drop files in and it gets the path but i cant find how to do it with dragged text from another application, all searches just yield DND widgets which isn't what im after – Daniel Law Nov 28 '18 at 09:09
  • Welcome to SO, please review [this article](https://stackoverflow.com/help/how-to-ask) and [this article](https://stackoverflow.com/help/mcve) and [edit](https://stackoverflow.com/posts/53515640/edit) your question. As it stands this question is far too broad to produce a meaningful answer. – Ethan Field Nov 28 '18 at 09:58
  • Ok, you've added a screenshot of a blank tkinter window and a URL. But what have you tried? What have you researched? What ideas do you have? Are you expecting us to write this for you? – Ethan Field Nov 28 '18 at 10:31
  • im asking coz the research came up with nothing, not write this for me but either point me to workable examples or share knowledge? its not something groundbreakingly new it should exist i just cannot find anything on it. – Daniel Law Nov 28 '18 at 10:36
  • @DanielLaw As stated in [this article](https://stackoverflow.com/help/on-topic) "Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.". Your research must have shown something, even if it wasn't an exact answer. So show us your attempts and your research and we will assist to the best of our ability. – Ethan Field Nov 28 '18 at 10:52

1 Answers1

0

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.

Community
  • 1
  • 1
James Kent
  • 5,763
  • 26
  • 50
  • Thank you this worked brilliantly, very helpful! shame there's no native support for this though in tkinter, it is something i would have thought a standard option for text box widgets. – Daniel Law Nov 28 '18 at 21:16
  • @DanielLaw no problem, I think part of the issue is because of the cross platform nature of the tk toolkit, and the vast difference between possible data depending on where it came from, for example rich text format, html, plain text, urls, files and that just things you expect a text output from, I also still haven't been able to get drag from a browser to work or from a tk text box to anything else either – James Kent Nov 28 '18 at 21:21