Python GUI(Tkinter)通过TkDND和TkinterDnD2实现文件拖放

简介

环境:Win10 x64
python 3.9
Tkinter是py3自带的库,可以实现GUI,但是要实现拖放就需要TkDND和TkinterDnD2了
TkinterDnD2的官方上,有说明在使用前要先安装tkDND2

If you want to use TkinterDnD2 you will of course have to install tkDnD2 first. Windows binaries as well as the sources can be found here.

下载资源

由于总所周知的原因,国外服务器经常多灾多难下载慢的一批,so把下载好的资源放在这里
链接: https://pan.baidu.com/s/1Q76uLiyYbFuc_tNtbCWFqQ
提取码: kbcb
在这里插入图片描述

TkDND

  • TkDND下载
    ix86是32位,x86_64是64位的,不知道自己几位的:右键我的电脑 - 属性
    下错了据说无法运行

在这里插入图片描述

TkinterDnD2

安装

tkdnd2.8

tkdnd2.8-win32-xxx.tar.gz 里面是个 tkdnd2.8 文件夹, 该文件夹应该复制到 pythton/tcl/tcl8.6/ 路径, 或者/Anaconda2/envs/python35/tcl/tclx.x/ 下面,下面我下载不到2.8x64的。。就下载了2.9试验版的

在这里插入图片描述
在这里插入图片描述

TkinterDnD2

TkinterDnD2(不是TkinterDnD2-0.3哦)则是放到pyxx\Lib\site-packages下面
在这里插入图片描述
在这里插入图片描述

使用

demo_canvas.py

# -*- coding: utf-8 -*-

import os
from TkinterDnD2 import *
try:
    from Tkinter import *
except ImportError:
    from tkinter import *

root = TkinterDnD.Tk()
root.withdraw()
root.title('TkinterDnD Canvas demo')
root.grid_rowconfigure(1, weight=1, minsize=250)
root.grid_columnconfigure(0, weight=1, minsize=300)

Label(root, text='Drag and drop files here:').grid(
                    row=0, column=0, padx=10, pady=5)
buttonbox = Frame(root)
buttonbox.grid(row=2, column=0, columnspan=2, pady=5)
Button(buttonbox, text='Quit', command=root.quit).pack(
                    side=LEFT, padx=5)

file_data = ('R0lGODlhGAAYAKIAANnZ2TMzMwAAAJmZmf///yH5BAEAAAAALAA'
        'AAAAYABgAAAPACBi63IqgC4GiyxwogaAbKLrMgSKBoBoousyBogEACIGiyxwoKgGAECI'
        '4uiyCExMTOACBosuNpDoAGCI4uiyCIkREOACBosutSDoAgSI4usyCIjQAGCi63Iw0ACE'
        'oOLrMgiI0ABgoutyMNAAhKDi6zIIiNAAYKLrcjDQAISg4usyCIjQAGCi63Iw0AIGiiqP'
        'LIyhCA4CBosvNSAMQKKo4ujyCIjQAGCi63Iw0AIGiy81IAxCBpMu9GAMAgKPL3QgJADs'
        '=')
folder_data = ('R0lGODlhGAAYAKECAAAAAPD/gP///yH+EUNyZWF0ZWQgd2l0aCBHSU1QA'
        'CH5BAEKAAIALAAAAAAYABgAAAJClI+pK+DvGINQKhCyztEavGmd5IQmYJXmhi7UC8frH'
        'EL0Hdj4rO/n41v1giIgkWU8cpLK4dFJhAalvpj1is16toICADs=')

file_icon = PhotoImage(data=file_data)
folder_icon = PhotoImage(data=folder_data)

canvas = Canvas(root, name='dnd_demo_canvas', bg='white', relief='sunken',
                bd=1, highlightthickness=1, takefocus=True, width=600)
canvas.grid(row=1, column=0, padx=5, pady=5, sticky='news')

# store the filename associated with each canvas item in a dictionary
canvas.filenames = {}
# store the next icon's x and y coordinates in a list
canvas.nextcoords = [50, 20]
# add a boolean flag to the canvas which can be used to disable
# files from the canvas being dropped on the canvas again
canvas.dragging = False

