从零到一:用Python Tkinter打造专业的文件行删除工具(一)

4 阅读15分钟

本文详细记录了一个实用的文件行删除工具的完整开发过程,涵盖了Tkinter GUI编程、文件操作、用户体验设计等多个方面。

 项目背景

在日常开发工作中,我们经常会遇到需要处理文本文件的情况。有时候需要删除日志文件的特定时间段记录,有时候需要清理数据文件的部分行,有时候需要移除配置文件中的某些设置。手动打开文本编辑器进行行删除不仅效率低下,而且容易出错。

为了解决这个问题,我决定开发一个专门的文件行删除工具,要求能够:

  • 可视化选择文件
  • 精确指定删除的行范围
  • 提供操作前的预览功能
  • 确保操作的安全性(备份机制)
  • 用户友好的界面

技术选型:为什么选择Tkinter?

Python GUI库比较

在开始之前,我对比了Python的几个主流GUI库:

库名优点缺点适用场景
TkinterPython内置,无需安装,跨平台,API稳定界面相对老旧,原生组件少快速开发小型工具
PyQt/PySide功能强大,界面美观,组件丰富学习曲线陡峭,需要安装需要复杂界面的桌面应用
wxPython原生外观,跨平台文档较少,社区较小跨平台原生风格应用
Kivy移动端友好,完全跨平台传统桌面应用不适合移动或触控应用

对于这个工具,我选择了Tkinter,因为:

  1. 零依赖:Python自带,用户无需安装额外库
  2. 快速开发:API简洁,原型开发快
  3. 足够功能:对于文件处理工具来说,功能完全足够
  4. 跨平台:Windows、macOS、Linux都能运行

架构设计

整体架构考虑

# 项目结构遵循MVC模式的思想
class FileLineDeleteApp:
    def __init__(self): ...
    # Model - 数据层(文件操作逻辑)
    # View - 视图层(UI组件)
    # Controller - 控制层(事件处理)

核心功能模块

  1. 文件选择模块:通过filedialog获取用户选择的文件
  2. 文件预览模块:显示文件信息,包括大小、行数、修改时间等
  3. 行号设置模块:提供方便的界面设置要删除的行范围
  4. 删除执行模块:安全的行删除,包含备份机制
  5. 日志显示模块:实时显示操作状态

 关键技术实现

1. 现代化UI设计

主题和样式设置

def setup_style(self):
    """设置ttk样式"""
    self.style = ttk.Style()
    
    # 尝试设置不同主题
    available_themes = self.style.theme_names()
    print("可用主题:", available_themes)
    
    # 根据平台选择合适的主题
    if 'vista' in available_themes:
        self.style.theme_use('vista')
    elif 'clam' in available_themes:
        self.style.theme_use('clam')
    else:
        self.style.theme_use('winnative')
    
    # 自定义样式
    self.style.configure('Title.TLabel', 
                        font=('微软雅黑', 16, 'bold'))
    self.style.configure('Danger.TButton',
                        font=('微软雅黑', 9),
                        background='#d9534f')  # Bootstrap danger红色

布局管理

使用pack()fill=tk.BOTH, expand=True实现自适应布局:

# 主容器使用自适应填充
main_container = ttk.Frame(self.root, padding="10")
main_container.pack(fill=tk.BOTH, expand=True)


# 文本区域带滚动条
self.info_text = tk.Text(info_frame, height=12, width=30)
info_scrollbar = ttk.Scrollbar(info_frame, 
                              orient=tk.VERTICAL,
                              command=self.info_text.yview)
self.info_text.configure(yscrollcommand=info_scrollbar.set)


# 分割窗格实现灵活的布局
info_paned = ttk.PanedWindow(main_container, orient=tk.HORIZONTAL)
info_paned.pack(fill=tk.BOTH, expand=True, pady=10)

2. 文件信息获取与显示

获取文件元数据

