原项目:[Tkinter项目]Python Tkinter NotePad记事本项目实战
https://www.bilibili.com/video/BV1qk4y1k7N2/?p=4
整体的功能是模仿记事本的一些基本简单设计,但是增加了工具栏的快捷方式。名字为PNote,“P”是指单词“penguin”(企鹅),简而言之,该记事本叫“企鹅记事本”,其icon就是使用一张小企鹅的表情包制成的,增添界面的趣味性。界面功能思维导图如下
提前找好工具栏的图片,图片的格式都为gif,大小一般为32x32或16x16。icon图片来源网站:https://www.iconfont.cn/
,下载的时候把图片后缀改成gif就可以了。先建一个py文件,随意起一个记事本的名字,py文件和图片文件分开放,图片放在同目录下的img文件中。ico图片转化网站:https://www.uupoop.com/ico/。将小企鹅表情包.jpg图片转换成尺寸大小32x32的icon图
记录工具:CSDN官网上的Markdown编辑器
根据想法中的思维导图整个应用程序的图形化界面分为四个part:菜单栏、工具栏、文本输入区域、右键弹出菜单,将组件代码分别写在这个四个方法中,其他的功能实现方法则另外处理
PS:此处的代码展示和源码文件的排版并不一致
#导入tkinter类库
from tkinter import *
from tkinter import filedialog, messagebox
from tkinter.ttk import Scrollbar, Checkbutton,Label,Button #tkk里面的组件会有所优化
import os
import sys
class PNote(Tk):
#工具栏所用图片的名称
icons = ["new_file","open_file","save","cut","copy","paste",
"undo","redo","find_text"]
icon_res = []
# 默认主题使用字典(背景色为白色,字体为黑色)、主题一(背景为企鹅灰,字体为深蓝色)、主题二(背景色为深蓝色、字体为白色)
theme_color = {"Default":"#000000.#FFFFFF",
"Penguin Gray":"#222b34.#e7e4e3",
"Night Mode":"#FFFFFF.#222b34"}
# 初始化操作
def __init__(self):
super().__init__() #继承父类的构造
#调用方法
self.set_window() #调用界面窗口方法
self.create_menu_bar() #菜单栏
self.create_tool_bar() #工具栏
self.create_body() #文本输入区域
self.create_pop_menu() #弹出菜单
# 设置窗口界面
def set_window(self):
self.title("PNote") #标题
max_width,max_height = self.maxsize() #宽高
# 居中对齐,像素
align_center = "800x600+%d+%d" % ((max_width-800)/2,(max_height-600)/2)
self.geometry(align_center)
self.iconbitmap("./img/penguin.ico")
#创建菜单栏
def create_menu_bar(self):
menu_bar = Menu(self)
self['menu']=menu_bar
#添加菜单栏项目
#文件
file_menu = Menu(menu_bar,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_menu.add_separator() #分割符
file_menu.add_command(label='退出',accelerator='Alt+F4',command=self.exit_notepad)
menu_bar.add_cascade(label='文件',menu=file_menu)
#文件菜单功能实现
# 打开文件功能
def open_file(self,event=None):
#打开文件并进行类型设置
input_file = filedialog.askopenfilename(filetypes=[("所有文件","*.*"),("文本文档","*.text")])
if input_file:
self.title("{}***NotePad".format(os.path.basename(input_file)))
self.file_name = input_file
self.context_text.delete(1.0,END)
with open(input_file,'r') as _file:
self.context_text.insert(1.0,_file.read())
#文件的保存,是保存(替代)原有文本文件的内容,先读写文件
def write_to_file(self,file_name):
try:
content = self.context_text.get(1.0,END)
with open(file_name,'w') as _file:
_file.write(content)
self.title("{}---NotePad".format(os.path.basename(file_name)))
except IOError:
messagebox.showerror("错误","文件保存失败!")
def save_file(self,event=None):
if not self.file_name:
self.save_as() #避免保存出来的新建的问题
else:
self.write_to_file(self.file_name)
#新建
def new_file(self,event=None):
self.title("新建---PNote")
self.context_text.delete(1.0,END)
self.file_name = None
#另存为
def save_as(self):
input_file = filedialog.askopenfilename(filetypes=[("所有文件","*.*"),("文本文档","*.text")])
if input_file:
self.file_name= input_file
self.write_to_file(self.file_name)
#退出
def exit_notepad(self):
if messagebox.askokcancel("退出","确定退出吗?"):
self.destroy()
if __name__ == '__main__':
app = PNote()
app.mainloop()
由于编辑的功能和工具栏差不多,所以会其方法放在body部分,但是“查找”功能在此单独说明
#在方法create_menu_bar(self)加入以下代码
#编辑
editor_menu = Menu(menu_bar,tearoff=0)
"""撤销,恢复,剪切,复制,粘贴,查找,全选"""
menu_bar.add_cascade(label='编辑',menu=editor_menu)
editor_menu.add_command(label='撤销',accelerator='Ctrl+Z',command=lambda:self.handle_menu_action('撤销'))
editor_menu.add_command(label='恢复',accelerator='Ctrl+Y',command=lambda:self.handle_menu_action('恢复'))
editor_menu.add_separator()
editor_menu.add_command(label='剪切',accelerator='Ctrl+X',command=lambda:self.handle_menu_action('剪切'))
editor_menu.add_command(label='复制',accelerator='Ctrl+C',command=lambda:self.handle_menu_action('复制'))
editor_menu.add_command(label='粘贴',accelerator='Ctrl+V',command=lambda:self.handle_menu_action('粘贴'))
editor_menu.add_separator()
editor_menu.add_command(label='查找',accelerator='Ctrl+F',command=self.find_text_dialog)
editor_menu.add_command(label='全选',accelerator='Ctrl+A',command=self.select_all)
查找文本功能
#设置查找对话框
def find_text_dialog(self):
search_dialog = Toplevel(self)
search_dialog.title('查找文本')
#居中
max_width,max_height = self.maxsize() #宽高
align_center = "300x80+%d+%d" % ((max_width-300)/2,(max_height-80)/2)
search_dialog.geometry(align_center)
search_dialog.resizable(False,False)
Label(search_dialog,text='查找全部').grid(row=0,column=0,sticky='e')
search_text = Entry(search_dialog,width=25)
search_text.grid(row=0,column=1,padx=2,pady=2,sticky="we")
search_text.focus_set()
#忽略大小写
ignore_case_value = IntVar()
Checkbutton(search_dialog,text="忽略大小写",variable=ignore_case_value).grid(
row=1,column=1,sticky='e',padx=2,pady=2
)
Button(search_dialog,text='查找',command=lambda:self.search_result(search_text.get(),
ignore_case_value.get(),
search_dialog,
search_text
)).grid(row=0,column=2,
sticky="w"+"e",padx=1,
pady=1)
# 关闭查找文本对话框
def close_search_dialog():
self.context_text.tag_remove('match',1.0,END)
search_dialog.destroy()
search_dialog.protocol("WM_DELETE_WINDOW",close_search_dialog)
return "break"
# 查找的方法
def search_result(self,key,ignore_case,search_dialog,search_box):
self.context_text.tag_remove('match',1.0,END)
matches_found = 0
if key:
start_pos = 1.0
while True:
start_pos = self.context_text.search(key,start_pos,
nocase=ignore_case,
stopindex=END)
if not start_pos:
break
end_pos = "{}+{}c".format(start_pos,len(key))
self.context_text.tag_add('match',start_pos,end_pos)
matches_found +=1
start_pos = end_pos
self.context_text.tag_config('match',foreground='white',
background='green')
search_box.focus_set()
search_dialog.title("发现了%d个匹配项" % matches_found)
# 全选
def select_all(self):
self.context_text.tag_add('sel',1.0,END)
return "break"
视图的下拉列表包括显示行号和显示高亮当前行、主题选择,前两者用checkbutton组件实现图形化界面,后者使用radiobutton组件实现图形化界面
# 在方法create_menu_bar(self)加入以下代码
#视图菜单
view_menu = Menu(menu_bar,tearoff=0)
menu_bar.add_cascade(label='视图',menu=view_menu)
"""显示行号"""
self.is_show_line_num = IntVar()
self.is_show_line_num.set(1)
view_menu.add_checkbutton(label="显示行号",
onvalue=0,offvalue=1,
variable=self.is_show_line_num,
command=self.update_line_num
)
"""显示高亮当前行"""
self.is_heighlight_line = IntVar()
view_menu.add_checkbutton(label="高亮当前行",
variable=self.is_heighlight_line,
command=self.toggle_highlight)
"""主题"""
themes_menu = Menu(menu_bar,tearoff=0)
view_menu.add_separator()
view_menu.add_cascade(label="主题",menu=themes_menu)
#主题选择
#默认主题
self.theme_choice = StringVar()
self.theme_choice.set("Default")
for k in sorted(self.theme_color):
themes_menu.add_radiobutton(label=k,
variable=self.theme_choice,
command=self.change_theme)
#关于菜单
about_menu = Menu(menu_bar,tearoff=0)
about_menu.add_command(label="关于",command=lambda:self.show_messagebox("关于"))
about_menu.add_command(label="帮助",command=lambda:self.show_messagebox("帮助"))
menu_bar.add_cascade(label='关于',menu=about_menu)
“关于”菜单的下拉列表都是使用messagebox弄两个对话框,简单化,如图所示
# 主题的切换
def change_theme(self):
selected_theme = self.theme_choice.get()
fg_bg = self.theme_color.get(selected_theme)
fg_color,bg_color = fg_bg.split('.')
self.context_text.config(bg=bg_color,fg=fg_color)
# 关于菜单
def show_messagebox(self,type):
if type == "帮助":
messagebox.showinfo("帮助","这是帮助文档",icon="question")
else:
messagebox.showinfo("关于","这是一个简单的记事本程序")
工具栏的,设计上是新建、打开、保存、剪切、复制、粘贴、撤销、重做、查找文本的icon逐一排列,和弹出菜单、菜单栏的功能是有些重复,是常用的功能快捷方式
def create_tool_bar(self):
tool_bar = Frame(self,height=25,background="#ffffff") # 容器Frame,白色
# 填充x轴
tool_bar.pack(fill="x")
# 生成图片文件放到对应的位置
for icon in self.icons:
tool_icon = PhotoImage(file="./img/%s.gif" % (icon,)) # 因为是元组所以有个逗号
tool_btn = Button(tool_bar,image=tool_icon,command=self.tool_bar_action(icon))
tool_btn.pack(side="left") # 图片左对齐
# 将tool_icon添加到icon_res里
self.icon_res.append(tool_icon)
def tool_bar_action(self,action_type):
def handle():
if action_type == 'open_file':
self.open_file()
elif action_type == "save":
self.save_file()
elif action_type == "new_file":
self.new_file()
elif action_type == "cut":
self.handle_menu_action("剪切")
elif action_type == "copy":
self.handle_menu_action("复制")
elif action_type == "paste":
self.handle_menu_action("粘贴")
elif action_type == "undo":
self.handle_menu_action("撤销")
elif action_type == "redo":
self.handle_menu_action("恢复")
elif action_type == "find_text":
self.find_text_dialog()
# handle返回处理
return handle
def create_body(self):
# 左:行号;右:滚动条;中:文本编辑区
# 行号区域
self.line_number_bar = Text(self,width=3, padx=3,takefocus=0,border=0,
background="#f0f0f0",state="disable"
) #state="disable"不能编辑状态
#左边填充整个y轴
self.line_number_bar.pack(side='left',fill='y')
# 文本编辑区
# undo=True是否具备文本取消功能,wrap:如何换行,word:按照单词自动换行,expand:可以拉伸
self.context_text = Text(self,wrap="word",undo=True)
# 热键绑定
self.context_text.bind("",self.open_file)
self.context_text.bind("",self.open_file)
self.context_text.bind("",self.save_file)
self.context_text.bind("",self.save_file)
self.context_text.bind("",self.new_file)
self.context_text.bind("",self.new_file)
self.context_text.bind('',lambda e:self.update_line_num())
self.context_text.pack(fill='both',expand="yes")
# 设置文本输入区
self.context_text.tag_config("active_line",background="#ffffff")
# 滚动条
scroll_bar = Scrollbar(self.context_text)
scroll_bar['command'] = self.context_text.yview
self.context_text["yscrollcommand"] = scroll_bar.set
scroll_bar.pack(side="right",fill="y")
# 行号处理
def update_line_num(self):
if self.is_show_line_num.get():
# 获取所有行
row,col = self.context_text.index(END).split('.')
# 列举每行的行号
line_num_content = "\n".join([str(i) for i in range(1,int(row))])
self.line_number_bar.config(state="normal")
self.line_number_bar.delete(1.0,END)
self.line_number_bar.insert(1.0,line_num_content)
self.line_number_bar.config(state='disable')
else:
self.line_number_bar.config(state='normal')
self.line_number_bar.delete(1.0,END)
self.line_number_bar.config(state='disable')
高亮的颜色默认是白色的,所以在
# 高亮当前行
def toggle_highlight(self):
if self.is_heighlight_line.get():
self.context_text.tag_remove('active_line',1.0,END)
# 设置高亮
self.context_text.tag_add("active_line","insert linestart",
"insert lineend+1c")
# 通过递归的方式进行处理,会很耗资源,想想有没有别的办法
self.context_text.after(200,self.toggle_highlight)
else:
self.context_text.tag_remove("active_line",1.0,END)
弹出菜单的功能选项有:剪切、复制、粘贴、撤销、恢复、全选
#弹出菜单(也叫右键菜单)
def create_pop_menu(self):
pop_menu = Menu(self.context_text,tearoff=0)
for item1,item2 in zip(['剪切','复制','粘贴','撤销','恢复'],
['cut','copy','paste','undo','redo',]):
pop_menu.add_command(label=item1,compound='left',command=self.tool_bar_action(item2))
pop_menu.add_separator() #分割
pop_menu.add_command(label="全选",command=self.select_all)
#绑定
self.context_text.bind("",lambda event:pop_menu.tk_popup(event.x_root,event.y_root))
#右键菜单的处理
def handle_menu_action(self,action_type):
if action_type == "撤销":
self.context_text.event_generate("<>")
elif action_type == "恢复":
self.context_text.event_generate("<>")
elif action_type == "剪切":
self.context_text.event_generate("<>")
elif action_type == "复制":
self.context_text.event_generate("<>")
elif action_type == "粘贴":
self.context_text.event_generate("<>")
#防止事件传递
return "break"
版本如以下动图所示:
网上找的教程python打包生成exe可执行文件
打包使用的是auto -py-to-exe
先安装pip install auto-py-to-exe,安装成功后输入auto-py-to-exe回车会弹出一个窗口
根据网上的教程为了便于打包,修改了图片的位置,所以修改了代码中图片的路径表达,
self.iconbitmap("D:\\PNote\\img\\penguin.ico")
tool_icon = PhotoImage(file="D:\\PNote\\img\\%s.gif" % (icon,))