本帖最后由 piaomusic 于 2025-6-7 23:02 编辑 # -*- coding: utf-8 -*- import os import configparser import tkinter as tk from tkinter import ttk, messagebox import subprocess import sys import threading import psutil class SoftwareInstaller: def __init__(self, root): self.root = root self.root.title("软件安装管理器") self.root.geometry("750x500") self.root.resizable(False, False) # 初始化变量 self.software_list = [] self.install_commands = {} self.data_dir = os.path.join(os.path.dirname(sys.argv[0]), "data") self.window_title = "软件安装管理器" self.default_select_all = False self.theme = "浅色" # 默认主题 # 安装相关变量 self.installation_processes = [] self.failed_installations = [] self.installation_complete = False self.progress_window = None # 检查并创建配置文件 self.init_config_file() # 验证配置文件完整性 if not self.verify_author_info(): messagebox.showerror("启动失败", "文件被篡改,程序无法启动!") sys.exit() # 加载配置 self.load_config() # 设置窗口居中 self.center_window() # 设置样式(根据配置) self.setup_styles() # 创建界面 self.create_widgets() # 加载软件列表 self.load_software_list() self.populate_treeview() def verify_author_info(self): """验证程序信息部分是否被修改""" REQUIRED_AUTHOR_INFO = { '作者': '缘起性空', '版权': '版权所有 © 2025', '联系': '32897251@qq.com' } if not os.path.exists('config.ini'): return False config = configparser.ConfigParser() config.read('config.ini', encoding='utf-8') if not config.has_section('程序信息'): return False for key, value in REQUIRED_AUTHOR_INFO.items(): if not config.has_option('程序信息', key): return False if config.get('程序信息', key) != value: return False return True def init_config_file(self): """初始化配置文件""" if not os.path.exists('config.ini'): try: with open('config.ini', 'w', encoding='utf-8') as f: f.write("""[程序信息] 作者 = 缘起性空 版权 = 版权所有 © 2025 联系 = 32897251@qq.com [全局设置] 窗口标题 = 软件安装管理器 默认全选 = 否 主题 = 浅色 # 可选值:浅色/深色 [软件列表] # 格式:软件名称 = 安装程序 参数 # 示例: # 谷歌浏览器 = chrome_installer.exe /silent # 微信办公版 = wechat_setup.exe /S """) messagebox.showinfo("提示", "已创建默认配置文件 config.ini") except Exception as e: messagebox.showerror("错误", f"创建配置文件失败: {e}") def center_window(self): """使窗口居中显示""" self.root.update_idletasks() width = self.root.winfo_width() height = self.root.winfo_height() x = (self.root.winfo_screenwidth() // 2) - (width // 2) y = (self.root.winfo_screenheight() // 2) - (height // 2) self.root.geometry(f'+{x}+{y}') def load_config(self): """加载配置文件设置""" config = configparser.ConfigParser() try: if os.path.exists('config.ini'): config.read('config.ini', encoding='utf-8') # 读取窗口标题 title = self.get_config_value(config, '全局设置', '窗口标题') if title: self.window_title = title self.root.title(self.window_title) # 读取默认全选设置 select_all = self.get_config_value(config, '全局设置', '默认全选') if select_all: select_all = select_all.strip().lower() self.default_select_all = select_all in ('是', 'true', '1', 'yes', '开启', 'on') # 读取主题设置 theme = self.get_config_value(config, '全局设置', '主题') if theme: self.theme = theme.strip() except Exception as e: messagebox.showerror("配置错误", f"读取配置文件失败: {e}") def get_config_value(self, config, section, key): """安全获取配置值""" try: if section in config and key in config[section]: return config[section][key] except: pass return None def setup_styles(self): """根据主题配置设置样式""" self.style = ttk.Style() self.style.theme_use('clam') # 根据主题选择配色方案 if self.theme == "深色": # 深色主题配色 bg_color = "#2c3e50" # 背景色 text_color = "#ecf0f1" # 文字颜色 tree_bg = "#34495e" # 表格背景 button_bg = "#34495e" # 按钮背景 primary_color = "#3498db" # 主色 secondary_color = "#2ecc71" # 辅助色 else: # 浅色主题配色(默认) bg_color = "#f5f5f5" # 背景色 text_color = "#333333" # 文字颜色 tree_bg = "#ffffff" # 表格背景 button_bg = "#e0e0e0" # 按钮背景 primary_color = "#4a90e2" # 主色 secondary_color = "#50c878" # 辅助色 # 设置窗口背景 self.root.configure(bg=bg_color) # 标题样式 self.style.configure("Title.TLabel", font=('Microsoft YaHei', 18, 'bold'), background=bg_color, foreground=text_color) # 表格样式 self.style.configure("Treeview", font=('Microsoft YaHei', 11), rowheight=30, background=tree_bg, fieldbackground=tree_bg, foreground=text_color, bordercolor="#dddddd") # 表头样式(保持默认) self.style.configure("Treeview.Heading", font=('Microsoft YaHei', 11, 'bold'), relief="flat") # 选中行样式 self.style.map("Treeview", background=[('selected', primary_color)], foreground=[('selected', 'white')]) # 按钮样式 self.style.configure("Exit.TButton", background=button_bg, foreground=text_color, font=('Microsoft YaHei', 11), padding=8) self.style.configure("Install.TButton", background=secondary_color, foreground="white", font=('Microsoft YaHei', 11), padding=8) # 按钮悬停效果 hover_bg = "#3d566e" if self.theme == "深色" else "#e8e8e8" self.style.map("Exit.TButton", background=[('active', hover_bg), ('pressed', '!disabled', button_bg)]) self.style.map("Install.TButton", background=[('active', '#60c080'), ('pressed', '!disabled', secondary_color)]) # 进度条样式 self.style.configure("Custom.Horizontal.TProgressbar", background=primary_color, troughcolor=button_bg, thickness=20) def create_widgets(self): """创建界面组件""" # 获取当前主题的背景色 bg_color = "#2c3e50" if self.theme == "深色" else "#f5f5f5" text_color = "#ecf0f1" if self.theme == "深色" else "#333333" # 标题区域 title_frame = tk.Frame(self.root, bg=bg_color) title_frame.pack(pady=(20, 10)) ttk.Label(title_frame, text=self.window_title, style="Title.TLabel").pack() # 表格区域 tree_frame = tk.Frame(self.root, bg=bg_color) tree_frame.pack(fill=tk.BOTH, expand=True, padx=30, pady=(0, 15)) # 创建Treeview self.tree = ttk.Treeview( tree_frame, columns=('选择', '序号', '软件名称', '软件大小'), show='headings', selectmode='none' ) # 设置列 columns = { '选择': {'width': 60, 'anchor': 'center'}, '序号': {'width': 60, 'anchor': 'center'}, '软件名称': {'width': 380, 'anchor': 'center'}, '软件大小': {'width': 120, 'anchor': 'center'} } for col, opts in columns.items(): self.tree.column(col, **opts) self.tree.heading(col, text=col) # 滚动条 scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 绑定复选框点击事件 self.tree.bind('<Button-1>', self.on_tree_click) # 底部按钮区域 bottom_frame = tk.Frame(self.root, bg=bg_color) bottom_frame.pack(fill=tk.X, padx=30, pady=(0, 20)) # 左侧按钮区域(全选/反选) left_button_frame = tk.Frame(bottom_frame, bg=bg_color) left_button_frame.pack(side="left") # 全选/反选复选框 self.select_all_var = tk.BooleanVar(value=self.default_select_all) select_all_cb = tk.Checkbutton( left_button_frame, text="全选/反选", variable=self.select_all_var, font=("Microsoft YaHei", 10), bg=bg_color, fg=text_color, activebackground=bg_color, activeforeground=text_color, selectcolor=bg_color, command=self.toggle_select_all ) select_all_cb.pack(side="left", padx=(0, 10)) # 右侧按钮区域 right_button_frame = tk.Frame(bottom_frame, bg=bg_color) right_button_frame.pack(side="right") # 退出按钮 exit_btn = ttk.Button( right_button_frame, text="退出安装", command=self.root.quit, style="Exit.TButton" ) exit_btn.pack(side="left", padx=5) # 开始安装按钮 install_btn = ttk.Button( right_button_frame, text="开始安装", command=self.start_installation, style="Install.TButton" ) install_btn.pack(side="left", padx=5) def on_tree_click(self, event): """处理复选框点击事件""" region = self.tree.identify("region", event.x, event.y) if region == "cell": column = self.tree.identify_column(event.x) if column == "#1": # 复选框列 item = self.tree.identify_row(event.y) current_values = self.tree.item(item, 'values') new_state = "☑" if current_values[0] == "☐" else "☐" self.tree.item(item, values=(new_state, *current_values[1:])) self.update_select_all_state() def update_select_all_state(self): """更新全选复选框状态""" all_selected = all( self.tree.item(item, 'values')[0] == "☑" for item in self.tree.get_children() ) self.select_all_var.set(all_selected) def load_software_list(self): """加载软件列表""" config = configparser.ConfigParser() try: if os.path.exists('config.ini'): config.read('config.ini', encoding='utf-8') if not os.path.exists(self.data_dir): os.makedirs(self.data_dir) software_section = None if '软件列表' in config: software_section = config['软件列表'] elif 'Software' in config: software_section = config['Software'] if software_section: for idx, (name, cmd) in enumerate(software_section.items(), 1): if name.startswith('#'): continue exe_name = cmd.split()[0] exe_path = os.path.join(self.data_dir, exe_name) software = { 'name': name, 'exe': exe_name, 'path': exe_path, 'cmd': cmd.replace(exe_name, exe_path), 'size': '未知' } if os.path.exists(exe_path): try: software['size'] = self.format_size(os.path.getsize(exe_path)) except Exception as e: print(f"获取 {name} 大小失败: {e}") else: print(f"文件不存在: {exe_path}") self.software_list.append(software) self.install_commands[name] = software['cmd'] else: messagebox.showerror("错误", "配置文件中缺少[软件列表]或[Software]段") except Exception as e: messagebox.showerror("错误", f"读取配置文件失败: {e}") def format_size(self, size): """格式化文件大小""" 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 populate_treeview(self): """填充表格数据""" for idx, software in enumerate(self.software_list, 1): initial_state = "☑" if self.default_select_all else "☐" self.tree.insert('', 'end', values=( initial_state, idx, software['name'], software['size'] )) self.select_all_var.set(self.default_select_all) def toggle_select_all(self): """全选/反选功能""" new_state = "☑" if self.select_all_var.get() else "☐" for item in self.tree.get_children(): current_values = self.tree.item(item, 'values') self.tree.item(item, values=(new_state, *current_values[1:])) def start_installation(self): """开始安装选中的软件""" selected_software = [] for item in self.tree.get_children(): values = self.tree.item(item, 'values') if values[0] == "☑": for software in self.software_list: if software['name'] == values[2]: selected_software.append(software) break if not selected_software: messagebox.showwarning("警告", "请至少选择一个软件进行安装") return # 禁用按钮 for widget in self.root.winfo_children(): if isinstance(widget, tk.Button): widget.config(state="disabled") # 创建安装进度窗口 self.progress_window = tk.Toplevel(self.root) self.progress_window.title("安装进度") self.progress_window.geometry("400x200") self.progress_window.resizable(False, False) bg_color = "#2c3e50" if self.theme == "深色" else "#f5f5f5" text_color = "#ecf0f1" if self.theme == "深色" else "#333333" self.progress_window.configure(bg=bg_color) self.progress_window.attributes('-topmost', True) # 居中窗口 self.progress_window.update_idletasks() width = self.progress_window.winfo_width() height = self.progress_window.winfo_height() x = (self.progress_window.winfo_screenwidth() // 2) - (width // 2) y = (self.progress_window.winfo_screenheight() // 2) - (height // 2) self.progress_window.geometry(f"{width}x{height}+{x}+{y}") # 进度窗口内容 tk.Label( self.progress_window, text="正在安装软件,请稍候...", font=("Microsoft YaHei", 12), bg=bg_color, fg=text_color, pady=20 ).pack() self.progress_var = tk.DoubleVar() progress_bar = ttk.Progressbar( self.progress_window, variable=self.progress_var, maximum=100, mode="determinate", style="Custom.Horizontal.TProgressbar" ) progress_bar.pack(fill="x", padx=20, pady=10) self.status_label = tk.Label( self.progress_window, text="准备安装...", font=("Microsoft YaHei", 10), bg=bg_color, fg=text_color ) self.status_label.pack() # 重置安装状态 self.installation_processes = [] self.failed_installations = [] self.installation_complete = False # 在新线程中执行安装 threading.Thread( target=self.install_software, args=(selected_software,), daemon=True ).start() # 检查安装状态 self.check_installation_status() def install_software(self, software_list): """安装软件""" total = len(software_list) success_count = 0 for i, software in enumerate(software_list): exe_path = os.path.join(self.data_dir, software["exe"]) if not os.path.exists(exe_path): self.failed_installations.append(f"{software['name']} (文件缺失)") continue try: self.root.after(0, lambda s=software: self.status_label.config(text=f"正在安装 {s['name']}...")) cmd = self.install_commands.get(software["name"], "") if not cmd: self.failed_installations.append(f"{software['name']} (未找到安装命令)") continue process = subprocess.Popen(cmd, shell=True) self.installation_processes.append((software["name"], process)) self.root.after(0, lambda p=(i + 1) / total * 100: self.progress_var.set(p)) success_count += 1 except Exception as e: self.failed_installations.append(f"{software['name']} (错误: {str(e)})") self.installation_complete = True if success_count > 0: self.root.after(0, lambda: self.status_label.config(text=f"已完成 {success_count}/{total} 个软件安装")) def check_installation_status(self): """检查安装状态""" if self.installation_complete and all(not self.is_process_running(p[1].pid) for p in self.installation_processes): if self.progress_window: self.progress_window.destroy() if self.failed_installations: failed_list = "\n".join(self.failed_installations) messagebox.showwarning( "安装完成", f"安装完成,但有 {len(self.failed_installations)} 个软件安装失败:\n{failed_list}" ) else: messagebox.showinfo("完成", "所有软件安装已完成") # 重新启用按钮 for widget in self.root.winfo_children(): if isinstance(widget, tk.Button): widget.config(state="normal") else: self.root.after(1000, self.check_installation_status) def is_process_running(self, pid): """检查进程是否在运行""" try: return psutil.pid_exists(pid) except: return False def main(): root = tk.Tk() app = SoftwareInstaller(root) root.mainloop() if __name__ == "__main__": main() |
什么都不说吧 发表于 2025-6-17 19:09 不支持 win7 |
piaomusic 发表于 2025-6-8 10:49 安装了运行库还是这样。 |
感谢分享! |
谢谢分享 |
感谢辛苦分享! |
感谢分享 |
piaomusic 发表于 2025-6-8 10:49 额................ 作为网管的话,假如涉及的员工数量极其惊人,在工作中做事的时候,经常需要运行库搞一通才能做后续的事,就有点不太常用这个了 |
本帖最后由 piaomusic 于 2025-6-8 10:52 编辑 2010天月来了 发表于 2025-6-8 08:43 你的系统缺少 微软运行库 微软常用运行库合集 Dreamcast |
你那个7系统的,还是不行 ![]() |
谢谢分享 |
奈绪 发表于 2025-6-7 19:39 我还在修改。 |
谢谢楼主分享 |
感谢楼主分享! |
感谢分享 |
感谢分享!! |
谢谢分享 |
感谢分享软件自选安装管理器 ,辛苦了 |
感谢分享 |
谢谢分享,赞 |
感谢分享 |
谢谢楼主分享 |
感谢分享 |
晚上更新了,下午的版本配置文件太麻烦。 Icons 文件夹存放图标,Data文件夹存放需要安装的EXE文件,程序会自动读取软件的版本信息和大小。 |
谢谢楼主无私分享! |
感謝分享 |
驚為天人的神作,看了讓人熱血沸騰,感謝分享。 |
Powered by Discuz! X3.3
© 2001-2017 Comsenz Inc.