根据输入框输入内容长度改变按钮状态-全局异常捕获

本文详细介绍了一个Android应用中UI更新的实现方式,通过监听EditText的文字变化来动态更新Button的状态和背景色。同时,深入探讨了如何使用自定义的UnCatchHandler全局捕获异常类来处理应用程序中的未捕获异常,确保应用的稳定运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MainActivity.java

public class MainActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();



    }

    private void init() {
        final Button button_change_bg_demo = findViewById(R.id.button_change_bg_demo);
        EditText edit_change_bg_demo = findViewById(R.id.edit_change_bg_demo);

        //給edit添加文字改变监听
        //注意:这里不是set,而是add开头
        edit_change_bg_demo.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //改变之前
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //按钮可以点击,并且改变颜色
                button_change_bg_demo.setEnabled((s.length() >= 6));
            }

            @Override
            public void afterTextChanged(Editable s) {
                //文字改变过后
            }
        });
    }


//    private void doError(){
//        Object o  = null;
//        o.toString();
//    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/edit_change_bg_demo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/button_change_bg_demo"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/selector_button_enable"
        android:enabled="false"
        />

</LinearLayout>

selector_button_ennable.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!--state_enabled  是否可用的状态-->
    <item android:state_enabled="true" android:drawable="@color/colorPrimary"/>
    <item android:state_enabled="false" android:drawable="@color/colorAccent"/>
</selector>

UnCatchHandler.java

/**
 * 全局捕获异常类,实现Thread.UncaughtExceptionHandler
 * @author hasee
 */
public class UnCatchHandler implements Thread.UncaughtExceptionHandler {
    private static UnCatchHandler mUnCatchHandler;
    private Context mContext;

    /**
     * 一定要写个单例
     * @return
     */
    public static UnCatchHandler getInstance(Context context) {
        if(mUnCatchHandler == null){
            synchronized (UnCatchHandler.class) {
                mUnCatchHandler = new UnCatchHandler(context);
            }
        }
        return mUnCatchHandler;
    }

    private UnCatchHandler(Context context) {
        init(context);
    }

    public void init(Context context) {
        //获取默认的系统异常捕获器
        //把当前的crash捕获器设置成默认的crash捕获器
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();
    }

    /**
     * 保存我们抛出的异常至SD卡
     * @param t
     * @param e
     */
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        try {
            //如果不需要写到SD卡,则不需要打开以下注释
//            saveSD(e);

            //打印异常信息
            Log.i("1607C", e.getLocalizedMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

//    /**
//     * 存储sd卡
//     */
//    private void saveSD(Throwable throwable) throws Exception {
//        //判断SD卡状态
//        //MEDIA_MOUNTED 存储媒体已经挂载,并且挂载点可读/写
//        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//            //彻底退出
//            android.os.Process.killProcess(android.os.Process.myPid());
//            System.exit(0);
//            return;
//        }
//
//        //获取手机的一些信息
//        PackageManager pm = mContext.getPackageManager();
//        PackageInfo inFo = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
//
//        //获取版本信息
//        String versionName = inFo.versionName;
//        int versionCode = inFo.versionCode;
//
//        int version_code = Build.VERSION.SDK_INT;
//
//        //Android版本号
//        String release = Build.VERSION.RELEASE;
//        //手机型号
//        String mobile = Build.MODEL;
//
//        //手机制造商
//        String mobileName = Build.MANUFACTURER;
//
//        //存储至sdCard下
//        String path = Environment.getExternalStorageDirectory().getAbsolutePath();
//
//        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
//        String time = simpleDateFormat.format(new Date());
//
//        //找到文件夹路径,创建exception文件夹
//        File f = new File(path, "exception");
//        f.mkdirs();
//
//        //找到文件夹路径,查找exception + time的txt文件夹
//        File file = new File(f.getAbsolutePath(), "exception" + time + ".txt");
//
//        //如果文件没有,则创建
//        if (!file.exists()) {
//            file.createNewFile();
//        }
//
//        String data = "\nMobile型号:" + mobile + "\nMobileName:" + mobileName + "\nSDK版本:" + version_code +
//                "\n版本名称:" + versionName + "\n版本号:" + versionCode + "\n异常信息:" + throwable.toString();
//
//        Log.i("dj", data);
//
//        //以下是写入文件
//        byte[] buffer = data.trim().getBytes();
//        FileOutputStream fileOutputStream = new FileOutputStream(file);
//        // 开始写入数据到这个文件。
//        fileOutputStream.write(buffer, 0, buffer.length);
//        fileOutputStream.flush();
//        fileOutputStream.close();
//
//        android.os.Process.killProcess(android.os.Process.myPid());
//        System.exit(0);
//    }
}

MyApplication.java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        UnCatchHandler.getInstance(getApplicationContext()).init(getApplicationContext());
//        CrashReport.initCrashReport(getApplicationContext(), "f9f67393ee", true);
    }
}

 

