不知不觉,在 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
(日记内容)字段。这一结构设计为后续的日记管理提供了坚实基础,确保每一篇日记都能被有序存储与高效检索。
python"># 连接到 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
组件构建菜单栏,包含新建、搜索、返回主页、删除和编辑等功能选项。简洁明了的布局,为用户提供了直观的操作路径,轻松实现对日记的各种管理操作。
python"># 创建菜单栏
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
函数从数据库获取数据并填充列表,每一行展示日期和天气信息,点击即可查看详细内容。
python"># 列表页面
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
函数对关键词进行高亮显示,帮助用户快速定位关键信息。
python">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
函数负责将用户输入的日记内容保存到数据库。如果日记已存在,会提示用户是否覆盖,确保数据的准确性与完整性。
python">def save_diary():global current_editing_dateweather = weather_entry.get().strip()if not weather:messagebox.showwarning("警告", "请输入天气信息")returndate = date_entry.get().strip()try:datetime.strptime(date, "%Y-%m-%d")except ValueError:messagebox.showwarning("警告", "日期格式错误,请使用 YYYY-MM-DD 格式")returncontent = 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 = Noneupdate_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
函数实现,用户输入关键词后,应用会在数据库中查询并在列表页面展示匹配结果,同样按照日期降序排列。这一功能大大提高了查找特定日记的效率。
python">def search_diaries():global is_searching, search_keywordkeyword = simpledialog.askstring("搜索", "请输入日期或日记内容关键词:")if keyword:is_searching = Truesearch_keyword = keywordupdate_diary_list()
3. 高亮显示:精准定位关键信息
highlight_text
函数利用 tkinter
的 Text
组件标签功能,在查看日记时对搜索关键词进行高亮显示,使关键信息一目了然。
python">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)
完整代码
python">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_dateweather = weather_entry.get().strip()if not weather:messagebox.showwarning("警告", "请输入天气信息")returndate = date_entry.get().strip()try:datetime.strptime(date, "%Y-%m-%d")except ValueError:messagebox.showwarning("警告", "日期格式错误,请使用 YYYY-MM-DD 格式")returncontent = 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 = Noneupdate_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("警告", "请选择一篇日记查看")returndate = 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 = resultshow_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 = rowvalues = (date, weather)diary_listbox.insert("", "end", values=values)def search_diaries():global is_searching, search_keywordkeyword = simpledialog.askstring("搜索", "请输入日期或日记内容关键词:")if keyword:is_searching = Truesearch_keyword = keywordupdate_diary_list()def return_to_home():global current_editing_date, is_searchingcurrent_editing_date = Noneis_searching = Falsesearch_keyword = ""update_diary_list()show_list_page()def new_diary():global current_editing_datecurrent_editing_date = Noneshow_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("警告", "请选择一篇日记进行删除")returndate = 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_dateselected_item = diary_listbox.selection()if not selected_item:messagebox.showwarning("警告", "请选择一篇日记进行编辑")returndate = 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 = resultshow_edit_page()current_editing_date = datedate_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()