我的创作纪念日 打造高效 Python 日记本应用:从基础搭建到功能优化全解析
不知不觉,在 CSDN 写博客已经有 5 年的时间了。这 5 年,就像一场充满惊喜与挑战的奇妙旅程,在我的成长之路上留下了深深浅浅的印记。到现在我的博客数据:
展现量 | 92万 |
---|---|
阅读量 | 31万 |
粉丝数 | 2万 |
文章数 | 200 |
这样的数据是我在写第一篇博客时未曾想到的。
回顾这 5 年的博客生涯,心中满是感慨。未来,我仍将与 CSDN 相伴前行,持续输出更多有价值的内容,记录学习历程的同时为技术社区贡献自己的一份力量 。
目录
- 打造高效 Python 日记本应用:从基础搭建到功能优化全解析
- 数据库搭建:稳固的数据基石
- 界面设计:美观与实用的融合
- 1. 菜单栏:便捷操作入口
- 2. 列表页面:日记的有序呈现
- 3. 编辑与查看页面:专注内容创作与回顾
- 功能实现:强大的交互体验
- 1. 日记保存与更新
- 2. 搜索与排序
- 3. 高亮显示:精准定位关键信息
- 完整代码
打造高效 Python 日记本应用:从基础搭建到功能优化全解析
在数字化时代,记录生活点滴的方式多种多样,而开发一个属于自己的日记本应用不仅充满趣味,还能极大提升记录的效率与个性化程度。今天,我们就一同深入探讨如何使用 Python 和 tkinter
库构建一个功能丰富的日记本应用,从数据库的连接与操作,到界面设计与交互逻辑的实现,再到搜索与高亮显示等高级功能的优化,全方位领略 Python 在桌面应用开发中的魅力。
数据库搭建:稳固的数据基石
应用的核心是数据存储,这里我们选择 SQLite 数据库。通过 sqlite3
模块连接数据库并创建日记表 diaries
,包含 date
(日期,主键)、weather
(天气)和 content
(日记内容)字段。这一结构设计为后续的日记管理提供了坚实基础,确保每一篇日记都能被有序存储与高效检索。
# 连接到 SQLite 数据库
conn = sqlite3.connect('diaries.db')
c = conn.cursor()
# 创建日记表(如果不存在)
c.execute('''CREATE TABLE IF NOT EXISTS diaries
(date TEXT PRIMARY KEY, weather TEXT, content TEXT)''')
conn.commit()
界面设计:美观与实用的融合
1. 菜单栏:便捷操作入口
利用 tkinter
的 Menu
组件构建菜单栏,包含新建、搜索、返回主页、删除和编辑等功能选项。简洁明了的布局,为用户提供了直观的操作路径,轻松实现对日记的各种管理操作。
# 创建菜单栏
menu_bar = tk.Menu(root)
file_menu = tk.Menu(menu_bar, tearoff=0)
# 去除不支持的 -fg 和 -bg 选项
file_menu.add_command(label="新建", command=new_diary, font=FONT)
file_menu.add_command(label="搜索日记", command=search_diaries, font=FONT)
file_menu.add_command(label="返回主页", command=return_to_home, font=FONT)
file_menu.add_command(label="删除日记", command=delete_diary, font=FONT)
file_menu.add_command(label="编辑日记", command=edit_diary, font=FONT)
menu_bar.add_cascade(label="文件", menu=file_menu, font=FONT)
root.config(menu=menu_bar)
2. 列表页面:日记的有序呈现
ttk.Treeview
组件用于展示日记列表,按照日期降序排列,让用户能快速找到最新的日记。通过 update_diary_list
函数从数据库获取数据并填充列表,每一行展示日期和天气信息,点击即可查看详细内容。
# 列表页面
list_frame = tk.Frame(root)
# 去除 font 选项
diary_listbox = ttk.Treeview(list_frame, columns=("日期", "天气"), show="headings")
diary_listbox.heading("日期", text="日期")
diary_listbox.heading("天气", text="天气")
diary_listbox.column("日期", width=150)
diary_listbox.column("天气", width=100)
diary_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
diary_listbox.bind("<ButtonRelease-1>", open_diary)
scrollbar = ttk.Scrollbar(list_frame, command=diary_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
diary_listbox.config(yscrollcommand=scrollbar.set)
update_diary_list()
list_frame.grid(row=1, column=0, sticky="nsew")
3. 编辑与查看页面:专注内容创作与回顾
编辑页面提供了日期、天气输入框以及用于撰写日记内容的 Text
组件,方便用户记录生活。查看页面则以只读形式展示日记详情,在搜索时还能通过 highlight_text
函数对关键词进行高亮显示,帮助用户快速定位关键信息。
def show_edit_page():
list_frame.grid_forget()
view_frame.grid_forget()
edit_frame.grid(row=1, column=0, sticky="nsew")
date_entry.delete(0, tk.END)
date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
weather_entry.delete(0, tk.END)
text.delete("1.0", tk.END)
def show_view_page(date, weather, content):
list_frame.grid_forget()
edit_frame.grid_forget()
view_frame.grid(row=1, column=0, sticky="nsew")
view_date_label.config(text=f"日期: {date}")
view_weather_label.config(text=f"天气: {weather}")
view_text.config(state=tk.NORMAL)
view_text.delete("1.0", tk.END)
view_text.insert(tk.END, content)
if is_searching:
highlight_text(view_text, search_keyword)
view_text.config(state=tk.DISABLED)
功能实现:强大的交互体验
1. 日记保存与更新
save_diary
函数负责将用户输入的日记内容保存到数据库。如果日记已存在,会提示用户是否覆盖,确保数据的准确性与完整性。
def save_diary():
global current_editing_date
weather = weather_entry.get().strip()
if not weather:
messagebox.showwarning("警告", "请输入天气信息")
return
date = date_entry.get().strip()
try:
datetime.strptime(date, "%Y-%m-%d")
except ValueError:
messagebox.showwarning("警告", "日期格式错误,请使用 YYYY-MM-DD 格式")
return
content = text.get("1.0", tk.END).strip()
try:
if current_editing_date:
if current_editing_date != date:
c.execute("DELETE FROM diaries WHERE date=?", (current_editing_date,))
c.execute("INSERT OR REPLACE INTO diaries VALUES (?,?,?)", (date, weather, content))
conn.commit()
messagebox.showinfo("提示", f"日期为 {date} 的日记已更新")
else:
c.execute("INSERT INTO diaries VALUES (?,?,?)", (date, weather, content))
conn.commit()
messagebox.showinfo("提示", f"日记已保存,日期: {date}")
current_editing_date = None
update_diary_list()
show_list_page()
except sqlite3.IntegrityError:
if messagebox.askyesno("提示", f"该日期 {date} 的日记已存在,是否覆盖?"):
c.execute("UPDATE diaries SET weather=?, content=? WHERE date=?", (weather, content, date))
conn.commit()
messagebox.showinfo("提示", f"日期为 {date} 的日记已更新")
update_diary_list()
show_list_page()
else:
messagebox.showinfo("提示", f"取消保存日期为 {date} 的日记")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {e}")
2. 搜索与排序
搜索功能通过 search_diaries
函数实现,用户输入关键词后,应用会在数据库中查询并在列表页面展示匹配结果,同样按照日期降序排列。这一功能大大提高了查找特定日记的效率。
def search_diaries():
global is_searching, search_keyword
keyword = simpledialog.askstring("搜索", "请输入日期或日记内容关键词:")
if keyword:
is_searching = True
search_keyword = keyword
update_diary_list()
3. 高亮显示:精准定位关键信息
highlight_text
函数利用 tkinter
的 Text
组件标签功能,在查看日记时对搜索关键词进行高亮显示,使关键信息一目了然。
def highlight_text(text_widget, keyword):
text_widget.tag_configure("highlight", background="yellow")
text = text_widget.get("1.0", tk.END)
pattern = re.compile(re.escape(keyword), re.IGNORECASE)
for match in pattern.finditer(text):
start_index = f"1.0+{match.start()}c"
end_index = f"1.0+{match.end()}c"
text_widget.tag_add("highlight", start_index, end_index)
完整代码
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
from tkinter import simpledialog
from datetime import datetime
import sqlite3
import re
# 连接到 SQLite 数据库
conn = sqlite3.connect('diaries.db')
c = conn.cursor()
# 创建日记表(如果不存在)
c.execute('''CREATE TABLE IF NOT EXISTS diaries
(date TEXT PRIMARY KEY, weather TEXT, content TEXT)''')
conn.commit()
current_editing_date = None
is_searching = False
search_keyword = ""
# 统一字体
FONT = ("Arial", 12)
def save_diary():
global current_editing_date
weather = weather_entry.get().strip()
if not weather:
messagebox.showwarning("警告", "请输入天气信息")
return
date = date_entry.get().strip()
try:
datetime.strptime(date, "%Y-%m-%d")
except ValueError:
messagebox.showwarning("警告", "日期格式错误,请使用 YYYY-MM-DD 格式")
return
content = text.get("1.0", tk.END).strip()
try:
if current_editing_date:
if current_editing_date != date:
c.execute("DELETE FROM diaries WHERE date=?", (current_editing_date,))
c.execute("INSERT OR REPLACE INTO diaries VALUES (?,?,?)", (date, weather, content))
conn.commit()
messagebox.showinfo("提示", f"日期为 {date} 的日记已更新")
else:
c.execute("INSERT INTO diaries VALUES (?,?,?)", (date, weather, content))
conn.commit()
messagebox.showinfo("提示", f"日记已保存,日期: {date}")
current_editing_date = None
update_diary_list()
show_list_page()
except sqlite3.IntegrityError:
if messagebox.askyesno("提示", f"该日期 {date} 的日记已存在,是否覆盖?"):
c.execute("UPDATE diaries SET weather=?, content=? WHERE date=?", (weather, content, date))
conn.commit()
messagebox.showinfo("提示", f"日期为 {date} 的日记已更新")
update_diary_list()
show_list_page()
else:
messagebox.showinfo("提示", f"取消保存日期为 {date} 的日记")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {e}")
def open_diary(event=None):
selected_item = diary_listbox.selection()
if not selected_item:
messagebox.showwarning("警告", "请选择一篇日记查看")
return
date = diary_listbox.item(selected_item, "values")[0]
try:
c.execute("SELECT weather, content FROM diaries WHERE date=?", (date,))
result = c.fetchone()
if result:
weather, content = result
show_view_page(date, weather, content)
else:
messagebox.showwarning("警告", f"未找到日期为 {date} 的日记")
except Exception as e:
messagebox.showerror("错误", f"打开失败: {e}")
def update_diary_list():
for item in diary_listbox.get_children():
diary_listbox.delete(item)
if is_searching:
c.execute("SELECT date, weather, content FROM diaries WHERE date LIKE? OR content LIKE? ORDER BY date DESC",
('%' + search_keyword + '%', '%' + search_keyword + '%'))
else:
c.execute("SELECT date, weather, content FROM diaries ORDER BY date DESC")
rows = c.fetchall()
for row in rows:
date, weather, content = row
values = (date, weather)
diary_listbox.insert("", "end", values=values)
def search_diaries():
global is_searching, search_keyword
keyword = simpledialog.askstring("搜索", "请输入日期或日记内容关键词:")
if keyword:
is_searching = True
search_keyword = keyword
update_diary_list()
def return_to_home():
global current_editing_date, is_searching
current_editing_date = None
is_searching = False
search_keyword = ""
update_diary_list()
show_list_page()
def new_diary():
global current_editing_date
current_editing_date = None
show_edit_page()
def show_list_page():
list_frame.grid(row=1, column=0, sticky="nsew")
edit_frame.grid_forget()
view_frame.grid_forget()
def show_edit_page():
list_frame.grid_forget()
view_frame.grid_forget()
edit_frame.grid(row=1, column=0, sticky="nsew")
date_entry.delete(0, tk.END)
date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
weather_entry.delete(0, tk.END)
text.delete("1.0", tk.END)
def show_view_page(date, weather, content):
list_frame.grid_forget()
edit_frame.grid_forget()
view_frame.grid(row=1, column=0, sticky="nsew")
view_date_label.config(text=f"日期: {date}")
view_weather_label.config(text=f"天气: {weather}")
view_text.config(state=tk.NORMAL)
view_text.delete("1.0", tk.END)
view_text.insert(tk.END, content)
if is_searching:
highlight_text(view_text, search_keyword)
view_text.config(state=tk.DISABLED)
def delete_diary():
selected_item = diary_listbox.selection()
if not selected_item:
messagebox.showwarning("警告", "请选择一篇日记进行删除")
return
date = diary_listbox.item(selected_item, "values")[0]
if messagebox.askyesno("确认删除", f"确定要删除日期为 {date} 的日记吗?"):
try:
c.execute("DELETE FROM diaries WHERE date=?", (date,))
conn.commit()
messagebox.showinfo("提示", f"日期为 {date} 的日记已删除")
if is_searching:
update_diary_list()
else:
update_diary_list()
show_list_page()
except Exception as e:
messagebox.showerror("错误", f"删除失败: {e}")
def edit_diary():
global current_editing_date
selected_item = diary_listbox.selection()
if not selected_item:
messagebox.showwarning("警告", "请选择一篇日记进行编辑")
return
date = diary_listbox.item(selected_item, "values")[0]
try:
c.execute("SELECT weather, content FROM diaries WHERE date=?", (date,))
result = c.fetchone()
if result:
weather, content = result
show_edit_page()
current_editing_date = date
date_entry.delete(0, tk.END)
date_entry.insert(0, date)
weather_entry.delete(0, tk.END)
weather_entry.insert(0, weather)
text.delete("1.0", tk.END)
text.insert(tk.END, content)
else:
messagebox.showwarning("警告", f"未找到日期为 {date} 的日记")
except Exception as e:
messagebox.showerror("错误", f"编辑失败: {e}")
def highlight_text(text_widget, keyword):
text_widget.tag_configure("highlight", background="yellow")
text = text_widget.get("1.0", tk.END)
pattern = re.compile(re.escape(keyword), re.IGNORECASE)
for match in pattern.finditer(text):
start_index = f"1.0+{match.start()}c"
end_index = f"1.0+{match.end()}c"
text_widget.tag_add("highlight", start_index, end_index)
root = tk.Tk()
root.title("日记本")
root.geometry("800x600")
# 创建菜单栏
menu_bar = tk.Menu(root)
file_menu = tk.Menu(menu_bar, tearoff=0)
# 去除不支持的 -fg 和 -bg 选项
file_menu.add_command(label="新建", command=new_diary, font=FONT)
file_menu.add_command(label="搜索日记", command=search_diaries, font=FONT)
file_menu.add_command(label="返回主页", command=return_to_home, font=FONT)
file_menu.add_command(label="删除日记", command=delete_diary, font=FONT)
file_menu.add_command(label="编辑日记", command=edit_diary, font=FONT)
menu_bar.add_cascade(label="文件", menu=file_menu, font=FONT)
root.config(menu=menu_bar)
# 创建样式对象
style = ttk.Style()
# 设置 Treeview 字体
style.configure("Treeview", font=FONT)
style.configure("Treeview.Heading", font=FONT)
# 列表页面
list_frame = tk.Frame(root)
# 去除 font 选项
diary_listbox = ttk.Treeview(list_frame, columns=("日期", "天气"), show="headings")
diary_listbox.heading("日期", text="日期")
diary_listbox.heading("天气", text="天气")
diary_listbox.column("日期", width=150)
diary_listbox.column("天气", width=100)
diary_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
diary_listbox.bind("<ButtonRelease-1>", open_diary)
scrollbar = ttk.Scrollbar(list_frame, command=diary_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
diary_listbox.config(yscrollcommand=scrollbar.set)
update_diary_list()
list_frame.grid(row=1, column=0, sticky="nsew")
# 分隔线
separator = ttk.Separator(root, orient=tk.HORIZONTAL)
separator.grid(row=2, column=0, sticky="ew")
# 编辑页面
edit_frame = tk.Frame(root)
date_label = tk.Label(edit_frame, text="日期 (YYYY-MM-DD):", font=FONT)
date_label.grid(row=0, column=0, padx=10, pady=5)
date_entry = tk.Entry(edit_frame, font=FONT)
date_entry.insert(0, datetime.now().strftime("%Y-%m-%d"))
date_entry.grid(row=0, column=1, padx=10, pady=5)
weather_label = tk.Label(edit_frame, text="输入天气:", font=FONT)
weather_label.grid(row=1, column=0, padx=10, pady=5)
weather_entry = tk.Entry(edit_frame, font=FONT)
weather_entry.grid(row=1, column=1, padx=10, pady=5)
text = tk.Text(edit_frame, wrap=tk.WORD, font=FONT)
text.grid(row=2, column=0, columnspan=2, padx=10, pady=5, sticky="nsew")
save_button = tk.Button(edit_frame, text="保存", command=save_diary, font=FONT)
save_button.grid(row=3, column=0, columnspan=2, padx=10, pady=5)
# 查看页面
view_frame = tk.Frame(root)
view_date_label = tk.Label(view_frame, text="", font=FONT)
view_date_label.grid(row=0, column=0, padx=10, pady=5)
view_weather_label = tk.Label(view_frame, text="", font=FONT)
view_weather_label.grid(row=1, column=0, padx=10, pady=5)
view_text = tk.Text(view_frame, wrap=tk.WORD, state=tk.DISABLED, font=FONT)
view_text.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
# 状态标签
status_label = tk.Label(root, text="准备就绪", font=FONT)
status_label.grid(row=3, column=0, sticky="ew")
# 设置网格权重,使界面可伸缩
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
# 关闭数据库连接
conn.close()