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

2024-03-28 13:45:43 发布

您现在位置:Python中文网/ 问答频道 /正文

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

PycharmVisual Studio Code这样的专业IDE允许复制文件夹,导航到特定目录并粘贴到那里。我还想实施这一点

但就我而言,shutil.copytree needs 2 arguments - source folder and destination folder.

那么,是否有任何方法可以复制文件夹,在资源管理器中导航,单击“粘贴”或按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()

Tags: topathtextimportself文件夹treeos
3条回答

您应该将文件或目录的“复制”值保留为内部变量,并且只将其回显到剪贴板。这样,您将享受与上述IDE相同的bahavior

请参阅函数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命令^{}。要运行此命令,请使用子流程模块。既然文件/文件夹已被复制,您现在可以使用ctrl+v或右键单击上下文菜单将其粘贴到文件资源管理器中

要处理粘贴,只需使用tkinter提供的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()

如果你想让它与其他操作系统一起工作,你必须找到相应的命令eg

或者,您也可以使用win32clipboard1,或者可以使用PyQt/pyslide的QClipboard或PyGTKclipboard,它们提供了方便的方法来执行这些类型的操作

注意:此答案不能回答OP的问题,因为它可以从外部文件浏览器复制到tkinter应用程序中选择的文件夹中,但不能按照OP的要求复制到相反的文件夹中

首先,为了便于检索项目的绝对路径,我在树中使用绝对路径作为项目标识符

然后,为了实现粘贴部分,我添加了一个.paste()方法,用Ctrl+V调用。在这种方法中,我通过获取当前选定的项目来获取目标文件夹。如果此项目是一个文件,则我使用父文件夹作为目标。我从剪贴板获取要复制的文件/文件夹的路径。如果它是一个文件,我使用shutil.copy2(src, dest)。因为即使文件已经存在于dest中,它也会复制该文件,所以您可能需要在选中该文件并显示messagebox之前添加一些代码。如果源是文件夹,我使用shutil.copytree(src, os.path.join(dest, src_dirname)),其中src_dirname是复制文件夹的名称

正如评论中所建议的,我使用了tkinter的方法.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,请导入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()})

相关问题 更多 >