Python项目源码68:文件搜索工具2.0(tkinter+re+threading)

功能测试说明:
1.基础文件搜索:输入关键字(如:“.txt”,或文件名),选择有效目录,点击"开始搜索"查看结果。
2.正则表达式搜索:勾选正则表达式选项,输入正则表达式(如:^report_\d{4}),验证匹配结果和高亮显示。
3.高亮显示测试:普通模式下搜索包含大小写字母的关键字,正则模式下测试边界匹配,观察文件名中的黄色高亮部分。
4.中断搜索测试:在大目录搜索过程中点击"停止搜索",验证搜索是否能正确终止。
5.错误处理测试:输入无效正则表达式,选择无效目录,验证错误提示是否正常显示。
6.该版本完整实现了以下功能:多线程文件搜索,正则表达式支持,实时进度更新,搜索关键词高亮,搜索中断功能,友好的错误提示,状态栏反馈。在这里插入图片描述

# -*- coding: utf-8 -*-
# @Author : 小红牛
# 微信公众号:WdPython
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import re
import threading
import queue

class FileSearchApp:
    def __init__(self, master):
        self.master = master
        master.title("文件搜索工具 v2.0")

        # 多线程相关变量
        self.search_thread = None
        self.stop_event = threading.Event()
        self.result_queue = queue.Queue()
        self.current_keyword = ""
        self.current_use_regex = False

        # 初始化界面组件
        self.create_widgets()
        self.setup_layout()
        self.setup_bindings()

        # 配置文本高亮样式
        self.result_text.tag_configure("highlight", background="yellow")

        # 启动队列检查
        self.check_queue()

    def create_widgets(self):
        """创建界面组件"""
        # 搜索选项部分
        self.keyword_label = ttk.Label(self.master, text="搜索内容:")
        self.keyword_entry = ttk.Entry(self.master, width=40)
        self.regex_var = tk.BooleanVar()
        self.regex_check = ttk.Checkbutton(
            self.master, text="正则表达式", variable=self.regex_var)

        # 目录选择部分
        self.dir_label = ttk.Label(self.master, text="搜索目录:")
        self.dir_entry = ttk.Entry(self.master, width=35)
        self.browse_btn = ttk.Button(self.master, text="浏览", command=self.browse_directory)

        # 控制按钮
        self.search_btn = ttk.Button(self.master, text="开始搜索", command=self.start_search)
        self.stop_btn = ttk.Button(
            self.master, text="停止搜索",
            state=tk.DISABLED,
            command=self.stop_search
        )

        # 搜索结果展示
        self.result_text = tk.Text(self.master, wrap=tk.WORD, height=15)
        self.scrollbar = ttk.Scrollbar(self.master, command=self.result_text.yview)
        self.result_text.configure(yscrollcommand=self.scrollbar.set)

        # 状态栏
        self.status_var = tk.StringVar()
        self.status_bar = ttk.Label(
            self.master,
            textvariable=self.status_var,
            relief=tk.SUNKEN,
            anchor=tk.W
        )

    def setup_layout(self):
        """布局管理"""
        # 第一行:搜索内容
        self.keyword_label.grid(row=0, column=0, padx=5, pady=5, sticky='e')
        self.keyword_entry.grid(row=0, column=1, padx=5, pady=5, sticky='we')
        self.regex_check.grid(row=0, column=2, padx=5, pady=5)

        # 第二行:目录选择
        self.dir_label.grid(row=1, column=0, padx=5, pady=5, sticky='e')
        self.dir_entry.grid(row=1, column=1, padx=5, pady=5, sticky='we')
        self.browse_btn.grid(row=1, column=2, padx=5, pady=5)

        # 第三行:控制按钮
        self.search_btn.grid(row=2, column=0, padx=5, pady=5)
        self.stop_btn.grid(row=2, column=2, padx=5, pady=5)

        # 第四行:搜索结果
        self.result_text.grid(row=3, column=0, columnspan=2, padx=5, pady=5, sticky='nsew')
        self.scrollbar.grid(row=3, column=2, sticky='ns')

        # 第五行:状态栏
        self.status_bar.grid(row=4, column=0, columnspan=3, sticky='ew')

        # 配置网格自适应
        self.master.grid_rowconfigure(3, weight=1)
        self.master.grid_columnconfigure(1, weight=1)

    def setup_bindings(self):
        """绑定事件"""
        self.master.protocol("WM_DELETE_WINDOW", self.on_close)

    def browse_directory(self):
        """选择目录"""
        selected_dir = filedialog.askdirectory()
        if selected_dir:
            self.dir_entry.delete(0, tk.END)
            self.dir_entry.insert(0, selected_dir)

    def start_search(self):
        """启动搜索线程"""
        # 获取输入参数
        self.current_keyword = self.keyword_entry.get().strip()
        directory = self.dir_entry.get().strip()
        self.current_use_regex = self.regex_var.get()

        # 输入验证
        if not self.validate_input(self.current_keyword, directory):
            return

        # 初始化搜索状态
        self.stop_event.clear()
        self.result_text.delete(1.0, tk.END)
        self.status_var.set("准备搜索...")
        self.toggle_buttons(False)

        # 创建并启动搜索线程
        self.search_thread = threading.Thread(
            target=self.file_search_worker,
            args=(directory, self.current_keyword, self.current_use_regex),
            daemon=True
        )
        self.search_thread.start()

    def stop_search(self):
        """停止搜索"""
        self.stop_event.set()
        self.status_var.set("正在停止搜索...")

    def on_close(self):
        """窗口关闭事件处理"""
        self.stop_search()
        if self.search_thread and self.search_thread.is_alive():
            self.search_thread.join(timeout=1)
        self.master.destroy()

    def file_search_worker(self, directory, keyword, use_regex):
        """多线程搜索工作函数"""
        try:
            # 预处理搜索模式
            if use_regex:
                try:
                    pattern = re.compile(keyword)
                except re.error as e:
                    self.result_queue.put(("error", f"正则表达式错误: {str(e)}"))
                    return
            else:
                keyword = keyword.lower()

            found_files = []
            for root, dirs, files in os.walk(directory):
                if self.stop_event.is_set():
                    break

                for file in files:
                    if self.stop_event.is_set():
                        break

                    try:
                        # 执行匹配判断
                        if use_regex:
                            match = pattern.search(file)
                        else:
                            match = keyword in file.lower()

                        if match:
                            full_path = os.path.join(root, file)
                            found_files.append(full_path)
                            # 每找到10个文件更新一次界面
                            if len(found_files) % 10 == 0:
                                self.result_queue.put(("progress", found_files[-10:]))
                    except Exception as e:
                        continue

            # 发送最终结果
            self.result_queue.put(("result", found_files))
        except Exception as e:
            self.result_queue.put(("error", str(e)))

    def check_queue(self):
        """定期检查结果队列"""
        try:
            while True:
                msg_type, content = self.result_queue.get_nowait()

                if msg_type == "progress":
                    self.display_progress(content)
                elif msg_type == "result":
                    self.display_result(content)
                elif msg_type == "error":
                    self.display_error(content)

        except queue.Empty:
            pass
        finally:
            self.master.after(100, self.check_queue)

    def display_progress(self, files):
        """显示进度更新"""
        for path in files:
            # 插入文件路径
            self.result_text.insert(tk.END, f"{path}\n")

            # 高亮匹配部分
            line_start = self.result_text.index("end-2c linestart")
            line_end = self.result_text.index("end-1c lineend")
            self.highlight_line(line_start, line_end, path)

            self.result_text.see(tk.END)
        self.status_var.set(f"已找到 {self.result_text.index('end-1c').split('.')[0]} 个文件")

    def highlight_line(self, line_start, line_end, path):
        """高亮指定行中的匹配部分"""
        # 获取文件名部分
        filename = os.path.basename(path)
        dir_part = os.path.dirname(path)

        # 计算文件名在路径中的起始位置
        filename_start = len(dir_part) + 1 if dir_part else 0

        # 匹配模式处理
        if self.current_use_regex:
            try:
                pattern = re.compile(self.current_keyword)
                matches = list(pattern.finditer(filename))
            except re.error:
                return
        else:
            keyword = self.current_keyword.lower()
            filename_lower = filename.lower()
            matches = []
            start = 0
            while True:
                idx = filename_lower.find(keyword, start)
                if idx == -1:
                    break
                matches.append((idx, idx + len(keyword)))
                start = idx + len(keyword)

        # 转换为完整路径中的位置
        for match in matches:
            if isinstance(match, re.Match):
                start, end = match.start(), match.end()
            else:
                start, end = match

            full_start = filename_start + start
            full_end = filename_start + end

            # 转换为文本组件坐标
            start_index = f"{line_start}+{full_start}c"
            end_index = f"{line_start}+{full_end}c"

            try:
                self.result_text.tag_add("highlight", start_index, end_index)
            except tk.TclError:
                pass

    def display_result(self, files):
        """显示最终结果"""
        self.toggle_buttons(True)
        count = len(files)
        if count > 0:
            self.result_text.insert(tk.END, f"\n\n搜索完成,共找到 {count} 个文件\n")
            for path in files:
                self.result_text.insert(tk.END, f"{path}\n")
                line_start = self.result_text.index("end-2c linestart")
                line_end = self.result_text.index("end-1c lineend")
                self.highlight_line(line_start, line_end, path)
        else:
            self.result_text.insert(tk.END, "\n\n未找到匹配文件")
        self.status_var.set("搜索完成" if count > 0 else "未找到匹配文件")

    def display_error(self, error_msg):
        """显示错误信息"""
        self.toggle_buttons(True)
        messagebox.showerror("发生错误", error_msg)
        self.status_var.set("搜索出错")

    def validate_input(self, keyword, directory):
        """验证输入有效性"""
        if not keyword:
            messagebox.showerror("错误", "请输入搜索内容")
            return False
        if not directory:
            messagebox.showerror("错误", "请选择搜索目录")
            return False
        if not os.path.isdir(directory):
            messagebox.showerror("错误", "目录不存在或无效")
            return False
        return True

    def toggle_buttons(self, enable):
        """切换按钮状态"""
        state = tk.NORMAL if enable else tk.DISABLED
        self.search_btn.config(state=state)
        self.stop_btn.config(state=tk.DISABLED if enable else tk.NORMAL)


if __name__ == "__main__":
    root = tk.Tk()
    app = FileSearchApp(root)
    root.mainloop()

完毕!!感谢您的收看

----------★★跳转到历史博文集合★★----------

我的零基础Python教程,Python入门篇 进阶篇 视频教程 Py安装py项目 Python模块 Python爬虫 Json Xpath 正则表达式 Selenium Etree CssGui程序开发 Tkinter Pyqt5 列表元组字典数据可视化 matplotlib 词云图 Pyecharts 海龟画图 Pandas Bug处理 电脑小知识office自动化办公 编程工具 NumPy Pygame

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值