繁体   English   中英

如何使用 ctrl+c 和 ctrl+v 将文件夹从一个文件夹复制、剪切到另一个文件夹

[英]How to copy, cut folder from one folder to another using ctrl+c and ctrl+v

我的标题可能看起来有点模棱两可,所以这里是一个解释。

PycharmVisual 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 应用程序复制到外部文件浏览器的部分实现:在这个方向上复制的问题是它是特定于平台的,因为不同平台对剪贴板的处理方式不同。 以下解决方案适用于LinuxXFCE桌面环境和Thunar 文件浏览器。

我使用klembord库访问系统剪贴板,其中包含比纯文本更丰富的内容。 如果已将文件/文件夹复制到剪贴板,则可以将其粘贴到 Thunar 中

klembord.set({'x-special/gnome-copied-files': f'copy\nfile://{abspath}'.encode()})

其中abspath是文件/文件夹的 HTML 转义绝对路径。

要将其实现到App ,请导入klembordurllib.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM