【问题标题】:How to copy, cut folder from one folder to another using ctrl+c and ctrl+v如何使用 ctrl+c 和 ctrl+v 将文件夹从一个文件夹复制、剪切到另一个文件夹
【发布时间】:2021-06-28 13:58:42
【问题描述】:

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

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

【问题讨论】:

  • 你想只复制文件夹的内容还是包含的文件夹?
  • 如果我在一个文件上按ctrl+c,然后复制该文件,如果我在一个文件夹上按ctrl+c,然后复制整个文件夹及其内容。 @艺术
  • @Sujay 而不是使用clipboard,您可以使用 tkinter 的内置方法,例如 .clipboard_get().clipboard_append(...).clipboard_clear()

标签: python tkinter


【解决方案1】:

注意:此答案不回答 OP 的问题,因为它可以从外部文件浏览器复制到 tkinter 应用程序中选择的文件夹中,但不是相反,正如 OP 所希望的那样。

首先,为了更容易检索项目的绝对路径,我使用绝对路径作为树中的项目标识符。

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

按照 cmets 中的建议,我使用了 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 应用程序复制到外部文件浏览器的部分实现: 在这个方向复制的问题在于它是特定于平台的,因为不同平台对剪贴板的处理方式不同。以下解决方案适用于 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()})

【讨论】:

  • 这是一个很好的答案,但我仅限于ttk.Treeview 中的文件夹。我无法将其复制粘贴到 Windows 资源管理器中的任何其他文件夹中
  • @Sujay 好的,我已更改代码以允许粘贴从应用程序外部复制的路径
  • 你能解释一下你做了什么改变吗?我仍然无法通过从Treeview 复制将文件夹粘贴到其他目录,导航到目标并粘贴
  • @Sujay 我删除了我检查剪贴板内容是否与从应用程序复制的路径相同的部分 (self._copied_path)。在我的电脑上,我可以从我的系统文件浏览器复制到应用程序中。但我使用的是 Linux,所以在 Windows 中可能存在一些差异。
  • @Sujay 如果您点击我的答案下方的“edited ... ago”文本,您将看到编辑的差异。
【解决方案2】:

您应该将文件或目录的“复制”值保留为内部变量,并且仅将其回显到剪贴板。这样,您将享受与上述 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()

【讨论】:

    【解决方案3】:

    对于 Windows,您可以使用 Powershell 命令Set-Clipboard。您可以使用 subprocess 模块运行命令。现在文件/文件夹已复制,您现在可以使用 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,它们提供了方便的方法来做这些操作

    【讨论】:

      猜你喜欢
      • 2020-12-07
      • 2013-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-14
      • 1970-01-01
      • 1970-01-01
      • 2014-12-02
      相关资源
      最近更新 更多