pythonGUI:账号密码管理器,本地数据库管理各种账号密码
tags::暂存
概述
本文档旨在为具有一定技术背景的读者详细解释一个使用 Python 编写的账号密码管理器应用程序的代码。
该应用程序使用 tkinter 库构建图形用户界面(GUI),sqlite3 库进行数据库操作,threading 和 queue 库实现多线程处理,从而提高程序的响应性和用户体验。
本文将深入探讨代码的功能、结构、组织方式、算法、数据结构以及潜在的改进空间。
代码结构与功能
该代码主要包含以下几个核心部分:
- 全局变量和辅助函数: 定义了数据库路径、表名等全局变量,以及设置和获取这些变量值的辅助函数。
- 数据库操作函数: 包括创建数据库和表、查询账号信息、添加账号信息、获取所有项目信息、获取查询建议等函数,这些函数封装了与数据库交互的各种操作。
- GUI 应用类 (AccountManagerApp): 这是应用程序的核心类,负责创建和管理 GUI 界面,处理用户交互,调用数据库操作函数,以及实现多线程处理。
1. 全局变量和辅助函数
这部分代码定义了一些全局变量,用于存储数据库路径和表名。同时,提供了一组辅助函数来设置和获取这些全局变量的值。
# 默认数据库路径
DEFAULT_DB_PATH = r"D:\data\database\mm.db"
# 默认表名
DEFAULT_TABLE_NAME = "connect_account_password"
# 全局变量存储当前数据库路径
current_db_path = DEFAULT_DB_PATH
# 全局变量存储当前使用的表名
current_table_name = DEFAULT_TABLE_NAME
# 设置当前使用的数据库路径
def set_database_path(path):
global current_db_path
current_db_path = path
return current_db_path
# 获取当前使用的数据库路径
def get_database_path():
return current_db_path
# 设置当前使用的表名
def set_table_name(table_name):
global current_table_name
current_table_name = table_name
return current_table_name
# 获取当前使用的表名
def get_table_name():
return current_table_name
- 作用: 这些全局变量和辅助函数的作用是提供一个统一的配置管理机制,方便在代码的不同部分访问和修改数据库路径和表名,而无需硬编码。
- 意义: 通过这种方式,可以提高代码的灵活性和可维护性。例如,如果需要更换数据库路径,只需修改 DEFAULT_DB_PATH 变量的值,而无需修改代码的其他部分。

