缘由:
朋友有一个这样的需求,有三个文件夹,文件夹A内含有很多txt文件,文件夹B中也内含有很多txt文件,文件夹C中还是含有很多txt文件。需要把txt文件复制到同个文件夹中

功能:
选择目录将目录下多个文件夹内的文件复制到一个文件夹中

运行图:
1724828039953.png

下载地址:
包含打包好的成品和源代码。
python打包使用的是Pyinstaller打包。
链接:https://ide.lanzouv.com/b0foqio5i
密码:ao4j

更新:
2024.08.27 v1.1.0
1.增加线程防止界面复制时候处于 未响应 状态
2.增加进度条显示

2024.08.27 v1.0.0
初始版本

源代码:
一共有4个文件:main.py、ui.py、control.py、logo.ico

main.py

# 导入布局文件
from ui import Win as MainWin
# 导入窗口控制器
from control import Controller as MainUIController
# 将窗口控制器 传递给UI
app = MainWin(MainUIController())
if __name__ == "__main__":
    # 启动
    app.mainloop()

ui.py

from tkinter import *
from tkinter.ttk import *

class WinGUI(Tk):
    def __init__(self):
        super().__init__()
        self.__win()
        self.tk_label_m0c6l6hz = self.__tk_label_m0c6l6hz(self)
        self.tk_input_m0c6lpcn = self.__tk_input_m0c6lpcn(self)
        self.tk_button_m0c6lvy7 = self.__tk_button_m0c6lvy7(self)
        self.tk_label_m0c6mm3a = self.__tk_label_m0c6mm3a(self)
        self.tk_input_m0c6n6vi = self.__tk_input_m0c6n6vi(self)
        self.tk_button_m0c6nb3j = self.__tk_button_m0c6nb3j(self)
        self.tk_button_m0c7djh2 = self.__tk_button_m0c7djh2(self)
        self.tk_progressbar_m0d8ueyu = self.__tk_progressbar_m0d8ueyu(self)
        self.tk_label_m0d8xhee = self.__tk_label_m0d8xhee(self)
        self.tk_label_m0dhb051 = self.__tk_label_m0dhb051(self)

    def __win(self):
        self.title("多个文件夹内的文件复制到一个文件夹中_v1.1.0")
        # 设置窗口大小、居中
        width = 572
        height = 263
        screenwidth = self.winfo_screenwidth()
        screenheight = self.winfo_screenheight()
        geometry = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
        self.geometry(geometry)

        self.resizable(width=False, height=False)

    def scrollbar_autohide(self, vbar, hbar, widget):
        """自动隐藏滚动条"""

        def show():
            if vbar: vbar.lift(widget)
            if hbar: hbar.lift(widget)

        def hide():
            if vbar: vbar.lower(widget)
            if hbar: hbar.lower(widget)

        hide()
        widget.bind("<Enter>", lambda e: show())
        if vbar: vbar.bind("<Enter>", lambda e: show())
        if vbar: vbar.bind("<Leave>", lambda e: hide())
        if hbar: hbar.bind("<Enter>", lambda e: show())
        if hbar: hbar.bind("<Leave>", lambda e: hide())
        widget.bind("<Leave>", lambda e: hide())

    def v_scrollbar(self, vbar, widget, x, y, w, h, pw, ph):
        widget.configure(yscrollcommand=vbar.set)
        vbar.config(command=widget.yview)
        vbar.place(relx=(w + x) / pw, rely=y / ph, relheight=h / ph, anchor='ne')

    def h_scrollbar(self, hbar, widget, x, y, w, h, pw, ph):
        widget.configure(xscrollcommand=hbar.set)
        hbar.config(command=widget.xview)
        hbar.place(relx=x / pw, rely=(y + h) / ph, relwidth=w / pw, anchor='sw')

    def create_bar(self, master, widget, is_vbar, is_hbar, x, y, w, h, pw, ph):
        vbar, hbar = None, None
        if is_vbar:
            vbar = Scrollbar(master)
            self.v_scrollbar(vbar, widget, x, y, w, h, pw, ph)
        if is_hbar:
            hbar = Scrollbar(master, orient="horizontal")
            self.h_scrollbar(hbar, widget, x, y, w, h, pw, ph)
        self.scrollbar_autohide(vbar, hbar, widget)

    def __tk_label_m0c6l6hz(self, parent):
        label = Label(parent, text="需要复制的文件夹:", anchor="center", )
        label.place(x=14, y=8, width=116, height=30)
        return label

    def __tk_input_m0c6lpcn(self, parent):
        ipt = Entry(parent, )
        ipt.place(x=14, y=40, width=454, height=30)
        return ipt

    def __tk_button_m0c6lvy7(self, parent):
        btn = Button(parent, text="选择路径", takefocus=False, )
        btn.place(x=486, y=40, width=73, height=30)
        return btn

    def __tk_label_m0c6mm3a(self, parent):
        label = Label(parent, text="保存的路径:", anchor="center", )
        label.place(x=14, y=80, width=81, height=30)
        return label

    def __tk_input_m0c6n6vi(self, parent):
        ipt = Entry(parent, )
        ipt.place(x=14, y=112, width=454, height=30)
        return ipt

    def __tk_button_m0c6nb3j(self, parent):
        btn = Button(parent, text="选择路径", takefocus=False, )
        btn.place(x=485, y=112, width=73, height=30)
        return btn

    def __tk_button_m0c7djh2(self, parent):
        btn = Button(parent, text="开始复制", takefocus=False, )
        btn.place(x=302, y=178, width=224, height=71)
        return btn

    def __tk_progressbar_m0d8ueyu(self, parent):
        progressbar = Progressbar(parent, orient=HORIZONTAL, )
        progressbar.place(x=14, y=184, width=195, height=30)
        return progressbar

    def __tk_label_m0d8xhee(self, parent):
        label = Label(parent, text="复制进度:", anchor="center", )
        label.place(x=14, y=152, width=57, height=30)
        return label

    def __tk_label_m0dhb051(self, parent):
        label = Label(parent, text="", anchor="center", )
        label.place(x=14, y=221, width=219, height=30)
        return label


