self.view.frame.size.height赋值问题

参考资料自:http://segmentfault.com/q/1010000000177216;http://blog.youkuaiyun.com/like7xiaoben/article/details/8969093

问题:

self.view.frame.size.height = 100f;

这样写没法通过编译,编译器会报错"expression is not assignable"

原因是,这句话里面的几个点有两种不同的含义。self.view.frame是Objective-C语法,是读取view属性的frame属性,在Objective-C中使用点来访问属性只是一种语法糖,所以self.view.frame这句话会被转换成:

[[self view] frame]

也就是说,实际上这是消息传递。

而frame属性是一个CGRect结构,所以frame.size.height是C语言的语法,就是访问CGRect结构中的size字段,同样,height是CGSize结构的一个字段。所以,你这句话实际上等于:

[[self view] frame].size.height = 100f;

而Objective-C只是对C语言的一个扩展,所以,上面这句话会被转成C语言的函数调用形式,类似于这种形式:

getframe().size.height = 100f;

getter里并不能赋值,因此,当你打算直接给函数的返回值赋值的时候,编译器告诉你"这个表达式无法被赋值"。这就是这个错误的出现原因。



处理方案:

所以,解决办法就是,用一个临时变量保存这个函数的返回值,修改这个临时变量,然后再赋给frame:

// 1. 用一个临时变量保存返回值。
CGRect temp = self.view.frame;

// 2. 给这个变量赋值。因为变量都是L-Value,可以被赋值
temp.size.height = 100f;

// 3. 修改frame的值
self.view.frame = temp;

我知道这样写看起来有点笨,也许有一天,Objective-C的编译器会变的智能一点,自动完成这种转换。


但是无论改不改,我还是要改变现状。

为了让自己的编写代码方便,我开始写各种工具类:suusatoshigi的frame小工具



使用非常简单:

// 导入头文件

#import "UIView+SUUFrameAdjust.h"

// 直接点语法赋值 self.view.height = screenHeight/2;
顺便写了一下屏幕的,内容很少,基本都能看得懂,就先用着吧。

