import shlex
import subprocess
import tkinter as tk
from tkinter import messagebox, Menu, filedialog
import winreg
from datetime import datetime
import webbrowser
import os
class UninstallerApp:
# 初始化函数,创建主界面和相关元素
def __init__(self, root):
self.root = root
self.root.title("程序卸载工具")
# 创建并配置主题选择菜单
# 默认主题为亮色
self.theme_var = tk.StringVar(value="light")
self.theme_menu = tk.Menu(self.root)
self.root.config(menu=self.theme_menu)
self.settings_menu = tk.Menu(self.theme_menu, tearoff=0)
self.theme_menu.add_cascade(label="设置", menu=self.settings_menu)
self.settings_menu.add_radiobutton(label="亮色主题", variable=self.theme_var, value="light", command=self.change_theme)
self.settings_menu.add_radiobutton(label="暗色主题", variable=self.theme_var, value="dark", command=self.change_theme)
# 设置程序列表框架
self.frame = tk.Frame(self.root)
self.frame.pack(fill=tk.BOTH, expand=True)
# 设置并配置搜索框,默认显示提示文字
self.default_search_text = "请输入要查找的程序"
self.search_var = tk.StringVar(value=self.default_search_text)
self.search_entry = tk.Entry(self.root, textvariable=self.search_var, fg='grey')
self.search_entry.pack()
# 绑定搜索框的焦点事件,以便在用户交互时清除或显示默认文本
self.search_entry.bind("<FocusIn>", self.on_search_focus_in)
self.search_entry.bind("<FocusOut>", self.on_search_focus_out)
# 监听搜索变量的变化,实时更新程序列表
self.search_var.trace("w", lambda name, index, mode, sv=self.search_var: self.update_list())
# 程序列表和滚动条的配置
self.listbox = tk.Listbox(self.frame)
self.listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
self.scrollbar = tk.Scrollbar(self.frame, orient="vertical")
self.scrollbar.config(command=self.listbox.yview)
self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.listbox.config(yscrollcommand=self.scrollbar.set)
# 排序选项的设置
self.sort_option = tk.StringVar()
self.sort_option.set("name") # 默认按名称排序
# 创建排序选项的单选按钮
tk.Radiobutton(self.root, text="按名称排序", variable=self.sort_option, value="name", command=self.update_list).pack(anchor=tk.W)
tk.Radiobutton(self.root, text="按安装时间排序", variable=self.sort_option, value="install_date", command=self.update_list).pack(anchor=tk.W)
# 添加刷新列表按钮
tk.Button(self.root, text="刷新列表", command=self.update_list).pack()
# 添加导出按钮
tk.Button(self.root, text="导出列表", command=self.export_list).pack()
# 显示当前查询到的程序数量的标签
self.status_label = tk.Label(self.root, text="")
self.status_label.pack()
# 初始化程序列表并立即更新显示
self.programs = []
self.update_list()
# 绑定列表框的选择和右键点击事件
self.listbox.bind('<<ListboxSelect>>', self.highlight_program)
self.listbox.bind('<Button-3>', self.show_context_menu)
self.selected_program_key = None # 用于存储当前选中程序的注册表路径
# 更改主题颜色的函数
def change_theme(self):
theme = self.theme_var.get()
if theme == "light":
self.root.config(bg="white")
# 更新其他元素的颜色以适应亮色主题
elif theme == "dark":
self.root.config(bg="gray")
# 更新其他元素的颜色以适应暗色主题
# 搜索框获取焦点时的事件处理
def on_search_focus_in(self, event):
if self.search_var.get() == self.default_search_text:
self.search_var.set('')
self.search_entry.config(fg='black')
# 搜索框失去焦点时的事件处理
def on_search_focus_out(self, event):
if not self.search_var.get():
self.search_var.set(self.default_search_text)
self.search_entry.config(fg='grey')
# 高亮显示选中的程序
def highlight_program(self, event):
pass
# 显示右键菜单的事件处理
def show_context_menu(self, event):
self.context_menu = Menu(self.root, tearoff=0)
# 右键菜单添加选项:卸载、在线搜索、打开注册表条目、打开文件安装目录
self.context_menu.add_command(label="卸载", command=self.uninstall_program)
self.context_menu.add_command(label="在Bing中搜索", command=self.search_online)
self.context_menu.add_command(label="注册表条目", command=self.open_reg_entry) # 新增注册表条目选项
self.context_menu.add_command(label="文件安装目录", command=self.open_install_directory)
self.context_menu.tk_popup(event.x_root, event.y_root)
# 根据选中的项目更新当前选中程序的安装位置
selection = self.listbox.curselection()
if selection:
_, _, _, _, self.selected_program_install_location = self.programs[selection[0]]
# 卸载程序的函数
def uninstall_program(self):
selection = self.listbox.curselection()
if selection:
program_name, uninstall_string, _, _, _ = self.programs[selection[0]]
# 分割命令字符串为命令和参数(适用于包含路径和参数的复杂命令)
command_parts = shlex.split(uninstall_string)
try:
# 执行卸载命令,等待命令完成并获取输出
result = subprocess.run(command_parts, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 根据卸载命令的执行结果给用户反馈
if result.returncode == 0:
messagebox.showinfo("卸载成功", f"{program_name} 卸载成功。")
else:
# 如果命令执行失败,显示错误信息
messagebox.showerror("卸载失败", f"{program_name} 卸载失败:{result.stderr}")
except subprocess.CalledProcessError as e:
# 捕获执行错误,向用户显示错误信息
messagebox.showerror("卸载失败", f"尝试卸载 {program_name} 时出错:{e.stderr}")
except Exception as e:
# 捕获其他可能的错误,并给予反馈
messagebox.showerror("卸载失败", f"卸载 {program_name} 时发生未知错误:{str(e)}")
# 在线搜索选中的程序
def search_online(self):
selection = self.listbox.curselection()
if selection:
program_name, _, _, _ = self.programs[selection[0]]
webbrowser.open(f"https://www.bing.com/search?q={program_name}", new=2)
# 打开选中程序的安装目录
def open_install_directory(self):
if self.selected_program_install_location:
os.startfile(self.selected_program_install_location)
else:
messagebox.showinfo("信息", "未找到安装目录或安装目录未指定。")
# 更新程序列表的函数
def update_list(self):
self.listbox.delete(0, tk.END)# 清空当前列表
self.programs = self.list_installed_programs()# 获取最新的程序列表
search_term = self.search_var.get().lower()
# 当搜索框显示默认文字时,显示所有程序
if search_term == self.default_search_text.lower():
search_term = ''
# 根据搜索条件过滤程序列表
filtered_programs = [program for program in self.programs if search_term in program[0].lower()]
# 在列表框中显示过滤后的程序列表
for program in filtered_programs:
name, uninstall_string, install_date, reg_key, install_location = program
display_text = f"{name} - 安装时间: {install_date.strftime('%Y-%m-%d') if install_date != datetime.min else '未知'}"
self.listbox.insert(tk.END, display_text)
# 更新状态标签显示当前查询到的程序数量
self.status_label.config(text=f"查询到的程序数量: {len(filtered_programs)}")
# 列出已安装程序的函数
def list_installed_programs(self):
programs = []
# 遍历注册表中的卸载信息
for key_path in [r"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", r"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"]:
base_key = r"HKEY_LOCAL_MACHINE"
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
for i in range(0, winreg.QueryInfoKey(key)[0]):
skey_name = winreg.EnumKey(key, i)
with winreg.OpenKey(key, skey_name) as skey:
try:
# 尝试获取程序的显示名称
name = winreg.QueryValueEx(skey, "DisplayName")[0]
# 获取程序的卸载命令字符串
uninstall_string = winreg.QueryValueEx(skey, "UninstallString")[0]
try:
# 尝试获取程序的安装位置
install_location = winreg.QueryValueEx(skey, "InstallLocation")[0]
except FileNotFoundError:
install_location = "" # 如果未找到安装位置,则设置为空字符串
try:
# 尝试获取程序的安装日期
install_date = winreg.QueryValueEx(skey, "InstallDate")[0]
# 转换日期格式
install_date = datetime.strptime(install_date, "%Y%m%d")
except:
# 如果未找到安装日期,则设置为最小日期值
install_date = datetime.min
# 构造完整的注册表键路径
full_key_path = f"{base_key}\\{key_path}\\{skey_name}"
programs.append((name, uninstall_string, install_date, full_key_path, install_location))
except OSError:
pass
# 如果遇到读取注册表项时的错误,则忽略该项并继续
# 根据用户选择的排序方式对程序列表进行排序
if self.sort_option.get() == "name":
# 如果用户选择按名称排序,则按程序名称的小写形式进行字母升序排序
programs.sort(key=lambda x: x[0].lower())
else:
# 如果用户选择按安装时间排序,则按程序的安装日期进行降序排序
programs.sort(key=lambda x: x[2], reverse=True)
return programs
# 返回排序后的程序列表
# 导出列表函数:将当前显示的程序列表导出到文本文件
def export_list(self):
# 弹出文件保存对话框,让用户选择保存文件的路径和名称,默认扩展名为.txt
file_path = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files", "*.txt")])
if file_path:
# 如果用户选择了文件路径,则打开该路径的文件进行写入操作
with open(file_path, "w", encoding="utf-8") as file:
# 遍历程序列表,将每个程序的名称、卸载字符串和安装日期写入文件
for name, uninstall_string, install_date, _ in self.programs:
# 格式化字符串:程序名称、卸载字符串、安装日期(如果未知则显示'N/A')
file.write(f"{name}\t{uninstall_string}\t{install_date.strftime('%Y-%m-%d') if install_date != datetime.min else 'N/A'}\n")
# 数据写入完成后,显示提示信息告知用户程序列表已成功导出
messagebox.showinfo("导出完成", "程序列表已导出至文本文件。")
# 打开注册表项函数:用于打开当前选中程序的注册表编辑器位置
def open_reg_entry(self):
# 检查当前是否有程序被选中(即selected_program_key是否非空)
if self.selected_program_key:
# 指定注册表编辑器的路径
regedit_path = r"regedit.exe"
# 使用subprocess模块运行注册表编辑器并打开到指定的注册表项
subprocess.run([regedit_path, self.selected_program_key])
if __name__ == "__main__":
root = tk.Tk()
app = UninstallerApp(root)
root.mainloop()