[Python] 纯文本查看 复制代码
import os
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import threading
import re
class TxtBatchEditor:
def __init__(self, root):
self.root = root
self.root.title("TXT文件批量修改工具")
self.root.geometry("700x500")
self.root.resizable(True, True)
# 设置中文字体支持
self.style = ttk.Style()
self.style.configure("TLabel", font=("SimHei", 10))
self.style.configure("TButton", font=("SimHei", 10))
self.style.configure("TEntry", font=("SimHei", 10))
self.style.configure("TText", font=("SimHei", 10))
# 选择文件夹
self.folder_frame = ttk.Frame(root, padding="10")
self.folder_frame.pack(fill=tk.X)
ttk.Label(self.folder_frame, text="目标文件夹:").pack(side=tk.LEFT, padx=5)
self.folder_path = tk.StringVar()
self.folder_entry = ttk.Entry(self.folder_frame, textvariable=self.folder_path, width=50)
self.folder_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
self.browse_btn = ttk.Button(self.folder_frame, text="浏览...", command=self.browse_folder)
self.browse_btn.pack(side=tk.LEFT, padx=5)
# 查找替换设置
self.find_replace_frame = ttk.Frame(root, padding="10")
self.find_replace_frame.pack(fill=tk.X)
ttk.Label(self.find_replace_frame, text="查找内容:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.find_text = tk.StringVar()
ttk.Entry(self.find_replace_frame, textvariable=self.find_text, width=40).grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
ttk.Label(self.find_replace_frame, text="替换为:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
self.replace_text = tk.StringVar()
ttk.Entry(self.find_replace_frame, textvariable=self.replace_text, width=40).grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
# 选项设置
self.options_frame = ttk.Frame(root, padding="10")
self.options_frame.pack(fill=tk.X)
self.case_sensitive = tk.BooleanVar(value=False)
ttk.Checkbutton(self.options_frame, text="区分大小写", variable=self.case_sensitive).pack(side=tk.LEFT, padx=10)
self.use_regex = tk.BooleanVar(value=False)
ttk.Checkbutton(self.options_frame, text="使用正则表达式", variable=self.use_regex).pack(side=tk.LEFT, padx=10)
# 按钮区域
self.buttons_frame = ttk.Frame(root, padding="10")
self.buttons_frame.pack(fill=tk.X)
self.start_btn = ttk.Button(self.buttons_frame, text="开始替换", command=self.start_replacement)
self.start_btn.pack(side=tk.LEFT, padx=5)
self.cancel_btn = ttk.Button(self.buttons_frame, text="取消", command=self.cancel_replacement, state=tk.DISABLED)
self.cancel_btn.pack(side=tk.LEFT, padx=5)
self.clear_log_btn = ttk.Button(self.buttons_frame, text="清空日志", command=self.clear_log)
self.clear_log_btn.pack(side=tk.RIGHT, padx=5)
# 进度条
self.progress_frame = ttk.Frame(root, padding="10")
self.progress_frame.pack(fill=tk.X)
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(self.progress_frame, variable=self.progress_var, maximum=100)
self.progress_bar.pack(fill=tk.X)
# 日志区域
self.log_frame = ttk.LabelFrame(root, text="操作日志", padding="10")
self.log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.log_text = tk.Text(self.log_frame, wrap=tk.WORD, state=tk.DISABLED)
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(self.log_frame, command=self.log_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.log_text.config(yscrollcommand=scrollbar.set)
# 状态变量
self.processing = False
self.cancel_requested = False
def browse_folder(self):
folder = filedialog.askdirectory()
if folder:
self.folder_path.set(folder)
self.log(f"已选择文件夹: {folder}")
def log(self, message):
"""在日志区域显示消息"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def clear_log(self):
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
def start_replacement(self):
"""开始替换过程"""
folder = self.folder_path.get()
find = self.find_text.get()
replace = self.replace_text.get()
if not folder:
messagebox.showerror("错误", "请选择目标文件夹")
return
if not find:
messagebox.showerror("错误", "请输入要查找的内容")
return
# 准备开始处理
self.processing = True
self.cancel_requested = False
self.start_btn.config(state=tk.DISABLED)
self.cancel_btn.config(state=tk.NORMAL)
self.browse_btn.config(state=tk.DISABLED)
self.log(f"开始替换操作...")
self.log(f"文件夹: {folder}")
self.log(f"查找: {find}")
self.log(f"替换为: {replace}")
self.log(f"区分大小写: {'是' if self.case_sensitive.get() else '否'}")
self.log(f"使用正则表达式: {'是' if self.use_regex.get() else '否'}")
# 在新线程中执行替换操作,避免UI冻结
threading.Thread(target=self.perform_replacement, args=(folder, find, replace), daemon=True).start()
def cancel_replacement(self):
if self.processing:
self.cancel_requested = True
self.log("正在取消操作...")
self.cancel_btn.config(text="取消中...")
def perform_replacement(self, folder, find, replace):
"""执行实际的替换操作"""
try:
# 获取所有TXT文件
txt_files = []
for root_dir, _, files in os.walk(folder):
for file in files:
if file.lower().endswith('.txt'):
txt_files.append(os.path.join(root_dir, file))
total_files = len(txt_files)
if total_files == 0:
self.root.after(0, lambda: self.log("未找到任何TXT文件"))
self.root.after(0, self.finish_processing)
return
self.root.after(0, lambda: self.log(f"找到 {total_files} 个TXT文件"))
modified_files = 0
# 处理每个文件
for i, file_path in enumerate(txt_files):
if self.cancel_requested:
self.root.after(0, lambda: self.log("操作已取消"))
break
# 更新进度条
progress = (i + 1) / total_files * 100
self.root.after(0, lambda p=progress: self.progress_var.set(p))
# 读取文件内容
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
# 尝试其他编码
try:
with open(file_path, 'r', encoding='gbk') as f:
content = f.read()
except:
self.root.after(0, lambda fp=file_path: self.log(f"无法读取文件: {fp}(编码问题)"))
continue
except Exception as e:
self.root.after(0, lambda fp=file_path, err=str(e): self.log(f"读取文件 {fp} 出错: {err}"))
continue
# 执行替换
new_content = content
if self.use_regex.get():
# 使用正则表达式替换
flags = 0 if self.case_sensitive.get() else re.IGNORECASE
try:
new_content, count = re.subn(find, replace, content, flags=flags)
except re.error as e:
self.root.after(0, lambda err=str(e): self.log(f"正则表达式错误: {err}"))
self.root.after(0, self.finish_processing)
return
else:
# 普通文本替换
if self.case_sensitive.get():
count = content.count(find)
if count > 0:
new_content = content.replace(find, replace)
else:
# 不区分大小写的替换
count = 0
new_content = ""
start = 0
content_lower = content.lower()
find_lower = find.lower()
len_find = len(find)
while True:
pos = content_lower.find(find_lower, start)
if pos == -1:
new_content += content[start:]
break
count += 1
new_content += content[start:pos] + replace
start = pos + len_find
# 如果内容有变化,保存文件
if new_content != content:
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
modified_files += 1
self.root.after(0, lambda fp=file_path, c=count: self.log(f"已修改 {fp}(替换 {c} 处)"))
except Exception as e:
self.root.after(0, lambda fp=file_path, err=str(e): self.log(f"写入文件 {fp} 出错: {err}"))
# 完成处理
if not self.cancel_requested:
self.root.after(0, lambda: self.log(f"处理完成,共修改 {modified_files} 个文件"))
except Exception as e:
self.root.after(0, lambda err=str(e): self.log(f"操作出错: {err}"))
self.root.after(0, self.finish_processing)
def finish_processing(self):
"""完成处理,恢复UI状态"""
self.processing = False
self.cancel_requested = False
self.progress_var.set(100 if not self.cancel_requested else 0)
self.start_btn.config(state=tk.NORMAL)
self.cancel_btn.config(state=tk.DISABLED, text="取消")
self.browse_btn.config(state=tk.NORMAL)
self.root.after(0, lambda: self.log("操作结束\n"))
if __name__ == "__main__":
root = tk.Tk()
app = TxtBatchEditor(root)
root.mainloop()