警告: 详细文件目录不存在 E:/SVN/DH_D82D_D83D/trunk/10COMMUNICATION/1005Delivery/01.KS/01_V字プロセス/3.2 自己チェック/ファイル比較実施/GA_D82DD83D_00-00-08 VS GA_D82DD83D_00-00-08\folder_diff_report_20250711_112946.html.files 正在打开Excel文件… 实际上目录文件名为:folder_diff_report_20250711_112946.files,而你查找的是folder_diff_report_20250711_112946.html.files import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import threading import queue import traceback import webbrowser import datetime class DiffProcessorApp: def __init__(self, root): self.root = root root.title("高级文件夹比较工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") # 创建现代风格主题 self.style = ttk.Style() self.style.theme_use('clam') # 自定义主题颜色 self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) # 创建主框架 main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题区域 header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) # 添加标题图标 try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="高级文件夹比较工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) # 文件选择区域 file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) # 文件夹选择 self.old_folder_entry, _ = self.create_folder_selector(file_frame, "原始文件夹:") self.new_folder_entry, _ = self.create_folder_selector(file_frame, "修改后文件夹:") # 比较选项区域 options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) # 递归比较选项 self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) # 文件过滤 filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) # 输出设置区域 self.excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") self.excel_frame.pack(fill=tk.X, pady=5) # 目标Excel选择 ttk.Label(self.excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(self.excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) # WinMerge路径设置 winmerge_frame = ttk.Frame(self.excel_frame) winmerge_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(winmerge_frame, text="WinMerge路径:").grid(row=0, column=0, sticky=tk.W) self.winmerge_entry = ttk.Entry(winmerge_frame, width=60) self.winmerge_entry.grid(row=0, column=1, padx=5) self.winmerge_entry.insert(0, r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe") # 默认路径 ttk.Button(winmerge_frame, text="浏览...", command=lambda: self.select_file(self.winmerge_entry, [("WinMerge 可执行文件", "*.exe")])).grid(row=0, column=2) # 执行按钮区域 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) # 停止按钮 self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) # 查看文件夹报告按钮 self.view_folder_report_button = ttk.Button(button_frame, text="查看文件夹报告", command=lambda: self.view_report("folder"), width=15, state=tk.DISABLED) self.view_folder_report_button.pack(side=tk.LEFT, padx=10) # 进度条 self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) # 状态信息 status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) # 日志和预览区域 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 文件夹结构标签 tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") # 创建树形视图 self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") # 日志标签 log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) # 设置网格权重 tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) # 线程控制 self.processing = False self.queue = queue.Queue() self.folder_report_path = None self.files_dir = None # 存储.files目录路径 # 启动队列处理 self.root.after(100, self.process_queue) def create_folder_selector(self, parent, label_text): """创建文件夹选择器组件""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry): """选择文件夹""" foldername = filedialog.askdirectory() if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) # 自动填充文件夹结构 self.populate_folder_tree(foldername) def select_file(self, entry, filetypes=None): """选择文件""" if filetypes is None: filetypes = [("所有文件", "*.*")] filename = filedialog.askopenfilename(filetypes=filetypes) if filename: entry.delete(0, tk.END) entry.insert(0, filename) def populate_folder_tree(self, path): """填充文件夹结构树""" self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return # 添加根节点 root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): """递归添加树节点""" try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): """记录日志消息""" self.queue.put(("log", message)) def update_progress(self, value): """更新进度条""" self.queue.put(("progress", value)) def update_status(self, message): """更新状态信息""" self.queue.put(("status", message)) def process_queue(self): """处理线程队列中的消息""" try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def view_report(self, report_type): """查看生成的报告""" if report_type == "folder" and self.folder_report_path and os.path.exists(self.folder_report_path): try: webbrowser.open(self.folder_report_path) except Exception as e: messagebox.showerror("错误", f"无法打开文件夹报告: {str(e)}") else: messagebox.showwarning("警告", f"没有可用的{report_type}报告文件") def process_folders(self, old_path, new_path, excel_file): """处理文件夹比较的线程函数""" try: # 设置报告目录为Excel文件所在目录 report_dir = os.path.dirname(excel_file) if not report_dir: report_dir = os.getcwd() os.makedirs(report_dir, exist_ok=True) # 生成唯一的时间戳 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") # 设置报告路径 self.folder_report_path = os.path.join(report_dir, f"folder_diff_report_{timestamp}.html") # 步骤1: 生成比较报告 self.update_status("生成比较报告...") self.update_progress(30) winmerge_path = self.winmerge_entry.get() if not self.run_winmerge(winmerge_path, old_path, new_path): self.update_status("WinMerge执行失败") return # 检查报告是否生成 if not os.path.exists(self.folder_report_path) or os.path.getsize(self.folder_report_path) == 0: self.log_message("警告: 文件夹报告为空或未生成") else: self.log_message(f"文件夹报告生成成功: {self.folder_report_path} ({os.path.getsize(self.folder_report_path)} bytes)") self.view_folder_report_button.config(state=tk.NORMAL) # 步骤2: 复制.files目录中的HTML文件 self.copy_detail_files(report_dir) # 步骤3: 打开Excel并等待用户操作 self.update_status("打开Excel文件...") self.update_progress(80) if not self.open_excel_file(excel_file): self.update_status("打开Excel失败") return # 完成 self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "已生成报告并打开Excel文件") except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: # 重新启用执行按钮 if self.processing: self.stop_processing() def copy_detail_files(self, report_dir): """复制.files目录中的HTML文件到报告目录""" # 获取.files目录路径 files_dir = os.path.splitext(self.folder_report_path)[0] + “.files” if not os.path.exists(files_dir): self.log_message(f"警告: 详细文件目录不存在 {files_dir}") return # 复制所有HTML文件 html_files = [f for f in os.listdir(files_dir) if f.lower().endswith('.html')] if not html_files: self.log_message(f"警告: 详细文件目录中没有HTML文件 {files_dir}") return copied_count = 0 for file_name in html_files: src_path = os.path.join(files_dir, file_name) dst_path = os.path.join(report_dir, file_name) try: shutil.copy2(src_path, dst_path) copied_count += 1 except Exception as e: self.log_message(f"复制文件失败: {file_name} - {str(e)}") self.log_message(f"已复制 {copied_count}/{len(html_files)} 个详细HTML文件到报告目录") def start_processing(self): """启动处理线程""" if self.processing: self.log_message("警告: 处理正在进行中") return # 获取路径 old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() # 详细路径验证 validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") winmerge_path = self.winmerge_entry.get() if not winmerge_path or not os.path.exists(winmerge_path): validation_errors.append("WinMerge路径无效或未设置") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return # 禁用执行按钮,启用停止按钮 self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.view_folder_report_button.config(state=tk.DISABLED) self.processing = True # 启动处理线程 thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def run_winmerge(self, winmerge_path, path1, path2): """针对WinMerge 2.16.12.0优化的报告生成方法""" # 验证WinMerge可执行文件 if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False # 确保报告目录存在 os.makedirs(os.path.dirname(self.folder_report_path), exist_ok=True) # 构建基本命令 cmd = [ winmerge_path, '/u', '/nosplash', # 不显示启动画面 '/dl', 'Base', '/dr', 'Modified', '/noninteractive' # 非交互模式 ] # 添加递归选项 if self.recursive_var.get(): cmd.append('/r') # 添加文件过滤 file_filter = self.filter_var.get() if file_filter and file_filter != "*.*": cmd.extend(['-f', file_filter]) # 添加文件夹报告参数 cmd.extend(['/or', self.folder_report_path]) # 添加要比较的文件夹路径 cmd.extend([path1, path2]) # 执行命令 self.update_status("正在生成比较报告...") return self.execute_winmerge_command(cmd, "比较报告") def execute_winmerge_command(self, cmd, report_type): """执行WinMerge命令并处理结果 - 增强版""" try: self.log_message(f"开始生成{report_type}...") self.log_message(f"执行命令: {' '.join(cmd)}") start_time = time.time() # 使用Popen执行命令 process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace', creationflags=subprocess.CREATE_NO_WINDOW ) # 设置超时(15分钟) timeout = 900 try: stdout, stderr = process.communicate(timeout=timeout) except subprocess.TimeoutExpired: process.kill() stdout, stderr = process.communicate() self.log_message(f"{report_type}生成超时({timeout}秒),已终止进程") return False elapsed = time.time() - start_time self.log_message(f"{report_type}生成完成,耗时: {elapsed:.2f}秒") # 记录详细输出 if stdout.strip(): self.log_message(f"WinMerge输出:\n{stdout[:2000]}") # 分析输出中是否包含报告文件信息 if "report" in stdout.lower(): self.log_message("检测到报告相关输出") # 提取可能的报告文件路径 report_lines = [line for line in stdout.split('\n') if "report" in line.lower()] for line in report_lines: self.log_message(f"报告线索: {line}") if stderr.strip(): self.log_message(f"WinMerge错误:\n{stderr[:1000]}") # 限制输出长度 # 检查退出码 if process.returncode == 0: self.log_message(f"{report_type}命令执行成功") return True elif process.returncode == 1: # WinMerge在发现差异时返回1 self.log_message(f"{report_type}命令执行完成(发现差异)") return True else: error_msg = f"{report_type}生成失败(退出码{process.returncode})" self.log_message(error_msg) return False except Exception as e: self.log_message(f"{report_type}生成错误: {str(e)}\n{traceback.format_exc()}") return False def open_excel_file(self, excel_path): """打开Excel文件并等待用户操作""" self.log_message("正在打开Excel文件...") try: # 验证Excel文件存在 if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False # 使用系统默认程序打开Excel文件 os.startfile(excel_path) self.log_message(f"Excel文件已打开: {excel_path}") # 提示用户手动操作 self.log_message("Excel已打开,请手动执行操作") messagebox.showinfo("Excel已打开", "Excel文件已打开,请手动执行所需操作") return True except Exception as e: self.log_message(f"打开Excel文件失败: {str(e)}\n{traceback.format_exc()}") return False def stop_processing(self): """停止处理""" self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.view_folder_report_button.config(state=tk.NORMAL) self.update_status("操作已停止") if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.mainloop()
07-12
import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import win32com.client as win32 import threading import tempfile import queue import traceback class DiffProcessorApp: def __init__(self, root): self.root = root root.title("高级文件夹比较工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") # 创建现代风格主题 self.style = ttk.Style() self.style.theme_use('clam') # 自定义主题颜色 self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) # 创建主框架 main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题区域 header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) # 添加标题图标 try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="📁", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="高级文件夹比较工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) # 文件选择区域 file_frame = ttk.LabelFrame(main_frame, text="文件夹选择", padding="12") file_frame.pack(fill=tk.X, pady=5) # 文件夹选择 self.old_folder_entry, self.new_folder_entry = self.create_folder_selector(file_frame, "原始文件夹:") self.new_folder_entry = self.create_folder_selector(file_frame, "修改后文件夹:")[0] # 比较选项区域 options_frame = ttk.LabelFrame(main_frame, text="比较选项", padding="12") options_frame.pack(fill=tk.X, pady=5) # 在比较选项区域添加文件报告选项 self.file_report_var = tk.BooleanVar(value=True) file_report_check = ttk.Checkbutton(options_frame, text="生成文件比较报告", variable=self.file_report_var) file_report_check.grid(row=0, column=2, padx=10, pady=5, sticky=tk.W) # 添加报告格式选择 report_frame = ttk.Frame(options_frame) report_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky=tk.W) ttk.Label(report_frame, text="报告格式:").pack(side=tk.LEFT, padx=(0, 5)) self.report_format_var = tk.StringVar(value="html") ttk.Radiobutton(report_frame, text="HTML", variable=self.report_format_var, value="html").pack(side=tk.LEFT, padx=5) ttk.Radiobutton(report_frame, text="XML", variable=self.report_format_var, value="xml").pack(side=tk.LEFT, padx=5) # 添加报告目录选择 report_dir_frame = ttk.Frame(excel_frame) report_dir_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(report_dir_frame, text="报告存储目录:").grid(row=0, column=0, sticky=tk.W) self.report_dir_entry = ttk.Entry(report_dir_frame, width=60) self.report_dir_entry.grid(row=0, column=1, padx=5) ttk.Button(report_dir_frame, text="浏览...", command=lambda: self.select_folder(self.report_dir_entry)).grid(row=0, column=2) # 递归比较选项 self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="递归比较子文件夹", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) # 文件过滤 filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件过滤:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) # 修改后:实例变量 self.excel_frame self.excel_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="12") self.excel_frame.pack(fill=tk.X, pady=5) # 目标Excel选择 ttk.Label(self.excel_frame, text="目标Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(self.excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(self.excel_frame, text="浏览...", command=lambda: self.select_file(self.excel_file_entry, [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) # 添加报告目录选择 report_dir_frame = ttk.Frame(self.excel_frame) # 使用实例变量 self.excel_frame report_dir_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, padx=5, pady=5) ttk.Label(report_dir_frame, text="报告存储目录:").grid(row=0, column=0, sticky=tk.W) self.report_dir_entry = ttk.Entry(report_dir_frame, width=60) self.report_dir_entry.grid(row=0, column=1, padx=5) ttk.Button(report_dir_frame, text="浏览...", command=lambda: self.select_folder(self.report_dir_entry)).grid(row=0, column=2) # 执行按钮区域 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="执行比较", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) # 停止按钮 self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) # 进度条 self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) # 状态信息 status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准备就绪") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) # 日志和预览区域 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 文件夹结构标签 tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件夹结构") # 创建树形视图 self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件夹结构", anchor=tk.W) self.tree.heading("Status", text="状态", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") # 日志标签 log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="执行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) # 设置网格权重 tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) # 线程控制 self.processing = False self.queue = queue.Queue() # 启动队列处理 self.root.after(100, self.process_queue) def create_folder_selector(self, parent, label_text): """创建文件夹选择器组件""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="浏览文件夹...", command=lambda: self.select_folder(entry)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry): """选择文件夹""" foldername = filedialog.askdirectory() if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) # 自动填充文件夹结构 self.populate_folder_tree(foldername) def select_file(self, entry, filetypes=None): """选择文件""" if filetypes is None: filetypes = [("所有文件", "*.*")] filename = filedialog.askopenfilename(filetypes=filetypes) if filename: entry.delete(0, tk.END) entry.insert(0, filename) def populate_folder_tree(self, path): """填充文件夹结构树""" self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return # 添加根节点 root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件夹",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): """递归添加树节点""" try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件夹",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"权限错误: 无法访问 {path}") def log_message(self, message): """记录日志消息""" self.queue.put(("log", message)) def update_progress(self, value): """更新进度条""" self.queue.put(("progress", value)) def update_status(self, message): """更新状态信息""" self.queue.put(("status", message)) def process_queue(self): """处理线程队列中的消息""" try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) # 添加报告文件处理方法 def process_report_files(self, report_dir): """处理生成的报告文件""" try: # 收集报告文件 report_files = [] for root, _, files in os.walk(report_dir): for file in files: if file.endswith(('.html', '.xml')): report_files.append(os.path.join(root, file)) # 更新日志 self.log_message(f"生成 {len(report_files)} 个文件比较报告") self.log_message(f"报告目录: {report_dir}") # 显示第一个报告内容预览 if report_files: with open(report_files[0], 'r', encoding='utf-8') as f: preview = f.read(500) + "..." if len(f.read()) > 500 else f.read() self.log_message(f"示例报告内容:\n{preview}") except Exception as e: self.log_message(f"报告处理错误: {str(e)}") def process_folders(self, old_path, new_path, excel_file): """处理文件夹比较的线程函数 - 简化流程:生成HTML后直接触发按钮""" output_html = None try: # 步骤1: 生成HTML差异文件 self.update_status("生成HTML差异文件...") self.update_progress(30) # 使用临时文件存储HTML报告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: output_html = temp_file.name if not self.run_winmerge(old_path, new_path, output_html): self.update_status("WinMerge执行失败") return # 步骤2: 将HTML文件与Excel放在同一目录 self.update_status("复制HTML报告到目标目录...") self.update_progress(60) excel_dir = os.path.dirname(excel_file) if excel_dir: target_html = os.path.join(excel_dir, "diff_report.html") try: shutil.copy(output_html, target_html) self.log_message(f"已将HTML文件复制到: {target_html}") except Exception as e: self.log_message(f"文件复制失败: {str(e)}") # 即使复制失败,我们仍然尝试触发按钮 # 步骤2.5: 处理文件级报告 if self.file_report_var.get(): report_dir = self.report_dir_entry.get() or os.path.dirname(excel_file) self.update_status("生成文件级比较报告...") self.update_progress(70) # 处理报告文件 self.process_report_files(report_dir) # 步骤3: 直接触发目标Excel中的"作成"按钮 self.update_status("触发Excel按钮...") self.update_progress(80) if not self.trigger_excel_button(excel_file): self.update_status("触发按钮失败") return # 完成 self.update_progress(100) self.update_status("处理完成!") self.log_message("文件夹比较流程执行完毕") messagebox.showinfo("完成", "已生成HTML报告并触发Excel处理") except Exception as e: error_msg = f"执行过程中发生错误: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("执行失败") messagebox.showerror("错误", f"处理失败: {str(e)}") finally: # 重新启用执行按钮 if self.processing: self.stop_processing() # 清理临时文件 if output_html and os.path.exists(output_html): try: os.remove(output_html) except: pass def start_processing(self): """启动处理线程 - 修复无响应问题""" if self.processing: self.log_message("警告: 处理正在进行中") return # 获取路径 old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() # 详细路径验证 validation_errors = [] if not old_path: validation_errors.append("原始文件夹路径为空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件夹路径无效: {old_path}") if not new_path: validation_errors.append("新文件夹路径为空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件夹路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径为空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必须是.xlsx或.xlsm格式") if validation_errors: self.log_message("错误: " + "; ".join(validation_errors)) messagebox.showerror("输入错误", "\n".join(validation_errors)) return # 检查WinMerge安装 winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge未安装在默认位置 {winmerge_path}") messagebox.showwarning("WinMerge未安装", "请确保WinMerge已安装或更新路径配置") return # 禁用执行按钮,启用停止按钮 self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.processing = True # 启动处理线程 thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("处理线程已启动") def run_winmerge(self, path1, path2, output_html): """调用WinMerge生成HTML差异文件""" winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" # 验证WinMerge可执行文件 if not os.path.exists(winmerge_path): self.log_message(f"错误: WinMerge路径不存在 {winmerge_path}") return False winmerge_cmd = [ winmerge_path, '/u', '/dl', 'Base', '/dr', 'Modified', '/or', output_html, path1, path2 ] # 添加递归选项 if self.recursive_var.get(): winmerge_cmd.insert(1, '/r') # 添加文件报告参数 if self.file_report_var.get(): # 创建报告目录 report_dir = self.report_dir_entry.get() or os.path.dirname(output_html) os.makedirs(report_dir, exist_ok=True) # 添加文件报告参数 winmerge_cmd.extend([ '/reporttype', self.report_format_var.get(), '/reportoutput', f'"{report_dir}"' ]) # 添加文件过滤 file_filter = self.filter_var.get() if file_filter and file_filter != "*.*": winmerge_cmd.extend(['-f', file_filter]) self.log_message(f"执行WinMerge命令: {' '.join(winmerge_cmd)}") try: result = subprocess.run( winmerge_cmd, capture_output=True, text=True, timeout=120, creationflags=subprocess.CREATE_NO_WINDOW ) if result.returncode == 0: self.log_message(f"HTML差异报告生成完成: {output_html}") return True else: error_msg = f"WinMerge执行失败(退出码{result.returncode}): {result.stderr}" self.log_message(error_msg) return False except subprocess.TimeoutExpired: self.log_message("WinMerge执行超时(120秒),请检查输入文件大小") return False except Exception as e: self.log_message(f"WinMerge执行错误: {str(e)}") return False def trigger_excel_button(self, excel_path): """触发目标Excel文件中的按钮""" self.log_message("正在打开Excel并触发按钮...") excel = None workbook = None try: # 验证Excel文件存在 if not os.path.exists(excel_path): self.log_message(f"错误: Excel文件不存在 {excel_path}") return False # 使用win32com打开Excel excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True excel.DisplayAlerts = False # 打开工作簿 try: workbook = excel.Workbooks.Open(os.path.abspath(excel_path)) except Exception as e: self.log_message(f"打开Excel文件失败: {str(e)}") return False # 检查工作表是否存在 sheet_names = [sheet.Name for sheet in workbook.Sheets] if "一覧" not in sheet_names: self.log_message("错误: Excel文件中缺少'一覧'工作表") return False sheet = workbook.Sheets("一覧") # 触发"作成"按钮 self.log_message("正在触发'作成'按钮...") try: # 查找按钮并点击 button = sheet.Buttons("作成") button.OnAction = "作成按钮的处理" button.Click() self.log_message("已触发'作成'按钮") # 等待处理完成 self.update_status("处理中...请等待") wait_time = 0 max_wait = 60 # 最大等待60秒 while self.processing and wait_time < max_wait: if excel.CalculationState == 0: # 0 = xlDone break time.sleep(1) wait_time += 1 self.log_message(f"处理中...({wait_time}秒)") if wait_time >= max_wait: self.log_message("警告: 处理超时") else: self.log_message("处理完成") return True except Exception as e: self.log_message(f"按钮操作失败: {str(e)}. 请手动点击'作成'按钮") return False except Exception as e: self.log_message(f"Excel操作失败: {str(e)}\n{traceback.format_exc()}") return False finally: # 确保正确关闭Excel try: if workbook: workbook.Close(SaveChanges=True) # 保存更改 if excel: excel.Quit() except Exception as e: self.log_message(f"关闭Excel时出错: {str(e)}") def stop_processing(self): """停止处理""" self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.update_status("操作已停止") if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.mainloop()请分析我的代码,解决其中的报错问题,给我一份完整的代码
07-11
class SCLMultiProcessor: def __init__(self, root): self.root = root # 创建日志目录 self.log_dir = "logs" self.root.title("SCL文件处理系统") self.root.geometry("1100x750") # 增加窗口尺寸以适应新控件 # 初始化变量 self.color_detector = EnhancedColorDetector() self.stats_processor = SCLRuleProcessor(self.color_detector) self.empty_cell_detector = EnhancedEmptyCellDetector(self.color_detector) self.progress_var = tk.DoubleVar() os.makedirs(self.log_dir, exist_ok=True) # 初始化日志系统 self.current_log_file = None self.setup_logger() # 创建主框架 self.main_frame = ttk.Frame(root, padding="10") self.main_frame.pack(fill=tk.BOTH, expand=True) # 创建UI self.create_ui() # 记录UI初始化完成 logger.info("用户界面初始化完成") def create_ui(self): """创建用户界面""" # 文件选择区域 file_frame = ttk.LabelFrame(self.main_frame, text="文件选择", padding="10") file_frame.pack(fill=tk.X, pady=5) # 输入文件选择 input_frame = ttk.Frame(file_frame) input_frame.pack(fill=tk.X, pady=5) ttk.Label(input_frame, text="统计表:").pack(side=tk.LEFT, padx=5) self.input_path_var = tk.StringVar() input_entry = ttk.Entry(input_frame, textvariable=self.input_path_var, width=70) input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) ttk.Button(input_frame, text="浏览...", command=self.browse_input_file).pack(side=tk.LEFT, padx=5) # 操作模式选择区域 mode_frame = ttk.LabelFrame(self.main_frame, text="操作模式", padding="10") mode_frame.pack(fill=tk.X, pady=5) # 添加操作模式单选按钮 self.operation_mode = tk.StringVar(value="stats") # 默认选择统计模式 ttk.Radiobutton(mode_frame, text="统计功能", variable=self.operation_mode, value="stats").pack(side=tk.LEFT, padx=10) ttk.Radiobutton(mode_frame, text="SCL格式检查", variable=self.operation_mode, value="empty_check").pack(side=tk.LEFT, padx=10) # 配置区域 config_frame = ttk.LabelFrame(self.main_frame, text="处理配置", padding="10") config_frame.pack(fill=tk.X, pady=5) # 添加SCL文件夹路径输入 scl_folder_frame = ttk.Frame(config_frame) scl_folder_frame.pack(fill=tk.X, pady=5) ttk.Label(scl_folder_frame, text="SCL文件夹路径:").grid(row=0, column=0, padx=5, sticky=tk.W) self.scl_folder_var = tk.StringVar() scl_folder_entry = ttk.Entry(scl_folder_frame, textvariable=self.scl_folder_var, width=60) scl_folder_entry.grid(row=0, column=1, padx=5, sticky=tk.W) ttk.Button(scl_folder_frame, text="浏览...", command=self.browse_scl_folder).grid(row=0, column=2, padx=5, sticky=tk.W) # 搜索选项 search_frame = ttk.Frame(config_frame) search_frame.pack(fill=tk.X, pady=5) ttk.Label(search_frame, text="文件前缀:").grid(row=0, column=0, padx=5, sticky=tk.W) self.prefix_var = tk.StringVar(value="SCL_") ttk.Entry(search_frame, textvariable=self.prefix_var, width=10).grid(row=0, column=1, padx=5, sticky=tk.W) # 添加CheckSheet路径输入 checksheet_frame = ttk.Frame(config_frame) checksheet_frame.pack(fill=tk.X, pady=5) ttk.Label(checksheet_frame, text="CheckSheet路径:").grid(row=0, column=0, padx=5, sticky=tk.W) self.checksheet_path_var = tk.StringVar() checksheet_entry = ttk.Entry(checksheet_frame, textvariable=self.checksheet_path_var, width=60) checksheet_entry.grid(row=0, column=1, padx=5, sticky=tk.W) ttk.Button(checksheet_frame, text="浏览...", command=self.browse_checksheet_path).grid(row=0, column=2, padx=5, sticky=tk.W) # 添加性能提示 ttk.Label(config_frame, text="(表头固定在第3行,数据从第4行开始)").pack(anchor=tk.W, padx=5, pady=2) # 日志选项 log_frame = ttk.Frame(config_frame) log_frame.pack(fill=tk.X, pady=5) ttk.Label(log_frame, text="日志级别:").grid(row=0, column=0, padx=5, sticky=tk.W) self.log_level_var = tk.StringVar(value="INFO") log_level_combo = ttk.Combobox( log_frame, textvariable=self.log_level_var, width=10, state="readonly" ) log_level_combo['values'] = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') log_level_combo.grid(row=0, column=1, padx=5, sticky=tk.W) log_level_combo.bind("<<ComboboxSelected>>", self.change_log_level) # 处理按钮 btn_frame = ttk.Frame(self.main_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="开始处理", command=self.process_file).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="查看日志", command=self.view_log).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="导出配置", command=self.export_config).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="加载配置", command=self.load_config).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="退出", command=self.root.destroy).pack(side=tk.RIGHT, padx=5) # 进度条 progress_frame = ttk.Frame(self.main_frame) progress_frame.pack(fill=tk.X, pady=5) ttk.Label(progress_frame, text="处理进度:").pack(side=tk.LEFT, padx=5) self.progress_bar = ttk.Progressbar( progress_frame, variable=self.progress_var, maximum=100, length=700 ) self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) self.progress_label = ttk.Label(progress_frame, text="0%") self.progress_label.pack(side=tk.LEFT, padx=5) # 结果展示区域 result_frame = ttk.LabelFrame(self.main_frame, text="处理结果", padding="10") result_frame.pack(fill=tk.BOTH, expand=True, pady=5) # 结果文本框 self.result_text = scrolledtext.ScrolledText( result_frame, wrap=tk.WORD, height=20 ) self.result_text.pack(fill=tk.BOTH, expand=True) self.result_text.config(state=tk.DISABLED) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, pady=5) logger.info("UI创建完成") def browse_checksheet_path(self): """浏览CheckSheet文件夹""" folder_path = filedialog.askdirectory(title="选择CheckSheet文件夹") if folder_path: self.checksheet_path_var.set(folder_path) logger.info(f"已选择CheckSheet文件夹: {folder_path}") def setup_logger(self): """配置日志记录器""" # 创建唯一日志文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") self.current_log_file = os.path.join(self.log_dir, f"scl_processor_{timestamp}.log") # 创建或获取日志记录器 self.logger = logging.getLogger("SCLProcessor") self.logger.setLevel(logging.INFO) # 移除所有现有处理器 for handler in self.logger.handlers[:]: self.logger.removeHandler(handler) # 创建文件处理器 file_handler = logging.FileHandler(self.current_log_file, encoding='utf-8') file_handler.setLevel(logging.INFO) # 创建控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 创建日志格式 formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 添加处理器 self.logger.addHandler(file_handler) self.logger.addHandler(console_handler) # 记录日志初始化信息 self.logger.info(f"日志系统已初始化,日志文件: {self.current_log_file}") self.logger.info(f"日志目录: {os.path.abspath(self.log_dir)}") def change_log_level(self, event=None): """动态更改日志级别并更新所有处理器(会话感知版本)""" try: # 获取选择的日志级别 level_str = self.log_level_var.get() log_level = getattr(logging, level_str.upper()) # 更新全局日志级别设置 self.current_log_level = log_level logger.info(f"请求更改日志级别为: {level_str}") # 设置根日志记录器级别 logger.setLevel(log_level) # 更新所有现有处理器的级别 for handler in logger.handlers: # 仅更新文件和控制台处理器 if isinstance(handler, (logging.FileHandler, logging.StreamHandler)): handler.setLevel(log_level) # 记录级别变更确认 logger.info(f"日志级别已成功更改为: {level_str}") # 添加调试信息显示当前处理器级别 handler_levels = [ f"{type(h).__name__}: {logging.getLevelName(h.level)}" for h in logger.handlers ] logger.debug(f"当前处理器级别: {', '.join(handler_levels)}") # 更新UI状态显示 self.status_var.set(f"日志级别: {level_str}") except AttributeError: logger.error(f"无效的日志级别: {level_str}") messagebox.showerror("错误", f"无效的日志级别: {level_str}") except Exception as e: logger.exception("更改日志级别时发生错误") messagebox.showerror("错误", f"更改日志级别失败: {str(e)}") def browse_input_file(self): """浏览输入文件""" file_path = filedialog.askopenfilename( filetypes=[("Excel 文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: self.input_path_var.set(file_path) self.input_file = file_path logger.info(f"已选择输入文件: {file_path}") def browse_scl_folder(self): """浏览SCL文件夹""" folder_path = filedialog.askdirectory(title="选择SCL文件夹") if folder_path: self.scl_folder_var.set(folder_path) logger.info(f"已选择SCL文件夹: {folder_path}") def highlight_cell(self, sheet, row, col, color="FFFF0000"): """为单元格设置背景色""" try: fill = PatternFill(start_color=color, end_color=color, fill_type="solid") sheet.cell(row=row, column=col).fill = fill return True except Exception as e: logger.error(f"设置单元格颜色失败: {str(e)}") return False def process_file(self): """处理文件 - 每次处理保存数据,下次运行重新开始""" if not self.input_path_var.get(): messagebox.showwarning("警告", "请先选择输入文件") logger.warning("未选择输入文件") return try: # 重置结果 self.result_text.config(state=tk.NORMAL) self.result_text.delete(1.0, tk.END) self.result_text.insert(tk.END, "开始处理...\n") self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) self.status_var.set("开始处理文件...") self.root.update() # 每次处理前重新初始化日志系统 self.setup_logger() # 记录处理开始信息 self.logger.info("=" * 50) self.logger.info(f"开始新处理会话: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") self.logger.info("=" * 50) # 更新UI显示当前日志文件 self.status_var.set(f"当前日志: {os.path.basename(self.current_log_file)}") # 获取输入文件目录 input_file = self.input_path_var.get() input_dir = os.path.dirname(input_file) logger.info(f"开始处理文件: {input_file}") logger.info(f"文件目录: {input_dir}") # 获取SCL文件夹路径 scl_folder = self.scl_folder_var.get() if not scl_folder: # 如果没有指定SCL文件夹,则使用输入文件所在目录 scl_folder = input_dir logger.info(f"未指定SCL文件夹,使用输入文件目录: {scl_folder}") # 使用openpyxl加载工作簿(保留格式) wb = openpyxl.load_workbook(input_file) sheet = wb.active logger.info(f"工作簿加载成功, 工作表: {sheet.title}") # 获取配置参数 prefix = self.prefix_var.get() operation_mode = self.operation_mode.get() logger.info(f"配置参数: 文件前缀={prefix}, 操作模式={operation_mode}") # 扫描E列(第5列) total_rows = sheet.max_row processed_count = 0 found_files = 0 problem_files = 0 logger.info(f"开始扫描E列, 总行数: {total_rows}") start_time = time.time() for row_idx in range(1, total_rows + 1): # 更新进度 progress = (row_idx / total_rows) * 100 self.progress_var.set(progress) self.progress_label.config(text=f"{progress:.1f}%") self.root.update() cell = sheet.cell(row=row_idx, column=5) cell_value = str(cell.value) if cell.value else "" # 检查是否包含前缀的文件名 if prefix in cell_value: # 提取文件名(可能有多个以空格分隔) file_names = re.findall(fr'{prefix}[^\s]+', cell_value) logger.info(f"行 {row_idx}: 找到文件: {', '.join(file_names)}") result_lines = [] file_has_problems = False # 标记当前行是否有问题文件 for file_name in file_names: # 构建文件路径 - 使用SCL文件夹 file_path = os.path.join(scl_folder, file_name) # 检查文件是否存在 if not os.path.exists(file_path): result_lines.append(f"{file_name}: 文件不存在") logger.warning(f"文件不存在: {file_path}") # 标记文件不存在的单元格为紫色 self.highlight_cell(sheet, row_idx, 5, "FF800080") file_has_problems = True problem_files += 1 continue # 根据操作模式执行不同处理 if operation_mode == "stats": # 统计模式 results, color_report, missing_data = self.stats_processor.process_file(file_path) # 如果有数据缺失 if missing_data: file_has_problems = True problem_files += 1 result_lines.append(f"{file_name}: 数据缺失!") for item in missing_data: result_lines.append(f" - {item['message']}") logger.warning(item['message']) else: result_lines.append(f"{file_name}: 处理完成") # 将结果写入主Excel文件的不同列 for rule_name, result_str in results.items(): target_col = self.stats_processor.RULE_MAPPING.get(rule_name) if target_col: target_cell = sheet.cell(row=row_idx, column=target_col) target_cell.value = result_str elif operation_mode == "empty_check": # SCL格式检查模式 checksheet_path = self.checksheet_path_var.get() missing_data, marked_file_path = self.empty_cell_detector.detect_empty_cells( file_path, checksheet_path ) if missing_data: file_has_problems = True problem_files += 1 result_lines.append(f"{file_name}: 发现空单元格!") for item in missing_data: result_lines.append(f" - {item['message']}") logger.warning(item['message']) # 添加标记文件路径信息 if marked_file_path: result_lines.append(f"已生成标记文件: {marked_file_path}") else: result_lines.append(f"{file_name}: 无空单元格问题") found_files += 1 # 如果该行有文件存在问题,将E列单元格标红 if file_has_problems: self.highlight_cell(sheet, row_idx, 5) logger.info(f"行 {row_idx} E列单元格标记为红色(存在问题)") # 更新结果文本框 self.result_text.config(state=tk.NORMAL) self.result_text.insert( tk.END, f"行 {row_idx} 处理结果:\n" + "\n".join(result_lines) + "\n\n" ) self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) processed_count += 1 # 保存修改后的Excel文件 - 每次处理保存数据 output_path = input_file.replace(".xlsx", "_processed.xlsx") wb.save(output_path) logger.info(f"结果已保存到: {output_path}") elapsed_time = time.time() - start_time status_msg = f"处理完成! 处理了 {processed_count} 个文件项, 耗时 {elapsed_time:.2f} 秒" if problem_files > 0: status_msg += f", {problem_files} 个文件存在问题" self.status_var.set(status_msg) logger.info(status_msg) # 更新结果文本框 self.result_text.config(state=tk.NORMAL) self.result_text.insert( tk.END, f"\n{status_msg}\n" f"结果已保存到: {output_path}\n" ) self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) messagebox.showinfo("完成", status_msg) except Exception as e: error_msg = f"处理文件时出错: {str(e)}" logger.exception(f"处理文件时出错: {str(e)}") messagebox.showerror("错误", error_msg) self.status_var.set(f"错误: {str(e)}") # 更新结果文本框 self.result_text.config(state=tk.NORMAL) self.result_text.insert(tk.END, f"\n错误: {error_msg}\n") self.result_text.see(tk.END) self.result_text.config(state=tk.DISABLED) def view_log(self): """查看日志""" log_window = tk.Toplevel(self.root) log_window.title("处理日志") log_window.geometry("800x600") log_frame = ttk.Frame(log_window, padding="10") log_frame.pack(fill=tk.BOTH, expand=True) # 日志文本框 log_text = scrolledtext.ScrolledText( log_frame, wrap=tk.WORD, height=30 ) log_text.pack(fill=tk.BOTH, expand=True) # 读取日志文件 log_file = 'scl_processor.log' try: if not os.path.exists(log_file): with open(log_file, 'w', encoding='utf-8') as f: f.write("日志文件已创建,暂无记录\n") with open(log_file, 'r', encoding='utf-8') as f: log_content = f.read() log_text.insert(tk.END, log_content) except Exception as e: log_text.insert(tk.END, f"无法读取日志文件: {str(e)}") # 设置为只读 log_text.config(state=tk.DISABLED) # 添加刷新按钮 refresh_btn = ttk.Button(log_frame, text="刷新日志", command=lambda: self.refresh_log(log_text)) refresh_btn.pack(pady=5) logger.info("日志查看窗口已打开") def refresh_log(self, log_text): """刷新日志内容""" log_text.config(state=tk.NORMAL) log_text.delete(1.0, tk.END) try: with open('scl_processor.log', 'r', encoding='utf-8') as f: log_content = f.read() log_text.insert(tk.END, log_content) except Exception as e: log_text.insert(tk.END, f"刷新日志失败: {str(e)}") log_text.config(state=tk.DISABLED) log_text.see(tk.END) logger.info("日志已刷新") def export_config(self): """导出配置到文件""" config = { "prefix": self.prefix_var.get(), "log_level": self.log_level_var.get(), "operation_mode": self.operation_mode.get(), "tolerance": self.tolerance_var.get(), "checksheet_path": self.checksheet_path_var.get(), "scl_folder": self.scl_folder_var.get() # 添加SCL文件夹路径 } file_path = filedialog.asksaveasfilename( defaultextension=".json", filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")] ) if file_path: try: with open(file_path, 'w', encoding='utf-8') as f: f.write(str(config)) messagebox.showinfo("成功", f"配置已导出到: {file_path}") logger.info(f"配置已导出到: {file_path}") except Exception as e: messagebox.showerror("错误", f"导出配置失败: {str(e)}") logger.error(f"导出配置失败: {str(e)}") def load_config(self): """从文件加载配置""" file_path = filedialog.askopenfilename( filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")] ) if file_path: try: with open(file_path, 'r', encoding='utf-8') as f: config = eval(f.read()) self.prefix_var.set(config.get("prefix", "SCL_")) self.log_level_var.set(config.get("log_level", "INFO")) self.operation_mode.set(config.get("operation_mode", "stats")) self.tolerance_var.set(config.get("tolerance", 30)) self.checksheet_path_var.set(config.get("checksheet_path", "")) self.scl_folder_var.set(config.get("scl_folder", "")) # 加载SCL文件夹路径 self.change_log_level() messagebox.showinfo("成功", "配置已加载") logger.info(f"配置已从 {file_path} 加载") except Exception as e: messagebox.showerror("错误", f"加载配置失败: {str(e)}") logger.error(f"加载配置失败: {str(e)}") 顺序改一下,用户先选择统计模式还是SCL格式检查模式,如果是统计模式就必须要选择统计表路径,但是如果是SCL格式检查模式就不用选择统计表路径
最新发布
08-12
import os import subprocess import shutil import time import tkinter as tk from tkinter import filedialog, ttk, scrolledtext, messagebox, PhotoImage import pandas as pd import win32com.client as win32 from bs4 import BeautifulSoup import threading import tempfile import queue import traceback class DiffProcessorApp: def __init__(self, root): self.root = root root.title("高?文件?比?工具") root.geometry("1000x700") root.configure(bg="#f5f5f5") # ?建?代?格主? self.style = ttk.Style() self.style.theme_use('clam') # 自定?主??色 self.style.configure('TButton', font=('Segoe UI', 10, 'bold'), borderwidth=1, foreground="#333", background="#4CAF50", bordercolor="#388E3C", relief="flat", padding=8, anchor="center") self.style.map('TButton', background=[('active', '#388E3C'), ('disabled', '#BDBDBD')], foreground=[('disabled', '#9E9E9E')]) self.style.configure('TLabel', font=('Segoe UI', 9), background="#f5f5f5") self.style.configure('TLabelframe', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", relief="flat", borderwidth=2) self.style.configure('TLabelframe.Label', font=('Segoe UI', 10, 'bold'), background="#f5f5f5", foreground="#2E7D32") self.style.configure('Treeview', font=('Segoe UI', 9), rowheight=25) self.style.configure('Treeview.Heading', font=('Segoe UI', 9, 'bold')) # ?建主框架 main_frame = ttk.Frame(root, padding="15") main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # ??区域 header_frame = ttk.Frame(main_frame) header_frame.pack(fill=tk.X, pady=(0, 15)) # 添加???? try: icon = PhotoImage(file="folder_icon.png") self.icon_label = ttk.Label(header_frame, image=icon) self.icon_label.image = icon self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) except: self.icon_label = ttk.Label(header_frame, text="?", font=("Arial", 24)) self.icon_label.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(header_frame, text="高?文件?比?工具", font=("Segoe UI", 18, "bold"), foreground="#2E7D32") title_label.pack(side=tk.LEFT) # 文件??区域 file_frame = ttk.LabelFrame(main_frame, text="文件???", padding="12") file_frame.pack(fill=tk.X, pady=5) # 文件??? self.old_folder_entry, self.new_folder_entry = self.create_folder_selector(file_frame, "原始文件?:") self.new_folder_entry = self.create_folder_selector(file_frame, "修改后文件?:")[0] # 比???区域 options_frame = ttk.LabelFrame(main_frame, text="比???", padding="12") options_frame.pack(fill=tk.X, pady=5) # ??比??? self.recursive_var = tk.BooleanVar(value=True) recursive_check = ttk.Checkbutton(options_frame, text="??比?子文件?", variable=self.recursive_var) recursive_check.grid(row=0, column=0, padx=10, pady=5, sticky=tk.W) # 文件?? filter_frame = ttk.Frame(options_frame) filter_frame.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W) ttk.Label(filter_frame, text="文件??:").pack(side=tk.LEFT, padx=(0, 5)) self.filter_var = tk.StringVar(value="*.*") filter_entry = ttk.Entry(filter_frame, textvariable=self.filter_var, width=15) filter_entry.pack(side=tk.LEFT) # 目?Excel?? excel_frame = ttk.LabelFrame(main_frame, text="?出?置", padding="12") excel_frame.pack(fill=tk.X, pady=5) ttk.Label(excel_frame, text="目?Excel文件:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) self.excel_file_entry = ttk.Entry(excel_frame, width=60) self.excel_file_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Button(excel_frame, text="??...", command=lambda: self.select_file(self.excel_file_entry, [("Excel文件", "*.xlsx *.xlsm")])).grid(row=0, column=2, padx=5, pady=5) # ?行按?区域 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=10) self.run_button = ttk.Button(button_frame, text="?行比?", command=self.start_processing, width=20, style='TButton') self.run_button.pack(side=tk.LEFT) # 停止按? self.stop_button = ttk.Button(button_frame, text="停止", command=self.stop_processing, width=10, state=tk.DISABLED) self.stop_button.pack(side=tk.LEFT, padx=10) # ?度条 self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=700, mode='determinate') self.progress.pack(fill=tk.X, pady=5) # 状?信息 status_frame = ttk.Frame(main_frame) status_frame.pack(fill=tk.X, pady=5) self.status_var = tk.StringVar(value="准?就?") status_label = ttk.Label(status_frame, textvariable=self.status_var, font=("Segoe UI", 9), foreground="#2E7D32") status_label.pack(side=tk.LEFT) # 日志和??区域 notebook = ttk.Notebook(main_frame) notebook.pack(fill=tk.BOTH, expand=True, pady=5) # 文件????? tree_frame = ttk.Frame(notebook, padding="5") notebook.add(tree_frame, text="文件???") # ?建?形?? self.tree = ttk.Treeview(tree_frame, columns=("Status"), show="tree") self.tree.heading("#0", text="文件???", anchor=tk.W) self.tree.heading("Status", text="状?", anchor=tk.W) self.tree.column("#0", width=400) self.tree.column("Status", width=100) vsb = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) hsb = ttk.Scrollbar(tree_frame, orient="horizontal", command=self.tree.xview) self.tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set) self.tree.grid(row=0, column=0, sticky="nsew") vsb.grid(row=0, column=1, sticky="ns") hsb.grid(row=1, column=0, sticky="ew") # 日志?? log_frame = ttk.Frame(notebook, padding="5") notebook.add(log_frame, text="?行日志") self.log_text = scrolledtext.ScrolledText(log_frame, height=10, wrap=tk.WORD, font=("Consolas", 9)) self.log_text.pack(fill=tk.BOTH, expand=True) self.log_text.config(state=tk.DISABLED) # ?置网格?重 tree_frame.grid_rowconfigure(0, weight=1) tree_frame.grid_columnconfigure(0, weight=1) # ?程控制 self.processing = False self.queue = queue.Queue() # ???列?理 self.root.after(100, self.process_queue) def create_folder_selector(self, parent, label_text): """?建文件???器?件""" frame = ttk.Frame(parent) frame.pack(fill=tk.X, pady=5) ttk.Label(frame, text=label_text).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) entry = ttk.Entry(frame, width=70) entry.grid(row=0, column=1, padx=5, pady=5) button = ttk.Button(frame, text="??文件?...", command=lambda: self.select_folder(entry)) button.grid(row=0, column=2, padx=5, pady=5) return entry, button def select_folder(self, entry): """??文件?""" foldername = filedialog.askdirectory() if foldername: entry.delete(0, tk.END) entry.insert(0, foldername) # 自?填充文件??? self.populate_folder_tree(foldername) def select_file(self, entry, filetypes=None): """??文件""" if filetypes is None: filetypes = [("所有文件", "*.*")] filename = filedialog.askopenfilename(filetypes=filetypes) if filename: entry.delete(0, tk.END) entry.insert(0, filename) def populate_folder_tree(self, path): """填充文件????""" self.tree.delete(*self.tree.get_children()) if not os.path.isdir(path): return # 添加根?点 root_node = self.tree.insert("", "end", text=os.path.basename(path), values=("文件?",), open=True) self.add_tree_nodes(root_node, path) def add_tree_nodes(self, parent, path): """??添加??点""" try: for item in os.listdir(path): item_path = os.path.join(path, item) if os.path.isdir(item_path): node = self.tree.insert(parent, "end", text=item, values=("文件?",)) self.add_tree_nodes(node, item_path) else: self.tree.insert(parent, "end", text=item, values=("文件",)) except PermissionError: self.log_message(f"?限??: 无法?? {path}") def log_message(self, message): """??日志消息""" self.queue.put(("log", message)) def update_progress(self, value): """更新?度条""" self.queue.put(("progress", value)) def update_status(self, message): """更新状?信息""" self.queue.put(("status", message)) def process_queue(self): """?理?程?列中的消息""" try: while not self.queue.empty(): msg_type, data = self.queue.get_nowait() if msg_type == "log": self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, data + "\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) elif msg_type == "progress": self.progress['value'] = data elif msg_type == "status": self.status_var.set(data) except queue.Empty: pass self.root.after(100, self.process_queue) def parse_html_diff(self, html_path): """解析HTML差?文件""" self.log_message("正在解析HTML差?文件...") try: with open(html_path, 'r', encoding='utf-8') as f: content = f.read() soup = BeautifulSoup(content, 'html.parser') diff_table = soup.find('table', {'class': 'diff'}) if not diff_table: self.log_message("??: 未找到差?表格") return None # 提取表格数据 diff_data = [] for row in diff_table.find_all('tr')[1:]: # 跳?表? cols = row.find_all('td') if len(cols) >= 3: # 修?TypeError: ?保所有?都是字符串 diff_type = str(cols[0].get_text(strip=True)) content_left = str(cols[1].get_text(strip=True)) content_right = str(cols[2].get_text(strip=True)) diff_data.append([diff_type, content_left, content_right]) # 修?TypeError: 使用f-string???果 self.log_message(f"成功解析 {len(diff_data)} 行差?数据") return diff_data except Exception as e: # 修?TypeError: 使用f-string???常 error_msg = f"解析HTML失?: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) return None def write_to_excel(self, excel_path, diff_data): """将差?数据写入Excel""" self.log_message("正在写入Excel文件...") try: # 使用win32com打?Excel excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True workbook = excel.Workbooks.Open(os.path.abspath(excel_path)) sheet = workbook.Sheets("一覧") # 从第6行?始写入数据 start_row = 6 for i, row_data in enumerate(diff_data): for j, value in enumerate(row_data[:6]): # ?保?是字符串?型 sheet.Cells(start_row + i, j + 1).Value = str(value) # 保存Excel workbook.Save() self.log_message(f"数据已写入Excel第{start_row}行?始") # 触?"作成"按? self.log_message("正在触?'作成'按?...") try: # ?找按?并点? button = sheet.Buttons("作成") button.OnAction = "作成按?的?理" button.Click() self.log_message("已触?'作成'按?") # 等待?理完成 self.update_status("?理中...?等待") # ??等待机制 for _ in range(30): # 最多等待30秒 if not self.processing: break if excel.CalculationState == 0: # 0 = xlDone break time.sleep(1) self.log_message("?理中...") self.log_message("?理完成") self.update_status("?理完成") except Exception as e: # 修?TypeError: 使用f-string???常 self.log_message(f"按?操作失?: {str(e)}. ?手?点?'作成'按?") # ??Excel workbook.Close() excel.Quit() return True except Exception as e: # 修?TypeError: 使用f-string???常 self.log_message(f"Excel操作失?: {str(e)}\n{traceback.format_exc()}") return False def start_processing(self): """???理?程 - 修?无????""" if self.processing: self.log_message("警告: ?理正在?行中") return # ?取路径 old_path = self.old_folder_entry.get() new_path = self.new_folder_entry.get() excel_file = self.excel_file_entry.get() # ??路径?? validation_errors = [] if not old_path: validation_errors.append("原始文件?路径?空") elif not os.path.isdir(old_path): validation_errors.append(f"原始文件?路径无效: {old_path}") if not new_path: validation_errors.append("新文件?路径?空") elif not os.path.isdir(new_path): validation_errors.append(f"新文件?路径无效: {new_path}") if not excel_file: validation_errors.append("Excel文件路径?空") elif not excel_file.lower().endswith(('.xlsx', '.xlsm')): validation_errors.append("Excel文件必?是.xlsx或.xlsm格式") if validation_errors: self.log_message("??: " + "; ".join(validation_errors)) messagebox.showerror("?入??", "\n".join(validation_errors)) return # ??WinMerge安装 winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" if not os.path.exists(winmerge_path): self.log_message(f"??: WinMerge未安装在默?位置 {winmerge_path}") messagebox.showwarning("WinMerge未安装", "??保WinMerge已安装或更新路径配置") return # 禁用?行按?,?用停止按? self.run_button.config(state=tk.DISABLED) self.stop_button.config(state=tk.NORMAL) self.processing = True # ???理?程 thread = threading.Thread(target=self.process_folders, args=(old_path, new_path, excel_file)) thread.daemon = True thread.start() self.log_message("?理?程已??") def process_folders(self, old_path, new_path, excel_file): """?理文件?比?的?程函数 - 增??常?理""" output_html = None try: # ??1: 生成HTML差?文件 self.update_status("生成HTML差?文件...") self.update_progress(20) # 使用??文件存?HTML?告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: output_html = temp_file.name if not self.run_winmerge(old_path, new_path, output_html): self.update_status("WinMerge?行失?") return # ??2: 将HTML文件与Excel放在同一目? self.update_status("准?文件...") self.update_progress(40) excel_dir = os.path.dirname(excel_file) if excel_dir: target_html = os.path.join(excel_dir, "diff_report.html") try: shutil.copy(output_html, target_html) self.log_message(f"已将HTML文件?制到: {target_html}") except Exception as e: self.log_message(f"文件?制失?: {str(e)}") return # ??3: 解析HTML差?文件 self.update_status("解析差?数据...") self.update_progress(60) diff_data = self.parse_html_diff(output_html) if not diff_data: self.update_status("HTML解析失?") return # ??4: 写入Excel并触?按? self.update_status("写入Excel并触??理...") self.update_progress(80) if not self.write_to_excel(excel_file, diff_data): self.update_status("Excel操作失?") return # 完成 self.update_progress(100) self.update_status("?理完成!") self.log_message("文件?比?流程?行完?") messagebox.showinfo("完成", "文件?比??理成功完成") except Exception as e: error_msg = f"?行?程中?生??: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("?行失?") messagebox.showerror("??", f"?理失?: {str(e)}") finally: # 重新?用?行按? if self.processing: self.stop_processing() # 清理??文件 if output_html and os.path.exists(output_html): try: os.remove(output_html) except: pass def run_winmerge(self, path1, path2, output_html): """?用WinMerge生成HTML差?文件 - 增????理""" winmerge_path = r"E:\App\WinMerge\WinMerge2.16.12.0\WinMergeU.exe" # ??WinMerge可?行文件 if not os.path.exists(winmerge_path): self.log_message(f"??: WinMerge路径不存在 {winmerge_path}") return False winmerge_cmd = [ winmerge_path, '/u', '/dl', 'Base', '/dr', 'Modified', '/or', output_html, path1, path2 ] # 添加???? if self.recursive_var.get(): winmerge_cmd.insert(1, '/r') self.log_message(f"?行WinMerge命令: {' '.join(winmerge_cmd)}") try: result = subprocess.run( winmerge_cmd, capture_output=True, text=True, timeout=120, creationflags=subprocess.CREATE_NO_WINDOW # 避免控制台窗口?? ) if result.returncode == 0: self.log_message(f"HTML差??告生成完成: {output_html}") return True else: error_msg = f"WinMerge?行失?(退出?{result.returncode}): {result.stderr}" self.log_message(error_msg) return False except subprocess.TimeoutExpired: self.log_message("WinMerge?行超?(120秒),????入文件大小") return False except Exception as e: self.log_message(f"WinMerge?行??: {str(e)}") return False def write_to_excel(self, excel_path, diff_data): """将差?数据写入Excel - 增?健壮性""" self.log_message("正在写入Excel文件...") excel = None workbook = None try: # ??Excel文件存在 if not os.path.exists(excel_path): self.log_message(f"??: Excel文件不存在 {excel_path}") return False # 使用win32com打?Excel excel = win32.gencache.EnsureDispatch('Excel.Application') excel.Visible = True excel.DisplayAlerts = False # 禁用警告提示 # ??打?工作簿 try: workbook = excel.Workbooks.Open(os.path.abspath(excel_path)) except Exception as e: self.log_message(f"打?Excel文件失?: {str(e)}") return False # ??工作表是否存在 sheet_names = [sheet.Name for sheet in workbook.Sheets] if "一覧" not in sheet_names: self.log_message("??: Excel文件中缺少'一覧'工作表") return False sheet = workbook.Sheets("一覧") # 从第6行?始写入数据 start_row = 6 for i, row_data in enumerate(diff_data): for j, value in enumerate(row_data[:6]): # ?保?是字符串?型 sheet.Cells(start_row + i, j + 1).Value = str(value) # 保存Excel workbook.Save() self.log_message(f"数据已写入Excel第{start_row}行?始") # 触?"作成"按? self.log_message("正在触?'作成'按?...") try: # ?找按?并点? button = sheet.Buttons("作成") button.OnAction = "作成按?的?理" button.Click() self.log_message("已触?'作成'按?") # 等待?理完成 self.update_status("?理中...?等待") wait_time = 0 max_wait = 60 # 最大等待60秒 while self.processing and wait_time < max_wait: if excel.CalculationState == 0: # 0 = xlDone break time.sleep(1) wait_time += 1 self.log_message(f"?理中...({wait_time}秒)") if wait_time >= max_wait: self.log_message("警告: ?理超?") else: self.log_message("?理完成") return True except Exception as e: self.log_message(f"按?操作失?: {str(e)}. ?手?点?'作成'按?") return False except Exception as e: self.log_message(f"Excel操作失?: {str(e)}\n{traceback.format_exc()}") return False finally: # ?保正???Excel try: if workbook: workbook.Close(SaveChanges=False) if excel: excel.Quit() except Exception as e: self.log_message(f"??Excel?出?: {str(e)}") def stop_processing(self): """停止?理""" self.processing = False self.stop_button.config(state=tk.DISABLED) self.run_button.config(state=tk.NORMAL) self.update_status("操作已停止") def process_folders(self, old_path, new_path, excel_file): """?理文件?比?的?程函数""" try: # ??1: 生成HTML差?文件 self.update_status("生成HTML差?文件...") self.update_progress(20) # 使用??文件存?HTML?告 with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as temp_file: output_html = temp_file.name if not self.run_winmerge(old_path, new_path, output_html): return # ??2: 将HTML文件与Excel放在同一目? self.update_status("准?文件...") self.update_progress(40) excel_dir = os.path.dirname(excel_file) if excel_dir: target_html = os.path.join(excel_dir, "diff_report.html") shutil.copy(output_html, target_html) self.log_message(f"已将HTML文件?制到: {target_html}") # ??3: 解析HTML差?文件 self.update_status("解析差?数据...") self.update_progress(60) diff_data = self.parse_html_diff(output_html) if not diff_data: return # ??4: 写入Excel并触?按? self.update_status("写入Excel并触??理...") self.update_progress(80) self.write_to_excel(excel_file, diff_data) # 完成 self.update_progress(100) self.update_status("?理完成!") self.log_message("文件?比?流程?行完?") except Exception as e: # 修?TypeError: 使用f-string???常 error_msg = f"?行?程中?生??: {str(e)}\n{traceback.format_exc()}" self.log_message(error_msg) self.update_status("?行失?") finally: # 重新?用?行按? if self.processing: self.stop_processing() # 清理??文件 if os.path.exists(output_html): try: os.remove(output_html) except: pass if __name__ == "__main__": root = tk.Tk() app = DiffProcessorApp(root) root.mainloop() 我想用这个代码完成上述功能
07-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值