def add_file(filename):
    icon = file_icon
    if os.path.isdir(filename):
        icon = folder_icon
    id1 = canvas.create_image(canvas.nextcoords[0], canvas.nextcoords[1],
                                image=icon, anchor='n', tags=('file',))
    id2 = canvas.create_text(canvas.nextcoords[0], canvas.nextcoords[1] + 30,
                                text=os.path.basename(filename), anchor='n',
                                justify='center', width=90)
    def select_item(ev):
        canvas.select_from(id2, 0)
        canvas.select_to(id2, 'end')
    canvas.tag_bind(id1, '<ButtonPress-1>', select_item)
    canvas.tag_bind(id2, '<ButtonPress-1>', select_item)
    canvas.filenames[id1] = filename
    canvas.filenames[id2] = filename
    if canvas.nextcoords[0] > 450:
        canvas.nextcoords = [50, canvas.nextcoords[1] + 80]
    else:
        canvas.nextcoords = [canvas.nextcoords[0] + 100, canvas.nextcoords[1]]

# drop methods

def drop_enter(event):
    event.widget.focus_force()
    print('Entering %s' % event.widget)
    return event.action

def drop_position(event):
    return event.action

def drop_leave(event):
    print('Leaving %s' % event.widget)
    return event.action

def drop(event):
    if canvas.dragging:
        # the canvas itself is the drag source
        return REFUSE_DROP
    if event.data:
        files = canvas.tk.splitlist(event.data)
        for f in files:
            add_file(f)
    return event.action

canvas.drop_target_register(DND_FILES)
canvas.dnd_bind('<<DropEnter>>', drop_enter)
canvas.dnd_bind('<<DropPosition>>', drop_position)
canvas.dnd_bind('<<DropLeave>>', drop_leave)
canvas.dnd_bind('<<Drop>>', drop)

# drag methods

def drag_init(event):
    data = ()
    sel = canvas.select_item()
    if sel:
        # in a decent application we should check here if the mouse
        # actually hit an item, but for now we will stick with this
        data = (canvas.filenames[sel],)
        canvas.dragging = True
        return ((ASK, COPY), (DND_FILES, DND_TEXT), data)
    else:
        # don't start a dnd-operation when nothing is selected; the
        # return "break" here is only cosmetical, return "foobar" would
        # probably do the same
        return 'break'

def drag_end(event):
    # reset the "dragging" flag to enable drops again
    canvas.dragging = False

canvas.drag_source_register(1, DND_FILES)
canvas.dnd_bind('<<DragInitCmd>>', drag_init)
canvas.dnd_bind('<<DragEndCmd>>', drag_end)

root.update_idletasks()
root.deiconify()
root.mainloop()

在这里插入图片描述
可以将文件拖放到窗体容器中,再拖动到其它地方粘贴

demo_files_text.py

# -*- coding: utf-8 -*-

import os
import platform
from TkinterDnD2 import *
try:
    from Tkinter import *
    from ScrolledText import ScrolledText
except ImportError:
    from tkinter import *
    from tkinter.scrolledtext import ScrolledText

root = TkinterDnD.Tk()
root.withdraw()
root.title('TkinterDnD demo')
root.grid_rowconfigure(1, weight=1, minsize=250)
root.grid_columnconfigure(0, weight=1, minsize=300)
root.grid_columnconfigure(1, weight=1, minsize=300)

def print_event_info(event):
    print('\nAction:', event.action)
    print('Supported actions:', event.actions)
    print('Mouse button:', event.button)
    print('Type codes:', event.codes)
    print('Current type code:', event.code)
    print('Common source types:', event.commonsourcetypes)
    print('Common target types:', event.commontargettypes)
    print('Data:', event.data)
    print('Event name:', event.name)
    print('Supported types:', event.types)
    print('Modifier keys:', event.modifiers)
    print('Supported source types:', event.supportedsourcetypes)
    print('Operation type:', event.type)
    print('Source types:', event.sourcetypes)
    print('Supported target types:', event.supportedtargettypes)
    print('Widget:', event.widget, '(type: %s)' % type(event.widget))
    print('X:', event.x_root)
    print('Y:', event.y_root, '\n')

Label(root, text='Drag and drop files here:').grid(
                    row=0, column=0, padx=10, pady=5)
Label(root, text='Drag and drop text here:').grid(
                    row=0, column=1, padx=10, pady=5)
buttonbox = Frame(root)
buttonbox.grid(row=2, column=0, columnspan=2, pady=5)
Button(buttonbox, text='Quit', command=root.quit).pack(
                    side=LEFT, padx=5)

##############################################################################
######   Basic demo window: a Listbox to drag & drop files                  ##
######   and a Text widget to drag & drop text                              ##
##############################################################################
listbox = Listbox(root, name='dnd_demo_listbox',
                    selectmode='extended', width=1, height=1)
