基于 tkinter 的简易IDE/文本编辑器。(参考)
功能:
- 菜单栏
- 文件操作功能:新建、保存、另存为
- 文本操作功能:复制、剪切、全选、撤销、重做
- 快捷键
- 主题切换
- 文本右键功能
- 文本高亮
- 搜索
- 行号
图标:、
、
、
、
、
、
、
、
预览:
配置文件:config.ini
[window] program_name = UINOTE Editor [icon] newfile = ico/newfile.png openfile = ico/open.png savefile = ico/save.png undofile = ico/undo.png redofile = ico/redo.png cutfile = ico/cut.png copyfile = ico/copy.png pastefile = ico/paste.png findfile = ico/find.png [textension] aa = save bb = D1D4D1 [theme] color_schemes = { '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'} select_themes = Default [common] bgstartsetting = False bgdir = bg bgname = default_bg.png
代码:app.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- from tkinter.filedialog import * from tkinter import * from pathlib import Path from PIL import Image, ImageTk import tkinter.messagebox as tmb import shutil from configparser import ConfigParser import os.path d = os.path.dirname(__file__) file_name = None def exitText(event=None): # 保存 不保存 if tmb.askokcancel("Quit?", "Really quit?"): ## 退出前自动保存文件 save() root.destroy() def cut(): '''剪切''' content_text.event_generate("<<Cut>>") def copy(): '''复制''' content_text.event_generate("<<Copy>>") def paste(): '''粘贴''' content_text.event_generate("<<Paste>>") def undo(event=None): '''向前撤销''' content_text.event_generate("<<Undo>>") return 'break' def redo(event=None): '''向后返回''' content_text.event_generate("<<Redo>>") return 'break' def select_all(event=None): '''全选''' content_text.tag_add('sel', '1.0', 'end') return 'break' def find_text(event=None): '''搜索字段''' search_toplevel = Toplevel(root) search_toplevel.title('Find Text') search_toplevel.transient(root) search_toplevel.resizable(False, False) Label(search_toplevel, text="Find All:").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='IgnoreCase', variable=ignore_case_value).grid(row=1, column=1, sticky='e', padx=2, pady=2) Button(search_toplevel, text="Find All", underline=0, command=lambda: search_output(search_entry_widget.get( ), ignore_case_value.get(), content_text, search_toplevel, search_entry_widget)).grid( row=0, column=2, sticky='ew', padx=2, pady=2) def close_search_window(): '''关闭选框''' content_text.tag_remove('match', '1.0', 'end') search_toplevel.destroy() search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window) return 'break' def search_output(needle, if_ignore_case, content_text, search_toplevel, search_box): '''匹配文字''' content_text.tag_remove('match', '1.0', 'end') matches_found = 0 if needle: start_pos = '1.0' while True: start_pos = content_text.search(needle, start_pos, nocase=if_ignore_case, stopindex='end') if not start_pos: break end_pos = '{}+{}c'.format(start_pos, len(needle)) content_text.tag_add('match', start_pos, end_pos) matches_found += 1 start_pos = end_pos content_text.tag_config('match', foreground='red', background='yellow') search_box.focus_set() search_toplevel.title('{} matches found'.format(matches_found)) def open_file(event=None): '''打开文件''' input_file_name = askopenfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt")]) if input_file_name: global file_name file_name = input_file_name root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) content_text.delete(1.0, 'end') with open(file_name) as _file: content_text.insert(1.0, _file.read()) update_line_numbers() def save(event=None): '''save文件''' print('保存') global file_name print('文件路径', file_name) try: if not file_name: print('新建,未打开文件') save_as() else: print('保存,已打开文件') write_to_file(file_name) except: pass tmb.showerror('异常', '文件保存异常,请重试!') return 'break' def save_as(event=None): '''另存为''' print('另存为') input_file_name = asksaveasfilename(defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Documents", "*.txt") ]) if input_file_name: global file_name file_name = input_file_name write_to_file(file_name) root.title('{} - {}'.format(os.path.basename(file_name), PROGRAM_NAME)) return 'break' def write_to_file(file_name): '''写入文件''' try: content = content_text.get(1.0, 'end') with open(file_name, 'w') as the_file: the_file.write(content) except IOError: pass def new_file(event=None): '''新建文件''' root.title("Untitled") global file_name file_name = None content_text.delete(1.0, 'end') def display_about_messagebox(event=None): '''about messagebox''' tmb.showinfo( "About", "{}{}".format(PROGRAM_NAME, "\nTkinter GUIApplication\n Development Blueprints")) def display_help_messagebox(event=None): '''help messagebox''' tmb.showinfo("Help", "帮助文档: \nTkinter GUI Application\n Development Blueprints", icon='question') def on_content_changed(event=None): '''输入框发送改变时''' update_line_numbers() update_cursor_info_bar() def get_line_numbers(): '''获取行号''' output = '' if show_line_number.get(): row, col = content_text.index("end").split('.') for i in range(1, int(row)): output += str(i) + '\n' return output def update_line_numbers(event=None): '''更新行号''' line_numbers = get_line_numbers() line_number_bar.config(state='normal') line_number_bar.delete('1.0', 'end') line_number_bar.insert('1.0', line_numbers) line_number_bar.config(state='disabled') def highlight_line(interval=100): '''高亮显示当前行''' content_text.tag_remove("active_line", 1.0, "end") content_text.tag_add("active_line", "insert linestart", "insert lineend+1c") content_text.after(interval, toggle_highlight) def undo_highlight(): '''取消高亮''' content_text.tag_remove("active_line", 1.0, "end") def toggle_highlight(event=None): '''高亮切换''' if to_highlight_line.get(): highlight_line() else: undo_highlight() def show_cursor_info_bar(): '''显示底部行列信息''' show_cursor_info_checked = show_cursor_info.get() if show_cursor_info_checked: cursor_info_bar.pack(expand='no', fill=None, side='right', anchor='se') else: cursor_info_bar.pack_forget() def update_cursor_info_bar(event=None): '''更新底部行列信息''' row, col = content_text.index(INSERT).split('.') line_num, col_num = str(int(row)), str(int(col) + 1) # col starts at 0 infotext = "Line: {0} | Column: {1}".format(line_num, col_num) cursor_info_bar.config(text=infotext) def change_theme(event=None): '''修改主题''' selected_theme = themes_choices.get() # 写入配置文件 conf.set('theme', 'select_themes', selected_theme) saveConf() fg_bg_colors = color_schemes.get(selected_theme) foreground_color, background_color = fg_bg_colors.split('.') content_text.config(background=background_color, fg=foreground_color) def select_background(event=None): '''选择背景图片,已经取消,因为tkinter透明度不好用''' conf.set('common', 'bgstartsetting', 'true') saveConf() bgStartChecked = bgStart.get() print('[*] 是否启用背景设置', bgStartChecked) if bgStartChecked: print('弹出选择文件框') imgSelect = askopenfilename(defaultextension=".txt", filetypes=[(".png", ".jpg"), ("Text Documents", "*.txt")]) if imgSelect: global imgSelectName imgSelectName = imgSelect print(imgSelectName) # C:/Users/hz/Desktop/我的文件/我的图片/11.jpg # 将图片拷贝到当前文件夹 修改配置参数 shutil.copyfile(imgSelectName, Path(d) / bgDir / bgName) else: print('隐藏图片') print('使用主题选择的颜色') def set_background(): '''设置背景''' pass def readSettings(): '''读取配置文件''' global PROGRAM_NAME PROGRAM_NAME = conf.get('window', 'PROGRAM_NAME') global newFile newFile = conf.get('icon', 'newFile') global openFile openFile = conf.get('icon', 'openFile') global saveFile saveFile = conf.get('icon', 'saveFile') global undoFile undoFile = conf.get('icon', 'undoFile') global redoFile redoFile = conf.get('icon', 'redoFile') global cutFile cutFile = conf.get('icon', 'cutFile') global copyFile copyFile = conf.get('icon', 'copyFile') global pasteFile pasteFile = conf.get('icon', 'pasteFile') global findFile findFile = conf.get('icon', 'findFile') global color_schemes color_schemes_str = conf.get('theme', 'color_schemes') color_schemes = eval(color_schemes_str) global select_themes select_themes = conf.get('theme', 'select_themes') global bgstartsetting bgstartsetting = conf.get('common', 'bgstartsetting') global bgDir bgDir = conf.get('common', 'bgdir') global bgName bgName = conf.get('common', 'bgname') def saveConf(): '''保存配置文件''' fp = Path(d) / 'config.ini' with open(fp, 'w') as fw: conf.write(fw) def show_popup_menu(event): '''显示右键菜单''' popup_menu.tk_popup(event.x_root, event.y_root) if __name__ == '__main__': ## 配置文件路径 fp = Path(d) / 'config.ini' print('config.ini', fp) ## 实例化 conf = ConfigParser() ## 读取配置文件 conf.read(fp, encoding='utf8') ## 配置文件编码是utf-8 ## 读取里面的内容 readSettings() ## 开始窗体部分 root = Tk() # 引入菜单 menu_bar = Menu(root) file_menu = Menu(menu_bar, tearoff=0) edit_menu = Menu(menu_bar, tearoff=0) view_menu = Menu(menu_bar, tearoff=0) help_menu = Menu(menu_bar, tearoff=0) themes_menu = Menu(menu_bar, tearoff=0) # 添加菜单列表 menu_bar.add_cascade(label='File', menu=file_menu) menu_bar.add_cascade(label='Edit', menu=edit_menu) menu_bar.add_cascade(label='View', menu=view_menu) menu_bar.add_cascade(label='Help', menu=help_menu) # File菜单 imageNew = ImageTk.PhotoImage(Image.open(Path(d) / newFile)) file_menu.add_command(label='New', accelerator='Ctrl+N', compound='left', image=imageNew, command=lambda: new_file()) imageOpen = ImageTk.PhotoImage(Image.open(Path(d) / openFile)) file_menu.add_command(label='Open', accelerator='Ctrl+O', compound='left', image=imageOpen, command=lambda: open_file()) imageSave = ImageTk.PhotoImage(Image.open(Path(d) / saveFile)) file_menu.add_command(label='Save', accelerator='Ctrl+S', compound='left', image=imageSave, command=lambda: save()) file_menu.add_command(label='Save as', accelerator='Ctrl+shift+s', compound='left', image='', command=lambda: save_as()) file_menu.add_separator() file_menu.add_command(label='Exit', accelerator='Alt+F4', compound='left', image='', command=lambda: exitText()) # Edit菜单 imageUndo = ImageTk.PhotoImage(Image.open(Path(d) / undoFile)) edit_menu.add_command(label='Undo', accelerator='Ctrl+Z', compound='left', image=imageUndo, command=lambda: undo()) imageRedo = ImageTk.PhotoImage(Image.open(Path(d) / undoFile)) edit_menu.add_command(label='Redo', accelerator='Ctrl+Y', compound='left', image=imageRedo, command=lambda: redo()) imageCut = ImageTk.PhotoImage(Image.open(Path(d) / cutFile)) edit_menu.add_command(label='Cut', accelerator='Ctrl+X', compound='left', image=imageCut, command=lambda: cut()) imageCopy = ImageTk.PhotoImage(Image.open(Path(d) / copyFile)) edit_menu.add_command(label='Copy', accelerator='Ctrl+C', compound='left', image=imageCopy, command=lambda: copy()) imagePaste = ImageTk.PhotoImage(Image.open(Path(d) / pasteFile)) edit_menu.add_command(label='Paste', accelerator='Ctrl+V', compound='left', image=imagePaste, command=lambda: paste()) edit_menu.add_separator() edit_menu.add_command(label='Find', accelerator='Ctrl+F', compound='left', image='', command=lambda: find_text()) edit_menu.add_separator() edit_menu.add_command(label='Select All', accelerator='Ctrl+A', compound='left', image='', command=lambda: select_all()) # Help菜单 help_menu.add_command(label='About', accelerator='', compound='left', image='', command=lambda: display_about_messagebox()) help_menu.add_command(label='Help', accelerator='', compound='left', image='', command=lambda: display_help_messagebox()) # 添加横向Frame shortcut_bar = Frame(root, height=25, background='light seagreen') shortcut_bar.pack(expand='no', fill='x') # 添加纵向Frame line_number_bar = Text(root, width=4, padx=3, takefocus=0, border=0, background='khaki', state='disabled', wrap='none') line_number_bar.pack(side='left', fill='y') # 添加文本框 ## undo True 可以无限撤销 False 不能撤销 content_text = Text(root, wrap='word', undo=True) ## 给文本框绑定鼠标事件 同时绑定执行函数 content_text.bind('<Control-y>', redo) # handling Ctrl + small-case y content_text.bind('<Control-Y>', redo) # handling Ctrl + upper-case Y content_text.bind('<Control-a>', select_all) # handling Ctrl + upper-case a content_text.bind('<Control-A>', select_all) # handling Ctrl + upper-case A content_text.bind('<Control-f>', find_text) #ctrl + f content_text.bind('<Control-F>', find_text) #ctrl + F content_text.bind('<Control-N>', new_file) #ctrl + N content_text.bind('<Control-n>', new_file) #ctrl + n content_text.bind('<Control-O>', open_file) #ctrl + O content_text.bind('<Control-o>', open_file) #ctrl + o content_text.bind('<Control-S>', save) #ctrl + S content_text.bind('<Control-s>', save) #ctrl + s content_text.bind('<Control-Shift-S>', save_as) #ctrl + shift + S content_text.bind('<Control-Shift-s>', save_as) #ctrl + sgift + s content_text.bind('<KeyPress-F1>', display_help_messagebox) content_text.bind('<Any-KeyPress>', on_content_changed) ## 切换行号 content_text.bind('<Button-1>', on_content_changed) content_text.tag_configure('active_line', background='ivory2') # 增加右键功能 popup_menu = Menu(content_text) popup_menu.add_command(label='Cut', compound='left', image=imageCut, command=lambda: cut()) popup_menu.add_command(label='Copy', compound='left', image=imageCopy, command=lambda: copy()) popup_menu.add_command(label='Paste', compound='left', image=imagePaste, command=lambda: paste()) popup_menu.add_command(label='Undo', compound='left', image=imageUndo, command=lambda: undo()) popup_menu.add_command(label='Redo', compound='left', image=imageRedo, command=lambda: redo()) popup_menu.add_separator() popup_menu.add_command(label='Select All', underline=7, command=select_all) ## 文本框绑定右键事件 content_text.bind('<Button-2>', show_popup_menu) ## 显示文本框 content_text.pack(expand='yes', fill='both') ## 增加滚动条 scroll_bar = Scrollbar(content_text) content_text.configure(yscrollcommand=scroll_bar.set) scroll_bar.config(command=content_text.yview) scroll_bar.pack(side='right', fill='y') # views 添加下拉选项 show_line_number = IntVar() show_line_number.set(1) view_menu.add_checkbutton(label="Show Line Number", variable=show_line_number, command=update_line_numbers) show_cursor_info = IntVar() show_cursor_info.set(1) view_menu.add_checkbutton(label="Show Cursor Location at Bottom", variable=show_cursor_info, command=show_cursor_info_bar) to_highlight_line = BooleanVar() to_highlight_line.set(1) view_menu.add_checkbutton(label="HighLight Current Line", variable=to_highlight_line, command=toggle_highlight) toggle_highlight() ## 增加分割线 view_menu.add_cascade(label="Themes", menu=themes_menu) # theme ## 增加theme菜单 themes_choices = StringVar() themes_choices.set(select_themes) for k, themes_choice in color_schemes.items(): themes_menu.add_radiobutton(label=k, variable=themes_choices, command=change_theme, value=k) # 添加设置背景功能 # bgStart = IntVar() # bgStart.set(0) # view_menu.add_checkbutton(label="Set Background", variable=bgStart, command=select_background) ## 添加快捷图标 icons = [(newFile, 'new_file'), (openFile, 'open_file'), (saveFile, 'save'), (cutFile, 'cut'), (copyFile, 'copy'), (pasteFile, 'paste'), (undoFile, 'undo'), (redoFile, 'redo'), (findFile, 'find_text')] for i, icon in enumerate(icons): tool_bar_icon = ImageTk.PhotoImage(Image.open(Path(d) / icon[0])) # cmd = eval(icon) tool_bar = Button(shortcut_bar, image=tool_bar_icon, command=eval(icon[1])) tool_bar.image = tool_bar_icon tool_bar.pack(side='left') ## 添加底部显示行号 cursor_info_bar = Label(content_text, text='Line: 1 | Column: 1') cursor_info_bar.pack(expand=NO, fill=None, side=RIGHT, anchor='se') ## 配置menu root.config(menu=menu_bar) ## 设置rootname root.title(PROGRAM_NAME) ## 设置最小size root.minsize(800, 600) ## 设置居中显示 root.geometry('%dx%d+%d+%d' % (800, 600, (root.winfo_screenwidth() - 1000) / 2, (root.winfo_screenheight() - 600) / 2)) ## 配置默认主题 change_theme() mainloop()