![](/img/trans.png)
[英]A simple Cut Paste operation for an element in Pandas DataFrame (as we do for an Excel's cell using a mouse or CTRL+C & CTRL+V)
[英]How to copy, cut folder from one folder to another using ctrl+c and ctrl+v
我的标题可能看起来有点模棱两可,所以这里是一个解释。
像Pycharm
或Visual Studio Code
这样的专业 IDE 允许复制文件夹,导航到特定目录并将其粘贴到那里。 我也想实现这一点。
但就我而言, shutil.copytree 需要 2 个参数 - 源文件夹和目标文件夹。
那么有什么方法可以复制文件夹、浏览资源管理器、单击粘贴或按ctrl+v
并且文件夹将被复制或粘贴到那里,不像用户已经需要提供路径的shutil.copytree
?
目前,我有一个代码可以将文件夹名称复制到剪贴板。
import os
import tkinter as tk
import tkinter.ttk as ttk
import clipboard
class App(tk.Frame):
def __init__(self, master, path):
tk.Frame.__init__(self, master)
self.tree = ttk.Treeview(self)
ysb = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
xsb = ttk.Scrollbar(self, orient='horizontal', command=self.tree.xview)
self.tree.configure(yscroll=ysb.set, xscroll=xsb.set)
self.tree.heading('#0', text=path, anchor='w')
abspath = os.path.abspath(path)
root_node = self.tree.insert('', 'end', text=abspath, open=True)
self.process_directory(root_node, abspath)
self.tree.bind("<Control-c>",self.copy_to_clipboard)
self.tree.grid(row=0, column=0)
ysb.grid(row=0, column=1, sticky='ns')
xsb.grid(row=1, column=0, sticky='ew')
self.grid()
def copy_to_clipboard(self,event,*args):
item = self.tree.identify_row(event.y)
clipboard.copy(self.tree.item(item,"text"))
def process_directory(self, parent, path):
try:
for p in os.listdir(path):
abspath = os.path.join(path, p)
isdir = os.path.isdir(abspath)
oid = self.tree.insert(parent, 'end', text=p, open=False)
if isdir:
self.process_directory(oid, abspath)
except PermissionError:
pass
root = tk.Tk()
path_to_my_project = 'C:\\Users\\91996\\Documents'
app = App(root, path=path_to_my_project)
app.mainloop()
注意:这个答案没有回答 OP 的问题,因为它可以从外部文件浏览器复制到 tkinter 应用程序中选择的文件夹中,但不是相反,正如 OP 所希望的那样。
首先,为了更容易地检索项目的绝对路径,我使用绝对路径作为树中的项目标识符。
然后,为了实现粘贴部分,我添加了一个.paste()
方法,用Ctrl + V调用。 在这种方法中,我通过获取当前选择的项目来获取目标文件夹。 如果这个项目是一个文件,那么我使用父文件夹作为目标。 我从剪贴板中获取要复制的文件/文件夹的路径。 如果它是一个文件,我使用shutil.copy2(src, dest)
。 由于它会复制文件,即使它已经存在于dest
,因此您可能希望在检查之前添加一些代码并显示一个消息框。 如果源是一个文件夹,我使用shutil.copytree(src, os.path.join(dest, src_dirname))
其中src_dirname
是复制文件夹的名称。
正如评论中所建议的,我使用了.clipboard_clear()
的方法.clipboard_clear()
、 .clipboard_append()
和.clipboard_get()
而不是使用clipboard
模块。
在.copy_to_clipboard()
,我建议您使用self.tree.focus()
而不是self.tree.identify_row(y)
,以便获得所选项目,而不是鼠标光标下方的项目(我刚刚添加了一个代码中相关行旁边的注释,但未实施此建议)。
这是代码:
import os
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.messagebox import showerror
import shutil
import traceback
class App(tk.Frame):
def __init__(self, master, path):
tk.Frame.__init__(self, master)
self.tree = ttk.Treeview(self)
ysb = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
xsb = ttk.Scrollbar(self, orient='horizontal', command=self.tree.xview)
self.tree.configure(yscroll=ysb.set, xscroll=xsb.set)
self.tree.heading('#0', text=path, anchor='w')
abspath = os.path.abspath(path)
self.tree.insert('', 'end', abspath, text=abspath, open=True)
self.process_directory(abspath)
self.tree.bind("<Control-c>", self.copy_to_clipboard)
self.tree.bind("<Control-v>", self.paste)
self.tree.grid(row=0, column=0)
ysb.grid(row=0, column=1, sticky='ns')
xsb.grid(row=1, column=0, sticky='ew')
self.grid()
def copy_to_clipboard(self, event, *args):
item = self.tree.identify_row(event.y) # you may want to use self.tree.focus() instead so that
# the selected item is copied, not the one below the mouse cursor
self.clipboard_clear()
self.clipboard_append(item)
def paste(self, event):
src = self.clipboard_get()
if not os.path.exists(src):
return
dest = self.tree.focus()
if not dest:
dest = self.tree.get_children("")[0] # get root folder path
elif not os.path.isdir(dest): # selected item is a file, use parent folder
dest = self.tree.parent(dest)
if os.path.isdir(src):
try:
dirname = os.path.split(src)[1]
newpath = shutil.copytree(src, os.path.join(dest, dirname))
self.tree.insert(dest, "end", newpath, text=dirname)
self.process_directory(newpath)
self.tree.item(dest, open=True)
except Exception:
showerror("Error", traceback.format_exc())
else:
try:
# you might want to check if the file already exists in dest and ask what to do
# otherwise shutil.copy2() will replace it
newpath = shutil.copy2(src, dest)
self.tree.insert(dest, "end", newpath, text=os.path.split(src)[1])
except tk.TclError: # the item already exists
pass
except Exception:
showerror("Error", traceback.format_exc())
def process_directory(self, path):
try:
for p in os.listdir(path):
abspath = os.path.join(path, p)
isdir = os.path.isdir(abspath)
# use abspath as item IID
self.tree.insert(path, 'end', abspath, text=p, open=False)
if isdir:
self.process_directory(abspath)
except PermissionError:
pass
root = tk.Tk()
path_to_my_project = '/tmp/truc'
app = App(root, path=path_to_my_project)
app.mainloop()
从 tkinter 应用程序复制到外部文件浏览器的部分实现:在这个方向上复制的问题是它是特定于平台的,因为不同平台对剪贴板的处理方式不同。 以下解决方案适用于Linux 、 XFCE桌面环境和Thunar 文件浏览器。
我使用klembord库访问系统剪贴板,其中包含比纯文本更丰富的内容。 如果已将文件/文件夹复制到剪贴板,则可以将其粘贴到 Thunar 中
klembord.set({'x-special/gnome-copied-files': f'copy\nfile://{abspath}'.encode()})
其中abspath
是文件/文件夹的 HTML 转义绝对路径。
要将其实现到App
,请导入klembord
和urllib.parse
并替换
self.clipboard_clear()
self.clipboard_append(item)
在.copy_to_clipboard()
klembord.set({'x-special/gnome-copied-files':
f'copy\nfile://{urllib.parse.quote(item)}'.encode()})
您应该将文件或目录“复制”的值保留为内部变量,并且仅将其回显到剪贴板。 通过这种方式,您将享受与上述 IDE 相同的行为。
请参阅函数copy_
和paste_
。
"""A directory browser using Tk Treeview.
Based on the demo found in Tk 8.5 library/demos/browse
"""
import os
import glob
import tkinter
import tkinter.ttk as ttk
import shutil
clipboard_val = ''
def populate_tree(tree, node):
if tree.set(node, "type") != 'directory':
return
path = tree.set(node, "fullpath")
tree.delete(*tree.get_children(node))
parent = tree.parent(node)
special_dirs = [] if parent else glob.glob('.') + glob.glob('..')
for p in special_dirs + os.listdir(path):
ptype = None
p = os.path.join(path, p).replace('\\', '/')
if os.path.isdir(p): ptype = "directory"
elif os.path.isfile(p): ptype = "file"
fname = os.path.split(p)[1]
id = tree.insert(node, "end", text=fname, values=[p, ptype])
if ptype == 'directory':
if fname not in ('.', '..'):
tree.insert(id, 0, text="dummy")
tree.item(id, text=fname)
elif ptype == 'file':
size = os.stat(p).st_size
tree.set(id, "size", "%d bytes" % size)
def populate_roots(tree):
dir = os.path.abspath('.').replace('\\', '/')
node = tree.insert('', 'end', text=dir, values=[dir, "directory"])
populate_tree(tree, node)
def update_tree(event):
tree = event.widget
populate_tree(tree, tree.focus())
def autoscroll(sbar, first, last):
"""Hide and show scrollbar as needed."""
first, last = float(first), float(last)
if first <= 0 and last >= 1:
sbar.grid_remove()
else:
sbar.grid()
sbar.set(first, last)
def copy_(event):
global clipboard_val
tree = event.widget
node = tree.focus()
if tree.parent(node):
path = os.path.abspath(tree.set(node, "fullpath"))
clipboard_val = path
root.clipboard_clear()
root.clipboard_append(clipboard_val)
def paste_(event):
global clipboard_val
tree = event.widget
node = tree.focus()
if tree.parent(node):
path = os.path.abspath(tree.set(node, "fullpath"))
# make sure path is a directory, even if a file selected
if os.path.isfile(path):
path = os.path.split(path)[0]
if os.path.exists(clipboard_val):
# copy regular file
if os.path.isfile(clipboard_val):
shutil.copy(clipboard_val, path)
# recursively copy directory
elif os.path.isdir(clipboard_val):
shutil.copytree(clipboard_val, os.path.join(path, os.path.split(clipboard_val)[1]))
# update the view
populate_tree(tree, node)
root = tkinter.Tk()
vsb = ttk.Scrollbar(orient="vertical")
hsb = ttk.Scrollbar(orient="horizontal")
tree = ttk.Treeview(columns=("fullpath", "type", "size"),
displaycolumns="size", yscrollcommand=lambda f, l: autoscroll(vsb, f, l),
xscrollcommand=lambda f, l:autoscroll(hsb, f, l))
vsb['command'] = tree.yview
hsb['command'] = tree.xview
tree.heading("#0", text="Directory Structure", anchor='w')
tree.heading("size", text="File Size", anchor='w')
tree.column("size", stretch=0, width=100)
populate_roots(tree)
tree.bind('<<TreeviewOpen>>', update_tree)
tree.bind('<Control-c>', copy_)
tree.bind('<Control-v>', paste_)
# Arrange the tree and its scrollbars in the toplevel
tree.grid(column=0, row=0, sticky='nswe')
vsb.grid(column=1, row=0, sticky='ns')
hsb.grid(column=0, row=1, sticky='ew')
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.mainloop()
对于 Windows,您可以使用 Powershell 命令Set-Clipboard
。 您可以使用 subprocess 模块运行命令。 现在文件/文件夹已复制,您现在可以使用ctrl + v或使用右键单击上下文菜单将其粘贴到文件资源管理器中。
要处理粘贴,只需使用clipboard_get()
提供的clipboard_get()
,它将为您提供文件/文件夹的路径。 然后,您可以使用shutil.copy
/ shutil.copytree
从应用程序中的 src 复制内容。
然后,您可以重新加载树视图以使更改可见。
例子:
import os
import subprocess
import shutil
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Frame):
def __init__(self, master, path):
tk.Frame.__init__(self, master)
self.tree = ttk.Treeview(self)
ysb = ttk.Scrollbar(self, orient='vertical', command=self.tree.yview)
xsb = ttk.Scrollbar(self, orient='horizontal', command=self.tree.xview)
self.tree.configure(yscroll=ysb.set, xscroll=xsb.set)
self.tree.heading('#0', text=path, anchor='w')
self.abspath = os.path.abspath(path)
self.tree.bind("<Control-c>",self.copy_to_clipboard)
self.tree.bind("<Control-v>",self.create_directory_from_clipboard)
self.tree.grid(row=0, column=0)
ysb.grid(row=0, column=1, sticky='ns')
xsb.grid(row=1, column=0, sticky='ew')
self.grid()
self.store_path = []
self.reload()
def copy_to_clipboard(self,event,*args):
item = self.tree.focus()
self.store_path.append(self.tree.item(item,"text"))
absolute_path = self.find_absolutePath(item)
#cmd = r"ls '{}' | Set-Clipboard".format(absolute_path) # if you only want the contents of folder to be copied
cmd = r"gi '{}' | Set-Clipboard".format(absolute_path) # copies both folder and its contents
subprocess.run(["powershell", "-command", cmd], shell=True) # windows specific
print("copied")
def find_absolutePath(self, item):
parent_id = self.tree.parent(item)
if parent_id:
parent = self.tree.item(parent_id, 'text')
self.store_path.append(parent)
return self.find_absolutePath(parent_id)
else:
absolute_path = os.path.join(*self.store_path[::-1])
self.store_path = []
return absolute_path
def create_directory_from_clipboard(self, event):
cur_item = self.tree.focus()
self.store_path.append(self.tree.item(cur_item, "text"))
dest = self.find_absolutePath(cur_item)
src_path = self.clipboard_get()
try:
if os.path.isdir(src_path):
src = os.path.basename(os.path.normpath(src_path))
#os.mkdir(os.path.join(src_path, src))
shutil.copytree(src_path, os.path.join(dest, src))
else:
shutil.copy(src_path, dest)
self.reload()
print("pasted")
except (FileExistsError, FileNotFoundError, shutil.SameFileError) as e:
print(f"Error: {e}")
def reload(self):
self.tree.delete(*self.tree.get_children())
root = self.tree.insert('', 'end', text=self.abspath, open=True)
self.process_directory(root, self.abspath)
def process_directory(self, parent, path):
try:
for p in os.listdir(path):
abspath = os.path.join(path, p)
isdir = os.path.isdir(abspath)
oid = self.tree.insert(parent, 'end', text=p, open=False)
if isdir:
self.process_directory(oid, abspath)
except PermissionError:
pass
root = tk.Tk()
path_to_my_project = r'mypath\ '
app = App(root, path=path_to_my_project)
app.mainloop()
如果您希望它与其他操作系统一起使用,则必须找到相应的命令, 例如
或者,您也可以使用win32clipboard , 1或者您可以使用 PyQt/pyslide 的QClipboard或 PyGTK 剪贴板,它们提供了进行此类操作的便捷方法
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.