listbox.grid(row=1, column=0, padx=5, pady=5, sticky='news')
text = Text(root, name='dnd_demo_text', wrap='word', undo=True, width=1, height=1)
text.grid(row=1, column=1, pady=5, sticky='news')

listbox.insert(END, os.path.abspath(__file__))
info = 'TkinterDnD demo\nDetected versions:\n'
info += '    Python: %s\n' % platform.python_version()
info += '    Tk    : %f\n' % TkVersion
info += '    Tkdnd : %s\n' % TkinterDnD.TkdndVersion
info += 'Use mouse button 3 to drag hightlighted text from the text box.\n'
text.insert(END, info)

# Drop callbacks can be shared between the Listbox and Text;
# according to the man page these callbacks must return an action type,
# however they also seem to work without

def drop_enter(event):
    event.widget.focus_force()
    print('Entering widget: %s' % event.widget)
    #print_event_info(event)
    return event.action

def drop_position(event):
    print('Position: x %d, y %d' %(event.x_root, event.y_root))
    #print_event_info(event)
    return event.action

def drop_leave(event):
    print('Leaving %s' % event.widget)
    #print_event_info(event)
    return event.action

def drop(event):
    if event.data:
        print('Dropped data:\n', event.data)
        #print_event_info(event)
        if event.widget == listbox:
            # event.data is a list of filenames as one string;
            # if one of these filenames contains whitespace characters
            # it is rather difficult to reliably tell where one filename
            # ends and the next begins; the best bet appears to be
            # to count on tkdnd's and tkinter's internal magic to handle
            # such cases correctly; the following seems to work well
            # at least with Windows and Gtk/X11
            files = listbox.tk.splitlist(event.data)
            for f in files:
                if os.path.exists(f):
                    print('Dropped file: "%s"' % f)
                    listbox.insert('end', f)
                else:
                    print('Not dropping file "%s": file does not exist.' % f)
        elif event.widget == text:
            # calculate the mouse pointer's text index
            bd = text['bd'] + text['highlightthickness']
            x = event.x_root - text.winfo_rootx() - bd
            y = event.y_root - text.winfo_rooty() - bd
            index = text.index('@%d,%d' % (x,y))
            text.insert(index, event.data)
        else:
            print('Error: reported event.widget not known')
    return event.action

# now make the Listbox and Text drop targets
listbox.drop_target_register(DND_FILES, DND_TEXT)
text.drop_target_register(DND_TEXT)

for widget in (listbox, text):
    widget.dnd_bind('<<DropEnter>>', drop_enter)
    widget.dnd_bind('<<DropPosition>>', drop_position)
    widget.dnd_bind('<<DropLeave>>', drop_leave)
    widget.dnd_bind('<<Drop>>', drop)
    #widget.dnd_bind('<<Drop:DND_Files>>', drop)
    #widget.dnd_bind('<<Drop:DND_Text>>', drop)

# define drag callbacks

def drag_init_listbox(event):
    print_event_info(event)
    # use a tuple as file list, this should hopefully be handled gracefully
    # by tkdnd and the drop targets like file managers or text editors
    data = ()
    if listbox.curselection():
        data = tuple([listbox.get(i) for i in listbox.curselection()])
        print('Dragging :', data)
    # tuples can also be used to specify possible alternatives for
    # action type and DnD type:
    return ((ASK, COPY), (DND_FILES, DND_TEXT), data)

def drag_init_text(event):
    print_event_info(event)
    # use a string if there is only a single text string to be dragged
    data = ''
    sel = text.tag_nextrange(SEL, '1.0')
    if sel:
        data = text.get(*sel)
        print('Dragging :\n', data)
    # if there is only one possible alternative for action and DnD type
    # we can also use strings here
    return (COPY, DND_TEXT, data)

def drag_end(event):
    #print_event_info(event)
    # this callback is not really necessary if it doesn't do anything useful
    print('Drag ended for widget:', event.widget)

# finally make the widgets a drag source

listbox.drag_source_register(1, DND_TEXT, DND_FILES)
text.drag_source_register(3, DND_TEXT)

listbox.dnd_bind('<<DragInitCmd>>', drag_init_listbox)
listbox.dnd_bind('<<DragEndCmd>>', drag_end)
text.dnd_bind('<<DragInitCmd>>', drag_init_text)
# skip the useless drag_end() binding for the text widget

root.update_idletasks()
root.deiconify()
root.mainloop()


在这里插入图片描述
左边支持文件拖放暂存,右边支持文字拖放(但是我只实现了复制粘贴编辑文字,拖动呢???)

demo_megawidgets.py

