全部代码:
首先创建一个editor_style.py文件,做为程序调用的一些参数、变量使用,该文件的代码:
theme_color = {
'Default': '#000000.#FFFFFF',
'Greygarious': '#83406A.#D1D4D1',
'Aquamarine': '#5B8340.#D1E7E0',
'Bold Beige': '#4B4620.#FFF0E1',
'Cobalt Blue': '#ffffBB.#3333aa',
'Olive Green': '#D1E7E0.#5B8340',
'Night Mode': '#FFFFFF.#000000',
}
#这里将快捷菜单栏上图表对应的名称存放列表中,和主题颜色参数放在一个py文件中,后面使用时候直接调用
ICONS = ['new_file', 'open_file', 'save', 'cut', 'copy', 'paste',
'undo', 'redo', 'find_text']
接下来就是准备好文本文件会用到的各种图标文件
下面就是主程序的所有代码:
from tkinter import * # 导入tkinter中的所有方法
from tkinter import filedialog,messagebox # 导入文件操作模块和消息框,这两个模块需要单独导入
from tkinter.ttk import Scrollbar,Checkbutton,Label,Button # 导入ttk模块中指定的几个组件
import os # 导入系统模块
from editor_style import theme_color,ICONS # 导入editor_style文件中的两个变量
class EditorPlus(Tk): # 继承 tkinter 类
def __init__(self): # 初始化 EditPlus 类
super().__init__() # 继承父类的初始化
self._set_window_() # 设置程序运行的主窗口,即调用运行类中的 _set_window 方法
self._create_menu_bar_() # 创建菜单组件
self.icon_res=[]
self.file_name = None
self._create_shortcut_bar_()
self._create_body_()
self._create_right_popup_menu()
def _set_window_(self):
# 初始化窗口
self.title('EditorPlus') # 设置窗口名称
scn_width, scn_height = self.maxsize() # 获得程序运行设备显示器的分辨率
wm_val = '750x450+{}+{}'.format((scn_width - 750) // 2, (scn_height - 450) // 2)
self.geometry(wm_val) # 设备窗口大小并居中显示
self.iconbitmap('img/editor.ico') # 加载窗口左上方图标
self.protocol('WM_DELETE_WINDOW', self.exit_editor) # 设置窗口关闭提醒,调用 exit_editor 方法
def _create_menu_bar_(self):
''' Create Menu Bar :return:No'''
# 创建菜单栏
menu_bar = Menu(self) # 继承 Tk 的 Menu 对象 (一级菜单)
# 文件菜单
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_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_editor)
menu_bar.add_cascade(label="文件", menu=file_menu) # 创建文件菜单栏
# 编辑菜单
edit_menu = Menu(menu_bar, tearoff=0)
edit_menu.add_command(label='撤销', accelerator="Ctrl+Z", command=lambda: self.handle_menu_action('撤销'))
edit_menu.add_command(label='恢复', accelerator="Ctrl+Y", command=lambda: self.handle_menu_action('恢复'))
edit_menu.add_separator()
edit_menu.add_command(label='剪切', accelerator="Ctrl+X", command=lambda: self.handle_menu_action('剪切'))
edit_menu.add_command(label='复制', accelerator="Ctrl+C", command=lambda: self.handle_menu_action('复制'))
edit_menu.add_command(label='粘贴', accelerator="Ctrl+V", command=lambda: self.handle_menu_action('粘贴'))
edit_menu.add_separator()
edit_menu.add_command(label='查找', accelerator="Ctrl+F", command=self.find_text)
edit_menu.add_separator()
edit_menu.add_command(label='全选', accelerator="Ctrl+A", command=lambda: self.handle_menu_action('全选'))
menu_bar.add_cascade(label="编辑", menu=edit_menu)
view_menu = Menu(menu_bar, tearoff=0)
self.is_show_line_number = IntVar() # 设置变量,为后面 checkbutton类型的菜单提供变动属性
self.is_show_line_number.set(1) # 设置下面(显示行号)的初始台状态
view_menu.add_checkbutton(label='显示行号', variable=self.is_show_line_number,command=self._update_line_num)
self.highlight_line = IntVar() # 设置变量,为后面 checkbutton类型的菜单提供变动属
view_menu.add_checkbutton(label='高亮当前行', onvalue=1, offvalue=0, variable=self.highlight_line,command=self._toggle_highlight)
# 在主题菜单中再添加一个子菜单
theme_menu = Menu(menu_bar, tearoff=0)
self.theme_choise = StringVar() # 设置一个字符串变量,用来保存字符串
self.theme_choise.set('Default') # 初始化这个字符串的值
for k in sorted(theme_color): # 这里的theme_color 就是之前从editor_style导入的字典变量
# 验证主题菜单的值,把字符串传到下面的 change_theme 方法中
theme_menu.add_radiobutton(label=k, variable=self.theme_choise,command=self.change_theme)
view_menu.add_cascade(label='主题', menu=theme_menu) # 这里添加菜单指定的对象为 theme_menu
menu_bar.add_cascade(label="视图", menu=view_menu) # 这里添加视图菜单指定的对象为 view_menu
# 一级菜单 “关于” 菜单,
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)
self['menu'] = menu_bar # 将菜单在程序的主窗口显示
def show_messagebox(self, type): # 这里是关于、帮助菜单点击时间弹出的消息框
if type == '帮助': # 判断点击选择的是帮助菜单
messagebox.showinfo('帮助', '这是帮助文档!', icon='question') # icon ='question' 消息框显示系统内置图标 question
else:
messagebox.showinfo('关于', 'EditorPlus_V0.1')
def change_theme(self): # 更新主题设置方法
selected_theme= self.theme_choise.get() # 获取用户选择的主题
fg_bg = theme_color.get(selected_theme) # 获取editor_style文件中的theme_color的对应值
fg_color,bg_color = fg_bg.split('.') # 以 “.” 为分隔符,获取两个字符串,分别赋值给两个变量 ('Default': '#000000.#FFFFFF')
self.content_text.config(bg=bg_color,fg=fg_color) # 设置 Text 的前景色和背景色
def _toggle_highlight(self): # 高亮显示方法
if self.highlight_line.get():
self.content_text.tag_remove('active_line',1.0,'end')
self.content_text.tag_add('active_line','insert linestart','insert lineend+1c')
self.content_text.after(200,self._toggle_highlight)
else:
self.content_text.tag_remove('active_line',1.0,'end')
def _update_line_num(self): # 显示行号的方法
if self.is_show_line_number.get():
row,col=self.content_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='disabled')
else:
self.line_number_bar.config(state='normal')
self.line_number_bar.delete('1.0', 'end')
self.line_number_bar.config(state='disabled')
def _create_shortcut_bar_(self):
# 创建快捷菜单
shortcut_bar = Frame(self, height=25, background='#20b2aa') # 创建一个 Frame 放置快捷按钮
shortcut_bar.pack(fill='x') # 横向填充( x 轴方向 )
for icon in ICONS: # 遍历循环设置快捷按钮
tool_icon = PhotoImage(file='img/{}.gif'.format(icon))
tool_btn = Button(shortcut_bar, image=tool_icon, command=self._shortcut_action(icon))
tool_btn.pack(side='left')
self.icon_res.append(tool_icon)
def _create_body_(self):
# 创建行号栏(takefocus 屏蔽焦点 )
self.line_number_bar = Text(self, width=4, padx=3, takefocus=0, border=0, background='#f0e68c',
state='disabled')
self.line_number_bar.pack(side='left', fill='y')
# 创建文本输入框
self.content_text = Text(self, wrap='word')
self.content_text.pack(expand='yes', fill='both')
# 创建滚动条
scroll_baar = Scrollbar(self.content_text)
scroll_baar['command'] = self.content_text.yview
self.content_text['yscrollcommand'] = scroll_baar.set
scroll_baar.pack(side='right', fill='y')
# 设置快捷按键,绑定方法
self.content_text.bind('<Control-N>', self.new_file)
self.content_text.bind('<Control-n>', self.new_file)
self.content_text.bind('<Control-O>', self.open_file)
self.content_text.bind('<Control-o>', self.open_file)
self.content_text.bind('<Control-S>', self.save)
self.content_text.bind('<Control-s>', self.save)
self.content_text.bind('<Control-Shift-S>', self.save_as)
self.content_text.bind('<Control-Shift-s>', self.save_as)
self.content_text.bind('<Control-F>', self.find_text)
self.content_text.bind('<Control-f>', self.find_text)
self.content_text.bind('<Alt-F4>', self.exit_editor)
self.content_text.bind("<Any-KeyPress>", lambda e: self._update_line_num())
self.content_text.tag_configure('active_line', background='#EEEEE0')
self.bind_all('<KeyPress-F1>', lambda e: self.show_messagebox('帮助'))
def _create_right_popup_menu(self):
popup_menu = Menu(self.content_text, tearoff=0)
for it1, it2 in zip(['剪切', '复制', '粘贴', '撤销', '恢复'], ['cut', 'copy', 'paste', 'undo', 'redo']):
popup_menu.add_command(label=it1, compound='left', command=self._shortcut_action(it2))
popup_menu.add_separator()
popup_menu.add_command(label='全选', command=lambda: self.handle_menu_action('全选'))
self.content_text.bind("<Button-3>", lambda event: popup_menu.tk_popup(event.x_root, event.y_root))
def _shortcut_action(self, type):
def handle():
if type == "new_file":
self.new_file()
elif type == 'open_file':
self.open_file()
elif type == 'save':
self.save()
elif type == 'cut':
self.handle_menu_action('剪切')
elif type == 'copy':
self.handle_menu_action('复制')
elif type == 'paste':
self.handle_menu_action('粘贴')
elif type == 'undo':
self.handle_menu_action('撤销')
elif type == 'redo':
self.handle_menu_action('恢复')
elif type == 'find_text':
self.find_text()
if type != 'copy' and type != 'save':
self._update_line_num()
return handle
def new_file(self, event=None):
# 新建功能模块函数
self.title('New - EditorPlus')
self.content_text.delete(1.0, END)
self.content_name = None
def open_file(self, event=None):
# 创建打开菜单模块函数
input_file = filedialog.askopenfilename(
filetypes=[('所有文件', '*.*'), ('文本文件', '*.txt')] # 弹出文件对话框,设置选择文件的类型
)
if input_file: # 如果用户选择了文本,则进行打开
# print(input_file)
self.title('{} - EditorPlus'.format(os.path.basename(input_file))) # 以文件的名称进行窗口标题的命名
self.file_name = input_file # 将这个打开的文件对象命名为其原来文件的名称
self.content_text.delete(1.0, END) # 删除当前文本内容中的数据
with open(input_file, 'r') as file:
self.content_text.insert(1.0, file.read()) # 将要打开文件中的数据写入到文本内容中
def save(self, event=None): # (保存方法)
if not self.file_name: # 这里就体现出来之前设置的 self.file_name 全局变量
self.save_as() # 没有文件名称的调用另存为
else:
self._write_to_file(self.file_name) # 有文件名称的直接写入文件(保存)
def save_as(self, event=None): # 另存为方法
input_file = filedialog.asksaveasfilename( # 弹文件保存对话框
filetypes=[('所有文件', '*.*'), ('文本文件', '*.txt')]
)
if input_file: # 对用户操作进行判定
self.file_name = input_file # 设置文件名称
self._write_to_file(self.file_name) # 写入本地
def _write_to_file(self, file_name):
try:
content = self.content_text.get(1.0, "end") # 获取文本框中的所有数据
with open(file_name, 'w') as file:
file.write(content) # 将数据写入到本地文件中
self.title('%s - EditorPlus' % os.path.basename(self.file_name)) # 重置一下当前窗口名称
except IOError:
messagebox.showwarning('保存', '保存失败!') # 如果保存失败的话,弹出对话框提示
def handle_menu_action(self, action_type):
if action_type == '撤销':
self.content_text.event_generate('<<Undo>>')
elif action_type == '恢复':
self.content_text.event_generate('<<Redo>>')
elif action_type == '剪切':
self.content_text.event_generate('<<Cut>>')
elif action_type == '复制':
self.content_text.event_generate('<<Copy>>')
elif action_type == '粘贴':
self.content_text.event_generate('<<Paste>>')
elif action_type == '全选':
self.content_text.event_generate('<<SelectAll>>')
if action_type != "复制":
self._update_line_num()
def find_text(self):
search_toplevel =Toplevel(self)
search_toplevel.title('查找文本')
search_toplevel.transient(self) # 设置查找窗口显示在程序顶层
search_toplevel.geometry('340x60+700+500')
search_toplevel.resizable(False,False)
Label(search_toplevel,text='查找全部').grid(row=0,column=0,sticky='e')
search_entry_widget = Entry(search_toplevel,width=25)
search_entry_widget.grid(row=0,column=1,padx=2,pady=2,sticky='we')
search_entry_widget.focus_set()
ignore_case_value = IntVar()
Checkbutton(search_toplevel,text='忽略大小写',variable=ignore_case_value).grid(
row=1, column=1, padx=2, pady=2, sticky='e'
)
Button(search_toplevel,text='查找',command=lambda :self.search_result(
search_entry_widget.get(), ignore_case_value.get(),search_toplevel,search_entry_widget
)).grid(row=0, column=2, padx=2, pady=2, sticky='ew')
def close_search_window():
self.content_text.tag_remove('match','1.0',"end")
search_toplevel.destroy()
search_toplevel.protocol('WM_DELETE_WINDOW',close_search_window)
return "break"
def search_result(self,key,ignore_case,search_toplevel,search_box):
self.content_text.tag_remove('match','1.0','end')
matches_found = 0
if key:
start_pos = '1.0'
while True:
start_pos = self.content_text.search(key,start_pos,nocase=ignore_case,stopindex='end')
if not start_pos:
break
end_pos = '{}={}c'.format(start_pos,len(key))
self.content_text.tag_add('match',start_pos,end_pos)
matches_found += 1
start_pos = end_pos
self.content_text.tag_config('match',foreground='red',background='yellow')
search_box.focus_set()
search_toplevel.title("发现%d个匹配的"%matches_found)
def exit_editor(self):
# 退出验证
if messagebox.askokcancel("退出?",'确定退出吗?'): # 设置文本提醒,弹出消息框
self.destroy() # 满足条件的话,主窗口退出
if __name__ == '__main__':
app = EditorPlus() # 实例化类
app.mainloop() # 保持显示窗口