17/03/2026 21:26
#1
# ----- Cấu hình giao diện dark theme ----- self.bg_color = "#2b2b2b" self.fg_color = "#ffffff" self.entry_bg = "#3c3c3c" self.button_bg = "#1e88e5" self.button_fg = "#ffffff" self.progress_color = "#1e88e5" self.root.configure(bg=self.bg_color) style = ttk.Style() style.theme_use('clam') style.configure("TLabel", background=self.bg_color, foreground=self.fg_color) style.configure("TButton", background=self.button_bg, foreground=self.button_fg, borderwidth=0, focuscolor="none") style.map("TButton", background=[('active', '#1565c0')]) style.configure("TEntry", fieldbackground=self.entry_bg, foreground=self.fg_color, insertcolor=self.fg_color) style.configure("Horizontal.TProgressbar", background=self.progress_color, troughcolor=self.entry_bg) # ----- Biến lưu trữ ----- self.file_path = tk.StringVar() self.keyword = tk.StringVar() self.progress_var = tk.DoubleVar() # ----- Giao diện chính ----- # Frame chọn file file_frame = ttk.Frame(root, padding="10") file_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(file_frame, text="File đầu vào:").grid(row=0, column=0, sticky=tk.W, padx=5) ttk.Entry(file_frame, textvariable=self.file_path, width=50).grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="Chọn file", command=self.select_file).grid(row=0, column=2, padx=5) # Frame nhập từ khóa keyword_frame = ttk.Frame(root, padding="10") keyword_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(keyword_frame, text="Từ khóa:").grid(row=0, column=0, sticky=tk.W, padx=5) ttk.Entry(keyword_frame, textvariable=self.keyword, width=40).grid(row=0, column=1, padx=5) ttk.Button(keyword_frame, text="Find", command=self.start_filter).grid(row=0, column=2, padx=5) # Progress bar progress_frame = ttk.Frame(root, padding="10") progress_frame.pack(fill=tk.X, padx=10, pady=5) self.progress_bar = ttk.Progressbar(progress_frame, orient=tk.HORIZONTAL, length=400, mode='determinate', variable=self.progress_var) self.progress_bar.pack(pady=5) self.status_label = ttk.Label(progress_frame, text="Sẵn sàng...") self.status_label.pack() # Nút mở thư mục button_frame = ttk.Frame(root, padding="10") button_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Button(button_frame, text="Mở thư mục chứa kết quả", command=self.open_output_folder).pack(side=tk.LEFT, padx=5) # Ghi chú note_frame = ttk.Frame(root, padding="10") note_frame.pack(fill=tk.X, padx=10, pady=5) ttk.Label(note_frame, text="Kết quả sẽ được lưu vào file output.txt trong cùng thư mục với chương trình.", font=('Arial', 9), foreground="#aaaaaa").pack() # Đường dẫn output mặc định: thư mục hiện tại self.output_file = os.path.join(os.getcwd(), "output.txt")def select_file(self): """Mở hộp thoại chọn file .txt""" filename = filedialog.askopenfilename(filetypes=[("Text files", "*.txt"), ("All files", "*.*")]) if filename: self.file_path.set(filename) self.status_label.config(text=f"Đã chọn file: {os.path.basename(filename)}")def start_filter(self): """Bắt đầu quá trình lọc trong luồng riêng để không block giao diện""" if not self.file_path.get(): messagebox.showwarning("Cảnh báo", "Vui lòng chọn file đầu vào.") return if not self.keyword.get().strip(): messagebox.showwarning("Cảnh báo", "Vui lòng nhập từ khóa cần tìm.") return # Vô hiệu hóa nút Find trong khi xử lý self.status_label.config(text="Đang xử lý...") threading.Thread(target=self.filter_file, daemon=True).start()def filter_file(self): """Đọc file, lọc dòng chứa từ khóa và ghi ra output.txt""" input_path = self.file_path.get() keyword = self.keyword.get().strip().lower() try: # Đếm tổng số dòng để cập nhật progress bar total_lines = 0 with open(input_path, 'r', encoding='utf-8', errors='ignore') as f: for _ in f: total_lines += 1 if total_lines == 0: self.root.after(0, lambda: messagebox.showinfo("Thông báo", "File trống.")) self.root.after(0, lambda: self.status_label.config(text="File trống.")) return # Xử lý lọc matched_lines = [] with open(input_path, 'r', encoding='utf-8', errors='ignore') as f: for i, line in enumerate(f, 1): if keyword in line.lower(): matched_lines.append(line.rstrip('\n')) # giữ nguyên dấu xuống dòng # Cập nhật progress bar (chạy trên luồng chính) progress = (i / total_lines) * 100 self.root.after(0, lambda v=progress: self.progress_var.set(v)) # Ghi kết quả with open(self.output_file, 'w', encoding='utf-8') as out_f: out_f.write('\n'.join(matched_lines)) # Thông báo hoàn tất self.root.after(0, lambda: self.progress_var.set(100)) self.root.after(0, lambda: messagebox.showinfo("Thành công", f"Đã tạo file output.txt thành công.\nSố dòng khớp: {len(matched_lines)}")) self.root.after(0, lambda: self.status_label.config(text=f"Hoàn tất. {len(matched_lines)} dòng được tìm thấy.")) except Exception as e: self.root.after(0, lambda: messagebox.showerror("Lỗi", f"Đã xảy ra lỗi: {str(e)}")) self.root.after(0, lambda: self.status_label.config(text="Lỗi xử lý.")) finally: # Cho phép tìm lại self.root.after(0, lambda: None) # không cần reset button vì không disabledef open_output_folder(self): """Mở thư mục chứa file output.txt bằng file manager mặc định""" folder = os.path.dirname(self.output_file) if os.path.exists(self.output_file): if os.name == 'nt': # Windows subprocess.run(['explorer', folder]) elif os.name == 'posix': # Linux, macOS subprocess.run(['xdg-open', folder]) else: messagebox.showinfo("Thông báo", "Chưa có file output.txt nào được tạo.")MANG TÍNH CHẤT THAM KHẢOKHÔNG TOXIC