Python+Tkinter实现Windows Hosts文件编辑管理工具

作者:

文章目录
  • 在日常开发、网络调试或科学上网场景中,Hosts文件修改是每个开发者都绕不开的"必修课"。传统的手动修改方式存在三大痛点: 操作风险高:直接修改系统文件可能导致语法错误 效率低下:每次修改需要定位到系统目录 缺乏管理:版本回溯困难,多人协作不便 本文将完整解析一个基于Python Tkinter开发的Windows Hosts管理工具(附完整源码),它具备: 可视化编辑 一键备份/恢复 GitHub加速预设 DNS缓存刷新 网络配置快速入口
  • 通过本工具的开发实践,我们不仅解决了Hosts管理的痛点,更展示了Python GUI开发的强大灵活性。工具已开源,欢迎Star和贡献代码! 完整源码下载 import tkinter as tk from tkinter import messagebox, filedialog import os import shutil import logging import datetime # 日志配置 LOG_DIR = os.path.join(os.path.dirname(__file__), “logs”) if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR) log_filename = os.path.join(LOG_DIR, f”hostsmanager_{datetime.datetime.now().strftime(‘%Y%m%d_%H%M%S’)}.log”) logging.basicConfig( level=logging.INFO, format=’%(asctime)s – %(levelname)s – %(message)s’, handlers=[ logging.FileHandler(log_filename, encoding=’utf-8′), logging.StreamHandler() # 同时输出到控制台,方便调试 ] ) HOSTS_PATH = r”C:WindowsSystem32driversetchosts” BACKUP_DIR = os.path.join(os.path.dirname(__file__), “backups”) class NetworkToolsManager: @staticmethod def flush_dns(): import subprocess subprocess.run([“ipconfig”, “/flushdns”], shell=True) class HostsManagerApp: def __init__(self, root): self.root = root self.root.title(“Windows Hosts编辑器”) self.root.geometry(“800×600″) # 增大窗口尺寸 self.root.resizable(True, True) # 允许调整大小 self.root.configure(bg=”#f0f0f0″) # 使用更柔和的背景色 # 卡片式布局,增加圆角和更细微的阴影 self.main_frame = tk.Frame(root, bg=”#ffffff”, bd=0, relief=”solid”, borderwidth=1, highlightbackground=”#dcdcdc”, highlightthickness=1) self.main_frame.place(relx=0.02, rely=0.02, relwidth=0.96, relheight=0.96) # 调整布局,减少顶部空白,增加高度 # Hosts内容文本框和滚动条 self.text_frame = tk.Frame(self.main_frame, bg=”#ffffff”) # 文本框容器 self.text_frame.place(relx=0.03, rely=0.10, relwidth=0.94, relheight=0.68) # 调整文本框位置和高度 self.scrollbar = tk.Scrollbar(self.text_frame, orient=”vertical”) self.scrollbar.pack(side=”right”, fill=”y”) self.text = tk.Text( self.text_frame, font=(“Segoe UI”, 11), # 调整字体大小 wrap=”none”, bg=”#ffffff”, relief=”flat”, bd=0, highlightthickness=1, highlightbackground=”#e0e0e0″, highlightcolor=”#64b5f6″, # 调整高亮颜色 yscrollcommand=self.scrollbar.set, padx=10, # 增加内边距 pady=10 ) self.text.pack(side=”left”, fill=”both”, expand=True) self.scrollbar.config(command=self.text.yview) # 按钮区 – 使用更现代的样式和布局 button_frame = tk.Frame(self.main_frame, bg=”#ffffff”) button_frame.place(relx=0.03, rely=0.02, relwidth=0.94, height=45) # 增加按钮区域高度 btn_style = { “font”: (“Segoe UI”, 10, “bold”), # 调整字体 “fg”: “white”, “bd”: 0, “relief”: “flat”, “cursor”: “hand2”, “height”: 1, “padx”: 10, “pady”: 5 } # 按钮颜色和悬停效果 colors = { “load”: (“#4CAF50”, “#66bb6a”), “save”: (“#2196F3”, “#64b5f6”), “backup”: (“#FF9800”, “#ffb74d”), “restore”: (“#9C27B0”, “#ba68c8”), “switch”: (“#78909C”, “#90a4ae”), “network”: (“#009688”, “#4db6ac”) } def create_button(parent, text, command, color_key, width): btn = tk.Button( parent, text=text, command=command, bg=colors[color_key][0], activebackground=colors[color_key][1], activeforeground=”white”, **btn_style ) btn.bind(“<Enter>”, lambda e, b=btn, c=colors[color_key][1]: b.config(bg=c)) btn.bind(“<Leave>”, lambda e, b=btn, c=colors[color_key][0]: b.config(bg=c)) return btn self.load_btn = create_button(button_frame, “加载hosts”, self.load_hosts, “load”, 100) self.load_btn.pack(side=”left”, padx=5) self.save_btn = create_button(button_frame, “保存hosts”, self.save_hosts, “save”, 100) self.save_btn.pack(side=”left”, padx=5) self.backup_btn = create_button(button_frame, “备份当前hosts”, self.backup_hosts, “backup”, 120) self.backup_btn.pack(side=”left”, padx=5) self.restore_btn = create_button(button_frame, “管理备份”, self.open_backup_window, “restore”, 100) self.restore_btn.pack(side=”left”, padx=5) self.switch_btn = create_button(button_frame, “切换hosts配置”, self.switch_hosts, “switch”, 130) self.switch_btn.pack(side=”left”, padx=5) # 网络工具菜单按钮 self.network_menu_btn = tk.Menubutton( self.main_frame, text=”网络工具”, bg=colors[“network”][0], activebackground=colors[“network”][1], activeforeground=”white”, **btn_style ) self.network_menu_btn.place(relx=0.03, rely=0.80, width=100, height=32) self.network_menu = tk.Menu(self.network_menu_btn, tearoff=0, bg=”#ffffff”, fg=”#333333″, font=(“Segoe UI”, 10)) self.network_menu.add_command(label=”加速GitHub”, command=self.accelerate_github) self.network_menu.add_command(label=”网络配置”, command=self.open_network_settings) self.network_menu.add_command(label=”刷新DNS缓存”, command=NetworkToolsManager.flush_dns) self.network_menu.add_command(label=”打开hosts文件”, command=self.open_hosts_file) self.network_menu_btn.config(menu=self.network_menu) self.network_menu_btn.bind( “<Enter>”, lambda e: self.network_menu_btn.config(bg=colors[“network”][1]) ) self.network_menu_btn.bind( “<Leave>”, lambda e: self.network_menu_btn.config(bg=colors[“network”][0]) ) # 状态栏 self.status_bar = tk.Label(self.main_frame, text=”就绪”, bd=1, relief=tk.SUNKEN, anchor=tk.W, bg=”#e3f2fd”, fg=”#37474f”) self.status_bar.place(relx=0.03, rely=0.92, relwidth=0.94, height=24) self.load_hosts() logging.info(“应用程序启动”) def open_hosts_file(self): import subprocess try: subprocess.Popen([“notepad.exe”, HOSTS_PATH]) logging.info(f”已用系统默认编辑器打开 hosts 文件: {HOSTS_PATH}”) except Exception as e: logging.exception(f”打开 hosts 文件失败: {HOSTS_PATH}”) messagebox.showerror(“错误”, f”打开hosts文件失败: {e}”) def load_hosts(self): try: with open(HOSTS_PATH, “r”, encoding=”utf-8″) as f: content = f.read() self.text.delete(1.0, tk.END) self.text.insert(tk.END, content) logging.info(f”成功加载 hosts 文件: {HOSTS_PATH}”) except Exception as e: logging.exception(f”加载 hosts 文件失败: {HOSTS_PATH}”) messagebox.showerror(“错误”, f”加载hosts失败: {e}”) def save_hosts(self): content = self.text.get(1.0, tk.END) try: with open(HOSTS_PATH, “w”, encoding=”utf-8″) as f: f.write(content) logging.info(f”成功保存 hosts 文件: {HOSTS_PATH}”) messagebox.showinfo(“成功”, “hosts文件已保存!”) except Exception as e: logging.exception(f”保存 hosts 文件失败: {HOSTS_PATH}”) messagebox.showerror(“错误”, f”保存hosts失败: {e}”) def backup_hosts(self): if not os.path.exists(BACKUP_DIR): try: os.makedirs(BACKUP_DIR) logging.info(f”创建备份目录: {BACKUP_DIR}”) except Exception as e: logging.exception(f”创建备份目录失败: {BACKUP_DIR}”) messagebox.showerror(“错误”, f”创建备份目录失败: {e}”) return backup_name = f”hosts_{datetime.datetime.now().strftime(‘%Y%m%d_%H%M%S’)}.bak” backup_path = os.path.join(BACKUP_DIR, backup_name) try: shutil.copy(HOSTS_PATH, backup_path) logging.info(f”成功备份 hosts 文件到: {backup_path}”) messagebox.showinfo(“成功”, f”备份成功: {backup_name}”) except Exception as e: logging.exception(f”备份 hosts 文件失败: {HOSTS_PATH} -> {backup_path}”) messagebox.showerror(“错误”, f”备份失败: {e}”) def open_backup_window(self): backup_window = tk.Toplevel(self.root) backup_window.title(“备份管理”) backup_window.geometry(“400×350″) backup_window.resizable(True, True) backup_window.configure(bg=”#f0f0f0″) backup_window.transient(self.root) backup_window.grab_set() # 备份列表区域 backup_frame = tk.Frame(backup_window, bg=”#ffffff”, bd=1, relief=”solid”, highlightbackground=”#dcdcdc”, highlightthickness=1) backup_frame.pack(padx=10, pady=10, fill=”both”, expand=True) label_backup = tk.Label( backup_frame, text=”备份列表:”, font=(“Segoe UI”, 10, “bold”), bg=”#ffffff”, fg=”#555555″, anchor=”w” ) label_backup.pack(side=”top”, fill=”x”, padx=5, pady=(5, 2)) listbox_frame = tk.Frame(backup_frame, bg=”#ffffff”) listbox_frame.pack(fill=”both”, expand=True, padx=5, pady=(0, 5)) backup_scrollbar = tk.Scrollbar(listbox_frame, orient=”vertical”) backup_scrollbar.pack(side=”right”, fill=”y”) backup_listbox = tk.Listbox( listbox_frame, font=(“Segoe UI”, 10), bd=0, highlightthickness=1, highlightbackground=”#e0e0e0″, relief=”flat”, bg=”#f9f9f9″, yscrollcommand=backup_scrollbar.set, selectbackground=”#64b5f6″, selectforeground=”white” ) backup_listbox.pack(side=”left”, fill=”both”, expand=True) backup_scrollbar.config(command=backup_listbox.yview) def refresh_list(): backup_listbox.delete(0, tk.END) if not os.path.exists(BACKUP_DIR): logging.warning(f”备份目录不存在: {BACKUP_DIR}”) return try: files = sorted(os.listdir(BACKUP_DIR), reverse=True) for fname in files: if fname.endswith(“.bak”): backup_listbox.insert(tk.END, fname) logging.info(f”成功加载备份列表从: {BACKUP_DIR}”) except Exception as e: logging.exception(f”加载备份列表失败: {BACKUP_DIR}”) messagebox.showerror(“错误”, f”加载备份列表失败: {e}”, parent=backup_window) def restore_selected_backup(): sel = backup_listbox.curselection() if not sel: messagebox.showwarning(“提示”, “请先选择一个备份文件!”, parent=backup_window) return backup_file = backup_listbox.get(sel[0]) backup_path = os.path.join(BACKUP_DIR, backup_file) try: shutil.copy(backup_path, HOSTS_PATH) self.load_hosts() logging.info(f”成功从备份还原 hosts 文件: {backup_path} -> {HOSTS_PATH}”) messagebox.showinfo(“成功”, “还原成功!”, parent=backup_window) backup_window.destroy() except Exception as e: logging.exception(f”从备份还原 hosts 文件失败: {backup_path} -> {HOSTS_PATH}”) messagebox.showerror(“错误”, f”还原失败: {e}”, parent=backup_window) def delete_selected_backup(): sel = backup_listbox.curselection() if not sel: messagebox.showwarning(“提示”, “请先选择一个备份文件!”, parent=backup_window) return backup_file = backup_listbox.get(sel[0]) backup_path = os.path.join(BACKUP_DIR, backup_file) if messagebox.askyesno(“确认”, f”确定要删除备份文件 ‘{backup_file}’ 吗?”, parent=backup_window): try: os.remove(backup_path) logging.info(f”成功删除备份文件: {backup_path}”) messagebox.showinfo(“成功”, “备份文件已删除!”, parent=backup_window) refresh_list() except Exception as e: logging.exception(f”删除备份文件失败: {backup_path}”) messagebox.showerror(“错误”, f”删除失败: {e}”, parent=backup_window) # 按钮区域 button_frame_backup = tk.Frame(backup_window, bg=”#f0f0f0″) button_frame_backup.pack(pady=(0, 10), fill=”x”, padx=10) restore_button = tk.Button(button_frame_backup, text=”还原选中备份”, command=restore_selected_backup, bg=”#4CAF50″, fg=”white”, relief=”flat”, font=(“Segoe UI”, 10, “bold”)) restore_button.pack(side=”left”, padx=5) delete_button = tk.Button(button_frame_backup, text=”删除选中备份”, command=delete_selected_backup, bg=”#f44336″, fg=”white”, relief=”flat”, font=(“Segoe UI”, 10, “bold”)) delete_button.pack(side=”left”, padx=5) refresh_button = tk.Button(button_frame_backup, text=”刷新列表”, command=refresh_list, bg=”#2196F3″, fg=”white”, relief=”flat”, font=(“Segoe UI”, 10, “bold”)) refresh_button.pack(side=”right”, padx=5) refresh_list() backup_window.mainloop() def switch_hosts(self): file_path = filedialog.askopenfilename( title=”选择hosts配置”, filetypes=[(“hosts文件”, “*.bak;*.txt;*.hosts;*.*”)], initialdir=BACKUP_DIR, ) if file_path: try: shutil.copy(file_path, HOSTS_PATH) self.load_hosts() logging.info(f”成功切换 hosts 文件: {file_path} -> {HOSTS_PATH}”) messagebox.showinfo(“成功”, “切换成功!”) except Exception as e: logging.exception(f”切换 hosts 文件失败: {file_path} -> {HOSTS_PATH}”) messagebox.showerror(“错误”, f”切换失败: {e}”) else: logging.info(“取消切换 hosts 文件”) def set_dns(self): import subprocess from tkinter import simpledialog dns = simpledialog.askstring( “DNS设置”, “请输入新的DNS服务器地址(如8.8.8.8):”, parent=self.root ) if dns: try: result = subprocess.check_output( “wmic nic where (NetEnabled=true) get NetConnectionID”, shell=True, encoding=”gbk”, stderr=subprocess.STDOUT ) adapters = [ line.strip() for line in result.splitlines() if line.strip() and “NetConnectionID” not in line ] if not adapters: logging.error(“未检测到可用的网络适配器”) messagebox.showerror(“错误”, “未检测到可用的网络适配器!”) return adapter = adapters[0] cmd = f’netsh interface ip set dns name=”{adapter}” static {dns}’ logging.info(f”尝试设置 DNS: {cmd}”) subprocess.check_call(cmd, shell=True) logging.info(f”成功设置 DNS 为 {dns} (适配器: {adapter})”) messagebox.showinfo(“成功”, f”DNS已设置为: {dns}n(适配器: {adapter})”) except subprocess.CalledProcessError as e: logging.exception(f”设置 DNS 命令执行失败: {cmd}n输出: {e.output}”) messagebox.showerror(“错误”, f”设置DNS失败: {e}n请检查是否以管理员权限运行”) except Exception as e: logging.exception(f”设置 DNS 失败”) messagebox.showerror(“错误”, f”设置DNS失败: {e}”) else: logging.info(“取消设置 DNS”) def open_network_settings(self): import subprocess try: logging.info(“尝试打开网络连接设置”) subprocess.Popen(“control.exe ncpa.cpl”, shell=True) except Exception as e: logging.exception(“打开网络连接设置失败”) messagebox.showerror(“错误”, f”无法打开网络配置页面: {e}”) def accelerate_github(self): “””一键加速GitHub访问””” github_hosts = “”” # GitHub Start 140.82.112.3 github.com 140.82.112.4 gist.github.com 140.82.113.4 api.github.com 140.82.114.4 assets-cdn.github.com 140.82.112.5 raw.githubusercontent.com 140.82.113.5 user-images.githubusercontent.com 140.82.114.5 favicons.githubusercontent.com 140.82.112.6 avatars.githubusercontent.com 140.82.113.6 avatars0.githubusercontent.com 140.82.114.6 avatars1.githubusercontent.com 140.82.112.7 avatars2.githubusercontent.com 140.82.113.7 avatars3.githubusercontent.com 140.82.114.7 avatars4.githubusercontent.com 140.82.112.8 avatars5.githubusercontent.com # GitHub End “”” try: with open(HOSTS_PATH, “r”, encoding=”utf-8″) as f: current_content = f.read() if “# GitHub Start” in current_content: logging.warning(“GitHub hosts 已存在,跳过添加”) messagebox.showwarning(“提示”, “GitHub加速配置似乎已存在,无需重复添加。”) return with open(HOSTS_PATH, “a”, encoding=”utf-8″) as f: f.write(“n” + github_hosts) self.load_hosts() logging.info(“成功应用 GitHub 加速 hosts”) messagebox.showinfo(“成功”, “GitHub加速已应用!”) except Exception as e: logging.exception(“应用 GitHub 加速 hosts 失败”) messagebox.showerror(“错误”, f”加速GitHub失败: {e}”) def validate_hosts(self, content): import re pattern = r”^(s*$|#.*|# —.*|(d{1,3}.){3}d{1,3}s+S+)?$” return all(re.match(pattern, line.strip()) for line in content.split(“n”) if line.strip()) def main(): root = tk.Tk() root.protocol(‘WM_DELETE_WINDOW’, root.destroy) app = HostsManagerApp(root) root.mainloop() if __name__ == “__main__”: try: main() except Exception as e: logging.critical(“应用程序发生未捕获的严重错误”, exc_info=True) messagebox.showerror(“严重错误”, f”应用程序遇到严重问题,请查看日志文件获取详细信息:n{log_filename}nn错误: {e}”) 互动提问: 你在Hosts管理中还遇到哪些痛点? 希望增加哪些新功能? 对UI设计有何改进建议? 到此这篇关于Python+Tkinter实现Windows Hosts文件编辑管理工具的文章就介绍到这了,更多相关Python Windows Hosts文件管理内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: python切换hosts文件代码示例 使用python Fabric动态修改远程机器hosts的方法 如何使用python切换hosts文件 Python实现批量修改文件时间属性 Python实现批量修改文件日期
  • 目录
    • 一、前言:为什么我们需要专业的Hosts管理工具
    • 二、工具核心功能全景图
      • 2.1 基础功能模块
      • 2.2 进阶网络工具
    • 三、关键技术实现深度解析
      • 3.1 安全文件操作机制
      • 3.2 智能备份系统设计
      • 3.3 界面交互优化技巧
    • 四、实战演示:从安装到高阶使用
      • 4.1 环境搭建步骤
      • 4.2 典型使用场景演示
    • 五、性能优化与异常处理
      • 5.1 大文件处理优化
      • 5.2 全面的错误处理机制
    • 六、工具扩展方向
      • 6.1 企业级功能增强
      • 6.2 跨平台适配方案
    • 七、结语与资源下载

      在日常开发、网络调试或科学上网场景中,Hosts文件修改是每个开发者都绕不开的"必修课"。传统的手动修改方式存在三大痛点:

      • 操作风险高:直接修改系统文件可能导致语法错误
      • 效率低下:每次修改需要定位到系统目录
      • 缺乏管理:版本回溯困难,多人协作不便

      本文将完整解析一个基于Python Tkinter开发的Windows Hosts管理工具(附完整源码),它具备:

      • 可视化编辑
      • 一键备份/恢复
      • GitHub加速预设
      • DNS缓存刷新
      • 网络配置快速入口

      功能模块 技术实现 使用场景
      可视化编辑 Tkinter Text组件 安全修改Hosts内容
      版本管理 shutil文件操作 配置回滚/多环境切换
      语法校验 正则表达式 防止配置错误

      class NetworkToolsManager:
          @staticmethod
          def flush_dns():
              subprocess.run(["ipconfig", "/flushdns"], shell=True)
      

      def save_hosts(self):
          try:
              with open(HOSTS_PATH, "w", encoding="utf-8") as f:
                  f.write(content)
              logging.info(f"成功保存hosts文件")
          except PermissionError:
              messagebox.showerror("错误", "请以管理员权限运行程序")
      

      关键点分析:

      • 强制UTF-8编码避免乱码
      • try-catch捕获权限异常
      • 操作日志全程记录

      备份策略采用"时间戳+增量"模式:

      backups/
         ├── hosts_20240507_143000.bak
         ├── hosts_20240507_143500.bak
         └── hosts_20240507_144000.bak

      核心代码逻辑:

      backup_name = f"hosts_{datetime.now().strftime('%Y%m%d_%H%M%S')}.bak"
      shutil.copy(HOSTS_PATH, os.path.join(BACKUP_DIR, backup_name))
      

      现代化UI实现方案:

      卡片式布局(CSS风格阴影效果)

      self.main_frame = tk.Frame(highlightbackground="#dcdcdc", highlightthickness=1)
      

      按钮悬停特效

      btn.bind("<Enter>", lambda e: btn.config(bg=hover_color))
      btn.bind("<Leave>", lambda e: btn.config(bg=normal_color))
      

      状态栏实时反馈

      self.status_bar = tk.Label(text="就绪", anchor=tk.W)
      

      安装Python 3.8+

      安装依赖库:

      pip install tkinter shutil logging datetime
      

      下载源码(文末提供)

      场景一:快速切换开发环境

      • 点击"加载hosts"读取当前配置
      • 修改测试环境域名指向
      • 保存后自动备份

      场景二:GitHub加速

      def accelerate_github(self):
          github_hosts = """
          140.82.112.3 github.com
          140.82.112.4 gist.github.com
          ..."""
      

      点击网络工具→加速GitHub一键注入最优IP

      # 分块读取大文件
      with open(HOSTS_PATH, "r") as f:
          while chunk := f.read(4096):
              self.text.insert(tk.END, chunk)
      

      错误类型 处理方案 用户提示
      文件不存在 自动创建备份目录 “正在初始化备份目录…”
      权限不足 捕获PermissionError “请以管理员身份运行”
      编码错误 强制UTF-8编码 “文件编码异常,已自动转换”

      增加多环境Profile管理

      开发团队协作版本控制

      添加配置差异对比功能

      # 自适应系统路径
      if sys.platform == "win32":
          HOSTS_PATH = r"C:WindowsSystem32driversetchosts"
      elif sys.platform == "linux":
          HOSTS_PATH = "/etc/hosts"
      

      通过本工具的开发实践,我们不仅解决了Hosts管理的痛点,更展示了Python GUI开发的强大灵活性。工具已开源,欢迎Star和贡献代码!

      完整源码下载

      import tkinter as tk
      from tkinter import messagebox, filedialog
      import os
      import shutil
      import logging
      import datetime
      
      # 日志配置
      LOG_DIR = os.path.join(os.path.dirname(__file__), "logs")
      if not os.path.exists(LOG_DIR):
          os.makedirs(LOG_DIR)
      
      log_filename = os.path.join(LOG_DIR, f"hostsmanager_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
      logging.basicConfig(
          level=logging.INFO,
          format='%(asctime)s - %(levelname)s - %(message)s',
          handlers=[
              logging.FileHandler(log_filename, encoding='utf-8'),
              logging.StreamHandler() # 同时输出到控制台,方便调试
          ]
      )
      
      HOSTS_PATH = r"C:WindowsSystem32driversetchosts"
      BACKUP_DIR = os.path.join(os.path.dirname(__file__), "backups")
      
      
      class NetworkToolsManager:
          @staticmethod
          def flush_dns():
              import subprocess
              subprocess.run(["ipconfig", "/flushdns"], shell=True)
      
      class HostsManagerApp:
          def __init__(self, root):
              self.root = root
              self.root.title("Windows Hosts编辑器")
              self.root.geometry("800x600") # 增大窗口尺寸
              self.root.resizable(True, True) # 允许调整大小
              self.root.configure(bg="#f0f0f0") # 使用更柔和的背景色
      
              # 卡片式布局,增加圆角和更细微的阴影
              self.main_frame = tk.Frame(root, bg="#ffffff", bd=0, relief="solid", borderwidth=1, highlightbackground="#dcdcdc", highlightthickness=1)
              self.main_frame.place(relx=0.02, rely=0.02, relwidth=0.96, relheight=0.96) # 调整布局,减少顶部空白,增加高度
      
              # Hosts内容文本框和滚动条
              self.text_frame = tk.Frame(self.main_frame, bg="#ffffff") # 文本框容器
              self.text_frame.place(relx=0.03, rely=0.10, relwidth=0.94, relheight=0.68) # 调整文本框位置和高度
      
              self.scrollbar = tk.Scrollbar(self.text_frame, orient="vertical")
              self.scrollbar.pack(side="right", fill="y")
              
              self.text = tk.Text(
                  self.text_frame,
                  font=("Segoe UI", 11), # 调整字体大小
                  wrap="none",
                  bg="#ffffff",
                  relief="flat",
                  bd=0,
                  highlightthickness=1,
                  highlightbackground="#e0e0e0",
                  highlightcolor="#64b5f6", # 调整高亮颜色
                  yscrollcommand=self.scrollbar.set,
                  padx=10, # 增加内边距
                  pady=10
              )
              self.text.pack(side="left", fill="both", expand=True)
              
              self.scrollbar.config(command=self.text.yview)
      
              # 按钮区 - 使用更现代的样式和布局
              button_frame = tk.Frame(self.main_frame, bg="#ffffff")
              button_frame.place(relx=0.03, rely=0.02, relwidth=0.94, height=45) # 增加按钮区域高度
      
              btn_style = {
                  "font": ("Segoe UI", 10, "bold"), # 调整字体
                  "fg": "white",
                  "bd": 0,
                  "relief": "flat",
                  "cursor": "hand2",
                  "height": 1,
                  "padx": 10,
                  "pady": 5
              }
      
              # 按钮颜色和悬停效果
              colors = {
                  "load": ("#4CAF50", "#66bb6a"),
                  "save": ("#2196F3", "#64b5f6"),
                  "backup": ("#FF9800", "#ffb74d"),
                  "restore": ("#9C27B0", "#ba68c8"),
                  "switch": ("#78909C", "#90a4ae"),
                  "network": ("#009688", "#4db6ac")
              }
      
              def create_button(parent, text, command, color_key, width):
                  btn = tk.Button(
                      parent,
                      text=text,
                      command=command,
                      bg=colors[color_key][0],
                      activebackground=colors[color_key][1],
                      activeforeground="white",
                      **btn_style
                  )
                  btn.bind("<Enter>", lambda e, b=btn, c=colors[color_key][1]: b.config(bg=c))
                  btn.bind("<Leave>", lambda e, b=btn, c=colors[color_key][0]: b.config(bg=c))
                  return btn
      
              self.load_btn = create_button(button_frame, "加载hosts", self.load_hosts, "load", 100)
              self.load_btn.pack(side="left", padx=5)
      
              self.save_btn = create_button(button_frame, "保存hosts", self.save_hosts, "save", 100)
              self.save_btn.pack(side="left", padx=5)
      
              self.backup_btn = create_button(button_frame, "备份当前hosts", self.backup_hosts, "backup", 120)
              self.backup_btn.pack(side="left", padx=5)
      
              self.restore_btn = create_button(button_frame, "管理备份", self.open_backup_window, "restore", 100)
              self.restore_btn.pack(side="left", padx=5)
      
              self.switch_btn = create_button(button_frame, "切换hosts配置", self.switch_hosts, "switch", 130)
              self.switch_btn.pack(side="left", padx=5)
      
              # 网络工具菜单按钮
              self.network_menu_btn = tk.Menubutton(
                  self.main_frame,
                  text="网络工具",
                  bg=colors["network"][0],
                  activebackground=colors["network"][1],
                  activeforeground="white",
                  **btn_style
              )
              self.network_menu_btn.place(relx=0.03, rely=0.80, width=100, height=32)
              
              self.network_menu = tk.Menu(self.network_menu_btn, tearoff=0, bg="#ffffff", fg="#333333", font=("Segoe UI", 10))
              self.network_menu.add_command(label="加速GitHub", command=self.accelerate_github)
              self.network_menu.add_command(label="网络配置", command=self.open_network_settings)
              self.network_menu.add_command(label="刷新DNS缓存", command=NetworkToolsManager.flush_dns)
              self.network_menu.add_command(label="打开hosts文件", command=self.open_hosts_file)
              
              self.network_menu_btn.config(menu=self.network_menu)
              self.network_menu_btn.bind(
                  "<Enter>", lambda e: self.network_menu_btn.config(bg=colors["network"][1])
              )
              self.network_menu_btn.bind(
                  "<Leave>", lambda e: self.network_menu_btn.config(bg=colors["network"][0])
              )
      
              # 状态栏
              self.status_bar = tk.Label(self.main_frame, text="就绪", bd=1, relief=tk.SUNKEN, 
                                      anchor=tk.W, bg="#e3f2fd", fg="#37474f")
              self.status_bar.place(relx=0.03, rely=0.92, relwidth=0.94, height=24)
      
              self.load_hosts()
              logging.info("应用程序启动")
      
          def open_hosts_file(self):
              import subprocess
              try:
                  subprocess.Popen(["notepad.exe", HOSTS_PATH])
                  logging.info(f"已用系统默认编辑器打开 hosts 文件: {HOSTS_PATH}")
              except Exception as e:
                  logging.exception(f"打开 hosts 文件失败: {HOSTS_PATH}")
                  messagebox.showerror("错误", f"打开hosts文件失败: {e}")
      
          def load_hosts(self):
              try:
                  with open(HOSTS_PATH, "r", encoding="utf-8") as f:
                      content = f.read()
                  self.text.delete(1.0, tk.END)
                  self.text.insert(tk.END, content)
                  logging.info(f"成功加载 hosts 文件: {HOSTS_PATH}")
              except Exception as e:
                  logging.exception(f"加载 hosts 文件失败: {HOSTS_PATH}")
                  messagebox.showerror("错误", f"加载hosts失败: {e}")
      
          def save_hosts(self):
              content = self.text.get(1.0, tk.END)
              try:
                  with open(HOSTS_PATH, "w", encoding="utf-8") as f:
                      f.write(content)
                  logging.info(f"成功保存 hosts 文件: {HOSTS_PATH}")
                  messagebox.showinfo("成功", "hosts文件已保存!")
              except Exception as e:
                  logging.exception(f"保存 hosts 文件失败: {HOSTS_PATH}")
                  messagebox.showerror("错误", f"保存hosts失败: {e}")
      
          def backup_hosts(self):
              if not os.path.exists(BACKUP_DIR):
                  try:
                      os.makedirs(BACKUP_DIR)
                      logging.info(f"创建备份目录: {BACKUP_DIR}")
                  except Exception as e:
                      logging.exception(f"创建备份目录失败: {BACKUP_DIR}")
                      messagebox.showerror("错误", f"创建备份目录失败: {e}")
                      return
      
              backup_name = f"hosts_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.bak"
              backup_path = os.path.join(BACKUP_DIR, backup_name)
              try:
                  shutil.copy(HOSTS_PATH, backup_path)
                  logging.info(f"成功备份 hosts 文件到: {backup_path}")
                  messagebox.showinfo("成功", f"备份成功: {backup_name}")
              except Exception as e:
                  logging.exception(f"备份 hosts 文件失败: {HOSTS_PATH} -> {backup_path}")
                  messagebox.showerror("错误", f"备份失败: {e}")
      
          def open_backup_window(self):
              backup_window = tk.Toplevel(self.root)
              backup_window.title("备份管理")
              backup_window.geometry("400x350")
              backup_window.resizable(True, True)
              backup_window.configure(bg="#f0f0f0")
              backup_window.transient(self.root)
              backup_window.grab_set()
      
              # 备份列表区域
              backup_frame = tk.Frame(backup_window, bg="#ffffff", bd=1, relief="solid", highlightbackground="#dcdcdc", highlightthickness=1)
              backup_frame.pack(padx=10, pady=10, fill="both", expand=True)
      
              label_backup = tk.Label(
                  backup_frame,
                  text="备份列表:",
                  font=("Segoe UI", 10, "bold"),
                  bg="#ffffff",
                  fg="#555555",
                  anchor="w"
              )
              label_backup.pack(side="top", fill="x", padx=5, pady=(5, 2))
      
              listbox_frame = tk.Frame(backup_frame, bg="#ffffff")
              listbox_frame.pack(fill="both", expand=True, padx=5, pady=(0, 5))
      
              backup_scrollbar = tk.Scrollbar(listbox_frame, orient="vertical")
              backup_scrollbar.pack(side="right", fill="y")
              
              backup_listbox = tk.Listbox(
                  listbox_frame,
                  font=("Segoe UI", 10),
                  bd=0,
                  highlightthickness=1,
                  highlightbackground="#e0e0e0",
                  relief="flat",
                  bg="#f9f9f9",
                  yscrollcommand=backup_scrollbar.set,
                  selectbackground="#64b5f6",
                  selectforeground="white"
              )
              backup_listbox.pack(side="left", fill="both", expand=True)
              backup_scrollbar.config(command=backup_listbox.yview)
      
              def refresh_list():
                  backup_listbox.delete(0, tk.END)
                  if not os.path.exists(BACKUP_DIR):
                      logging.warning(f"备份目录不存在: {BACKUP_DIR}")
                      return
                  try:
                      files = sorted(os.listdir(BACKUP_DIR), reverse=True)
                      for fname in files:
                          if fname.endswith(".bak"):
                              backup_listbox.insert(tk.END, fname)
                      logging.info(f"成功加载备份列表从: {BACKUP_DIR}")
                  except Exception as e:
                      logging.exception(f"加载备份列表失败: {BACKUP_DIR}")
                      messagebox.showerror("错误", f"加载备份列表失败: {e}", parent=backup_window)
      
              def restore_selected_backup():
                  sel = backup_listbox.curselection()
                  if not sel:
                      messagebox.showwarning("提示", "请先选择一个备份文件!", parent=backup_window)
                      return
                  backup_file = backup_listbox.get(sel[0])
                  backup_path = os.path.join(BACKUP_DIR, backup_file)
                  try:
                      shutil.copy(backup_path, HOSTS_PATH)
                      self.load_hosts()
                      logging.info(f"成功从备份还原 hosts 文件: {backup_path} -> {HOSTS_PATH}")
                      messagebox.showinfo("成功", "还原成功!", parent=backup_window)
                      backup_window.destroy()
                  except Exception as e:
                      logging.exception(f"从备份还原 hosts 文件失败: {backup_path} -> {HOSTS_PATH}")
                      messagebox.showerror("错误", f"还原失败: {e}", parent=backup_window)
      
              def delete_selected_backup():
                  sel = backup_listbox.curselection()
                  if not sel:
                      messagebox.showwarning("提示", "请先选择一个备份文件!", parent=backup_window)
                      return
                  backup_file = backup_listbox.get(sel[0])
                  backup_path = os.path.join(BACKUP_DIR, backup_file)
                  if messagebox.askyesno("确认", f"确定要删除备份文件 '{backup_file}' 吗?", parent=backup_window):
                      try:
                          os.remove(backup_path)
                          logging.info(f"成功删除备份文件: {backup_path}")
                          messagebox.showinfo("成功", "备份文件已删除!", parent=backup_window)
                          refresh_list()
                      except Exception as e:
                          logging.exception(f"删除备份文件失败: {backup_path}")
                          messagebox.showerror("错误", f"删除失败: {e}", parent=backup_window)
      
              # 按钮区域
              button_frame_backup = tk.Frame(backup_window, bg="#f0f0f0")
              button_frame_backup.pack(pady=(0, 10), fill="x", padx=10)
      
              restore_button = tk.Button(button_frame_backup, text="还原选中备份", command=restore_selected_backup, bg="#4CAF50", fg="white", relief="flat", font=("Segoe UI", 10, "bold"))
              restore_button.pack(side="left", padx=5)
      
              delete_button = tk.Button(button_frame_backup, text="删除选中备份", command=delete_selected_backup, bg="#f44336", fg="white", relief="flat", font=("Segoe UI", 10, "bold"))
              delete_button.pack(side="left", padx=5)
      
              refresh_button = tk.Button(button_frame_backup, text="刷新列表", command=refresh_list, bg="#2196F3", fg="white", relief="flat", font=("Segoe UI", 10, "bold"))
              refresh_button.pack(side="right", padx=5)
      
              refresh_list()
              backup_window.mainloop()
      
          def switch_hosts(self):
              file_path = filedialog.askopenfilename(
                  title="选择hosts配置",
                  filetypes=[("hosts文件", "*.bak;*.txt;*.hosts;*.*")],
                  initialdir=BACKUP_DIR,
              )
              if file_path:
                  try:
                      shutil.copy(file_path, HOSTS_PATH)
                      self.load_hosts()
                      logging.info(f"成功切换 hosts 文件: {file_path} -> {HOSTS_PATH}")
                      messagebox.showinfo("成功", "切换成功!")
                  except Exception as e:
                      logging.exception(f"切换 hosts 文件失败: {file_path} -> {HOSTS_PATH}")
                      messagebox.showerror("错误", f"切换失败: {e}")
              else:
                  logging.info("取消切换 hosts 文件")
      
          def set_dns(self):
              import subprocess
              from tkinter import simpledialog
      
              dns = simpledialog.askstring(
                  "DNS设置", "请输入新的DNS服务器地址(如8.8.8.8):", parent=self.root
              )
              if dns:
                  try:
                      result = subprocess.check_output(
                          "wmic nic where (NetEnabled=true) get NetConnectionID",
                          shell=True,
                          encoding="gbk",
                          stderr=subprocess.STDOUT
                      )
                      adapters = [
                          line.strip()
                          for line in result.splitlines()
                          if line.strip() and "NetConnectionID" not in line
                      ]
                      if not adapters:
                          logging.error("未检测到可用的网络适配器")
                          messagebox.showerror("错误", "未检测到可用的网络适配器!")
                          return
                      adapter = adapters[0]
                      cmd = f'netsh interface ip set dns name="{adapter}" static {dns}'
                      logging.info(f"尝试设置 DNS: {cmd}")
                      subprocess.check_call(cmd, shell=True)
                      logging.info(f"成功设置 DNS 为 {dns} (适配器: {adapter})")
                      messagebox.showinfo("成功", f"DNS已设置为: {dns}n(适配器: {adapter})")
                  except subprocess.CalledProcessError as e:
                      logging.exception(f"设置 DNS 命令执行失败: {cmd}n输出: {e.output}")
                      messagebox.showerror("错误", f"设置DNS失败: {e}n请检查是否以管理员权限运行")
                  except Exception as e:
                      logging.exception(f"设置 DNS 失败")
                      messagebox.showerror("错误", f"设置DNS失败: {e}")
              else:
                  logging.info("取消设置 DNS")
      
          def open_network_settings(self):
              import subprocess
              try:
                  logging.info("尝试打开网络连接设置")
                  subprocess.Popen("control.exe ncpa.cpl", shell=True)
              except Exception as e:
                  logging.exception("打开网络连接设置失败")
                  messagebox.showerror("错误", f"无法打开网络配置页面: {e}")
                  
          def accelerate_github(self):
              """一键加速GitHub访问"""
              github_hosts = """
      # GitHub Start
      140.82.112.3 github.com
      140.82.112.4 gist.github.com
      140.82.113.4 api.github.com
      140.82.114.4 assets-cdn.github.com
      140.82.112.5 raw.githubusercontent.com
      140.82.113.5 user-images.githubusercontent.com
      140.82.114.5 favicons.githubusercontent.com
      140.82.112.6 avatars.githubusercontent.com
      140.82.113.6 avatars0.githubusercontent.com
      140.82.114.6 avatars1.githubusercontent.com
      140.82.112.7 avatars2.githubusercontent.com
      140.82.113.7 avatars3.githubusercontent.com
      140.82.114.7 avatars4.githubusercontent.com
      140.82.112.8 avatars5.githubusercontent.com
      # GitHub End
      """
              try:
                  with open(HOSTS_PATH, "r", encoding="utf-8") as f:
                      current_content = f.read()
                  if "# GitHub Start" in current_content:
                      logging.warning("GitHub hosts 已存在,跳过添加")
                      messagebox.showwarning("提示", "GitHub加速配置似乎已存在,无需重复添加。")
                      return
      
                  with open(HOSTS_PATH, "a", encoding="utf-8") as f:
                      f.write("n" + github_hosts)
                  self.load_hosts()
                  logging.info("成功应用 GitHub 加速 hosts")
                  messagebox.showinfo("成功", "GitHub加速已应用!")
              except Exception as e:
                  logging.exception("应用 GitHub 加速 hosts 失败")
                  messagebox.showerror("错误", f"加速GitHub失败: {e}")
                  
          def validate_hosts(self, content):
              import re
              pattern = r"^(s*$|#.*|# ---.*|(d{1,3}.){3}d{1,3}s+S+)?$"
              return all(re.match(pattern, line.strip()) for line in content.split("n") if line.strip())
      
      
      def main():
          root = tk.Tk()
          root.protocol('WM_DELETE_WINDOW', root.destroy)
          app = HostsManagerApp(root)
          root.mainloop()
      
      
      if __name__ == "__main__":
          try:
              main()
          except Exception as e:
              logging.critical("应用程序发生未捕获的严重错误", exc_info=True)
              messagebox.showerror("严重错误", f"应用程序遇到严重问题,请查看日志文件获取详细信息:n{log_filename}nn错误: {e}")
      

      互动提问

      • 你在Hosts管理中还遇到哪些痛点?
      • 希望增加哪些新功能?
      • 对UI设计有何改进建议?

      到此这篇关于Python+Tkinter实现Windows Hosts文件编辑管理工具的文章就介绍到这了,更多相关Python Windows Hosts文件管理内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

      您可能感兴趣的文章:

      • python切换hosts文件代码示例
      • 使用python Fabric动态修改远程机器hosts的方法
      • 如何使用python切换hosts文件
      • Python实现批量修改文件时间属性
      • Python实现批量修改文件日期

      站内搜索