# -*- coding: utf-8 -*-

import os
import platform
from TkinterDnD2 import *
try:
    from Tkinter import *
    from ScrolledText import ScrolledText
except ImportError:
    from tkinter import *
    from tkinter.scrolledtext import ScrolledText

root = TkinterDnD.Tk()
root.withdraw()
root.title('TkinterDnD megawidget demo')
root.grid_rowconfigure(1, weight=1, minsize=250)
root.grid_columnconfigure(0, weight=1, minsize=300)

Label(root, text='Drop text here:').grid(
                    row=0, column=0, padx=10, pady=5)
buttonbox = Frame(root)
buttonbox.grid(row=2, column=0, columnspan=2, pady=5)
Button(buttonbox, text='Quit', command=root.quit).pack(
                    side=LEFT, padx=5)

msg = 'Dropping onto compound widgets works only with the latest versions ' +\
      'of tkdnd.\nIf you can drop text here, the installed version of ' +\
      'tkdnd already supports this feature.\n'

lf = LabelFrame(root, text='"Megawidget" text box')
lf.grid(row=1, column=0, padx=5, pady=5, sticky='news')
text = ScrolledText(lf)
text.pack(fill='both', expand=1)
text.insert('end', msg)

# make the text box a drop target:

def drop_enter(event):
    event.widget.focus_force()
    print('Entering %s' % event.widget)
    return event.action

def drop_position(event):
    print('Position: x %d, y %d' %(event.x_root, event.y_root))
    return event.action

def drop_leave(event):
    print('Leaving %s' % event.widget)
    return event.action

def drop(event):
    if event.data:
        text.insert('end', event.data)
    return event.action

text.drop_target_register(DND_TEXT)
text.dnd_bind('<<DropEnter>>', drop_enter)
text.dnd_bind('<<DropPosition>>', drop_position)
text.dnd_bind('<<DropLeave>>', drop_leave)
text.dnd_bind('<<Drop>>', drop)

root.update_idletasks()
root.deiconify()
root.mainloop()

在这里插入图片描述

demo_simple_text.py

# -*- coding: utf-8 -*-

import os
import platform
from TkinterDnD2 import *
try:
    from Tkinter import *
    from ScrolledText import ScrolledText
except ImportError:
    from tkinter import *
    from tkinter.scrolledtext import ScrolledText

root = TkinterDnD.Tk()
root.withdraw()
root.title('TkinterDnD simple text demo')
root.grid_rowconfigure(1, weight=1, minsize=250)
root.grid_columnconfigure(0, weight=1, minsize=300)

Label(root, text='Drag and drop text here:').grid(
                    row=0, column=0, padx=10, pady=5)
buttonbox = Frame(root)
buttonbox.grid(row=2, column=0, columnspan=2, pady=5)
Button(buttonbox, text='Quit', command=root.quit).pack(
                    side=LEFT, padx=5)

msg = "You can drop text onto the Label to append it to the Label's text "+\
      "string.\nYou can drag the Label's text string into other " +\
      "applications.\n"
label = Label(root, name='dnd_demo_label', bg='white', relief='sunken',
                bd=1, highlightthickness=1, text=msg, takefocus=True,
                justify='left', anchor='nw', padx=5, pady=5)
label.grid(row=1, column=0, padx=5, pady=5, sticky='news')

# make the Label a drop target:

def drop_enter(event):
    event.widget.focus_force()
    print('Entering %s' % event.widget)
    return event.action

def drop_position(event):
    print('Position: x %d, y %d' %(event.x_root, event.y_root))
    return event.action

def drop_leave(event):
    print('Leaving %s' % event.widget)
    return event.action

def drop(event):
    if event.data:
        label.configure(text=label['text'] + event.data)
    return event.action

label.drop_target_register(DND_TEXT)
label.dnd_bind('<<DropEnter>>', drop_enter)
label.dnd_bind('<<DropPosition>>', drop_position)
label.dnd_bind('<<DropLeave>>', drop_leave)
label.dnd_bind('<<Drop>>', drop)

# make the Label a drag source:

def drag_init(event):
    data = label['text']
    return (COPY, DND_TEXT, data)

label.drag_source_register(DND_TEXT)
label.dnd_bind('<<DragInitCmd>>', drag_init)

root.update_idletasks()
root.deiconify()
root.mainloop()

在这里插入图片描述

目标:simple_file.py

拖放文件获取路径demo

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

在这里插入图片描述
在这里插入图片描述

如果你想和我一样把这玩意打包成exe,pyinstaller了解一下

References

  • 9
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值