29

我对 Python 还很陌生。我正在尝试将文件名(包含完整路径)输入到 TKinter 条目小部件。由于文件名的路径可能很长,我希望能够直接从 Windows 资源管理器拖放文件。在 Perl 中,我看到了以下内容:

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',);

我可以在 Python 中使用 TKinter 做类似的事情吗?

4

3 回答 3

23

Tk 没有任何命令来处理它,并且 Python 不包含任何额外的 Tk 扩展来执行拖放间应用程序,因此您需要一个扩展来执行该操作。Tkdnd(http://sourceforge.net/projects/tkdnd上的 Tk 扩展,不是 Tkdnd.py 模块)对我有用。要从 Python 中使用它,需要一个包装器。赶紧找了一个,好像http://mail.python.org/pipermail/tkinter-discuss/2005-July/000476.html包含这样的代码。我又做了一个,因为我不喜欢另一个。我的包装器的问题是它未经测试,实际上我只使用了该功能bindtarget并且只使用了 10 秒左右。

使用下面的包装器,您可以创建一些小部件并宣布它支持接收拖动的文件。这是一个例子:

# 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()

这是代码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, )

与 Tkdnd 一起,您会发现一个tkdnd.tcl比它提供的自己的 C 扩展更高级别的程序。我没有包装这个更高级别的代码,但是在 Python 中复制它可能比使用这个较低级别的包装器更有趣。

于 2013-01-11T17:24:41.577 回答
19

自从这个问题最初发布以来,事情已经取得了进展,并且可以在 SourceForge.net 上轻松获得 tkdnd2.8(Tcl 扩展)和 TkinterDnD2(tkdnd2.8 的 Python 包装器)。

这是最小的示例代码,可以完全按照您的要求进行操作。

import Tkinter
from TkinterDnD2 import *

def drop(event):
    entry_sv.set(event.data)

root = TkinterDnD.Tk()
entry_sv = Tkinter.StringVar()
entry = Tkinter.Entry(root, textvar=entry_sv, width=80)
entry.pack(fill=Tkinter.X)
entry.drop_target_register(DND_FILES)
entry.dnd_bind('<<Drop>>', drop)
root.mainloop()

您可以查看如何在 OSX 上通过 Python 2.7 Tkinter 安装和使用 TkDnD,以获取 Python 2.7 和 3.6 上 Windows 和 Mac 的下载和安装信息。

于 2017-10-21T16:11:32.173 回答
1

您现在可以简单地使用tkinterdnd2。我已经分叉了它,构建了它,并将它上传到 pypi,这样你就可以简单地pip install tkinterdnd2. 这里的用法示例

或者如果你太懒,这里有一个简单的例子:

import tkinter as tk

from tkinterdnd2 import DND_FILES, TkinterDnD

root = TkinterDnD.Tk()  # notice - use this instead of tk.Tk()

lb = tk.Listbox(root)
lb.insert(1, "drag files to here")

# register the listbox as a drop target
lb.drop_target_register(DND_FILES)
lb.dnd_bind('<<Drop>>', lambda e: lb.insert(tk.END, e.data))

lb.pack()
root.mainloop()

这将打开这个 在此处输入图像描述 你只需拖动文件

(顺便说一句,我使用了列表框而不是条目,但它的工作原理完全相同)

于 2021-12-07T09:55:33.157 回答