python的卸载基于Python打造高颜值软件卸载工具python3卸载

目录
  • 前言:为什么需要自制卸载工具
  • 一、核心功能架构设计
    • 1.1 技术栈选型
    • 1.2 程序流程图
  • 二、关键技术实现详解
    • 2.1 多源注册表扫描(核心代码解析)
    • 2.2 动态图标提取技术
  • 三、高质量功能实现
    • 3.1 智能文件大致计算
    • 3.2 强力卸载模式
  • 四、UI美化实战技巧
    • 4.1 现代化暗黑主题
    • 4.2 响应式布局设计
  • 五、性能优化方案
    • 5.1 图标缓存机制
    • 5.2 多线程加载
  • 项目资料扩展
    • 完整项目源码

      前言:为什么需要自制卸载工具

      在Windows体系中,自带的"添加/删除程序"功能一直饱受诟病:加载慢、功能弱、残留多。第三方卸载工具如GeekUninstaller虽然好用,但毕竟是闭源商业软件。今天我们将用Python+tkinter打造一款颜值与实力并存的卸载工具,具备下面内容杀手级特性:

      • 现代化UI界面(暗黑主题+高亮配色)
      • 精准程序扫描(三路注册表探测)
      • 强力卸载模式(支持MSI静默卸载)
      • 智能残留清理(全盘扫描关联文件)
      • 原生图标提取(EXE文件图标解析)

      一、核心功能架构设计

      1.1 技术栈选型

      技术组件 影响说明 替代方案
      tkinter GUI界面开发 PyQt/PySide
      winreg Windows注册表访问 _winreg
      Pillow 图标图像处理 OpenCV
      pywin32 Windows API调用 ctypes
      shutil 文件体系操作 os模块

      1.2 程序流程图

      二、关键技术实现详解

      2.1 多源注册表扫描(核心代码解析)

      def load_installed_programs(self): reg_paths = [ (winreg.HKEY_LOCAL_MACHINE, r”SOFTWAREMicrosoftWindowsCurrentVersionUninstall”), 64位体系兼容路径 (winreg.HKEY_LOCAL_MACHINE, r”SOFTWAREWOW6432Node…”), 当前用户安装路径 (winreg.HKEY_CURRENT_USER, r”SOFTWAREMicrosoft…”) ] for hive, path in reg_paths: try: with winreg.OpenKey(hive, path) as key: for i in range(winreg.QueryInfoKey(key)[0]): 提取程序信息… program = “name”: name, “version”: version, “install_location”: install_path, “uninstall_string”: uninstall_cmd }

      技术要点:

      • 同时扫描HKLM和HKCU两大主键
      • 处理64位体系的WOW6432Node兼容路径
      • 异常处理确保扫描经过不中断

      2.2 动态图标提取技术

      def get_icon_from_exe(self, exe_path): 使用Win32 API提取图标 large, small = win32gui.ExtractIconEx(exe_path, 0) hdc = win32ui.CreateDCFromHandle(win32gui.GetDC(0)) 创建兼容位图 hbmp = win32ui.CreateBitmap() hbmp.CreateCompatibleBitmap(hdc, 16, 16) 转换为PIL图像 bmpstr = hbmp.GetBitmapBits(True) icon = Image.frombuffer(‘RGB’, (16,16), bmpstr, ‘raw’, ‘BGRX’, 0, 1) return ImageTk.PhotoImage(icon)

      创新点:

      • 直接从EXE/DLL提取原始图标
      • 自动降采样到16&215;16尺寸
      • 异常时回退到默认图标

      三、高质量功能实现

      3.1 智能文件大致计算

      def get_program_size(self, path): total = 0 for root, dirs, files in os.walk(path): for f in files: try: total += os.path.getsize(os.path.join(root, f)) except: continue return total???????def format_size(self, size): 智能转换单位 units = [‘B’, ‘KB’, ‘MB’, ‘GB’] for unit in units: if size < 1024.0: return f”size:.1f} unit}” size /= 1024.0 return f”size:.1f} TB”

      3.2 强力卸载模式

      卸载类型 处理方式 示例命令
      标准卸载程序 直接执行UninstallString C:Program Files&8230;
      MSI安装包 调用msiexec静默卸载 msiexec /x GUID}
      无卸载程序 提示手动删除 &8211;

      四、UI美化实战技巧

      4.1 现代化暗黑主题

      self.bg_color = “2d2d2d” 背景色self.fg_color = “ffffff” 前景色self.accent_color = “4CAF50” 强调色style = ttk.Style()style.theme_use(“clam”)style.configure(“Treeview”, background=”3d3d3d”, foreground=self.fg_color, fieldbackground=”3d3d3d”)

      4.2 响应式布局设计

      主界面采用Pack布局main_frame.pack(fill=tk.BOTH, expand=True) 左侧列表区域list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 右侧按钮区域button_frame.pack(side=tk.RIGHT, fill=tk.Y)

      五、性能优化方案

      5.1 图标缓存机制

      self.icon_cache = } 缓存字典def get_program_icon(self, program): if program[‘name’] in self.icon_cache: return self.icon_cache[program[‘name’]] icon = self._extract_icon(program) self.icon_cache[program[‘name’]] = icon return icon

      5.2 多线程加载

      from threading import Threaddef load_data_async(self): Thread(target=self.load_installed_programs, daemon=True).start()

      项目资料扩展

      通过本项目,我们实现了:

      • 完整的程序卸载管理功能
      • 媲美商业软件的UI体验
      • 高效的注册表扫描机制
      • 智能化的残留检测

      未来优化路线:

      • 增加云端垃圾文件特征库
      • 实现卸载历史记录功能
      • 添加软件更新检测模块

      完整项目源码

      import osimport winregimport subprocessimport shutilimport tkinter as tkfrom tkinter import ttk, messagebox, scrolledtextfrom PIL import Image, ImageTkimport ctypesclass GeekUninstallerApp: def __init__(self, root): self.root = root self.root.title(“PyGeek Uninstaller”) self.root.geometry(“900×600”) self.root.minsize(800, 500) 设置主题颜色 self.bg_color = “2d2d2d” self.fg_color = “ffffff” self.accent_color = “4CAF50” self.secondary_color = “2196F3” self.warning_color = “FF5722” self.highlight_color = “FFC107” 初始化样式 self.setup_styles() 创建UI self.create_widgets() 加载已安装程序 self.load_installed_programs() def setup_styles(self): style = ttk.Style() style.theme_use(“clam”) 树状视图样式 style.configure(“Treeview”, background=”3d3d3d”, foreground=self.fg_color, fieldbackground=”3d3d3d”, borderwidth=0 ) style.configure(“Treeview.Heading”, background=”4d4d4d”, foreground=self.fg_color, relief=tk.FLAT ) style.map(“Treeview”, background=[(“selected”, self.secondary_color)]) 配置主窗口背景 self.root.configure(bg=self.bg_color) def create_widgets(self): 顶部深入了解栏 header_frame = tk.Frame(self.root, bg=self.bg_color) header_frame.pack(fill=tk.X, padx=10, pady=10) 深入了解 title_label = tk.Label( header_frame, text=”PyGeek Uninstaller”, font=(“Segoe UI”, 18, “bold”), fg=self.highlight_color, bg=self.bg_color ) title_label.pack(side=tk.LEFT) 搜索框 search_frame = tk.Frame(header_frame, bg=self.bg_color) search_frame.pack(side=tk.RIGHT, fill=tk.X, expand=True) search_label = tk.Label( search_frame, text=”Search:”, font=(“Segoe UI”, 10), fg=self.fg_color, bg=self.bg_color ) search_label.pack(side=tk.LEFT, padx=(20, 5)) self.search_var = tk.StringVar() self.search_var.trace(“w”, self.filter_programs) search_entry = tk.Entry( search_frame, textvariable=self.search_var, font=(“Segoe UI”, 10), bg=”3d3d3d”, fg=self.fg_color, insertbackground=self.fg_color, relief=tk.FLAT ) search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=2) 主内容区域 main_frame = tk.Frame(self.root, bg=self.bg_color) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10)) 程序列表 list_frame = tk.Frame(main_frame, bg=self.bg_color) list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 树状视图 self.tree = ttk.Treeview( list_frame, columns=(“name”, “publisher”, “version”, “size”), selectmode=”extended” ) 配置列 self.tree.heading(“0″, text=”Icon”, anchor=tk.W) self.tree.heading(“name”, text=”Name”, anchor=tk.W, command=lambda: self.treeview_sort_column(self.tree, “name”, False)) self.tree.heading(“publisher”, text=”Publisher”, anchor=tk.W, command=lambda: self.treeview_sort_column(self.tree, “publisher”, False)) self.tree.heading(“version”, text=”Version”, anchor=tk.W, command=lambda: self.treeview_sort_column(self.tree, “version”, False)) self.tree.heading(“size”, text=”Size”, anchor=tk.W, command=lambda: self.treeview_sort_column(self.tree, “size”, False)) self.tree.column(“0”, width=30, minwidth=30, stretch=tk.NO) self.tree.column(“name”, width=250, minwidth=150, stretch=tk.YES) self.tree.column(“publisher”, width=200, minwidth=100, stretch=tk.YES) self.tree.column(“version”, width=100, minwidth=70, stretch=tk.NO) self.tree.column(“size”, width=100, minwidth=70, stretch=tk.NO) 滚动条 scrollbar = ttk.Scrollbar(list_frame, orient=”vertical”, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.tree.pack(fill=tk.BOTH, expand=True) 绑定双击事件 self.tree.bind(“<Double-1>”, self.show_program_details) 操作按钮区域 button_frame = tk.Frame(main_frame, bg=self.bg_color) button_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(0, 10)) 按钮样式 button_style = “font”: (“Segoe UI”, 10), “bg”: “4d4d4d”, “fg”: self.fg_color, “activebackground”: self.secondary_color, “activeforeground”: self.fg_color, “relief”: tk.FLAT, “bd”: 0, “padx”: 15, “pady”: 8 } 操作按钮 self.uninstall_btn = tk.Button( button_frame, text=”Uninstall”, command=self.uninstall_selected, button_style ) self.uninstall_btn.pack(fill=tk.X, pady=(0, 5)) self.force_btn = tk.Button( button_frame, text=”Force Remove”, command=self.force_remove, button_style ) self.force_btn.pack(fill=tk.X, pady=(0, 5)) self.details_btn = tk.Button( button_frame, text=”Details”, command=self.show_program_details, button_style ) self.details_btn.pack(fill=tk.X, pady=(0, 5)) self.clean_btn = tk.Button( button_frame, text=”Clean Residues”, command=self.clean_residues, button_style ) self.clean_btn.pack(fill=tk.X, pady=(0, 5)) self.refresh_btn = tk.Button( button_frame, text=”Refresh”, command=self.refresh_list, button_style ) self.refresh_btn.pack(fill=tk.X, pady=(0, 5)) 情形栏 self.status_var = tk.StringVar() self.status_var.set(“Ready”) status_bar = tk.Label( self.root, textvariable=self.status_var, font=(“Segoe UI”, 9), fg=self.fg_color, bg=”3d3d3d”, anchor=tk.W, relief=tk.SUNKEN ) status_bar.pack(fill=tk.X, side=tk.BOTTOM, ipady=5) def treeview_sort_column(self, tv, col, reverse): l = [(tv.set(k, col), k) for k in tv.get_children(”)] 尝试转换为数字进行排序 try: l.sort(key=lambda t: float(t[0]) if t[0].replace(‘.’, ”).isdigit() else t[0], reverse=reverse) except: l.sort(reverse=reverse) 重新排列项目 for index, (val, k) in enumerate(l): tv.move(k, ”, index) 下次反向排序 tv.heading(col, command=lambda: self.treeview_sort_column(tv, col, not reverse)) def load_installed_programs(self): self.tree.delete(*self.tree.get_children()) self.programs = [] 从注册表获取已安装程序 reg_paths = [ (winreg.HKEY_LOCAL_MACHINE, r”SOFTWAREMicrosoftWindowsCurrentVersionUninstall”), (winreg.HKEY_LOCAL_MACHINE, r”SOFTWAREWOW6432NodeMicrosoftWindowsCurrentVersionUninstall”), (winreg.HKEY_CURRENT_USER, r”SOFTWAREMicrosoftWindowsCurrentVersionUninstall”) ] for hive, path in reg_paths: try: with winreg.OpenKey(hive, path) as key: for i in range(0, winreg.QueryInfoKey(key)[0]): try: subkey_name = winreg.EnumKey(key, i) with winreg.OpenKey(key, subkey_name) as subkey: try: name = winreg.QueryValueEx(subkey, “DisplayName”)[0] if not name: continue publisher = winreg.QueryValueEx(subkey, “Publisher”)[0] if winreg.QueryValueEx(subkey, “Publisher”) else “” version = winreg.QueryValueEx(subkey, “DisplayVersion”)[0] if winreg.QueryValueEx(subkey, “DisplayVersion”) else “” install_location = winreg.QueryValueEx(subkey, “InstallLocation”)[0] if winreg.QueryValueEx(subkey, “InstallLocation”) else “” uninstall_string = winreg.QueryValueEx(subkey, “UninstallString”)[0] if winreg.QueryValueEx(subkey, “UninstallString”) else “” size = self.get_program_size(install_location) program = “name”: name, “publisher”: publisher, “version”: version, “size”: size, “install_location”: install_location, “uninstall_string”: uninstall_string, “reg_key”: f”path}\subkey_name}”, “hive”: hive } self.programs.append(program) 插入到树状视图 self.tree.insert(“”, “end”, values=( name, publisher, version, self.format_size(size) )) except (WindowsError, ValueError): continue except (WindowsError, ValueError): continue except WindowsError: continue 按名称排序 self.programs.sort(key=lambda x: x[“name”].lower()) self.treeview_sort_column(self.tree, “name”, False) self.status_var.set(f”Loaded len(self.programs)} programs”) def get_program_size(self, install_location): if not install_location or not os.path.isdir(install_location): return 0 total_size = 0 for dirpath, dirnames, filenames in os.walk(install_location): for f in filenames: fp = os.path.join(dirpath, f) try: total_size += os.path.getsize(fp) except: continue return total_size def format_size(self, size): if size == 0: return “N/A” for unit in [‘B’, ‘KB’, ‘MB’, ‘GB’]: if size < 1024.0: return f”size:.1f} unit}” size /= 1024.0 return f”size:.1f} TB” def filter_programs(self, *args): query = self.search_var.get().lower() for item in self.tree.get_children(): values = self.tree.item(item)[“values”] if query in values[0].lower() or query in values[1].lower(): self.tree.selection_set(item) self.tree.see(item) else: self.tree.selection_remove(item) def get_selected_program(self): selected_items = self.tree.selection() if not selected_items: messagebox.showwarning(“Warning”, “Please select a program first!”) return None item = selected_items[0] values = self.tree.item(item)[“values”] for program in self.programs: if program[“name”] == values[0] and program[“publisher”] == values[1]: return program return None def show_program_details(self, event=None): program = self.get_selected_program() if not program: return details_window = tk.Toplevel(self.root) details_window.title(f”Details – program[‘name’]}”) details_window.geometry(“600×400”) details_window.configure(bg=self.bg_color) 详细信息文本 details_text = scrolledtext.ScrolledText( details_window, wrap=tk.WORD, font=(“Consolas”, 10), bg=”3d3d3d”, fg=self.fg_color, insertbackground=self.fg_color ) details_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) 添加信息 details = f”””Program Name: program[‘name’]}Publisher: program[‘publisher’]}Version: program[‘version’]}Size: self.format_size(program[‘size’])}Install Location: program[‘install_location’]}Uninstall Command: program[‘uninstall_string’]}Registry Key: program[‘reg_key’]}””” details_text.insert(tk.END, details) details_text.configure(state=”disabled”) 关闭按钮 close_btn = tk.Button( details_window, text=”Close”, command=details_window.destroy, font=(“Segoe UI”, 10), bg=”4d4d4d”, fg=self.fg_color, activebackground=self.secondary_color, activeforeground=self.fg_color, relief=tk.FLAT ) close_btn.pack(pady=(0, 10)) def uninstall_selected(self): program = self.get_selected_program() if not program: return if not program[“uninstall_string”]: messagebox.showerror(“Error”, “No uninstall command found for this program!”) return try: 运行卸载命令 if program[“uninstall_string”].lower().endswith(“.msi”): MSI 包 cmd = f’msiexec /x “program[“uninstall_string”]}” /quiet’ else: 普通卸载程序 cmd = program[“uninstall_string”] subprocess.Popen(cmd, shell=True) self.status_var.set(f”Uninstalling program[‘name’]}…”) except Exception as e: messagebox.showerror(“Error”, f”Failed to start uninstaller: str(e)}”) def force_remove(self): program = self.get_selected_program() if not program: return if not messagebox.askyesno(“Warning”, f”Force removal will delete all files and registry entries for program[‘name’]}.n” “This action cannot be undone. Continue?”): return 删除安装目录 if program[“install_location”] and os.path.isdir(program[“install_location”]): try: shutil.rmtree(program[“install_location”]) self.status_var.set(f”Deleted installation folder: program[‘install_location’]}”) except Exception as e: messagebox.showerror(“Error”, f”Failed to delete installation folder: str(e)}”) 删除注册表

      版权声明

      返回顶部