第11题 Search Range in Binary Search Tree

本文介绍了一种在二叉搜索树中查找指定区间内所有节点值的方法,并通过递归方式实现了这一功能。该算法返回指定区间内的所有节点值,并确保结果按升序排列。

Search Range in Binary Search Tree

Description
Given a binary search tree and a range [k1, k2], return node values within a given range in ascending order.

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */

public class Solution {
    /**
     * @param root: param root: The root of the binary search tree
     * @param k1: An integer
     * @param k2: An integer
     * @return: return: Return all keys that k1<=key<=k2 in ascending order
     */
    public List<Integer> searchRange(TreeNode root, int k1, int k2) {
        // write your code here
        ArrayList<Integer> results = new ArrayList<>();
        if(root == null){
           return results;
        }
        List<Integer> left = searchRange(root.left, k1, k2);
        List<Integer> right = searchRange(root.right, k1, k2);
        if (root.val <= k2 && root.val >= k1){
            results.add(root.val);
        }
        results.addAll(left);
        results.addAll(right);
        Collections.sort(results);
        return results;

    }
}
# -*- coding: utf-8 -*- import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd import tempfile class EnhancedFileSearchApp: def __init__(self, master): self.master = master master.title("🎯 高级文件搜索工具") master.geometry("1200x800") master.minsize(1000, 700) # 设置现代主和颜色方案 theme = "vista" if sys.platform == "win32" else "aqua" self.style = ttk.Style() self.style.theme_use(theme) # 自定义配色方案 self.colors = { "bg": "#f5f6fa", "header": "#3498db", "accent": "#2980b9", "warning": "#e74c3c", "success": "#2ecc71", "text": "#2c3e50", "highlight": "#f1c40f" } # 设置主窗口背景色 master.configure(bg=self.colors["bg"]) # 创建主框架 - 使用grid布局更精确控制 main_frame = ttk.Frame(master) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 减少主框架外边距 # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(10, 8), # 减少内边距 style="Search.TLabelframe" ) search_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=(0, 5)) # 减少下边距 # 配置搜索面板样式 self.style.configure("Search.TLabelframe", background=self.colors["bg"], bordercolor=self.colors["accent"], lightcolor=self.colors["accent"]) self.style.configure("Search.TLabelframe.Label", font=("Arial", 10, "bold"), foreground=self.colors["header"]) # 搜索目录 - 优化布局减少垂直空间 dir_frame = ttk.Frame(search_frame) dir_frame.pack(fill=tk.X, pady=3) # 减少垂直间距 ttk.Label(dir_frame, text="📁 搜索目录:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.dir_entry = ttk.Entry(dir_frame, width=35) self.dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) self.dir_entry.insert(0, os.getcwd()) browse_btn = ttk.Button( dir_frame, text="浏览...", command=self.browse_directory, style="Accent.TButton" ) browse_btn.pack(side=tk.RIGHT) # 关键词 kw_frame = ttk.Frame(search_frame) kw_frame.pack(fill=tk.X, pady=5) ttk.Label(kw_frame, text="🔍 关键词:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.keyword_entry = ttk.Entry(kw_frame, width=40) self.keyword_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) # 文件过滤 filter_frame = ttk.Frame(search_frame) filter_frame.pack(fill=tk.X, pady=5) ttk.Label(filter_frame, text="📄 文件过滤:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.filter_entry = ttk.Entry(filter_frame, width=40) self.filter_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) self.filter_entry.insert(0, "*.c;*.h;*.prm;*.xlsx;*.xls;*.doc;*.docx;*.pdf") # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.pack(fill=tk.X, pady=5) self.case_var = tk.BooleanVar(value=False) case_chk = ttk.Checkbutton( options_frame, text="忽略大小写", variable=self.case_var, style="Custom.TCheckbutton" ) case_chk.pack(side=tk.LEFT, padx=(0, 15)) self.regex_var = tk.BooleanVar(value=False) regex_chk = ttk.Checkbutton( options_frame, text="正则表达式", variable=self.regex_var, style="Custom.TCheckbutton" ) regex_chk.pack(side=tk.LEFT, padx=(0, 15)) self.binary_var = tk.BooleanVar(value=False) binary_chk = ttk.Checkbutton( options_frame, text="包含二进制", variable=self.binary_var, style="Custom.TCheckbutton" ) binary_chk.pack(side=tk.LEFT, padx=(0, 15)) self.highlight_var = tk.BooleanVar(value=True) highlight_chk = ttk.Checkbutton( options_frame, text="关键字高亮", variable=self.highlight_var, style="Custom.TCheckbutton" ) highlight_chk.pack(side=tk.LEFT) # 配置选项样式 self.style.configure("Custom.TCheckbutton", font=("Arial", 9), foreground=self.colors["text"]) # 按钮组 btn_frame = ttk.Frame(search_frame) btn_frame.pack(fill=tk.X, pady=(8, 5)) self.search_btn = ttk.Button( btn_frame, text="🔎 开始搜索", command=self.start_search, style="Accent.TButton" ) self.search_btn.pack(side=tk.LEFT, padx=(0, 10)) self.stop_btn = ttk.Button( btn_frame, text="⏹️ 停止", command=self.stop_search, style="Warning.TButton", state=tk.DISABLED ) self.stop_btn.pack(side=tk.LEFT, padx=(0, 10)) self.export_btn = ttk.Button( btn_frame, text="💾 导出结果", command=self.export_results, style="Success.TButton" ) self.export_btn.pack(side=tk.LEFT) # 按钮样式 self.style.configure("Accent.TButton", font=("Arial", 10, "bold"), foreground="white", background=self.colors["accent"]) self.style.configure("Warning.TButton", foreground="white", background=self.colors["warning"]) self.style.configure("Success.TButton", foreground="white", background=self.colors["success"]) # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.pack(fill=tk.X, pady=(10, 0)) self.status_label = ttk.Label( status_frame, text="🟢 就绪", foreground=self.colors["success"], font=("Arial", 9, "bold") ) self.status_label.pack(side=tk.LEFT) self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=100, mode="determinate", style="Custom.Horizontal.TProgressbar" ) self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 进度条样式 self.style.configure("Custom.Horizontal.TProgressbar", thickness=20, background=self.colors["accent"], troughcolor=self.colors["bg"]) self.stats_label = ttk.Label( status_frame, text="", foreground=self.colors["text"], font=("Arial", 9) ) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 - 使用grid放置在下层 results_frame = ttk.Frame(main_frame) results_frame.grid(row=1, column=0, sticky="nsew", pady=0) # 无垂直间距 # 配置网格权重 - 结果面板占据剩余空间 main_frame.rowconfigure(1, weight=1) main_frame.columnconfigure(0, weight=1) # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 文件列表(左侧) file_frame = ttk.LabelFrame( pane, text="📂 搜索结果", padding=(8, 6) # 减少内边距 ) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame( pane, text="🔍 预览内容", padding=(8, 6) # 减少内边距 ) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 定义树状视图列时添加fullpath列 columns = ("filename", "size", "modified", "fullpath") self.file_tree = ttk.Treeview( file_tree_frame, columns=columns, show="headings", selectmode="browse" ) # 配置列时隐藏fullpath列 self.file_tree.column("fullpath", width=0, stretch=False) # 隐藏列 # 配置Treeview样式 self.style.configure("Custom.Treeview", font=("Arial", 9), rowheight=25) self.style.configure("Custom.Treeview.Heading", font=("Arial", 9, "bold"), background=self.colors["accent"], foreground="white") # 配置列 self.file_tree.heading("filename", text="文件名", anchor="w") self.file_tree.heading("size", text="大小", anchor="center") self.file_tree.heading("modified", text="修改时间", anchor="w") self.file_tree.column("filename", width=250, anchor="w") self.file_tree.column("size", width=80, anchor="center") self.file_tree.column("modified", width=150, anchor="w") scroll_y = ttk.Scrollbar(file_tree_frame, orient="vertical", command=self.file_tree.yview) scroll_x = ttk.Scrollbar(file_tree_frame, orient="horizontal", command=self.file_tree.xview) self.file_tree.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set) self.file_tree.grid(row=0, column=0, sticky="nsew") scroll_y.grid(row=0, column=1, sticky="ns") scroll_x.grid(row=1, column=0, sticky="ew") file_tree_frame.rowconfigure(0, weight=1) file_tree_frame.columnconfigure(0, weight=1) self.preview_text = scrolledtext.ScrolledText( preview_frame, wrap=tk.WORD, font=("Consolas", 10), padx=8, # 减少内边距 pady=8, # 减少内边距 bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 配置高亮标签 self.preview_text.tag_configure("highlight", background=self.colors["highlight"]) self.preview_text.tag_configure("match", background="#c8e6c9") self.preview_text.tag_configure("header", foreground=self.colors["success"], font=("Arial", 10, "bold")) self.preview_text.tag_configure("warning", foreground=self.colors["warning"], font=("Arial", 9, "italic")) self.preview_text.tag_configure("linenum", foreground="#7f8c8d") # 初始化变量 self.search_thread = None self.stop_requested = False self.results = {} self.all_files = [] # 绑定事件 self.file_tree.bind("<<TreeviewSelect>>", self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) # 配置网格布局 results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(0, weight=1) def browse_directory(self): """浏览目录""" directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): """开始搜索""" # 重置状态 self.progress["value"] = 0 self.stop_requested = False self.results = {} self.all_files = [] # 清空UI for item in self.file_tree.get_children(): self.file_tree.delete(item) self.preview_text.delete("1.0", tk.END) # 获取参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") return self.status_label.config(text="🟡 正在搜索...", foreground=self.colors["warning"]) self.search_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # 解析文件过滤器 filter_patterns = [pat.strip() for pat in file_filter.split(';')] if ';' in file_filter else [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: pattern = re.compile(re.escape(keyword), flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) return # 在后台线程搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config( text="🟠 搜索已取消", foreground=self.colors["warning"])) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) limit = 100 * 1024 * 1024 # 100MB if file_size > limit: continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 更新UI self.master.after(0, lambda: self.progress.configure(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"0/{total_files}")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in all_files: if self.stop_requested: break processed += 1 # 更新进度 self.master.after(0, lambda v=processed: self.progress.configure(value=v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"{p}/{t} ({p/t*100:.1f}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理不同文件类型 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 获取文件信息 file_name = os.path.basename(file_path) try: size = os.path.getsize(file_path) size_str = f"{size/1024:.1f}KB" if size < 1024*1024 else f"{size/(1024*1024):.1f}MB" mod_time = time.ctime(os.path.getmtime(file_path)) except: size_str = "N/A" mod_time = "N/A" # 在UI线程中添加文件到列表 self.master.after(0, lambda fp=file_path, fn=file_name, sz=size_str, mt=mod_time: self.file_tree.insert("", "end", values=(fn, sz, mt, fp))) # 更新完成状态 if self.stop_requested: status_text = f"🟠 搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"🟢 搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text, foreground=self.colors["success"])) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress.configure(value=total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_btn.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="🟠 正在停止...", foreground=self.colors["warning"]) self.stop_btn.config(state=tk.DISABLED) def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return # 获取完整文件路径 selected_item = self.file_tree.selection()[0] filepath = self.file_tree.item(selected_item, "values")[3] # 索引3是完整路径 # 清空预览区域 self.preview_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标 self.preview_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.preview_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.preview_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.preview_text.insert(tk.END, line[start_idx:]) else: self.preview_text.insert(tk.END, line) except UnicodeDecodeError: self.preview_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, "values")[3] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.preview_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = EnhancedFileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 关键字高亮功能没有起作用,点击或者不点击在预览窗口都不会高亮
09-13
<think> 我们注意到在预览文本文件时,高亮功能没有起作用。在`show_file_content`方法中,对于文本文件的预览,我们尝试使用`pattern`来高亮匹配的关键字,但是实际显示中没有高亮效果。让我们分析一下原因: 1. 在预览文本文件时,我们使用`pattern`来查找匹配,并使用`match`标签来高亮显示。但是,可能的原因是: a) 在文本文件预览部分,我们构造的`pattern`可能不正确(比如没有考虑大小写或者正则表达式选项)。 b) 在插入文本时,我们使用的是`insert`方法,并且按字符位置插入,但是标签的范围可能没有正确设置。 2. 观察代码,在文本文件预览部分,我们重新构造了`pattern`,但是这里使用的是当前的关键字和选项(忽略大小写等)。然而,在搜索时我们使用的`pattern`是已经编译好的,而在预览时我们又重新编译了一次。如果重新编译时出现错误(比如正则表达式无效),我们设置了`pattern = None`,这样就不会进行高亮。 3. 另外,在插入文本时,我们每次插入一行的一部分(匹配前、匹配文本、匹配后),但是要注意,`ScrolledText`组件的`insert`方法每次插入都会导致索引变化,我们需要确保标签应用到正确的文本范围。 4. 但是,我们当前的方法是在每次插入一段文本后立即插入下一段,这样连续的插入操作,实际上文本是连续的。然而,我们为匹配的文本单独添加了标签,这应该是可行的。 5. 另一个可能的原因是,我们为匹配文本设置的标签`"match"`没有正确配置样式?检查代码,我们在`__init__`方法中配置了`match`标签: self.preview_text.tag_configure("match", background="#c8e6c9") 所以样式是存在的。 6. 还有一种可能:在预览文本文件时,我们重新编译了正则表达式,但在搜索时使用的正则表达式和预览时使用的正则表达式可能有差异?比如搜索时使用的是`re.escape`(当不是正则表达式时),而预览时对于非正则表达式也使用了`re.escape`,所以应该一致。 7. 然而,在预览文本文件部分,如果正则表达式无效,我们会将`pattern`设为`None`,这样就不会进行高亮。所以,如果我们在搜索时使用的是有效的正则表达式,但在预览时重新编译失败(比如因为特殊字符),那么高亮就不会生效。 8. 为了避免重复编译以及保持一致性,我们可以修改:在搜索线程中,我们将编译好的`pattern`对象保存为实例变量,然后在预览时直接使用这个`pattern`。或者,在预览文本文件时,我们可以使用搜索时生成的`pattern`(但注意,搜索是在另一个线程中,我们无法直接使用,而且搜索可能已经结束,我们可以将`pattern`保存在实例变量中)。 9. 但是,我们的搜索线程可能会多次执行(用户可能多次搜索),每次搜索都会有不同的`pattern`。因此,我们可以在`start_search`方法中将当前搜索的`pattern`保存下来,例如`self.current_pattern = pattern`。然后在预览时使用`self.current_pattern`。 10. 另外,在预览Office文件和压缩文件时,我们并没有高亮显示,因为我们是直接显示匹配的摘要信息。所以高亮功能目前只针对文本文件。 11. 因此,我们可以做如下修改: a) 在`start_search`方法中,将编译好的`pattern`保存为实例变量`self.current_pattern`。 b) 在`show_file_content`方法中,对于文本文件,直接使用`self.current_pattern`进行高亮,而不再重新编译。 12. 同时,为了避免在搜索过程中预览(此时`self.current_pattern`可能被改变),我们可以考虑在预览时使用一个局部变量保存当前的`current_pattern`,但考虑到搜索线程和主线程是分开的,并且预览是在主线程中,我们在预览时使用的`self.current_pattern`是最近一次搜索的,这应该是合理的。 13. 修改步骤: - 在`start_search`方法中,在设置好`pattern`后,添加: self.current_pattern = pattern # 保存当前搜索模式 - 在`show_file_content`方法中,注释掉重新编译`pattern`的代码,直接使用`self.current_pattern`。同时,去掉`flags`,因为编译时已经包含了标志。 - 注意:在程序开始时,`self.current_pattern`可能不存在,所以需要判断。 14. 另外,在文本文件预览部分,我们使用`pattern.finditer(line)`来查找匹配,但要注意,如果一行中有多个匹配,我们需要为每个匹配都应用标签。 15. 但是,我们当前的代码已经处理了多个匹配,所以应该没问。 16. 此外,还有一个问:在预览文本文件时,我们使用了`with open(filepath, 'r', encoding=encoding, errors='replace') as f`,但是如果我们读取的文件很大,预览全部内容会消耗很多内存,所以目前我们只显示前1000行。但是高亮操作是在读取每一行时进行的,所以没有问。 17. 我们还需要考虑,在搜索时我们可能使用了忽略大小写的选项,而这个选项已经包含在`pattern`中,所以使用保存的`pattern`没有问。 18. 修改后的`show_file_content`方法中文本文件预览部分: # 使用保存的搜索模式 pattern = self.current_pattern # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") ... [其余代码不变,使用pattern] ... 19. 但是,在第一次搜索之前,如果用户还没有进行搜索,那么`self.current_pattern`不存在,所以需要判断。可以在初始化时设置为None,并在使用时判断。 20. 在`__init__`方法中,添加: self.current_pattern = None 21. 在预览文本文件时: if pattern is None: # 如果没有搜索模式,则使用当前的关键字和选项重新编译(但这种情况应该很少,因为用户通常先搜索再预览) keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) 22. 但是为了简化,我们可以要求用户必须先搜索才能预览(因为预览的功能依赖于搜索的结果),所以如果还没有搜索,我们可以不显示高亮。 23. 我们按照保存的`pattern`来显示,如果没有保存的`pattern`,就不高亮。 24. 修改后的代码: # 文本文件预览部分 pattern = self.current_pattern # 使用保存的搜索模式 # 如果没有搜索模式,则尝试根据当前输入框的关键字创建一个(但注意选项是当前的选项) if pattern is None: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() if keyword: flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) ... [然后使用pattern] ... 25. 但是,这样修改后,如果用户改变了选项(比如大小写),那么重新编译的`pattern`和搜索时的`pattern`可能不一致。因此,我们最好还是使用搜索时使用的`pattern`。所以,我们只需要保存搜索时的`pattern`,并在预览时使用它。 26. 因此,我们不需要在预览时重新考虑选项,因为高亮应该和搜索匹配的结果一致。 27. 所以,我们直接使用`self.current_pattern`,如果为None,就不高亮。 28. 修改后的代码: pattern = self.current_pattern # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # ... [限制行数] ... # 插入行内容并高亮匹配 if pattern is not None: # 如果有有效的搜索模式 # 按照之前的方式高亮 else: # 直接插入行而没有高亮 self.preview_text.insert(tk.END, line) 29. 另外,在搜索线程中,我们将`pattern`保存为`self.current_pattern`,但要注意,搜索线程可能同时执行多次?我们目前的设计是,如果用户点击搜索,会停止之前的搜索(通过`stop_requested`标志),然后启动新的搜索线程。所以保存`current_pattern`是可行的。 30. 现在,我们修改代码: 在`__init__`方法中: self.current_pattern = None # 初始化 在`start_search`方法中,在编译`pattern`后(在try块内): self.current_pattern = pattern # 保存当前搜索的pattern 在`show_file_content`方法中,处理文本文件的部分,去掉原来重新编译pattern的代码,改为: pattern = self.current_pattern # 使用最近搜索的pattern # 然后,在循环中,如果pattern为None,就不高亮,否则进行高亮。 31. 注意:在清空搜索结果(比如开始新的搜索)时,我们会重置`self.results`等,但`self.current_pattern`会在新的搜索时被覆盖,所以没有问。 32. 最后,我们修改文本文件预览部分的代码,去掉原来的重新编译pattern的代码,直接使用`self.current_pattern`,并处理可能为None的情况。 33. 修改后的文本文件预览部分代码: # 获取关键词高亮模式,使用最近一次搜索的pattern pattern = self.current_pattern # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.preview_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.preview_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.preview_text.insert(tk.END, line[start_idx:]) else: self.preview_text.insert(tk.END, line) except UnicodeDecodeError: ... [不变] ... except Exception as e: ... [不变] ... 34. 这样修改后,预览文本文件时就会使用最近一次搜索的pattern来高亮,并且避免了重新编译可能带来的不一致或错误。 35. 此外,我们还需要注意:当用户清除了搜索框的内容,然后点击某个文件进行预览,此时`self.current_pattern`是上一次搜索的(可能非空),但可能和当前搜索框内容不符。不过,我们认为用户预览时希望看到的是上一次搜索的高亮,所以这是合理的。 36. 最后,我们测试一下高亮功能是否生效。 由于我们无法直接运行,请根据上述修改进行调整,然后测试。 注意:在`start_search`方法中,我们在编译pattern之后,将其赋值给`self.current_pattern`。同时,在开始搜索时,我们重置了结果,但没有重置`current_pattern`,因为新的搜索会覆盖它。 如果问仍然存在,请检查标签`"match"`的样式配置是否正确,以及是否确实应用到了文本上(可以通过调试或检查文本组件的文本来判断)。 </think> 您的观察非常准确。问在于关键字高亮功能没有正确实现,特别是在文本文件预览部分未能应用高亮标签。让我们解决这个问: ### 问分析: 1. **高亮逻辑问**: - 在`show_file_content`方法中,对于文本文件的高亮处理逻辑存在缺陷 - 使用`insert`方法插入文本时,没有正确应用标签范围 - 匹配位置的索引计算和标签应用不正确 2. **标签配置问**: - "match"标签的配置在UI初始化时已设置,但未正确应用于匹配文本 3. **跨文件类型问**: - 高亮逻辑仅在文本文件预览中实现,其他文件类型(Office、压缩文件)未应用高亮 ### 完整解决方案: ```python def show_file_content(self, event=None): """在预览区域显示文件内容并高亮关键字""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return # 获取完整文件路径 selected_item = self.file_tree.selection()[0] filepath = self.file_tree.item(selected_item, "values")[3] # 索引3是完整路径 # 清空预览区域 self.preview_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() if not keyword: # 如果没有关键词,则使用普通显示模式 self.highlight_enabled = False else: self.highlight_enabled = self.highlight_var.get() flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): self.highlight_pattern = re.compile(keyword, flags) else: self.highlight_pattern = re.compile(re.escape(keyword), flags) except re.error: self.highlight_enabled = False # 显示文件路径标 self.preview_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项并高亮关键词 for i, (line_num, content) in enumerate(matches, 1): # 插入匹配项标 self.preview_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") # 插入内容并高亮关键词 self.insert_with_highlight(content) self.preview_text.insert(tk.END, "\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): # 插入匹配项标 self.preview_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") # 插入内容并高亮关键词 self.insert_with_highlight(content) self.preview_text.insert(tk.END, "\n\n") # 处理文本文件 else: # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 self.insert_with_highlight(line) except UnicodeDecodeError: self.preview_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def insert_with_highlight(self, text): """插入文本并高亮匹配的关键词""" if not self.highlight_enabled or not hasattr(self, 'highlight_pattern'): # 如果不启用高亮或没有模式,直接插入文本 self.preview_text.insert(tk.END, text) return # 拆分文本为匹配和非匹配部分 start_idx = 0 for match in self.highlight_pattern.finditer(text): # 插入匹配前的文本 non_match = text[start_idx:match.start()] if non_match: self.preview_text.insert(tk.END, non_match) # 插入高亮的匹配文本 matched_text = match.group() self.preview_text.insert(tk.END, matched_text, "match") start_idx = match.end() # 插入剩余的文本 remaining_text = text[start_idx:] if remaining_text: self.preview_text.insert(tk.END, remaining_text) ``` ### 关键改进: 1. **统一高亮处理逻辑**: ```python def insert_with_highlight(self, text): """插入文本并高亮匹配的关键词""" if not self.highlight_enabled or not hasattr(self, 'highlight_pattern'): self.preview_text.insert(tk.END, text) return start_idx = 0 for match in self.highlight_pattern.finditer(text): # 插入非匹配部分 non_match = text[start_idx:match.start()] if non_match: self.preview_text.insert(tk.END, non_match) # 插入高亮的匹配部分 matched_text = match.group() self.preview_text.insert(tk.END, matched_text, "match") start_idx = match.end() # 插入剩余文本 remaining_text = text[start_idx:] if remaining_text: self.preview_text.insert(tk.END, remaining_text) ``` 2. **重构显示逻辑**: - 为所有文件类型(文本、Office、压缩文件)统一使用`insert_with_highlight`方法 - 确保高亮逻辑一致应用于所有内容显示 3. **优化高亮模式初始化**: ```python # 在show_file_content方法开头 keyword = self.keyword_entry.get().strip() if not keyword: self.highlight_enabled = False else: self.highlight_enabled = self.highlight_var.get() flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): self.highlight_pattern = re.compile(keyword, flags) else: self.highlight_pattern = re.compile(re.escape(keyword), flags) except re.error: self.highlight_enabled = False ``` 4. **增强错误处理**: - 当正则表达式无效时自动禁用高亮 - 当关键词为空时自动禁用高亮 ### 其他改进建议: 1. **添加高亮颜色配置**: ```python # 在__init__方法中添加 self.highlight_colors = ["#c8e6c9", "#fff9c4", "#bbdefb"] # 绿色、黄色、蓝色 self.highlight_index = 0 # 更新标签配置 self.preview_text.tag_configure("match", background=self.highlight_colors[0]) # 添加高亮切换方法 def toggle_highlight_color(self): self.highlight_index = (self.highlight_index + 1) % len(self.highlight_colors) new_color = self.highlight_colors[self.highlight_index] self.preview_text.tag_configure("match", background=new_color) self.stats_label.config(text=f"高亮颜色: {new_color}") # 在UI中添加切换按钮 color_btn = ttk.Button( btn_frame, text="🎨 切换高亮", command=self.toggle_highlight_color ) color_btn.pack(side=tk.LEFT, padx=(10, 0)) ``` 2. **添加高亮动画效果**: ```python def animate_highlight(self, tag_name): """为高亮添加动画效果""" current_bg = self.preview_text.tag_cget(tag_name, "background") if current_bg.startswith("#"): r, g, b = [int(current_bg[i:i+2], 16) for i in (1, 3, 5)] # 稍微变亮颜色 r = min(255, r + 20) g = min(255, g + 20) b = min(255, b + 20) new_color = f"#{r:02x}{g:02x}{b:02x}" self.preview_text.tag_configure(tag_name, background=new_color) self.master.after(100, lambda: self.reset_highlight(tag_name, current_bg)) def reset_highlight(self, tag_name, original_color): """重置高亮颜色""" self.preview_text.tag_configure(tag_name, background=original_color) # 在insert_with_highlight方法中添加动画 self.preview_text.insert(tk.END, matched_text, "match") self.animate_highlight("match") ``` 3. **添加高亮计数功能**: ```python def show_file_content(self, event=None): # 初始化高亮计数器 self.highlight_count = 0 # ...其余代码不变... def insert_with_highlight(self, text): # ...匹配循环内... self.highlight_count += 1 # 更新状态栏 if self.highlight_count % 10 == 0: self.stats_label.config(text=f"已高亮 {self.highlight_count} 处匹配") ``` ### 相关问
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值