2. 数据库操作函数
这部分代码封装了与 SQLite 数据库交互的各种操作,包括创建数据库和表、查询账号信息、添加账号信息、获取所有项目信息、获取查询建议等。
- get_all_tables(): 获取数据库中所有表的列表。
def get_all_tables():
db_path = get_database_path()
tables = []
try:
# 如果数据库不存在,返回空列表
if not os.path.exists(db_path):
return []
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 查询sqlite_master表获取所有表名
cursor.execute("""
SELECT name FROM sqlite_master
WHERE type='table' AND name NOT LIKE 'sqlite_%'
ORDER BY name
""")
tables = [row[0] for row in cursor.fetchall()]
cursor.close()
conn.close()
except Exception as e:
print(f"获取表列表时出错:{e}")
return tables
- get_table_columns(table_name=None): 获取指定表的所有列名。
def get_table_columns(table_name=None):
if table_name is None:
table_name = get_table_name()
db_path = get_database_path()
columns = []
try:
# 如果数据库不存在,返回空列表
if not os.path.exists(db_path):
return []
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 使用PRAGMA table_info查询表结构
cursor.execute(f"PRAGMA table_info({table_name})")
# 获取列名(排除id列)
columns = [row[1] for row in cursor.fetchall() if row[1].lower() != 'id']
cursor.close()
conn.close()
except Exception as e:
print(f"获取表列名时出错:{e}")
return columns
- create_account_database(db_data_path=None, table_name=None): 创建账号密码数据库及表,参数为数据库路径和表名。
def create_account_database(db_data_path=None, table_name=None):
if db_data_path is None:
db_data_path = get_database_path()
if table_name is None:
table_name = get_table_name()
try:
# 确保目录存在
os.makedirs(os.path.dirname(db_data_path), exist_ok=True)
# 创建数据库连接
conn = sqlite3.connect(db_data_path)
cursor = conn.cursor()
# 创建表
cursor.execute(f'''
CREATE TABLE IF NOT EXISTS {table_name} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_name TEXT NOT NULL,
username TEXT,
password TEXT
)
''')
# 提交更改并关闭连接
conn.commit()
conn.close()
return f"已成功创建数据库 {db_data_path} 和表 {table_name}"
except Exception as e:
return f"创建数据库时出错:{e}"
- check_account(search_column, search_value, table_name=None): 读取数据库,查询账号信息,返回指定条件下的完整记录。
def check_account(search_column, search_value, table_name=None):
if table_name is None:
table_name = get_table_name()
db_path = get_database_path()
try:
# 如果数据库不存在,先创建数据库
if not os.path.exists(db_path):
create_account_database()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 获取表的所有列
cursor.execute(f"PRAGMA table_info({table_name})")
columns = [row[1] for row in cursor.fetchall()]
if not columns:
return None
# 动态构建查询语句,查询所有列
columns_str = ", ".join(columns)
# 构建查询语句
query = f"""
SELECT {columns_str}
FROM {table_name}
WHERE {search_column} = ?
"""
cursor.execute(query, (search_value,))
result = cursor.fetchall()
# 获取列名作为字典的键
cursor.close()
conn.close()
if result:
# 将结果转换为字典列表,每行一个字典
result_dicts = []
for row in result:
row_dict = {columns[i]: row[i] for i in range(len(columns))}
result_dicts.append(row_dict)
return result_dicts
else:
return None
except Exception as e:
return f"数据库操作错误:{e}"
- add_account(data_dict, table_name=None): 向数据库添加账号,使用动态参数。
def add_account(data_dict, table_name=None):
if table_name is None:
table_name = get_table_name()
db_path = get_database_path()
try:
# 如果数据库不存在,先创建数据库
if not os.path.exists(db_path):
create_account_database()
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 动态构建插入语句
columns = list(data_dict.keys())
placeholders = ", ".join(["?" for _ in columns])
columns_str = ", ".join(columns)
values = [data_dict[col] for col in columns]
# 执行插入
cursor.execute(f'''
INSERT INTO {table_name} ({columns_str})
VALUES ({placeholders})
''', values)
# 提交更改并关闭连接
conn.commit()
cursor.close()
conn.close()
return f"已成功添加记录到表 {table_name}"
except Exception as e:
return f"添加记录时出错:{e}"
- get_all_projects(table_name=None): 获取所有项目信息。
def get_all_projects(table_name=None):
if table_name is None:
table_name = get_table_name()
db_path = get_database_path()
try:
# 如果数据库不存在,先创建数据库
if not os.path.exists(db_path):
create_account_database()
return []
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 获取表的所有列
cursor.execute(f"PRAGMA table_info({table_name})")
columns = [row[1] for row in cursor.fetchall()]
if not columns:
return []
# 动态构建查询语句,查询所有列
columns_str = ", ".join(columns)
# 执行查询
cursor.execute(f"""
SELECT {columns_str}
FROM {table_name}
""")
result = cursor.fetchall()
cursor.close()
conn.close()
return result
except Exception as e:
print(f"获取项目信息时出错:{e}")
return []
- get_suggestions(search_column, search_value, table_name=None): 获取查询建议的函数,添加在check_account函数之后。
def get_suggestions(search_column, search_value, table_name=None):
if table_name is None:
table_name = get_table_name()
if not search_value or len(search_value) < 2:
return []
db_path = get_database_path()
try:
# 如果数据库不存在,先创建数据库
if not os.path.exists(db_path):
create_account_database()
return []
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 构建模糊查询语句,使用LIKE进行部分匹配
query = f"""
SELECT DISTINCT {search_column}
FROM {table_name}
WHERE {search_column} LIKE ?
ORDER BY {search_column}
LIMIT 20
"""
cursor.execute(query, (f'%{search_value}%',))
result = cursor.fetchall()
cursor.close()
conn.close()
if result:
# 提取列值并返回
return [row[0] for row in result]
else:
return []
except Exception as e:
print(f"获取建议时出错:{e}")
return []
- 作用: 这些函数提供了一组操作数据库的接口,使得代码的其他部分可以通过调用这些函数来实现对数据库的各种操作,而无需直接操作数据库连接和 SQL 语句。
- 意义: 这种封装提高了代码的可读性和可维护性,同时也降低了代码的复杂性。例如,如果需要更换数据库类型,只需修改这些函数中的数据库操作代码,而无需修改代码的其他部分。
3. GUI 应用类 (AccountManagerApp)
AccountManagerApp 类是应用程序的核心,负责创建和管理 GUI 界面,处理用户交互,调用数据库操作函数,以及实现多线程处理。
- __init__(self, master): 构造函数,初始化 GUI 界面。
class AccountManagerApp:
def __init__(self, master):
self.master = master
master.title("账号密码管理器(公众号:余汉波)")
# 修改窗口大小为1086x600
master.geometry("800x750")
# 设置应用程序图标
try:
icon_path = "logo.ico"
# 检查图标文件是否存在
if os.path.exists(icon_path):
master.iconbitmap(icon_path)
else:
self.log(f"警告: 图标文件 {icon_path} 不存在")
except Exception as e:
# 如果设置图标出错,记录错误但不影响程序运行
print(f"设置图标失败: {e}")
# 创建消息队列用于线程间通信
self.queue = queue.Queue()
# 动态输入字段存储
self.input_entries = {}
# 创建顶部的数据库设置区域
self.setup_db_settings()
# 创建标签页控件
self.tab_control = ttk.Notebook(master)
# 创建标签页
self.tab_add = ttk.Frame(self.tab_control)
self.tab_query = ttk.Frame(self.tab_control)
self.tab_list = ttk.Frame(self.tab_control)
self.tab_control.add(self.tab_query, text="查询账号")
self.tab_control.add(self.tab_add, text="添加账号")
self.tab_control.add(self.tab_list, text="账号列表")
self.tab_control.pack(expand=1, fill="both")
# 创建添加账号的框架(会在setup_add_tab中填充内容)
self.add_frame = ttk.LabelFrame(self.tab_add, text="添加新账号")
self.add_frame.pack(padx=10, pady=10, fill="both", expand=True)
# 设置添加账号标签页
self.setup_add_tab()
# 设置查询账号标签页
self.setup_query_tab()
# 设置账号列表标签页
self.setup_list_tab()
# 创建日志输出区域
self.setup_log_area()
# 定期处理队列中的消息
self.master.after(100, self.process_queue)
# 记录初始日志
self.log(f"当前使用的数据库路径: {get_database_path()}")
self.log(f"当前使用的数据表: {get_table_name()}")
# 初始化表选择
self.refresh_table_list()
- setup_db_settings(self): 创建数据库设置区域,包括数据库路径和表名选择。
def setup_db_settings(self):
# 创建数据库设置框架
db_frame = ttk.LabelFrame(self.master, text="数据库设置")
db_frame.pack(padx=10, pady=10, fill="x")
# 第一行:数据库路径显示及设置
ttk.Label(db_frame, text="数据库路径:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
# 创建一个变量来存储和显示当前路径
self.db_path_var = tk.StringVar()
self.db_path_var.set(get_database_path())
# 创建路径显示框
self.db_path_entry = ttk.Entry(db_frame, textvariable=self.db_path_var, width=50)
self.db_path_entry.grid(row=0, column=1, padx=5, pady=5, sticky="we")
# 创建浏览按钮
browse_button = ttk.Button(db_frame, text="浏览...", command=self.browse_db_path)
browse_button.grid(row=0, column=2, padx=5, pady=5)
# 创建应用按钮
apply_button = ttk.Button(db_frame, text="应用", command=self.apply_db_path)
apply_button.grid(row=0, column=3, padx=5, pady=5)
# 第二行:数据表选择
ttk.Label(db_frame, text="数据表:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
# 创建表名下拉框
self.table_combo = ttk.Combobox(db_frame, width=48)
self.table_combo.grid(row=1, column=1, padx=5, pady=5, sticky="we")
self.table_combo.set(get_table_name())
# 创建刷新按钮
refresh_table_button = ttk.Button(db_frame, text="刷新", command=self.refresh_table_list)
refresh_table_button.grid(row=1, column=2, padx=5, pady=5)
# 创建应用表名按钮
apply_table_button = ttk.Button(db_frame, text="应用表名", command=self.apply_table_name)
apply_table_button.grid(row=1, column=3, padx=5, pady=5)
# 允许第1列(路径输入框)随窗口大小调整
db_frame.columnconfigure(1, weight=1)
- browse_db_path(self): 打开文件对话框选择数据库文件。
def browse_db_path(self):
# 打开文件对话框选择数据库文件
initial_dir = os.path.dirname(get_database_path())
filename = filedialog.askopenfilename(
initialdir=initial_dir,
title="打开数据库文件",
filetypes=(("SQLite数据库", "*.db"), ("所有文件", "*.*"))
)
if filename:
self.db_path_var.set(filename)
# 如果用户没有选择文件,提供创建新数据库的选项
elif messagebox.askyesno("创建新数据库", "没有选择现有数据库文件。是否创建新数据库?"):
filename = filedialog.asksaveasfilename(
initialdir=initial_dir,
title="创建新数据库文件",
filetypes=(("SQLite数据库", "*.db"), ("所有文件", "*.*")),
defaultextension=".db"
)
if filename:
self.db_path_var.set(filename)
- apply_db_path(self): 应用新的数据库路径。
def apply_db_path(self):
# 应用新的数据库路径
new_path = self.db_path_var.get().strip()
if not new_path:
messagebox.showerror("错误", "数据库路径不能为空")
return
# 检查路径是否有效
try:
# 确保目录存在
os.makedirs(os.path.dirname(new_path), exist_ok=True)
# 尝试设置新路径
set_database_path(new_path)
# 刷新表列表
self.refresh_table_list()
# 记录日志
self.log(f"已设置数据库路径: {new_path}")
# 尝试创建当前表(如果不存在)
result = create_account_database()
self.log(result)
# 刷新列表
self.refresh_list()
except Exception as e:
messagebox.showerror("错误", f"设置数据库路径失败:{e}")
self.log(f"设置数据库路径失败:{e}")
- refresh_table_list(self): 获取所有表名并更新下拉框。
def refresh_table_list(self):
# 获取所有表名并更新下拉框
tables = get_all_tables()
if not tables:
# 如果没有表,添加默认表
tables = [DEFAULT_TABLE_NAME]
self.table_combo['values'] = tables
# 如果当前选中的表不在列表中,设置为第一个表
if get_table_name() not in tables and tables:
self.table_combo.set(tables[0])
else:
self.table_combo.set(get_table_name())
- apply_table_name(self): 应用选中的表名。
def apply_table_name(self):
# 应用选中的表名
new_table = self.table_combo.get()
if not new_table:
messagebox.showerror("错误", "表名不能为空")
return
# 设置新表名
set_table_name(new_table)
# 记录日志
self.log(f"已切换到表: {new_table}")
# 更新添加账号表单
self.update_add_tab()
# 刷新数据列表
self.refresh_list()
# 获取并显示当前表的列
columns = get_table_columns()
if columns:
self.log(f"表 {new_table} 的列: {', '.join(columns)}")
- setup_add_tab(self): 设置添加账号标签页。
def setup_add_tab(self):
# 清空当前框架中的所有控件
for widget in self.add_frame.winfo_children():
widget.destroy()
# 清空输入字段字典
self.input_entries = {}
# 获取当前表的列
columns = get_table_columns()
if not columns:
# 如果没有列,显示提示信息
ttk.Label(self.add_frame, text="当前表没有可用字段,请选择其他数据表").pack(pady=20)
return
# 为每个列创建输入字段
for i, column in enumerate(columns):
ttk.Label(self.add_frame, text=f"{column}:").grid(row=i, column=0, padx=5, pady=5, sticky="w")
# 如果是密码字段,使用密码输入
if "password" in column.lower() or "密码" in column.lower():
entry = ttk.Entry(self.add_frame, width=30, show="*")
else:
entry = ttk.Entry(self.add_frame, width=30)
entry.grid(row=i, column=1, padx=5, pady=5)
self.input_entries[column] = entry
# 添加按钮,放在最后一行
add_button = ttk.Button(self.add_frame, text="添加记录", command=self.add_account_gui)
add_button.grid(row=len(columns), column=0, columnspan=2, padx=5, pady=20)
- update_add_tab(self): 更新添加账号标签页。
def update_add_tab(self):
# 更新添加账号标签页
self.setup_add_tab()
- add_account_gui(self): 获取用户在GUI中输入的账号信息,并添加到数据库。
def add_account_gui(self):
# 获取所有输入字段的值
data = {}
for column, entry in self.input_entries.items():
value = entry.get().strip()
data[column] = value
# 检查是否有必需字段为空
if not data:
messagebox.showerror("错误", "没有输入字段")
return
# 至少要有一个字段不为空
if not any(data.values()):
messagebox.showerror("错误", "请至少填写一个字段")
return
# 使用线程执行添加操作
threading.Thread(target=self.execute_add_account,
args=(data,),
daemon=True).start()
- execute_add_account(self, data): 执行添加账号操作,并更新GUI。
def execute_add_account(self, data):
# 调用添加账号函数
result = add_account(data)
# 记录日志
self.log(result)
# 清空输入框
self.master.after(0, self.clear_add_form)
# 刷新列表
self.master.after(0, self.refresh_list)
- clear_add_form(self): 清空添加账号表单。
def clear_add_form(self):
# 清空所有输入字段
for entry in self.input_entries.values():
entry.delete(0, tk.END)
- on_canvas_configure(self, event): 更新画布中窗口的宽度以填充整个画布。
def on_canvas_configure(self, event):
# 更新画布中窗口的宽度以填充整个画布
self.result_canvas.itemconfig(self.results_frame_id, width=event.width)
- copy_to_clipboard(self, text): 复制文本到剪贴板。
def copy_to_clipboard(self, text):
# 复制文本到剪贴板
self.master.clipboard_clear()
self.master.clipboard_append(text)
self.log(f"已复制到剪贴板: {text}")
- clear_results_frame(self): 清除所有查询结果控件。
def clear_results_frame(self):
# 清除所有查询结果控件
for widget in self.results_display_frame.winfo_children():
widget.destroy()
- setup_query_tab(self): 设置查询账号标签页。
def setup_query_tab(self):
# 创建查询框架
query_frame = ttk.LabelFrame(self.tab_query, text="查询账号信息")
query_frame.pack(padx=10, pady=10, fill="both", expand=False)
# 查询条件输入区域
ttk.Label(query_frame, text="查询字段:").grid(row=0, column=0, padx=5, pady=5, sticky="w")
self.query_type = tk.StringVar()
# 将查询类型改为下拉框,可以选择表中的任意列
self.columns_combo = ttk.Combobox(query_frame, textvariable=self.query_type, width=28)
self.columns_combo.grid(row=0, column=1, padx=5, pady=5, sticky="w")
ttk.Label(query_frame, text="查询值:").grid(row=1, column=0, padx=5, pady=5, sticky="w")
# 创建一个StringVar变量来跟踪输入值的变化
self.query_value_var = tk.StringVar()
self.query_value_var.trace_add("write", self.on_query_value_change)
# 创建一个框架来容纳查询输入框和清除按钮
query_input_frame = ttk.Frame(query_frame)
query_input_frame.grid(row=1, column=1, padx=5, pady=5, sticky="w")
# 使用Combobox替换原来的Entry,以支持下拉选择
self.query_value_entry = ttk.Combobox(query_input_frame, width=28, textvariable=self.query_value_var)
self.query_value_entry.pack(side="left", padx=(0, 5))
# 添加清除按钮
clear_button = ttk.Button(query_input_frame, text="清除", width=5, command=self.clear_query_value)
clear_button.pack(side="left")
# 查询按钮
query_button = ttk.Button(query_frame, text="查询", command=self.query_account_gui)
query_button.grid(row=2, column=0, columnspan=2, padx=5, pady=10)
# 创建结果显示区域
results_frame = ttk.LabelFrame(self.tab_query, text="查询结果")
results_frame.pack(padx=10, pady=10, fill="both", expand=True)
# 创建带滚动条的画布来显示结果
self.result_canvas = tk.Canvas(results_frame)
scrollbar = ttk.Scrollbar(results_frame, orient="vertical", command=self.result_canvas.yview)
self.result_canvas.configure(yscrollcommand=scrollbar.set)
# 放置画布和滚动条
self.result_canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# 创建一个框架来放置结果
self.results_display_frame = ttk.Frame(self.result_canvas)
self.results_frame_id = self.result_canvas.create_window((0, 0), window=self.results_display_frame, anchor="nw")
# 配置画布滚动区域
self.results_display_frame.bind("", lambda e: self.result_canvas.configure(scrollregion=self.result_canvas.bbox("all")))
self.result_canvas.bind("", self.on_canvas_configure)
# 为查询值下拉框绑定选择事件
self.query_value_entry.bind("<>", self.on_suggestion_selected)
# 初始化查询字段下拉框
self.update_column_combo()
- query_account_gui(self): 从GUI获取查询条件,并查询数据库。
def query_account_gui(self):
# 获取输入值
search_value = self.query_value_var.get().strip()
search_column = self.query_type.get()
# 验证输入
if not search_value:
messagebox.showerror("错误", "请输入查询值")
return
if not search_column:
messagebox.showerror("错误", "请选择查询字段")
return
# 清除之前的结果
self.clear_results_frame()
# 使用线程执行查询操作
threading.Thread(target=self.execute_query_account,
args=(search_column, search_value),
daemon=True).start()
- display_results(self, results): 在GUI中显示查询结果。
def display_results(self, results):
# 清除之前的结果
self.clear_results_frame()
if not results or isinstance(results, str):
no_result_label = ttk.Label(self.results_display_frame, text=results if isinstance(results, str) else "未找到匹配记录")
no_result_label.pack(padx=10, pady=10)
return
# 为每条记录创建一个框架
for i, record in enumerate(results):
record_frame = ttk.LabelFrame(self.results_display_frame, text=f"记录 {i+1}")
record_frame.pack(padx=10, pady=10, fill="x", expand=True)
# 为每个字段创建标签和值
for row, (field, value) in enumerate(record.items()):
# 创建框架放置每行内容
field_frame = ttk.Frame(record_frame)
field_frame.pack(padx=5, pady=2, fill="x")
# 字段名称
field_label = ttk.Label(field_frame, text=f"{field}:", width=15, anchor="e")
field_label.pack(side="left", padx=5)
# 复制按钮 - 移到字段名称后面,字段值前面
value_str = str(value) if value is not None else ""
copy_button = ttk.Button(
field_frame,
text="复制",
width=8,
command=lambda v=value_str: self.copy_to_clipboard(v)
)
copy_button.pack(side="left", padx=5)
# 字段值 - 设置最大显示宽度,并使用省略号
if len(value_str) > 50:
display_value = value_str[:47] + "..."
else:
display_value = value_str
value_label = ttk.Label(field_frame, text=display_value, anchor="w")
value_label.pack(side="left", padx=5, fill="x", expand=True)
- execute_query_account(self, search_column, search_value): 执行查询账号操作,并更新GUI。
def execute_query_account(self, search_column, search_value):
# 调用查询账号函数
results = check_account(search_column, search_value)
版权声明:
作者:余汉波
链接:https://www.sanrenjz.com/2025/05/06/pythongui%ef%bc%9a%e8%b4%a6%e5%8f%b7%e5%af%86%e7%a0%81%e7%ae%a1%e7%90%86%e5%99%a8%ef%bc%8c%e6%9c%ac%e5%9c%b0%e6%95%b0%e6%8d%ae%e5%ba%93%e7%ae%a1%e7%90%86%e5%90%84%e7%a7%8d%e8%b4%a6%e5%8f%b7%e5%af%86/
文章版权归作者所有,未经允许请勿转载。
THE END