文章目录
- 一、前言
- 二、计算器
- 2.1 概述
- 2.2 代码
- 三、待办事项
- 3.1 概述
- 3.2 代码
- 四、文本编辑器
- 4.1 概述
- 4.2 代码
一、前言
为掌握tkinter模块的使用,现记录一下使用tkinter模块完成各功能的过程。
二、计算器
2.1 概述
实现简单的计算功能,页面示意图:
相关知识点:Button与Entry小部件、eval函数(用于对表达式进行运算)以及正则表达式(对输入的表达式匹配合法的数学表达式)。
eval函数格式为eval(expression,globals=None,locals=None)
expression
:必须参数,是一个字符串(被解析为Python表达式执行)或代码对象(直接执行)globals
:可选,字典,表示全局命名空间,默认使用当前全局命名空间(globals()
)locals</font>
:可选,是一个映射对象,表示局部命名空间,默认使用当前局部命名空间(locals()
),如果只提供了globals
,则默认与globals
相同
eval函数使用示例:
globals_dict = {"x": 10, "y": 20}
locals_dict = {"y": 5}result = eval("x + y", globals_dict, locals_dict)
print(result) # 输出: 15(x 来自 globals,y 来自 locals)
2.2 代码
import tkinter as tk
from tkinter import ttk, messagebox
import reclass Calculator:BUTTON_LAYOUT = [("7", 0, 0), ("8", 0, 1), ("9", 0, 2),("4", 1, 0), ("5", 1, 1), ("6", 1, 2),("1", 2, 0), ("2", 2, 1), ("3", 2, 2),("0", 3, 0), (".", 3, 1), ("=", 3, 2),("+", 4, 0), ("-", 4, 1), ("*", 4, 2),("/", 5, 0), ("C", 5, 1), ("⌫", 5, 2)]def __init__(self):self.root = tk.Tk()self.root.title("计算器")self.root.geometry("300x400")self.show_num = tk.StringVar()self._setup_ui()def _setup_ui(self):entry = ttk.Entry(self.root, textvariable=self.show_num, font=("Arial", 20), justify="right")entry.pack(fill="x", padx=10, pady=10, ipady=10)button_frame = ttk.Frame(self.root)button_frame.pack(expand=True, fill="both", padx=10, pady=10)for (text, row, col) in self.BUTTON_LAYOUT:btn = ttk.Button(button_frame, text=text, command=lambda t=text: self.btn_click(t))btn.grid(row=row, column=col, sticky="nsew", padx=2, pady=2)button_frame.rowconfigure(row, weight=1)button_frame.columnconfigure(col, weight=1)self.root.bind("<Return>", lambda e: self.btn_click("="))self.root.bind("<BackSpace>", lambda e: self.btn_click("⌫"))def btn_click(self, num):current = self.show_num.get()if num == "=":try:if re.match(r"^[+\-*/0-9.()\s]*$", current):current = current.replace("+", " + ")current = current.replace("-", " - ")current = current.replace("*", " * ")current = current.replace("/", " / ")result = eval(current)self.show_num.set(str(result))else:raise Exception("无效表达式")except:messagebox.showerror("错误", "无效表达式")elif num == "C":self.show_num.set("")elif num == "⌫":self.show_num.set(current[:-1])else:self.show_num.set(current + str(num))def run(self):self.root.mainloop()if __name__ == '__main__':calc = Calculator()calc.run()
三、待办事项
3.1 概述
实现简单的待办事项功能:双击可以修改待办事项,右键可以删除,退出界面会自动保存到文件todo.csv;
界面如下:
相关知识点:Treeview、Menu、Scrollar、Combobox等小组件、csv模块
3.2 代码
import tkinter as tk
from tkinter import messagebox, ttk
import csvclass ToduItem:def __init__(self, todo, plan_time, status, remark):self.todo = todoself.plan_time = plan_timeself.status = statusself.remark = remarkclass TodoList:STATUS_OPTIONS = ["未完成", "已完成"]TODO_TITLE = ["待办事项", "计划时间", "状态", "备注"]def __init__(self):self.root = tk.Tk()self.root.title("待办事项")self.root.geometry("700x500")self.todo_list = []self.data = [tk.StringVar() for _ in range(4)]self._create_input_frame()self._create_tree_frame()self.load_from_csv()self.popup_menu = tk.Menu(self.root, tearoff=0)self.popup_menu.add_command(label="删除", command=self.delete_todo)self.tree_list.bind("<Button-3>", lambda e: self.popup_menu.post(e.x_root, e.y_root))self.root.protocol("WM_DELETE_WINDOW", self.on_close)def delete_todo(self):selected_item = self.tree_list.selection()if not selected_item:messagebox.showwarning("提示", "请选择待办事项")returnif messagebox.askyesno("删除待办事项", "确定删除吗?"):for item in selected_item:index = self.tree_list.index(item)self.tree_list.delete(item)self.todo_list.pop(index)messagebox.showinfo("提示", "删除成功")def load_from_csv(self):try:with open("todo.csv", "r", encoding="utf-8") as file:reader = csv.DictReader(file)for row in reader:todo = ToduItem(row.get("待办事项", ""),row.get("计划时间", ""),row.get("状态", "未完成"),row.get("备注", ""))self.todo_list.append(todo)self.tree_list.insert("", "end", values=(todo.todo, todo.plan_time, todo.status, todo.remark))except FileNotFoundError:messagebox.showinfo("提示", "无法打开文件:todo.csv")def save_to_csv(self):try:with open("todo.csv", "w", encoding="utf-8", newline="") as file:writer = csv.DictWriter(file, fieldnames=self.TODO_TITLE)writer.writeheader()for todo in self.todo_list:writer.writerow({"待办事项": todo.todo, "计划时间": todo.plan_time, "状态": todo.status, "备注": todo.remark})except Exception as e:messagebox.showerror("保存失败", f"无法保存文件:{str(e)}")def on_close(self):self.save_to_csv()self.root.destroy()def _create_input_frame(self):frm_insert = ttk.Frame(self.root, width=600, height=500, padding=10)frm_insert.pack(fill="both", expand=True)for i in range(4):ttk.Label(frm_insert, text=self.TODO_TITLE[i]).grid(row=0, column=i, padx=5, pady=5)if i == 2:ttk.Combobox(frm_insert, values=self.STATUS_OPTIONS, width=20, height=20,textvariable=self.data[i]).grid(row=1, column=i, padx=5, pady=5)else:ttk.Entry(frm_insert, width=20, textvariable=self.data[i]).grid(row=1, column=i, padx=5, pady=5)frm_insert.rowconfigure(i, weight=1)frm_insert.columnconfigure(i, weight=1)btn_insert = ttk.Button(frm_insert, text="添加", command=self._insert_tree)btn_insert.grid(row=1, column=4, padx=5, pady=5)def _create_tree_frame(self):frm_todo = ttk.Frame(self.root, width=600, height=500, padding=10)frm_todo.pack(fill="both", expand=True)self.tree_list = ttk.Treeview(frm_todo, columns=tuple(self.TODO_TITLE), show="headings", height=20,cursor="hand2")for i in range(4):self.tree_list.heading("#" + str(i + 1), text=self.TODO_TITLE[i])self.tree_list.column("#" + str(i + 1), width=100, anchor="center")self.tree_list.pack(fill="both", expand=True)scrollbar_todo = ttk.Scrollbar(frm_todo, orient="vertical", command=self.tree_list.yview)scrollbar_todo.pack(side="right", fill="y")self.tree_list.config(yscrollcommand=scrollbar_todo.set)self.tree_list.bind("<Double-1>", lambda event: self._edit_ui()) # 双击事件def _validate_data(self, data):if not all(data[:3]):messagebox.showwarning("提示", "请填写必要信息(待办事项, 计划时间, 状态)")return Falseelif data[2] not in self.STATUS_OPTIONS:messagebox.showwarning("提示", "状态信息填写错误")return Falsereturn Truedef _insert_tree(self):# 获取输入框的值todo_data = [self.data[i].get() for i in range(4)]if not self._validate_data(todo_data[:3]):returntodo_data = ToduItem(todo_data[0], todo_data[1], todo_data[2], todo_data[3])print(todo_data)self.todo_list.append(todo_data)self.tree_list.insert("", tk.END, values=list(todo_data.__dict__.values()))for var in self.data:var.set("")def _edit_tree(self, selected_item):todo_data = [self.data[i].get() for i in range(4)]if not self._validate_data(todo_data[:3]):return Falsetry:index = self.tree_list.index(selected_item)except:messagebox.showwarning("错误", "无效的选择")return Falsetry:self.todo_list[index] = ToduItem(*todo_data)self.tree_list.item(selected_item, values=todo_data)messagebox.showinfo("成功", "待办事项已更新")return Trueexcept Exception as e:messagebox.showerror("错误", f"更新失败: {str(e)}")return Falsedef _edit_ui(self):selection = self.tree_list.selection()if not selection:messagebox.showwarning("提示", "请选择待办事项")returnindex = selection[0]todo_data = self.tree_list.item(index, "values")edit_window = tk.Toplevel(self.root)edit_window.title("编辑待办事项")edit_window.geometry("400x200")for i in range(4):ttk.Label(edit_window, text=self.tree_list.heading("#" + str(i + 1))["text"]).grid(row=i, column=0, padx=5,pady=5)if i == 2:ttk.Combobox(edit_window, values=self.STATUS_OPTIONS, width=10, textvariable=self.data[i]).grid(row=i, column=1, padx=5, pady=5)else:ttk.Entry(edit_window, width=20, textvariable=self.data[i]).grid(row=i, column=1, padx=5, pady=5)self.data[i].set(todo_data[i])btn_save = ttk.Button(edit_window, text="确定",command=lambda: self.confirm_edit(index, edit_window))btn_save.grid(row=4, column=1, padx=5, pady=5)btn_cancel = ttk.Button(edit_window, text="取消", command=edit_window.destroy)btn_cancel.grid(row=4, column=2, padx=5, pady=5)def confirm_edit(self, index, window):verify = self._edit_tree(index)if verify:window.destroy()returndef run(self):self.root.mainloop()if __name__ == '__main__':todo_list = TodoList()todo_list.run()
四、文本编辑器
4.1 概述
实现简单的文本编辑器功能,可以新建、保存、另存为、打开文件,页面示意图:
4.2 代码
import tkinter as tk
import os
from tkinter import filedialog, messagebox, ttkclass TabData:def __init__(self, text_widget, file_path=None):self.text_widget = text_widgetself.file_path = file_pathself.saved = Falseself.first_save = Truedef __str__(self):return f"TextWidget: {self.text_widget}, FilePath: {self.file_path}, Saved: {self.saved}, FirstSave: {self.first_save}"class TextEditor:def __init__(self):self.root = tk.Tk()self.root.title("文本编辑器")self.root.geometry("1000x700")self.tabs = {}self._create_menu()self.root.protocol("WM_DELETE_WINDOW", self.on_close)# 全局快捷键绑定self.root.bind_all("<Control-n>", lambda e: self.new_file())self.root.bind_all("<Control-o>", lambda e: self.open_file())self.root.bind_all("<Control-s>", lambda e: self.save_file())self.root.bind_all("<Control-S>", lambda e: self.save_as_file())self.root.bind_all("<Control-w>", lambda e: self.close_current_tab())def _create_menu(self):menubar = tk.Menu(self.root)file_menu = tk.Menu(menubar, tearoff=0)file_menu.add_command(label="新建", accelerator="Ctrl+N", command=self.new_file)file_menu.add_command(label="打开", accelerator="Ctrl+O", command=self.open_file)file_menu.add_command(label="保存", accelerator="Ctrl+S", command=self.save_file)file_menu.add_command(label="另存为", accelerator="Ctrl+Shift+S", command=self.save_as_file)file_menu.add_separator()file_menu.add_command(label="关闭标签页", accelerator="Ctrl+W", command=self.close_current_tab)file_menu.add_command(label="退出", accelerator="Alt+F4", command=self.on_close)menubar.add_cascade(label="文件", menu=file_menu)edit_menu = tk.Menu(menubar, tearoff=0)edit_menu.add_command(label="撤销", accelerator="Ctrl+Z",command=lambda: self._get_current_text().event_generate("<<Undo>>"))edit_menu.add_command(label="重做", accelerator="Ctrl+Y",command=lambda: self._get_current_text().event_generate("<<Redo>>"))menubar.add_cascade(label="编辑", menu=edit_menu)self.root.config(menu=menubar)# 标签页组件self.notebook = ttk.Notebook(self.root)self.notebook.pack(fill=tk.BOTH, expand=True)def create_new_tab(self, content="", file_path=None):text_widget = tk.Text(self.notebook, wrap="word", undo=True)text_widget.pack(fill=tk.BOTH, expand=True)scrollbar = ttk.Scrollbar(text_widget, command=text_widget.yview)scrollbar.pack(side="right", fill="y")text_widget.config(yscrollcommand=scrollbar.set)self.notebook.add(text_widget, text=file_path if file_path else "新建文件")self.notebook.select(text_widget)tab_id = self.notebook.select()self.tabs[tab_id] = TabData(text_widget, file_path)self.tabs[tab_id].original_content = contenttext_widget.insert("1.0", content)def close_current_tab(self):current_tab = self.notebook.select()if current_tab:tab_data = self.tabs[current_tab]if tab_data.original_content != tab_data.text_widget.get("1.0", "end-1c"):tab_data.saved = Falseif not tab_data.saved:if messagebox.askyesno("确认", "是否保存更改?"):self.save_file()else:messagebox.showinfo("提示", "未保存的更改将丢失。")self.notebook.forget(current_tab)del self.tabs[current_tab]def _get_current_text(self):current_tab = self.notebook.select()return self.tabs[current_tab].text_widgetdef new_file(self):self.create_new_tab()def open_file(self):file_path = filedialog.askopenfilename(filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])filename = os.path.basename(file_path)if file_path:try:with open(file_path, "r", encoding="utf-8") as file:content = file.read()self.create_new_tab(content, file_path)self.tabs[self.notebook.select()].saved = Trueself.notebook.tab(self.notebook.select(), text=filename) # 更新标签页标题except Exception as e:messagebox.showerror("打开文件失败", f"打开文件时出错:{str(e)}")def verify_file(self, current_tab):if not current_tab:messagebox.showwarning("警告", "没有激活的标签页。")return Falsetab_data = self.tabs.get(current_tab)if not tab_data:messagebox.showwarning("警告", "无法找到当前标签页的数据。")return Falsecontent = tab_data.text_widget.get("1.0", "end-1c")if content == "":messagebox.showwarning("警告", "文件内容为空,无法保存。")return Falseelif tab_data.first_save:tab_data.first_save = Falsereturn Trueelse:tab_data.saved = False # 更新保存状态为未保存return Truedef _save_to_file(self, current_tab, file_path=None):if not self.verify_file(current_tab):returntab_data = self.tabs[current_tab]if not file_path and not tab_data.file_path:file_path = filedialog.asksaveasfilename(defaultextension=".txt",filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])if not file_path:returncontent = tab_data.text_widget.get("1.0", "end-1c")if content == tab_data.original_content and tab_data.file_path == file_path:return # 文件内容未更改且文件路径未更改,无需保存try:content = tab_data.text_widget.get("1.0", "end-1c")save_path = file_path if file_path else tab_data.file_pathwith open(save_path, "w", encoding="utf-8") as file:file.write(content)tab_data.file_path = save_pathtab_data.saved = Truetab_data.original_content = contenttab_data.first_save = Falseself.notebook.tab(current_tab, text=os.path.basename(file_path))messagebox.showinfo("保存成功", "文件已保存。")except Exception as e:messagebox.showerror("保存失败", f"保存文件时出错:{str(e)}")def save_as_file(self):current_tab = self.notebook.select()if not self.verify_file(current_tab):returnfile_path = filedialog.asksaveasfilename(defaultextension=".txt",filetypes=[("Text Files", "*.txt"), ("All Files", "*.*")])# 如果用户取消选择路径if not file_path:returnself._save_to_file(current_tab, file_path)def save_file(self):current_tab = self.notebook.select()if not self.verify_file(current_tab):returntab_data = self.tabs[current_tab]file_path = tab_data.file_pathself._save_to_file(current_tab, file_path)def on_close(self):for tab_id in list(self.tabs.keys()):self.notebook.select(tab_id)if not self.tabs[tab_id].saved:file_path = self.tabs[tab_id].file_pathfilename = os.path.basename(file_path) if file_path else "新建文件"if messagebox.askyesno("确认", f"{filename}是否保存更改?"):self.save_file()self.notebook.forget(tab_id)del self.tabs[tab_id]self.root.destroy()def run(self):self.root.mainloop()if __name__ == "__main__":editor = TextEditor()editor.run()