class Win(WinGUI):
    def __init__(self, controller):
        self.ctl = controller
        super().__init__()
        self.__event_bind()
        self.__style_config()
        self.ctl.init(self)

    def __event_bind(self):
        self.tk_button_m0c6lvy7.bind('<Button-1>', self.ctl.choosePatha)
        self.tk_button_m0c6nb3j.bind('<Button-1>', self.ctl.choosePathb)
        self.tk_button_m0c7djh2.bind('<Button-1>', self.ctl.startCopy)
        pass

    def __style_config(self):
        pass


if __name__ == "__main__":
    win = WinGUI()
    win.mainloop()

control.py

import threading
import os
import shutil
import tkinter as tk
from tkinter import filedialog
from tkinter.messagebox import showinfo

class Controller:
    # 导入UI类后,替换以下的 object 类型,将获得 IDE 属性提示功能
    ui: object
    def __init__(self):
        self.monitor_running = None
    def init(self, ui):
        self.ui = ui
        # TODO 组件初始化 赋值操作
    def choosePatha(self,evt):
        if self.monitor_running == True:
            return False
        directory_path = filedialog.askdirectory(
            title="选择文件夹"
        )
        # 显示选定的文件夹路径
        if directory_path:
            # print("选择的文件夹:", directory_path)
            # 更新界面或其他逻辑
            self.ui.tk_input_m0c6lpcn.delete(0, tk.END)
            self.ui.tk_input_m0c6lpcn.insert(0, directory_path)

    def choosePathb(self, evt):
        if self.monitor_running == True:
            return False
        directory_path = filedialog.askdirectory(
            title="选择文件夹"
        )
        # 显示选定的文件夹路径
        if directory_path:
            # print("选择的文件夹:", directory_path)
            # 更新界面或其他逻辑
            self.ui.tk_input_m0c6n6vi.delete(0, tk.END)
            self.ui.tk_input_m0c6n6vi.insert(0, directory_path)

    def startCopy(self,evt):
        if self.monitor_running == True:
            return # 如果监控已经在运行,则不执行任何操作
        fromPath = self.ui.tk_input_m0c6lpcn.get()
        savePath = self.ui.tk_input_m0c6n6vi.get()
        if fromPath == "" or savePath == "":
            showinfo(title="提示", message="两个路径都需要输入!")
            return False
        if fromPath in savePath:
            showinfo(title="提示", message="保存的路径不能选择到需要复制的文件夹内!")
            return False

        self.stop_event = threading.Event()
        self.thread = threading.Thread(target=self.update_counter, args=(fromPath,savePath))
        self.thread.daemon = True
        self.thread.start()

        self.ui.tk_button_m0c6lvy7.config(state=tk.DISABLED)
        self.ui.tk_button_m0c6nb3j.config(state=tk.DISABLED)
        self.ui.tk_button_m0c7djh2.config(state=tk.DISABLED)
        self.ui.tk_input_m0c6lpcn.config(state="readonly")
        self.ui.tk_input_m0c6n6vi.config(state="readonly")
        self.monitor_running = True

    def update_counter(self, fromPath,savePath):
        self.CreateDir(savePath)
        fileNum = self.count_files_in_directory(fromPath)
        self.ui.tk_progressbar_m0d8ueyu.config(length=fileNum,maximum=fileNum)
        self.ui.tk_progressbar_m0d8ueyu["value"] = 0
        self.ui.update()
        self.CopyFile(fromPath, savePath, fileNum)
        successfulText = "成功复制了" + str(fileNum) + "个文件"
        self.ui.tk_label_m0dhb051.config(text=successfulText)
        showinfo(title="提示", message="复制成功!")
        # 按钮启用
        self.ui.tk_button_m0c6lvy7.config(state=tk.NORMAL)
        self.ui.tk_button_m0c6nb3j.config(state=tk.NORMAL)
        self.ui.tk_button_m0c7djh2.config(state=tk.NORMAL)
        self.ui.tk_input_m0c6lpcn.config(state="normal")
        self.ui.tk_input_m0c6n6vi.config(state="normal")
        self.monitor_running = False
        self.stop_event.set()  # 设置停止事件
        self.stop_event.clear()
        #置0
        self.ui.tk_progressbar_m0d8ueyu["value"] = 0
        self.ui.update()
        return True

    def count_files_in_directory(self,directory_path):
        # 初始化文件计数
        file_count = 0
        # 使用 os.walk 遍历文件夹及其子目录
        for root, dirs, files in os.walk(directory_path):
            # 对于每个目录,累加文件数量
            file_count += len(files)
        # 返回文件总数
        return file_count

    def CopyFile(self,filepath, newPath, fileNum):
        # 获取当前路径下的文件名,返回List
        fileNames = os.listdir(filepath)
        for file in fileNames:
            # 将文件命加入到当前文件路径后面
            newDir = filepath + '/' + file
            # 如果是文件
            if os.path.isfile(newDir):
                newFile = newPath + '/'+ file
                shutil.copyfile(newDir, newFile)
                self.ui.tk_progressbar_m0d8ueyu["value"] += 1
                self.ui.update()
                successfulText = "进度:" + str(self.ui.tk_progressbar_m0d8ueyu["value"]) + " / " + str(fileNum)
                self.ui.tk_label_m0dhb051.config(text=successfulText)
            # 如果不是文件,递归这个文件夹的路径
            else:
                self.CopyFile(newDir, newPath, fileNum)

    def CreateDir(self,path):
        isExists = os.path.exists(path)
        # 判断结果
        if not isExists:
            # 如果不存在则创建目录
            os.makedirs(path)
            # print(path + ' 目录创建成功')
        # else:
            # 如果目录存在则不创建,并提示目录已存在
            # print(path + ' 目录已存在')