def get_file_info(self, filename):
    """获取文件的详细信息"""
    try:
        # 获取文件大小(格式化显示)
        file_size = os.path.getsize(filename)
        
        # 获取修改时间(注意引号嵌套的处理)
        mod_time = os.path.getmtime(filename)
        
        # 外层单引号,内层双引号
        time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mod_time))
        
        # 读取文件行数
        with open(filename, 'r', encoding='utf-8', errors='ignore') as file:
            lines = file.readlines()
            total_lines = len(lines)
        
        return {
            'size': f"{file_size:,} 字节",
            'mod_time': time_str,
            'total_lines': total_lines,
            'content_preview': lines[:5]
        }
        
    except Exception as e:
        return {'error': str(e)}

3. 安全的文件操作

备份机制实现

def delete_lines_in_range(self, file_path, start_line, end_line):
    """删除指定文件中的行范围"""
    try:
        # 1. 读取原始文件
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
            lines = file.readlines()
        
        total_lines = len(lines)
        
        # 2. 行号验证
        if start_line < 1 or end_line > total_lines:
            return False, f"行号超出范围 (1-{total_lines})"
        
        # 3. 创建备份(非常重要!)
        backup_path = file_path + '.bak'
        import shutil
        shutil.copy2(file_path, backup_path)  # copy2会保留元数据
        
        # 4. 执行删除操作
        new_lines = []
        deleted_lines = []
        for index, line in enumerate(lines, start=1):
            if start_line <= index <= end_line:
                deleted_lines.append(line)  # 保存被删除的行(可选)
            else:
                new_lines.append(line)  # 保留的行
        
        # 5. 写回新文件
        with open(file_path, 'w', encoding='utf-8') as file:
            file.writelines(new_lines)
        
        # 6. 返回操作结果
        deleted_count = end_line - start_line + 1
        return True, f"已删除 {deleted_count} 行,原始文件已备份为: {backup_path}"
        
    except PermissionError:
        return False, "没有文件写入权限"
    except FileNotFoundError:
        return False, "文件未找到"
    except Exception as e:
        return False, f"发生错误: {str(e)}"

4. 用户体验优化

DPI自适应(针对高分辨率屏幕)

# 在Windows高DPI屏幕上自动调整
if sys.platform == 'win32':
    try:
        from ctypes import windll
        windll.shcore.SetProcessDpiAwareness(1)
    except:
        pass  # 如果失败,继续执行

窗口居中显示