1.页面结构搭建: 首页:设计一个简洁的导航栏,包含 “录入试题”“管理试题”“导出试题” 等主要功能入口。 录入试题页面: 1.使用 Vue.js 的组件化思想,为每种题型(单选题、判断题、填空题、编程题、简答题)创建单独的录入表单组件。例如,单选题表单包含题目内容输入框、多个选项输入框、正确答案选择框、答案解析输入框等。 2.表单使用 Element - UI 的表单组件进行布局和样式设计,确保输入框、选择框等元素排列整齐,具有良好的视觉效果。并且为每个输入框添加必要的表单验证,如题目内容不能为空、单选题选项至少有两个等。 管理试题页面: 1.设计一个表格,展示所有已录入试题的关键信息,如题型、题目内容、创建时间等。使用 Element - UI 的表格组件实现,该组件支持排序、筛选等功能,方便用户查找特定试题。 2.为每一行数据添加操作按钮,包括 “查看详情”“编辑”“删除”。点击 “查看详情” 按钮弹出对话框,展示该试题的完整信息,包括所有选项、正确答案、答案解析等;“编辑” 按钮则跳转到对应的录入表单页面,并预先填充好该试题的已有数据,方便用户修改;“删除” 按钮点击后弹出确认框,确认后从数据库中删除该试题(这里只是前端触发删除操作,实际删除需与后端交互,后续前后端集成时实现)。 导出试题页面: 1.创建一个简单的页面,包含导出格式选择(Word 和 Excel)以及导出范围选择(全部试题或自定义筛选条件后的试题)的功能。提供一个按钮,点击后根据用户选择的格式和范围进行试题导出。 2.交互逻辑实现: 录入功能:用户在录入表单中输入数据并点击 “提交” 按钮后,使用 JavaScript 收集表单数据,进行前端验证(如格式是否正确、必填项是否填写等)。验证通过后,将数据以合适的格式(如 JSON)暂存(实际发送到后端保存将在前后端集成时完成),并弹出提示框告知用户录入成功。 增删查改功能: 1.查询:在管理试题页面的表格上方添加搜索框,用户输入关键词后,通过 JavaScript 过滤表格数据,实时展示符合条件的试题。同时,利用表格自带的排序和筛选功能,实现多条件查询。 2.新增:点击 “录入试题” 导航进入录入页面,用户录入数据提交后,在管理试题页面的表格中实时更新显示新录入的试题(模拟数据更新,实际更新需与后端交互)。 3.修改:用户在编辑页面修改数据并提交后,同样在管理试题页面实时更新该试题的显示信息(模拟数据更新)。 4.删除:点击 “删除” 按钮并确认后,从管理试题页面的表格中移除该试题记录(模拟数据删除)。 导出功能: 1.Excel 导出:用户在导出试题页面选择 Excel 格式并点击导出按钮后,使用 XLSX.js 库将暂存的试题数据(或从后端获取的最新数据,前后端集成后实现)转换为 Excel 文件格式,并触发浏览器下载。 2.Word 导出:选择 Word 格式并点击导出按钮后,利用 Docxtemplater 库结合预先设计好的 Word 模板,将试题数据填充到模板中生成 Word 文件。通过调用 Pandoc 工具将生成的临时文件转换为最终的 Word 文件,并触发浏览器下载。
04-03
<think>我们遇到了登录后闪退的问题。根据经验,这通常是由于窗口管理问题或未处理的异常导致的。在之前的修复中,我们已经改用了Toplevel窗口并添加了异常处理,但问题仍然存在。因此,我们需要进一步诊断。 可能的原因: 1. 登录窗口销毁后,主窗口未正确显示。 2. 数据库连接在窗口之间传递时被关闭。 3. 多线程问题(但我们的代码中没有使用多线程)。 4.捕获的异常导致程序崩溃。 我们将采取以下措施: 1. 确保在登录成功后,登录窗口被销毁(而不是主窗口),并且主窗口正常显示。 2. 确保数据库连接在整个应用程序生命周期中保持打开状态,或者确保每个窗口使用自己的连接(但要注意避免连接过多)。 3. 添加全局异常捕获,以便在发生异常时记录错误信息。 修改点: 1. 在`LoginWindow`的`authenticate`方法中,成功登录后销毁登录窗口,然后显示主窗口。 2. 在`EmployeeManagementSystem`中,我们使用自己的数据库连接,但注意:登录窗口的数据库连接在登录窗口销毁后可能被关闭?因此,我们让主窗口自己创建新的数据库连接。 3. 添加全局异常处理。 但是,我们注意到在登录成功后,我们创建了一个新的`Toplevel`窗口,同时隐藏了根窗口(登录窗口)。然后,当主窗口关闭时,我们尝试同时销毁根窗口和主窗口。然而,如果主窗口在创建过程中发生异常,就会导致闪退。 因此,我们将在主窗口初始化时添加异常捕获,如果初始化失败,则重新显示登录窗口。 此外,我们还可以添加一个日志系统来记录错误。 由于代码较长,我们只修改关键部分: 修改主程序入口部分,添加全局异常处理,并调整窗口管理逻辑。 我们重新设计窗口管理: - 登录窗口是根窗口(root) - 登录成功后,创建主窗口(Toplevel),并隐藏根窗口(登录窗口) - 当主窗口关闭时,销毁主窗口并显示登录窗口(或退出程序) 同时,我们确保每个窗口类(LoginWindow和EmployeeManagementSystem)使用独立的数据库连接,避免连接被关闭的问题。 以下是修改后的代码(只修改了关键部分,其他部分不变):</think>您遇到的登录后闪退问题通常是由于窗口管理或资源初始化错误导致的。我将提供一个完全修复的解决方案,确保程序稳定运行: ```python import sqlite3 import tkinter as tk from tkinter import ttk, messagebox import hashlib import re from datetime import datetime import sys import traceback # 全局异常处理 def global_exception_handler(exc_type, exc_value, exc_traceback): """捕获所有未处理异常""" error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) print(f"未处理异常:\n{error_msg}") messagebox.showerror("致命错误", f"程序遇到未处理异常:\n{str(exc_value)}\n\n详细信息已输出到控制台") sys.exit(1) sys.excepthook = global_exception_handler class LoginWindow: """登录窗口""" def __init__(self, root, on_login_success): self.root = root self.root.title("员工管理系统 - 登录") self.root.geometry("300x200") self.root.protocol("WM_DELETE_WINDOW", self.on_close) # 处理窗口关闭事件 self.on_login_success = on_login_success # 创建UI ttk.Label(root, text="用户名:").pack(pady=5) self.username_entry = ttk.Entry(root) self.username_entry.pack(pady=5) self.username_entry.focus() # 初始焦点 ttk.Label(root, text="密码:").pack(pady=5) self.password_entry = ttk.Entry(root, show="*") self.password_entry.pack(pady=5) self.password_entry.bind("<Return>", lambda e: self.authenticate()) # 回车登录 ttk.Button(root, text="登录", command=self.authenticate).pack(pady=10) # 初始化数据库 self.conn = self.create_connection() self.setup_database() def create_connection(self): """创建数据库连接""" try: conn = sqlite3.connect('employee.db') conn.execute("PRAGMA foreign_keys = ON") # 启用外键约束 return conn except sqlite3.Error as e: messagebox.showerror("数据库错误", f"无法连接数据库:\n{str(e)}") sys.exit(1) def setup_database(self): """创建用户和权限表""" try: cursor = self.conn.cursor() # 创建用户表 cursor.execute(''' CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL, role TEXT NOT NULL CHECK(role IN ('admin', 'user')) ) ''') # 创建权限表 cursor.execute(''' CREATE TABLE IF NOT EXISTS permissions ( role TEXT PRIMARY KEY, can_add BOOLEAN, can_edit BOOLEAN, can_delete BOOLEAN, can_generate_report BOOLEAN ) ''') # 初始化权限 cursor.execute(''' INSERT OR IGNORE INTO permissions (role, can_add, can_edit, can_delete, can_generate_report) VALUES ('admin', 1, 1, 1, 1), ('user', 0, 0, 0, 1) ''') # 添加默认管理员账户 admin_password = self.hash_password("admin123") cursor.execute(''' INSERT OR IGNORE INTO users (username, password, role) VALUES (?, ?, ?) ''', ("admin", admin_password, "admin")) self.conn.commit() except sqlite3.Error as e: messagebox.showerror("数据库错误", f"初始化失败:\n{str(e)}") sys.exit(1) def hash_password(self, password): """密码哈希处理""" return hashlib.sha256(password.encode()).hexdigest() def authenticate(self): """验证用户凭据""" username = self.username_entry.get() password = self.password_entry.get() if not username or not password: messagebox.showerror("错误", "用户名和密码不能为空") return try: cursor = self.conn.cursor() cursor.execute("SELECT password, role FROM users WHERE username=?", (username,)) result = cursor.fetchone() if result and result[0] == self.hash_password(password): # 获取用户权限 cursor.execute("SELECT * FROM permissions WHERE role=?", (result[1],)) permissions = cursor.fetchone() self.root.withdraw() # 隐藏登录窗口 self.on_login_success(username, result[1], dict(zip( ['role', 'can_add', 'can_edit', 'can_delete', 'can_generate_report'], permissions ))) else: messagebox.showerror("错误", "用户名或密码不正确") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"查询失败:\n{str(e)}") def on_close(self): """处理窗口关闭事件""" self.conn.close() self.root.destroy() sys.exit(0) class EmployeeManagementSystem: def __init__(self, parent, username, role, permissions): self.parent = parent self.root = tk.Toplevel(parent) self.root.title(f"员工管理系统 v1.0 - 用户: {username} ({role})") self.root.geometry("800x500") self.root.protocol("WM_DELETE_WINDOW", self.on_close) # 处理窗口关闭事件 self.permissions = permissions self.username = username self.role = role # 初始化数据库 self.conn = self.create_connection() self.create_table() # 创建UI self.create_widgets() self.load_data() # 添加用户管理菜单 if role == 'admin': menubar = tk.Menu(self.root) system_menu = tk.Menu(menubar, tearoff=0) system_menu.add_command(label="用户管理", command=self.show_user_manager) menubar.add_cascade(label="系统管理", menu=system_menu) self.root.config(menu=menubar) # 显示权限提示 self.show_permission_hint() # 初始焦点 self.entries["Name"].focus() def create_connection(self): """创建数据库连接""" try: conn = sqlite3.connect('employee.db') conn.execute("PRAGMA foreign_keys = ON") return conn except sqlite3.Error as e: messagebox.showerror("数据库错误", f"无法连接数据库:\n{str(e)}") sys.exit(1) def on_close(self): """处理窗口关闭事件""" self.conn.close() self.root.destroy() self.parent.destroy() # 同时销毁父窗口 sys.exit(0) def show_permission_hint(self): """显示当前用户权限提示""" hint_text = f"当前权限: {self.role}角色 | " hint_text += f"添加: {'✓' if self.permissions['can_add'] else '✗'} " hint_text += f"编辑: {'✓' if self.permissions['can_edit'] else '✗'} " hint_text += f"删除: {'✓' if self.permissions['can_delete'] else '✗'} " hint_text += f"报表: {'✓' if self.permissions['can_generate_report'] else '✗'}" hint_frame = ttk.Frame(self.root) hint_frame.pack(fill="x", padx=10, pady=5, side="bottom") hint_label = ttk.Label( hint_frame, text=hint_text, background="#f0f0f0", foreground="blue" if self.role == "admin" else "green", font=("Arial", 9) ) hint_label.pack(fill="x", padx=5, pady=2) def create_table(self): """创建员工表""" try: cursor = self.conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS employees ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, position TEXT, department TEXT, salary REAL, hire_date TEXT ) ''') self.conn.commit() except sqlite3.Error as e: messagebox.showerror("数据库错误", f"创建表失败:\n{str(e)}") def create_widgets(self): """创建界面组件""" # 输入区域 input_frame = ttk.LabelFrame(self.root, text="员工信息") input_frame.pack(fill="x", padx=10, pady=5) # 创建输入验证 validation = self.root.register # 第一行 - 姓名输入框 ttk.Label(input_frame, text="姓名:").grid(row=0, column=0, padx=5, pady=5, sticky="e") name_entry = ttk.Entry(input_frame, width=20) name_entry.grid(row=0, column=1, padx=5, pady=5) ttk.Label(input_frame, text="职位:").grid(row=0, column=2, padx=5, pady=5, sticky="e") position_entry = ttk.Entry(input_frame, width=20) position_entry.grid(row=0, column=3, padx=5, pady=5) # 第二行 - 部门输入框 ttk.Label(input_frame, text="部门:").grid(row=1, column=0, padx=5, pady=5, sticky="e") department_entry = ttk.Entry(input_frame, width=20) department_entry.grid(row=1, column=1, padx=5, pady=5) # 薪资输入框(保留验证) ttk.Label(input_frame, text="薪资:").grid(row=1, column=2, padx=5, pady=5, sticky="e") salary_entry = ttk.Entry( input_frame, width=20, validate="key", validatecommand=(validation(self.validate_salary), '%P') ) salary_entry.grid(row=1, column=3, padx=5, pady=5) # 第三行 - 入职日期输入框 ttk.Label(input_frame, text="入职日期:").grid(row=2, column=0, padx=5, pady=5, sticky="e") hire_date_entry = ttk.Entry(input_frame, width=20) hire_date_entry.insert(0, "YYYY-MM-DD") hire_date_entry.config(foreground="gray") hire_date_entry.bind("<FocusIn>", lambda e: self.on_date_focus_in(e, hire_date_entry)) hire_date_entry.bind("<FocusOut>", lambda e: self.on_date_focus_out(e, hire_date_entry)) hire_date_entry.grid(row=2, column=1, padx=5, pady=5) # 保存输入框引用 self.entries = { "Name": name_entry, "Position": position_entry, "Department": department_entry, "Salary": salary_entry, "HireDate": hire_date_entry } # 按钮区域 btn_frame = ttk.Frame(self.root) btn_frame.pack(fill="x", padx=10, pady=5) buttons = [ ("添加", self.add_employee, self.permissions['can_add'], "success.TButton"), ("更新", self.update_employee, self.permissions['can_edit'], "info.TButton"), ("删除", self.delete_employee, self.permissions['can_delete'], "danger.TButton"), ("搜索", self.search_employee, True, "primary.TButton"), ("生成报表", self.generate_report, self.permissions['can_generate_report'], "warning.TButton"), ("退出", self.on_close, True, "") ] for text, command, enabled, style in buttons: btn = ttk.Button( btn_frame, text=text, command=command, style=style if enabled and style else "" ) btn.pack(side="left", padx=5) if not enabled: btn.state(['disabled']) # 添加悬停提示 btn.bind("<Enter>", lambda e, t=text: self.show_tooltip(f"您没有{t}员工的权限")) btn.bind("<Leave>", lambda e: self.hide_tooltip()) # 数据显示区域 tree_frame = ttk.Frame(self.root) tree_frame.pack(fill="both", expand=True, padx=10, pady=5) # 创建Treeview columns = ("ID", "Name", "Position", "Department", "Salary", "HireDate") self.tree = ttk.Treeview(tree_frame, columns=columns, show="headings") self.tree.pack(side="left", fill="both", expand=True) scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview) scrollbar.pack(side="right", fill="y") self.tree.configure(yscrollcommand=scrollbar.set) column_widths = [ ("ID", 50), ("Name", 100), ("Position", 100), ("Department", 100), ("Salary", 80), ("HireDate", 100) ] for col, width in column_widths: self.tree.heading(col, text=col) self.tree.column(col, width=width, anchor="center") self.tree.bind("<<TreeviewSelect>>", self.on_select) # 添加工具提示 self.tooltip = ttk.Label(self.root, background="#ffffe0", relief="solid", borderwidth=1) self.tooltip.place_forget() def validate_salary(self, value): """实时验证薪资输入""" if not value: return True # 允许空值,因为可能正在输入中 # 允许输入小数点和数字 if value == ".": return True try: # 尝试转换为浮点数 float(value) return True except ValueError: return False def on_date_focus_in(self, event, entry): """日期输入框获得焦点时的处理""" if entry.get() == "YYYY-MM-DD": entry.delete(0, "end") entry.config(foreground="black") def on_date_focus_out(self, event, entry): """日期输入框失去焦点时的处理""" if not entry.get(): entry.insert(0, "YYYY-MM-DD") entry.config(foreground="gray") def show_tooltip(self, text): """显示工具提示""" x, y, _, _ = self.root.bbox("current") self.tooltip.config(text=text) self.tooltip.place(x=x, y=y-30) def hide_tooltip(self): """隐藏工具提示""" self.tooltip.place_forget() def load_data(self, query="SELECT * FROM employees", params=()): """加载员工数据""" # 清空现有数据 for item in self.tree.get_children(): self.tree.delete(item) # 从数据库加载 try: cursor = self.conn.cursor() cursor.execute(query, params) rows = cursor.fetchall() for row in rows: self.tree.insert("", "end", values=row) except sqlite3.Error as e: messagebox.showerror("数据库错误", f"加载数据失败:\n{str(e)}") def on_select(self, event): """选中员工时填充表单""" selected = self.tree.focus() if not selected: return values = self.tree.item(selected, "values") if values: fields = ["Name", "Position", "Department", "Salary", "HireDate"] for i, field in enumerate(fields): self.entries[field].delete(0, "end") self.entries[field].insert(0, values[i+1]) # 特殊处理日期字段 if self.entries["HireDate"].get() == "YYYY-MM-DD": self.entries["HireDate"].delete(0, "end") self.entries["HireDate"].insert(0, values[5]) self.entries["HireDate"].config(foreground="black") def add_employee(self): """添加新员工""" try: # 再次检查权限 if not self.permissions['can_add']: messagebox.showwarning("权限不足", "您没有添加员工的权限") return # 获取并验证输入 name = self.entries["Name"].get().strip() position = self.entries["Position"].get().strip() department = self.entries["Department"].get().strip() salary_str = self.entries["Salary"].get().strip() hire_date = self.entries["HireDate"].get().strip() # 姓名验证 if not name: raise ValueError("姓名不能为空") if len(name) < 2 or len(name) > 50: raise ValueError("姓名长度需在2-50个字符之间") # 验证薪资 if not salary_str: raise ValueError("薪资不能为空") try: salary = float(salary_str) if salary <= 0: raise ValueError("薪资必须大于0") except ValueError: raise ValueError("薪资必须是有效的数字") # 验证入职日期 if hire_date and hire_date != "YYYY-MM-DD": try: datetime.strptime(hire_date, "%Y-%m-%d") except ValueError: raise ValueError("入职日期格式无效(应为 YYYY-MM-DD)") else: hire_date = None # 检查姓名是否已存在 cursor = self.conn.cursor() cursor.execute("SELECT COUNT(*) FROM employees WHERE name=?", (name,)) count = cursor.fetchone()[0] if count > 0: raise ValueError(f"员工 '{name}' 已存在") # 插入数据库 cursor.execute(''' INSERT INTO employees (name, position, department, salary, hire_date) VALUES (?, ?, ?, ?, ?) ''', (name, position, department, salary, hire_date)) self.conn.commit() self.load_data() self.clear_entries() messagebox.showinfo("成功", f"员工 {name} 添加成功!") except ValueError as ve: messagebox.showerror("输入错误", str(ve)) except sqlite3.IntegrityError as ie: messagebox.showerror("数据库错误", f"添加失败: 数据库约束冲突\n{str(ie)}") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"添加失败: {str(e)}") def update_employee(self): """更新员工信息""" try: # 权限检查 if not self.permissions['can_edit']: messagebox.showwarning("权限不足", "您没有修改员工信息的权限") return selected = self.tree.focus() if not selected: messagebox.showwarning("警告", "请先选择员工") return # 获取员工ID emp_id = self.tree.item(selected)["values"][0] original_name = self.t极ree.item(selected)["values"][1] # 获取并验证输入 name = self.entries["Name"].get().strip() position = self.entries["Position"].get().strip() department = self.entries["Department"].get().strip() salary_str = self.entries["Salary"].get().strip() hire_date = self.entries["HireDate"].get().strip() # 姓名验证 if not name: raise ValueError("姓名不能为空") if len(name) < 2 or len(name) > 50: raise ValueError("姓名长度需在2-50个字符之间") # 验证薪资 if not salary_str: raise ValueError("薪资不能为空") try: salary = float(salary_str) if salary <= 0: raise ValueError("薪资必须大于0") except ValueError: raise ValueError("薪资必须是有效的数字") # 验证入职日期 if hire_date and hire_date != "YYYY-MM-DD": try: datetime.strptime(hire_date, "%Y-%m-%d") except ValueError: raise ValueError("入职日期格式无效(应为 YYYY-MM-DD)") else: hire_date = None # 检查姓名是否已存在(排除当前员工) if name != original_name: cursor = self.conn.cursor() cursor.execute("SELECT COUNT(*) FROM employees WHERE name=? AND id != ?", (name, emp_id)) count = cursor.fetchone()[0] if count > 0: raise ValueError(f"员工 '{name}' 已存在") # 更新数据库 cursor = self.conn.cursor() cursor.execute(''' UPDATE employees SET name=?, position=?, department=?, salary=?, hire_date=? WHERE id=? ''', (name, position, department, salary, hire_date, emp_id)) self.conn.commit() self.load_data() messagebox.showinfo("成功", f"员工 {name} 信息更新成功!") except ValueError as ve: messagebox.showerror("输入错误", str(ve)) except sqlite3.IntegrityError as ie: messagebox.showerror("数据库错误", f"更新失败: 数据库约束冲突\n{str(ie)}") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"更新失败: {str(e)}") def delete_employee(self): """删除员工""" # 权限检查 if not self.permissions['can_delete']: messagebox.showwarning("权限不足", "您没有删除员工的权限") return selected = self.tree.focus() if not selected: messagebox.showwarning("警告", "请先选择员工") return if not messagebox.askyesno("确认", "确定要删除该员工吗?"): return try: emp_id = self.tree.item(selected)["values"][0] cursor = self.conn.cursor() cursor.execute("DELETE FROM employees WHERE id=?", (emp_id,)) self.conn.commit() self.load_data() self.clear_entries() messagebox.showinfo("成功", "员工已删除!") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"删除失败: {str(e)}") def search_employee(self): """搜索员工""" name = self.entries["Name"].get().strip() department = self.entries["Department"].get().strip() query = "SELECT * FROM employees WHERE 1=1" params = [] if name: query += " AND name LIKE ?" params.append(f"%{name}%") if department: query += " AND department LIKE ?" params.append(f"%{department}%") self.load_data(query, params) def generate_report(self): """生成简单报表""" # 权限检查 if not self.permissions['can_generate_report']: messagebox.showwarning("权限不足", "您没有生成报表的权限") return try: cursor = self.conn.cursor() cursor.execute("SELECT department, COUNT(*), AVG(salary) FROM employees GROUP BY department") results = cursor.fetchall() report = "部门员工统计报表:\n" report += "-" * 40 + "\n" report += "部门\t\t人数\t平均薪资\n" report += "-" * 40 + "\n" for dept, count, avg_salary in results: report += f"{dept}\t{count}\t{avg_salary:.2f}\n" messagebox.showinfo("部门报表", report) except sqlite3.Error as e: messagebox.showerror("数据库错误", f"生成报表失败: {str(e)}") def clear_entries(self): """清空输入框""" for field, entry in self.entries.items(): entry.delete(0, "end") # 重置日期输入框 self.entries["HireDate"].insert(0, "YYYY-MM-DD") self.entries["HireDate"].config(foreground="gray") self.entries["Name"].focus() # 焦点回到姓名输入框 def show_user_manager(self): """显示用户管理窗口""" # 权限检查 - 只有管理员可以访问 if self.role != "admin": messagebox.showwarning("权限不足", "只有管理员可以访问用户管理") return user_manager = tk.Toplevel(self.root) user_manager.title("用户管理") user_manager.geometry("600x400") # 用户列表 tree_frame = ttk.Frame(user_manager) tree_frame.pack(fill="both", expand=True, padx=10, pady=10) scrollbar = ttk.Scrollbar(tree_frame) scrollbar.pack(side="right", fill="y") columns = ("ID", "Username", "Role") self.user_tree = ttk.Treeview(tree_frame, columns=columns, show="headings", yscrollcommand=scrollbar.set) self.user_tree.pack(fill="both", expand=True) scrollbar.config(command=self.user_tree.yview) column_widths = [("ID", 50), ("Username", 150), ("Role", 100)] for col, width in column_widths: self.user_tree.heading(col, text=col) self.user_tree.column(col, width=width, anchor="center") # 按钮区域 btn_frame = ttk.Frame(user_manager) btn_frame.pack(fill="x", padx=10, pady=5) ttk.Button(btn_frame, text="添加用户", command=self.show_add_user_dialog).pack(side="left", padx=5) ttk.Button(btn_frame, text="删除用户", command=self.delete_user).pack(side="left", padx=5) ttk.Button(btn_frame, text="重置密码", command=self.show_reset_password_dialog).pack(side="left", padx=5) # 加载用户数据 self.load_users() def load_users(self): """加载用户数据""" for item in self.user_tree.get_children(): self.user_tree.delete(item) try: cursor = self.conn.cursor() cursor.execute("SELECT id, username, role FROM users") for row in cursor.fetchall(): self.user_tree.insert("", "end", values=row) except sqlite3.Error as e: messagebox.showerror("数据库错误", f"加载用户数据失败: {str(e)}") def show_add_user_dialog(self): """显示添加用户对话框""" dialog = tk.Toplevel(self.root) dialog.title("添加新用户") dialog.geometry("300x200") dialog.transient(self.root) dialog.grab_set() ttk.Label(dialog, text="用户名:").pack(pady=5) username_entry = ttk.Entry(dialog) username_entry.pack(pady=5) ttk.Label(dialog, text="密码:").pack(pady=5) password_entry = ttk.Entry(dialog, show="*") password_entry.pack(pady=5) ttk.Label(dialog, text="角色:").pack(pady=5) role_var = tk.StringVar(value="user") ttk.Radiobutton(dialog, text="管理员", variable=role_var, value="admin").pack() ttk.Radiobutton(dialog, text="普通用户", variable=role_var, value="user").pack() def add_user(): username = username_entry.get() password = password_entry.get() role = role_var.get() if not username or not password: messagebox.showerror("错误", "用户名和密码不能为空") return try: cursor = self.conn.cursor() hashed_pw = hashlib.sha256(password.encode()).hexdigest() cursor.execute("INSERT INTO users (username, password, role) VALUES (?, ?, ?)", (username, hashed_pw, role)) self.conn.commit() self.load_users() dialog.destroy() messagebox.showinfo("成功", "用户添加成功") except sqlite3.IntegrityError: messagebox.showerror("错误", "用户名已存在") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"添加用户失败: {str(e)}") ttk.Button(dialog, text="添加", command=add_user).pack(pady=10) def delete_user(self): """删除选中用户""" selected = self.user_tree.selection() if not selected: messagebox.showwarning("警告", "请选择要删除的用户") return user_id = self.user_tree.item(selected)['values'][0] username = self.user_tree.item(selected)['values'][1] if username == "admin": messagebox.showerror("错误", "不能删除管理员账户") return if messagebox.askyesno("确认", f"确定要删除用户 {username} 吗?"): try: cursor = self.conn.cursor() cursor.execute("DELETE FROM users WHERE id=?", (user_id,)) self.conn.commit() self.load_users() messagebox.showinfo("成功", "用户已删除") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"删除用户失败: {str(e)}") def show_reset_password_dialog(self): """显示重置密码对话框""" selected = self.user_tree.selection() if not selected: messagebox.showwarning("警告", "请选择要重置密码的用户") return user_id = self.user_tree.item(selected)['values'][0] username = self.user_tree.item(selected)['values'][1] dialog = tk.Toplevel(self.root) dialog.title(f"重置 {username} 的密码") dialog.geometry("300x150") dialog.transient(self.root) dialog.grab_set() ttk.Label(dialog, text="新密码:").pack(pady=5) password_entry = ttk.Entry(dialog, show="*") password_entry.pack(pady=5) ttk.Label(dialog, text="确认密码:").pack(pady=5) confirm_entry = ttk.Entry(dialog, show="*") confirm_entry.pack(pady=5) def reset_password(): new_pw = password_entry.get() confirm_pw = confirm_entry.get() if not new_pw: messagebox.showerror("错误", "密码不能为空") return if new_pw != confirm_pw: messagebox.showerror("错误", "两次输入的密码不一致") return try: cursor = self.conn.cursor() hashed_pw = hashlib.sha256(new_pw.encode()).hexdigest() cursor.execute("UPDATE users SET password=? WHERE id=?", (hashed_pw, user_id)) self.conn.commit() dialog.destroy() messagebox.showinfo("成功", "密码已重置") except sqlite3.Error as e: messagebox.showerror("数据库错误", f"重置密码失败: {str(e)}") ttk.Button(dialog, text="重置", command=reset_password).pack(pady=10) # 配置主题样式 def configure_styles(): style = ttk.Style() style.configure("success.TButton", foreground="white", background="#28a745") style.configure("info.TButton", foreground="white", background="#17a2b8") style.configure("danger.TButton", foreground="white", background="#dc3545") style.configure("primary.TButton", foreground="white", background="#007bff") style.configure("warning.TButton", foreground="white", background="#ffc107") style.configure("Disabled.TButton", foreground="gray", background="#e9ecef") # 主程序入口 if __name__ == "__main__": # 设置全局异常处理 sys.excepthook = global_exception_handler root = tk.Tk() root.withdraw() # 先隐藏主窗口 configure_styles() # 配置按钮样式 # 显示登录窗口 login_window = tk.Toplevel(root) login_window.title("员工管理系统 - 登录") def on_login_success(username, role, permissions): # 创建主窗口 EmployeeManagementSystem(root, username, role, permissions) # 创建登录窗口 login = LoginWindow(login_window, on_login_success) # 处理主窗口关闭事件 root.protocol("WM_DELETE_WINDOW", lambda: sys.exit(0)) root.mainloop() ``` ### 关键修复点 1. **窗口管理重构**: ```python # 主程序入口 if __name__ == "__main__": root = tk.Tk() root.withdraw() # 隐藏主窗口 # 显示登录窗口 login_window = tk.Toplevel(root) login = LoginWindow(login_window, on_login_success) root.mainloop() ``` - 使用单一Tk实例作为应用程序根 - 登录窗口作为Toplevel创建 - 主窗口作为另一个Toplevel创建 2. **全局异常处理**: ```python # 全局异常处理 def global_exception_handler(exc_type, exc_value, exc_traceback): error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback)) messagebox.showerror("致命错误", f"程序遇到未处理异常:\n{str(exc_value)}") sys.exit(1) sys.excepthook = global_exception_handler ``` - 捕获所有未处理异常 - 显示友好的错误信息 - 防止程序静默崩溃 3. **数据库连接管理**: ```python def create_connection(self): try: conn = sqlite3.connect('employee.db') conn.execute("PRAGMA foreign_keys = ON") return conn except sqlite3.Error as e: messagebox.showerror("数据库错误", f"无法连接数据库:\n{str(e)}") sys.exit(1) ``` - 每个窗口使用独立的数据库连接 - 添加连接错误处理 - 启用外键约束 4. **窗口关闭处理**: ```python self.root.protocol("WM_DELETE_WINDOW", self.on_close) def on_close(self): self.conn.close() self.root.destroy() self.parent.destroy() sys.exit(0) ``` - 确保窗口关闭时释放资源 - 正确关闭数据库连接 - 完全退出应用程序 ### 完整修复方案说明 1. **窗口生命周期管理**: - 使用单一Tk实例作为应用程序根 - 登录窗口和主窗口都是Toplevel - 正确处理窗口关闭事件链 2. **异常处理增强**: - 全局异常捕获防止程序崩溃 - 所有数据库操作添加异常处理 - 关键操作添加空值检查 3. **资源管理优化**: - 每个窗口使用独立的数据库连接 - 确保连接在窗口关闭时被释放 - 添加连接错误处理 4. **用户体验改进**: - 初始焦点设置 - 回车键登录支持 - 操作后自动聚焦 - 更友好的错误提示 ### 测试建议 1. **测试登录流程**: - 使用用户名`admin`和密码`admin123`登录 - 测试无效用户名/密码 - 测试空用户名/密码 2. **测试窗口关闭**: - 点击登录窗口关闭按钮 - 点击主窗口关闭按钮 - 测试窗口关闭后资源释放 3. **测试边界情况**: - 添加同名员工 - 输入超大薪资数值 - 输入无效日期格式 - 删除不存在的用户 4. **测试权限系统**: - 使用普通用户登录,验证权限限制 - 使用管理员登录,验证完整权限 ### 替代诊断方法 如果问题仍然存在,可以添加日志系统: ```python import logging # 配置日志 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', filename='employee_system.log', filemode='w' ) # 在关键位置添加日志 logging.debug("正在初始化数据库...") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值