def center_window(window, width, height):
    """将窗口居中显示"""
    # 先更新窗口信息,确保获取到正确的尺寸
    window.update_idletasks()
    
    # 获取屏幕尺寸
    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    
    # 计算位置
    x = (screen_width // 2) - (width // 2)
    y = (screen_height // 2) - (height // 2)
    
    # 设置窗口位置
    window.geometry(f'{width}x{height}+{x}+{y}')

自动滚动

def update_status(self, message):
    """更新状态信息并自动滚动到底部"""
    self.status_text.insert(tk.END, f"> {message}\n")
    self.status_text.see(tk.END)  # 关键:自动滚动到底部

5. 输入验证与错误处理

def execute_deletion(self):
    """执行删除操作前的完整验证"""
    # 1. 验证文件存在
    filename = self.file_path.get()
    if not filename or not os.path.exists(filename):
        self.show_error("请先选择一个有效的文件")
        return
    
    # 2. 验证行号格式
    try:
        start = int(self.start_line.get())
        end = int(self.end_line.get())
    except ValueError:
        self.show_error("请输入有效的整数行号")
        return
    
    # 3. 验证逻辑关系
    if start < 1 or end < 1:
        self.show_error("行号必须大于0")
        return
    
    if start > end:
        self.show_error("起始行不能大于结束行")
        return
    
    # 4. 验证文件实际行数
    try:
        with open(filename, 'r', encoding='utf-8', errors='ignore') as file:
            lines = file.readlines()
            total_lines = len(lines)
    except Exception as e:
        self.show_error(f"无法读取文件: {str(e)}")
        return
    
    # 5. 超出范围的处理
    if start > total_lines:
        self.show_error(f"起始行号超出范围 (最大行数: {total_lines})")
        return
    
    if end > total_lines:
        # 智能调整:询问用户是否调整到最大值
        response = messagebox.askyesno(
            "行号调整",
            f"结束行号超出范围,是否调整为最大行数 {total_lines}?"
        )
        if response:
            end = total_lines
        else:
            self.update_status("已取消操作")
            return
    
    # 6. 确认对话框
    lines_to_delete = end - start + 1
    confirm = messagebox.askyesno(
        "确认删除",
        f"确认删除行 {start}{end} 吗?\n"
        f"共 {lines_to_delete} 行\n\n"
        "注意:此操作不可逆!",
        icon='warning'  # ⚠️ 警告图标
    )
    
    if confirm:
        self.perform_deletion(filename, start, end, lines_to_delete)

扩展功能建议

这个基本版本已经相当完善,但还可以进一步扩展:

1. 批量处理模式

def batch_process_files(self):
    """批量处理多个文件"""
    files = filedialog.askopenfilenames(title="选择多个文件")
    if files:
        for file in files:
            # 对每个文件执行相同的删除操作
            self.process_single_file(file, start_line, end_line)

2. 历史记录

class HistoryManager:
    def __init__(self):
        self.history = []
        
    def add_record(self, operation):
        """添加历史记录"""
        self.history.append({
            'time': datetime.now(),
            'operation': operation,
            'file': operation['file'],
            'lines_deleted': operation['lines_count']
        })
        
    def undo_last(self):
        """撤销最后一次操作"""
        if self.history:
            last_op = self.history.pop()
            # ...撤销逻辑...

界面设计

布局层次

  1. 顶部:标题和主要操作按钮
  2. 中部:输入区域和设置区域
  3. 下部:信息显示和状态反馈
  4. 底部:状态栏

颜色与视觉线索

  • 危险操作:使用红色按钮(Bootstrap Danger红色 #d9534f)
  • 成功状态:绿色文字
  • 错误状态:红色文字和图标
  • 提示信息:灰色小字

交互设计原则

  1. 及时反馈:每个操作都有明确的状态反馈
  2. 安全第一:危险操作有确认对话框,自动备份
  3. 容错设计:错误输入有清晰的提示
  4. 逐步引导:界面按照操作步骤排列

测试注意事项

不同文件类型的测试

test_files = {
    "文本文件": "test.txt",
    "Python文件": "test.py",
    "CSV文件": "data.csv",
    "大型文件": "large.log",
    "空文件": "empty.txt",
    "只读文件": "readonly.txt",
    "不存在的文件": "nonexist.txt"
}

边界情况处理

  1. 空文件处理
  2. 超大文件内存管理
  3. 特殊字符编码
  4. 行号越界
  5. 权限问题
  6. 磁盘空间不足

学习要点总结

对于Tkinter新手

  1. 了解常用组件:Label, Entry, Button, Text, Scrollbar, Frame
  2. 掌握布局管理器:pack, grid, place的选择与组合
  3. 使用ttk提高美观度:Tkinter的增强版组件
  4. 事件绑定:使用bind连接界面与逻辑

对于文件操作

  1. 安全第一:永远先备份再修改
  2. 编码处理:使用UTF-8编码,添加errors='ignore'处理异常
  3. 大文件处理:考虑分块读取时的情况
  4. 异常处理:详细的错误信息和用户友好的提示

对于项目结构

  1. 模块化设计:将功能拆分为独立方法
  2. 文档注释:清晰的函数说明和参数说明
  3. 版本控制:关键功能的版本备份
  4. 用户反馈:明确的操作结果反馈

性能优化建议

大文件处理

def process_large_file(self, filename, start_line, end_line):
    """处理大文件的优化版本"""
    # 1. 只读取需要的行
    with open(filename, 'r') as file:
        # 跳过开头不需要的行
        for _ in range(start_line - 1):
            next(file, None)
        
        # 2. 逐行处理,减少内存消耗
        remaining_lines = []
        for line_num, line in enumerate(file, start=start_line):
            if line_num > end_line:
                remaining_lines.append(line)
    
    # 3. 写入剩余部分
    # ... 写入逻辑 ...

调试技巧

添加调试输出

import logging

def setup_logging(self):
    """设置日志系统"""
    logging.basicConfig(
        level=logging.DEBUG,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
        filename='file_deleter.log'
    )
    self.logger = logging.getLogger(__name__)

结语

这款文件行删除工具虽然功能简单,但涵盖了一个完整GUI应用的方方面面。从界面设计到文件操作,从用户体验到错误处理,每一个细节都体现了良好的软件开发实践。

通过这个项目,不仅掌握了Tkinter的基本用法,还深入理解了:

  • 如何设计直观的用户界面
  • 如何确保文件操作的安全性和可靠性
  • 如何提供有意义的错误信息和反馈
  • 如何优化代码结构和可维护性

希望这篇详细的开发记录对正在学习Python GUI编程的你有所帮助。工具的开发不仅仅是功能的实现,更是对用户体验、安全性和健壮性的全面考虑。 附上源码

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import time
import sys


class FileLineDeleteApp:
    def __init__(self, root):
        self.file_entry = None
        self.style = None
        self.root = root

        # 变量初始化(必须在创建widgets之前!)
        self.file_path = tk.StringVar()
        self.start_line = tk.StringVar()
        self.end_line = tk.StringVar()

        self.root.title("文件行删除工具")
        self.root.geometry("650x750")

        # 设置主题,按钮,文字样式
        self.setup_style()

        # 创建UI
        self.create_widgets()

    def setup_style(self):
        """设置ttk样式"""
        self.style = ttk.Style()

        # 尝试设置不同主题
        available_themes = self.style.theme_names()
        print("可用主题:", available_themes)

        # 选择可用的主题
        if 'vista' in available_themes:
            self.style.theme_use('vista')
        elif 'clam' in available_themes:
            self.style.theme_use('clam')
        else:
            self.style.theme_use('winnative')

        # 自定义样式
        self.style.configure('Title.TLabel', font=('微软雅黑', 16, 'bold'))
        self.style.configure('Header.TLabel', font=('微软雅黑', 10, 'bold'))
        self.style.configure('Normal.TLabel', font=('微软雅黑', 9))

        # 为按钮创建样式
        self.style.configure('Danger.TButton',
                             font=('微软雅黑', 9),
                             background='#d9534f'
                             )
        # 为按钮创建样式
        self.style.configure('Primary.TButton',
                             font=('微软雅黑', 9),
                             )

    def create_widgets(self):
        """创建所有UI组件"""
        # 主容器 框架会填满父容器的宽度和高度
        main_container = ttk.Frame(self.root, padding="10")
        main_container.pack(fill=tk.BOTH, expand=True)

        # ========== 上半部分:标题和文件选择 ==========

        # 标题
        title_label = ttk.Label(main_container,
                                text="文件行删除工具",
                                style='Title.TLabel')
        title_label.pack(pady=(0, 10))
        # 分割线
        separator = ttk.Separator(main_container, orient='horizontal')
        separator.pack(fill=tk.X, pady=5)

        # 文件选择部分
        file_frame = ttk.LabelFrame(main_container,
                                    text="1. 选择文件",
                                    padding="10",
                                    relief=tk.RIDGE)
        file_frame.pack(fill=tk.X, pady=10)

        file_row = ttk.Frame(file_frame)
        file_row.pack(fill=tk.X, pady=5)

        ttk.Label(file_row, text="文件路径:",
                  style='Header.TLabel',
                  width=10).pack(side=tk.LEFT, padx=(0, 5))

        self.file_entry = ttk.Entry(file_row,
                                    textvariable=self.file_path,
                                    width=50,
                                    font=('微软雅黑', 9))
        self.file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)

        browse_btn = ttk.Button(file_row,
                                text="浏览...",
                                command=self.browse_file)
        browse_btn.pack(side=tk.LEFT, padx=(5, 0))

        # 行号输入部分
        line_frame = ttk.LabelFrame(main_container,
                                    text="2. 设置删除范围",
                                    padding="10",
                                    relief=tk.RIDGE)
        line_frame.pack(fill=tk.X, pady=10)

        # 起始行号
        start_frame = ttk.Frame(line_frame)
        start_frame.pack(fill=tk.X, pady=5)

        ttk.Label(start_frame, text="起始行号:",
                  style='Normal.TLabel',
                  width=12).pack(side=tk.LEFT)

        # 使用Combobox代替Spinbox,更稳定
        self.start_combo = ttk.Combobox(start_frame,
                                        textvariable=self.start_line,
                                        width=10,
                                        font=('微软雅黑', 9),
                                        state='normal')
        self.start_combo.pack(side=tk.LEFT, padx=5)

        # 添加一些示例值
        self.start_combo['values'] = tuple(range(1, 101))

        # 结束行号
        end_frame = ttk.Frame(line_frame)
        end_frame.pack(fill=tk.X, pady=5)

        ttk.Label(end_frame, text="结束行号:",
                  style='Normal.TLabel',
                  width=12).pack(side=tk.LEFT)

        self.end_combo = ttk.Combobox(end_frame,
                                      textvariable=self.end_line,
                                      width=10,
                                      font=('微软雅黑', 9),
                                      state='normal')
        self.end_combo.pack(side=tk.LEFT, padx=5)

        # 添加一些示例值
        self.end_combo['values'] = tuple(range(10, 201, 10))

        # 行号提示
        ttk.Label(line_frame, text="提示:输入要删除的行号范围(包含起止行)",
                  font=('微软雅黑', 8),
                  foreground='gray').pack(pady=(5, 0))

        # ========== 中间部分:操作按钮 ==========

        button_frame = ttk.Frame(main_container)
        button_frame.pack(pady=20)

        ttk.Button(button_frame,
                   text="预览文件信息",
                   command=self.preview_file_info,
                   style='Primary.TButton',
                   width=15).pack(side=tk.LEFT, padx=5)

        ttk.Button(button_frame,
                 text="执行删除",
                 command=self.execute_deletion,
                 style='Danger.TButton',
                 ).pack(side=tk.LEFT, padx=5)

        ttk.Button(button_frame,
                   text="清除",
                   command=self.clear_all,
                   style='Primary.TButton',
                   width=15).pack(side=tk.LEFT, padx=5)

        ttk.Button(button_frame,
                   text="退出",
                   command=self.root.quit,
                   style='Primary.TButton',
                   width=15).pack(side=tk.LEFT, padx=5)

        # ========== 下半部分:信息显示 ==========

        # 使用PanedWindow分割信息显示区域
        info_paned = ttk.PanedWindow(main_container, orient=tk.HORIZONTAL)
        info_paned.pack(fill=tk.BOTH, expand=True, pady=10)

        # 文件信息框架
        info_frame = ttk.LabelFrame(info_paned,
                                    text="文件信息",
                                    padding="5")
        info_paned.add(info_frame, weight=1)

        # 使用Text控件显示文件信息
        self.info_text = tk.Text(info_frame,
                                 height=12,
                                 width=30,
                                 font=('微软雅黑', 9),
                                 bg='#f8f9fa',
                                 relief=tk.FLAT,
                                 wrap=tk.WORD)

        info_scrollbar = ttk.Scrollbar(info_frame,
                                       orient=tk.VERTICAL,
                                       command=self.info_text.yview)
        self.info_text.configure(yscrollcommand=info_scrollbar.set)

        self.info_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        info_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 状态信息框架
        status_frame = ttk.LabelFrame(info_paned,
                                      text="操作状态",
                                      padding="5")
        info_paned.add(status_frame, weight=1)

        # 状态文本区域
        self.status_text = tk.Text(status_frame,
                                   height=12,
                                   width=30,
                                   font=('微软雅黑', 9),
                                   bg='#f8f9fa',
                                   relief=tk.FLAT,
                                   wrap=tk.WORD)

        status_scrollbar = ttk.Scrollbar(status_frame,
                                         orient=tk.VERTICAL,
                                         command=self.status_text.yview)
        self.status_text.configure(yscrollcommand=status_scrollbar.set)

        self.status_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        status_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 底部状态栏
        self.status_bar = ttk.Label(self.root,
                                    text="就绪",
                                    relief=tk.SUNKEN,
                                    anchor=tk.W,
                                    font=('微软雅黑', 9),
                                    padding=(5, 2))
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=5)

    def browse_file(self):
        """浏览并选择文件"""
        filename = filedialog.askopenfilename(
            title="选择要处理的文件",
            filetypes=[
                ("所有文件", "*.*"),
                ("Python 文件", "*.py"),
                ("文本文件", "*.txt"),
                ("CSV 文件", "*.csv"),
                ("日志文件", "*.log")
            ]
        )
        if filename:
            self.file_path.set(filename)
            self.update_status(f"已选择文件: {os.path.basename(filename)}")

    def preview_file_info(self):
        """预览文件信息"""
        filename = self.file_path.get()
        if not filename or not os.path.exists(filename):
            self.show_error("请选择一个有效的文件")
            return

        try:
            # 清空文本区域
            self.info_text.delete(1.0, tk.END)

            with open(filename, 'r', encoding='utf-8', errors='ignore') as file:
                lines = file.readlines()
                total_lines = len(lines)

            # 获取文件信息
            file_size = os.path.getsize(filename)
            file_name = os.path.basename(filename)
            file_dir = os.path.dirname(filename)
            mod_time = os.path.getmtime(filename)

            # 显示文件信息
            self.info_text.insert(tk.END, "文件信息:\n")
            self.info_text.insert(tk.END, "=" * 30 + "\n")
            self.info_text.insert(tk.END, f"文件名: {file_name}\n")
            self.info_text.insert(tk.END, f"大小: {file_size:,} 字节\n")
            self.info_text.insert(tk.END, f"总行数: {total_lines:,} 行\n")
            self.info_text.insert(tk.END, f"路径: {file_dir}\n")
            self.info_text.insert(tk.END, f'修改时间: {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(mod_time))}\n')
            self.info_text.insert(tk.END, f"有效范围: 1 - {total_lines}\n")
            self.info_text.insert(tk.END, "=" * 30 + "\n")

            # 如果有内容,显示前几行预览
            if lines:
                self.info_text.insert(tk.END, "\n前5行预览:\n")
                for i, line in enumerate(lines[:5], 1):
                    line_preview = line.strip()[:80]
                    if len(line.strip()) > 80:
                        line_preview += "..."
                    self.info_text.insert(tk.END, f"{i:3d}: {line_preview}\n")

            # 更新状态
            self.update_status("文件信息已加载")
            self.status_bar.config(text=f"加载完成: {file_name}", foreground="green")

        except Exception as e:
            self.show_error(f"读取文件失败: {str(e)}")

    def execute_deletion(self):
        """执行删除操作"""
        filename = self.file_path.get()

        # 验证输入
        if not filename or not os.path.exists(filename):
            self.show_error("请先选择一个有效的文件")
            return

        try:
            start = int(self.start_line.get())
            end = int(self.end_line.get())
        except ValueError:
            self.show_error("请输入有效的整数行号")
            return

        if start < 1 or end < 1:
            self.show_error("行号必须大于0")
            return

        if start > end:
            self.show_error("起始行不能大于结束行")
            return

        # 读取文件以验证行号范围
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as file:
                lines = file.readlines()
                total_lines = len(lines)
        except Exception as e:
            self.show_error(f"无法读取文件: {str(e)}")
            return

        if start > total_lines:
            self.show_error(f"起始行号超出范围 (最大行数: {total_lines})")
            return

        if end > total_lines:
            # 自动调整到最大行数
            response = messagebox.askyesno("行号调整",
                                           f"结束行号超出范围,是否调整为最大行数 {total_lines}?")
            if response:
                end = total_lines
            else:
                self.update_status("已取消操作")
                return

        # 显示要删除的行数
        lines_to_delete = end - start + 1

        # 确认对话框
        confirm = messagebox.askyesno(
            "确认删除",
            f"确认删除行 {start}{end} 吗?\n"
            f"共 {lines_to_delete} 行\n\n"
            "注意:此操作不可逆!",
            icon='warning'
        )

        if confirm:
            try:
                success, message = self.delete_lines_in_range(filename, start, end)

                if success:
                    self.update_status(f"✓ {message}")
                    self.update_status(f"删除行: {start} - {end}")
                    self.update_status(f"删除数量: {lines_to_delete} 行")

                    # 清空输入框
                    self.start_line.set("")
                    self.end_line.set("")

                    self.status_bar.config(text="删除成功", foreground="green")

                    # 重新预览文件信息
                    self.preview_file_info()

                    # 显示成功对话框
                    messagebox.showinfo("成功",
                                        f"成功删除 {lines_to_delete} 行!\n"
                                        f"原始文件已备份为: {filename}.bak")
                else:
                    self.show_error(message)

            except Exception as e:
                error_msg = f"删除过程中发生错误: {str(e)}"
                self.show_error(error_msg)

    def delete_lines_in_range(self, file_path, start_line, end_line):
        """删除指定文件中的行范围"""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
                lines = file.readlines()

            total_lines = len(lines)

            # 验证行号范围
            if start_line < 1 or end_line > total_lines:
                return False, f"行号超出范围 (1-{total_lines})"
            if start_line > end_line:
                return False, "起始行不能大于结束行"

            # 创建备份文件
            backup_path = file_path + '.bak'
            import shutil
            shutil.copy2(file_path, backup_path)

            # 进行删除操作
            new_lines = []
            deleted_lines = []
            for index, line in enumerate(lines, start=1):
                if start_line <= index <= end_line:
                    deleted_lines.append(line)
                else:
                    new_lines.append(line)

            with open(file_path, 'w', encoding='utf-8') as file:
                file.writelines(new_lines)

            return True, f"已删除 {end_line - start_line + 1} 行"

        except FileNotFoundError:
            return False, "文件未找到"
        except PermissionError:
            return False, "没有文件写入权限"
        except Exception as e:
            return False, f"发生错误: {str(e)}"

    def clear_all(self):
        """清除所有输入和显示"""
        self.file_path.set("")
        self.start_line.set("")
        self.end_line.set("")

        # 清空文本区域
        self.info_text.delete(1.0, tk.END)
        self.status_text.delete(1.0, tk.END)

        # 更新状态
        self.update_status("所有内容已清除")
        self.status_bar.config(text="就绪", foreground="black")

    def update_status(self, message):
        """更新状态信息"""
        self.status_text.insert(tk.END, f"> {message}\n")
        self.status_text.see(tk.END)  # 自动滚动到底部

    def show_error(self, message):
        """显示错误信息"""
        self.status_bar.config(text=f"错误: {message}", foreground="red")
        self.update_status(f"✗ {message}")
        messagebox.showerror("错误", message)


def main():
    """主函数"""
    root = tk.Tk()

    # 设置DPI感知(Windows)
    # 在Windows系统中,当程序运行在高分辨率的屏幕上(如4K显示器、Retina屏),可能会出现字体模糊、界面元素过小的问题。这段代码告诉Windows系统:我的程序支持高DPI缩放。
    if sys.platform == 'win32':
        try:
            from ctypes import windll
            windll.shcore.SetProcessDpiAwareness(1)
        except:
            pass  # 如果失败,继续执行

    # 设置窗口图标
    try:
        if sys.platform == 'win32':
            root.iconbitmap(default='icon.ico')
    except:
        pass

    app = FileLineDeleteApp(root)

    # 设置窗口居中
    root.update_idletasks()
    width = 700  # 固定宽度
    height = 750  # 固定高度
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f'{width}x{height}+{x}+{y}')

    # 绑定回车键触发浏览按钮
    root.bind('<Return>', lambda e: app.browse_file())

    root.mainloop()


if __name__ == "__main__":
    main()