上一篇:Python 使用tkinter复刻Windows记事本UI和菜单功能(二)-CSDN博客
下一篇:敬请耐心等待,如发现BUG以及建议,请在评论区发表,谢谢!
本文章完成了记事本的新建、保存、另存、打开文件、状态栏显隐等部分基本功能,还设计还原了页面设置UI。很抱歉现阶段我没有精力再去完善和优化这个项目了,不止是能力受限,还耽搁太多时间了。
运行结果
代码实例
string = \
"""复刻Windows记事本BUG:1、快捷键:Ctrl+O 打开文件实现时发现光标处会插入换行'\\n'(现在我仍未知是否是为解释器BUG)未实现:1、文件的新窗口无法实现(未使用线程)2、无法实现单击菜单栏显示菜单项后与键盘交互(非快捷键),因为Menu无法与bind捆绑事件及交互3、文件的页面设置的具体功能交互还没完成,只完成UI和交互框架4、文件的打印还没实现(我不知道怎么连接外设)4、除了文件以外的菜单还没实现
"""# 通配符
__all__ = ['main']import tkinter as tk
from tkinter import ttk
from tkinter import font
import tkinter.messagebox as tkmb
import tkinter.filedialog as tkfd# 全局变量
# 初始化
FONT_SIZE = 12 # 默认字体大小
# 永久保存变量
PAPER_VAR = 'A4'
PAPER_ORIENT = 1
LEFT_VAR = 20
RIGHT_VAR = 20
TOP_VAR = 25
BOTTOM_VAR = 25
HEADER_VAR = None
FOOTER_VAR = None# (打印)页面设置UI
class PageSetup:orientVar = NoneleftVar = NonerightVar = NonetopVar = NonebottomVar = NoneheaderVar = NonefooterVar = None# (打印)页面设置@classmethoddef pageSetup(cls):set = tk.Toplevel() # 页面设置顶级窗口set.title('页面设置') # 窗口标题set.geometry(f'622x418+{set.winfo_screenwidth()//4+60}+{set.winfo_screenheight()//8+52}')set.focus_set() # 设置窗口焦点set.resizable(0, 0) # 禁止窗口的放大set.grab_set() # 锁定父窗口# 窗口布局# (打印)纸张选择paperFrame = ttk.LabelFrame(set, text='纸张', padding=(191, 38))paperFrame.place(x=14, y=16)tk.Label(paperFrame).pack()# 大小size = tk.Label(set, text='大小(Z):')size.place(x=24, y=48)# 来源source = tk.Label(set, text='来源(S):', state='disable')source.place(x=24, y=92)# 纸张大小下拉菜单# 修改 OptionMenu 的样式style = ttk.Style()style.configure("my.TMenubutton", background='#DCDCDC', width=35)# 纸张大小下拉菜单paperVar = tk.StringVar(value=PAPER_VAR)paperOption = [paperVar.get(), f'A3{" "*55}', 'A4', 'A5', 'B4 (JIS)', 'B5 (JIS)', 'Executive', 'Statement', 'Tabloid', '法律专用纸', '信纸']paperMenu = ttk.OptionMenu(set, paperVar, *paperOption, style="my.TMenubutton", command=cls.paperOption)paperMenu.place(x=110, y=46)# 默认选择(打印纸张)cls.paperOption(paperVar.get())# 纸张来源下拉菜单# 修改 OptionMenu 的样式style.configure("my2.TMenubutton", background='#C0C0C0', width=35)# 纸张大小下拉菜单cls.sourceVar = tk.StringVar()sourceOption = [None, f'选项1{" " * 55}', '选项2', '选项3']sourceMenu = ttk.OptionMenu(set, cls.sourceVar, *sourceOption, style="my2.TMenubutton")sourceMenu.config(state="disabled")sourceMenu.place(x=110, y=90)# (打印纸张)方向选择orientFrame = ttk.LabelFrame(set, text='方向', padding=(50, 38))orientFrame.place(x=14, y=147)tk.Label(orientFrame).pack()cls.orientVar = tk.IntVar(value=PAPER_ORIENT)# (打印纸张)纵向lengthways = ttk.Radiobutton(set, text='纵向(O)', variable=cls.orientVar, value=1, command=cls.orientOption)lengthways.place(x=26, y=180)# (打印纸张)横向crosswise = ttk.Radiobutton(set, text='横向(A)', variable=cls.orientVar, value=2, command=cls.orientOption)crosswise.place(x=26, y=220)# 默认(打印纸张)纵向cls.orientOption()# (打印纸张)页边距(毫米)marginFrame = ttk.LabelFrame(set, text='页边距(毫米)', padding=(130, 38))marginFrame.place(x=136, y=147)tk.Label(marginFrame).pack()# 文字标签tk.Label(set, text='左(L):').place(x=148, y=180)tk.Label(set, text='右(R):').place(x=274, y=180)tk.Label(set, text='上(T):').place(x=148, y=220)tk.Label(set, text='下(B):').place(x=274, y=220)# 输入框cls.leftVar = tk.StringVar(value=LEFT_VAR)cls.rightVar = tk.StringVar(value=RIGHT_VAR)cls.topVar = tk.StringVar(value=TOP_VAR)cls.bottomVar = tk.StringVar(value=BOTTOM_VAR)leftEntry = ttk.Entry(set, width=6, textvariable=cls.leftVar)leftEntry.place(x=200, y=180)rightEntry = ttk.Entry(set, width=6, textvariable=cls.rightVar)rightEntry.place(x=326, y=180)topEntry = ttk.Entry(set, width=6, textvariable=cls.topVar)topEntry.place(x=200, y=220)bottomEntry = ttk.Entry(set, width=6, textvariable=cls.bottomVar)bottomEntry.place(x=326, y=220)# (打印纸张)预览previewFrame = ttk.LabelFrame(set, text='预览', padding=(88, 147))previewFrame.place(x=420, y=16)tk.Label(previewFrame).pack()image = tk.PhotoImage(file='.\\..\\photo\\微信余额.png')tk.Label(set, image=image).place(x=421, y=37)cls.headerVar = tk.StringVar(value=HEADER_VAR)cls.footerVar = tk.StringVar(value=FOOTER_VAR)# 页眉tk.Label(set, text='页眉(H):').place(x=14, y=288)headerEntry = ttk.Entry(set, width=42, textvariable=cls.headerVar)headerEntry.place(x=106, y=288)# 页脚tk.Label(set, text='页脚(F):').place(x=14, y=330)footerEntry = ttk.Entry(set, width=42, textvariable=cls.footerVar)footerEntry.place(x=106, y=330)# 页眉页脚输入值网页详情介绍# 修改 Button 的样式# style.configure("my.TButton", width=6, font=("Arial", 10, 'underline'), foreground="blue")# ttk.Button(set, text='输入值', style='my.TButton').place(x=106, y=360)headerFooterWeb = tk.Label(set, text='输入值', relief='flat', foreground="blue", font=("Arial", 10, 'underline'))headerFooterWeb.place(x=106, y=360)# 捆绑跳转网页事件import webbrowserheaderFooterWeb.bind('<Button-1>', lambda event: webbrowser.open('https://support.microsoft.com/zh-cn/windows/更改记事本中的页眉和页脚命令-c1b0e27b-497d-c478-c4c1-0da491cac148'))# 确定# 修改 Button 的样式style.map("my.TButton", background=[('!active', '!disabled', '#00BFFF')])confirm = ttk.Button(set, text='确定', width=13, style='my.TButton', command=lambda: cls.confirmCancel('确定', set))confirm.place(x=394, y=372)# 取消cancel = ttk.Button(set, text='取消', width=13, command=lambda: cls.confirmCancel('取消', set))cancel.place(x=506, y=372)# 捆绑获取输入框的数据事件set.bind('<KeyRelease>', cls.getEntry)set.mainloop() # 窗口循环# (打印纸张)页面设置确定与取消@classmethoddef confirmCancel(cls, option, win=None):print(option)if option == '确定':# 修改的数值保存到文件# 发出警告声音win.bell()pass# 关闭当前窗口win.destroy()# 获取输入框的数据@classmethoddef getEntry(cls, event=None):# (打印纸张)设置页边距(毫米)print('页边距:',cls.leftVar.get(),cls.rightVar.get(),cls.topVar.get(),cls.bottomVar.get())global LEFT_VAR, RIGHT_VAR, TOP_VAR, BOTTOM_VARLEFT_VAR = cls.leftVar.get()RIGHT_VAR = cls.rightVar.get()TOP_VAR = cls.topVar.get()BOTTOM_VAR = cls.bottomVar.get()# (打印纸张)设置页眉页脚print('页眉/页脚:',cls.headerVar.get(),cls.footerVar.get())global HEADER_VAR, FOOTER_VARHEADER_VAR = cls.headerVar.get()FOOTER_VAR = cls.footerVar.get()# (打印纸张)方向选择@classmethoddef orientOption(cls):global PAPER_ORIENTPAPER_ORIENT = cls.orientVar.get()# (打印纸张)方向选择if PAPER_ORIENT == 1:print('方向:纵向')elif PAPER_ORIENT == 2:print('方向:横向')# 纸张选择@classmethoddef paperOption(cls, option):global PAPER_VARPAPER_VAR = option# 纸张设置if option == 'A3':print('大小:A3')elif option == 'A4':print('大小:A4')elif option == 'A5':print('大小:A5')elif option == 'B4 (JIS)':print('大小:B4 (JIS)')elif option == 'B5 (JIS)':print('大小:B5 (JIS)')elif option == 'Executive':print('大小:Executive')elif option == 'Statement':print('大小:Statement')elif option == 'Tabloid':print('大小:Tabloid')elif option == '法律专用纸':print('大小:法律专用纸')elif option == '信纸':print('大小:信纸')# 文本编辑器窗口UI
class WindowsUI(PageSetup):readText = '' # 读取文本数据@classmethoddef __init__(cls, base):cls.base = base# cls.base = tk.Tk() # 新建一个窗口cls.base.title('无标题 - 记事本') # 窗口标题cls.base.geometry(f'750x550+{cls.base.winfo_screenwidth()//4}+{cls.base.winfo_screenheight()//8}')# 创建一级菜单栏(此时为空)cls.menubar = tk.Menu(cls.base)cls.base.config(menu=cls.menubar)# 文件菜单# 创建二级菜单栏(此时为空)cls.fileMenu = tk.Menu(cls.menubar, tearoff=0)# 向一级菜单栏添加 文件 项,并与二级菜单(fileMenu)建立级联关系(从属/上下级)cls.menubar.add_cascade(label='文件(F)', menu=cls.fileMenu)# 文件的二级菜单栏添加 ... 项cls.fileMenu.add_command(label=f'新建(N){" "*28}Ctrl+N', command=cls.newText)cls.fileMenu.add_command(label=f'新窗口(W){" "*16}Ctrl+Shift+N', command=newWindow)cls.fileMenu.add_command(label=f'打开(O)...{" "*26}Ctrl+O', command=cls.openFile)cls.fileMenu.add_command(label=f'保存(S){" "*29}Ctrl+S', command=cls.saveFile)cls.fileMenu.add_command(label=f'另存为(A)...{" "*15}Ctrl+Shift+S', command=cls.saveAsFile)cls.fileMenu.add_command(label=f'页面设置(U)...', command=cls.pageSetup)cls.fileMenu.add_command(label=f'打印(P)...{" "*27}Ctrl+P')cls.fileMenu.add_command(label=f'退出(X)', command=cls.base.destroy)# 菜单之间插入分隔线cls.fileMenu.insert_separator(5)cls.fileMenu.insert_separator(8)# 编辑菜单# 创建二级菜单栏(此时为空)cls.editMenu = tk.Menu(cls.menubar, tearoff=0)# 向一级菜单栏添加 编辑 项,并与二级菜单(editMenu)建立级联关系(从属/上下级)cls.menubar.add_cascade(label='编辑(E)', menu=cls.editMenu)# 编辑的二级菜单栏添加 ... 项cls.editMenu.add_command(label=f'撤销(U){" "*26}Ctrl+Z', command=cls.repealEdit)cls.editMenu.add_command(label=f'剪切(T){" "*26}Ctrl+X')cls.editMenu.add_command(label=f'复制(C){" "*26}Ctrl+C')cls.editMenu.add_command(label=f'粘贴(V){" "*26}Ctrl+V')cls.editMenu.add_command(label=f'删除(L){" "*27}Delete')cls.editMenu.add_command(label=f'使用 Bing 搜索...{" "*14}Ctrl+E')cls.editMenu.add_command(label=f'查找(F)...{" "*25}Ctrl+F')cls.editMenu.add_command(label=f'查找上一个(N){" "*23}F3')cls.editMenu.add_command(label=f'查找下一个(V){" "*15}Shift+F3')cls.editMenu.add_command(label=f'替换(R)...{" "*23}Ctrl+H')cls.editMenu.add_command(label=f'转到(G)...{" "*23}Ctrl+G')cls.editMenu.add_command(label=f'全选(A){" "*26}Ctrl+A')cls.editMenu.add_command(label=f'时间/日期(D){" "*25}F5')# 菜单之间插入分隔线cls.editMenu.insert_separator(1)cls.editMenu.insert_separator(6)cls.editMenu.insert_separator(13)# 格式菜单# 全局变量cls.wrap = tk.BooleanVar(value=True)# 创建二级菜单栏(此时为空)cls.formatMenu = tk.Menu(cls.menubar, tearoff=0)# 向一级菜单栏添加 格式 项,并与二级菜单(formatMenu)建立级联关系(从属/上下级)cls.menubar.add_cascade(label='格式(O)', menu=cls.formatMenu)# 格式的二级菜单栏添加 ... 项cls.formatMenu.add_checkbutton(label='自动换行(W)', variable=cls.wrap, onvalue=1, offvalue=0, command=cls.setWrap)cls.formatMenu.add_command(label='字体(F)...')# 查看菜单# 全局变量cls.state = tk.BooleanVar(value=True)# 创建二级菜单栏(此时为空)cls.viewMenu = tk.Menu(cls.menubar, tearoff=0)# 向一级菜单栏添加 查看 项,并与二级菜单(checkMenu)建立级联关系(从属/上下级)cls.menubar.add_cascade(label='查看(V)', menu=cls.viewMenu)# 创建三级菜单栏(此时为空)cls.threeViewMenu = tk.Menu(cls.viewMenu, tearoff=0)# 查看的二级菜单栏添加 ... 项cls.viewMenu.add_cascade(label='缩放(Z)', menu=cls.threeViewMenu)cls.viewMenu.add_checkbutton(label='状态栏(S)', variable=cls.state, onvalue=1, offvalue=0, command=cls.setState)# 缩放的三级菜单栏添加 ... 项cls.threeViewMenu.add_command(label=f'放大(I){" " * 14}Ctrl + 加号', command=lambda: cls.FontSizeEvent('放大'))cls.threeViewMenu.add_command(label=f'缩小(O){" " * 13}Ctrl + 减号', command=lambda: cls.FontSizeEvent('缩小'))cls.threeViewMenu.add_command(label=f'恢复默认缩放{" " * 11}Ctrl+0', command=lambda: cls.FontSizeEvent('默认缩放'))# 帮助菜单# 创建二级菜单栏(此时为空)cls.helpMenu = tk.Menu(cls.menubar, tearoff=0)# 向一级菜单栏添加 帮助 项,并与二级菜单(helpMenu)建立级联关系(从属/上下级)cls.menubar.add_cascade(label='帮助(H)', menu=cls.helpMenu)# 帮助的二级菜单栏添加 ... 项cls.helpMenu.add_command(label='查看帮助(H)')cls.helpMenu.add_command(label='发送反馈(F)')cls.helpMenu.add_command(label='关于文本编辑器(A)')# 菜单之间插入分隔线cls.helpMenu.insert_separator(2)# 右键菜单# 创建二级菜单栏(此时为空)cls.rightKeyMenu = tk.Menu(cls.base, tearoff=0)# 创建三级菜单栏(此时为空)cls.threeRightMenu = tk.Menu(cls.rightKeyMenu, tearoff=0)# 右键菜单的二级菜单栏添加 ... 项cls.rightKeyMenu.add_command(label='撤销(U)')cls.rightKeyMenu.add_command(label='剪切(T)')cls.rightKeyMenu.add_command(label='复制(C)')cls.rightKeyMenu.add_command(label='粘贴(P)')cls.rightKeyMenu.add_command(label='删除(D)')cls.rightKeyMenu.add_command(label='全选(A)')cls.rightKeyMenu.add_checkbutton(label='从右到左的阅读顺序(R)')cls.rightKeyMenu.add_checkbutton(label='显示 Unicode 控制字符(S)')cls.rightKeyMenu.add_cascade(label='插入 Unicode 控制字符(I)', menu=cls.threeRightMenu)cls.rightKeyMenu.add_command(label='关闭输入法(L)')cls.rightKeyMenu.add_command(label='汉字重选(R)')cls.rightKeyMenu.add_command(label='使用 Bing 搜索(B)...')# 插入 Unicode 控制字符(I)的三级菜单栏添加 ... 项cls.threeRightMenu.add_command(label='特殊字符1')cls.threeRightMenu.add('command', label='特殊字符2')cls.threeRightMenu.insert(3, 'command', label='特殊字符3')# ...# 菜单之间插入分隔线cls.rightKeyMenu.insert_separator(1)cls.rightKeyMenu.insert_separator(6)cls.rightKeyMenu.insert_separator(8)cls.rightKeyMenu.insert_separator(12)cls.rightKeyMenu.insert_separator(15)# 捆绑鼠标右键事件cls.base.bind('<Button-3>', lambda event: cls.rightKeyEvent(event, cls.rightKeyMenu))# 底行内容显示# 底部内容框架cls.bottomFrame = tk.Frame(cls.base)cls.bottomFrame.pack(side=tk.BOTTOM, fill='both')# 状态栏框架cls.stateFrame = tk.Frame(cls.bottomFrame, borderwidth=2, relief=tk.GROOVE)cls.stateFrame.pack(side=tk.BOTTOM, fill='both')# 字符编码cls.charCodeLabel = tk.Label(cls.stateFrame, text=' UTF-8', width=16, anchor='w', borderwidth=2, relief=tk.GROOVE)cls.charCodeLabel.pack(side=tk.RIGHT)# 换行方式(回车换行)cls.CRLFlabel = tk.Label(cls.stateFrame, text=' Windows (CRLF)', width=17, anchor='w', borderwidth=2, relief=tk.GROOVE)cls.CRLFlabel.pack(side=tk.RIGHT)# 字体大小cls.fontSizeLabel = tk.Label(cls.stateFrame, text='100%', width=6, borderwidth=2, relief=tk.GROOVE)cls.fontSizeLabel.pack(side=tk.RIGHT)# 光标位置cls.locationLabel = tk.Label(cls.stateFrame, text=' 第 1 行,第 1 列', width=19, anchor='w', borderwidth=2, relief=tk.GROOVE)cls.locationLabel.pack(side=tk.RIGHT)# 空白填充(也可以按需显示内容)cls.blankLabel = tk.Label(cls.stateFrame, text='欢迎使用记事本', borderwidth=2, relief=tk.GROOVE)cls.blankLabel.pack(fill=tk.BOTH)# 右侧滚动条cls.rightScrollbar = tk.Scrollbar(cls.base, orient='vertical')cls.rightScrollbar.pack(side=tk.RIGHT, fill='both')# 底侧滚动条cls.bottomScrollbar = tk.Scrollbar(cls.bottomFrame, orient="horizontal")# 文本编辑区域cls.fontSize = tk.IntVar()cls.fontSize.set(FONT_SIZE)cls.setFont = font.Font(family='Tahoma', size=cls.fontSize.get())cls.text = tk.Text(cls.base, wrap="word", xscrollcommand=cls.bottomScrollbar.set, yscrollcommand=cls.rightScrollbar.set, font=cls.setFont)cls.text.pack(expand=True, fill='both')# 将焦点设置到Text控件上cls.text.focus_set()# 底侧滚动条与文本域关联cls.bottomScrollbar.config(command=cls.text.xview)# 右侧滚动条与文本域关联cls.rightScrollbar.config(command=cls.text.yview)# 修改窗口标题的图片cls.icon = tk.PhotoImage(file='.\\..\\photo\\记事本.png')cls.base.iconphoto(True, cls.icon)# cls.base.mainloop() # 窗口主循环# 类里的变量base = Nonetext = NonefontSize = NonesetFont = NonefontSizeLabel = NonelocationLabel = Nonewrap = NonebottomScrollbar = NonerightScrollbar = Nonestate = NonebottomFrame = NonestateFrame = NoneeditMenu = NonetextGet = None@classmethoddef mainLoop(cls):cls.base.mainloop() # 窗口主循环# 项目运行函数@classmethoddef workFunc(cls):# 捆绑事件,获取Text文本的光标位置cls.text.bind('<KeyPress>', cls.cursorPosition) # 键盘按下触发cls.text.bind('<KeyRelease>', cls.cursorPosition) # 键盘释放触发cls.text.bind('<ButtonPress>', cls.cursorPosition) # 鼠标按下触发cls.text.bind('<ButtonRelease>', cls.cursorPosition) # 鼠标释放触发# 自定义注册事件# cls.text.event_add('<<CursorEvent>>', *('<KeyPress>', '<KeyRelease>', '<ButtonPress>', '<ButtonRelease>'))# cls.text.bind('<<CursorEvent>>', cls.cursorPosition)# 自定义注册缩放事件('<<ZoomEvent>>')cls.base.event_add('<<ZoomEvent>>', *('<Control-MouseWheel>', '<Control-Key-=>', '<Control-Key-+>', '<Control-minus>', '<Control-Key-0>'))# 捆绑自定义注册缩放事件改变字体大小cls.base.bind('<<ZoomEvent>>', cls.FontSizeEvent) # 鼠标上滚缩小,下滚放大# 捆绑按键按下编辑文本事件cls.base.bind('<KeyPress>', cls.editText)# 新建文本cls.base.bind('<Control-Key-n>', cls.newText)# 创建新窗口cls.base.bind('<Control-Shift-Key-N>', newWindow)# 打开文件(BUG)cls.base.bind('<Control-Key-o>', cls.openFile)# 保存文件cls.base.bind('<Control-Key-s>', cls.saveFile)# 文件另存为cls.base.bind('<Control-Shift-Key-S>', cls.saveAsFile)# 文件打印(未完成)# 撤销编辑cls.base.bind('<Control-Key-z>', cls.repealEdit)# 撤销返编辑cls.base.bind('<Control-Shift-Key-Z>', cls.repealEdit)# 编辑撤销菜单状态cls.base.bind('<Motion>', cls.repealState)# 编辑剪切# 编辑复制# 编辑粘贴# 编辑删除# 编辑撤销菜单状态@classmethoddef repealState(cls, event=None):if len(cls.editData) > 1 and cls.editIndex != -len(cls.editData):cls.editMenu.entryconfig(0, state='active', activebackground='#4169E1')elif cls.editIndex == -len(cls.editData):cls.editMenu.entryconfig(0, state='disable', activebackground='#DCDCDC')# 撤销编辑editData = [('\n', FONT_SIZE)] # 编辑数据editIndex = -1@classmethoddef repealEdit(cls, event=None):# 菜单栏触发if not event:cls.base.event_generate('<Control-Key-z>')return# 快捷键触发elif event.keysym == 'z':# 限制条件if len(cls.editData) == 1 or -len(cls.editData) == cls.editIndex:returncls.editIndex -= 1# 快捷键触发elif event.keysym == 'Z':# 限制条件if cls.editIndex >= -1:returncls.editIndex += 1# 初始化文本cls.text.delete('1.0', 'end')# 插入上次编辑的文本数据cls.text.insert('1.0', cls.editData[cls.editIndex][0][:-1:])# 修改字体大小cls.fontSize.set(cls.editData[cls.editIndex][1])# 改变字体大小cls.setFont.config(size=cls.fontSize.get())# 改变底部显示字体大小百分比cls.fontSizeLabel.config(text='{:.0%}'.format(cls.fontSize.get() / FONT_SIZE))# 文件另存为@classmethoddef saveAsFile(cls, event=None):global openPath# 文件保存类型filetypes = [("文本文档", ".txt"), ("所有文件", ".*")]# 保存文件对话框savePath = tkfd.asksaveasfile(defaultextension=".txt", initialfile='*.txt', filetypes=filetypes)# 确定保存if savePath:# 把文本编辑的数据写入文件with open(savePath.name, 'w', encoding=savePath.encoding) as file:file.write(cls.text.get('1.0', 'end')[:-1:])# 窗口标题前去掉'*'cls.base.title(cls.base.title()[1::])# 修改标题cls.base.title(f'{savePath.name.split("/")[-1]} - 记事本')cls.readText = cls.text.get('1.0', 'end')[:-1:]openPath = savePath# 保存文件@classmethoddef saveFile(cls, event=None):global openPath# 判断文本是否编辑过if cls.base.title()[0] == '*':# 从程序打开进入的编辑if not cls.readText:# 消息对话框选择是否保存ifYes = tkmb.askyesnocancel('记事本', f'你想将更改保存到 无标题 吗?')# 如果选择是if ifYes:# 文件另存为cls.saveAsFile()# 打开文件后保存else:# 保存文本数据,写入文件with open(openPath.name, 'w', encoding=openPath.encoding) as file:file.write(cls.text.get('1.0', 'end')[:-1:])# 窗口标题前去掉'*'cls.base.title(cls.base.title()[1::])cls.readText = cls.text.get('1.0', 'end')[:-1:]# 打开文件@classmethoddef openFile(cls, keyTrigger=None):global openPathifYes = TruesavePath = True# 如果是键盘触发(必须)if keyTrigger:# Ctrl+O 触发,光标处会加入换行(不知道是否为BUG,求指教)cls.text.delete('1.0', 'end')cls.text.insert('1.0', cls.textGet[:-1:])# 判断文本是否编辑过if cls.base.title()[0] == '*':# 从程序打开进入的编辑if not cls.readText:fileName = f'你想将更改保存到 {cls.base.title().split(" ")[0][1::]} 吗?'# 从文件打开进入的编辑else:fileName = f'你想将更改保存到\n{openPath.name}\n吗?'# 在消息对话框选择是否保存当前编辑的文本ifYes = tkmb.askyesnocancel('记事本', fileName)# 确定if ifYes:# 判断保存的文件是否存在if not cls.readText:# 文件另存为cls.saveAsFile()else:# 保存文本数据,写入文件with open(openPath.name, 'w', encoding=openPath.encoding) as file:file.write(cls.text.get('1.0', 'end')[:-1:])# 窗口标题前去掉'*'cls.base.title(cls.base.title()[1::])cls.readText = cls.text.get('1.0', 'end')[:-1:]# 打开文件if ifYes != None and savePath:# 消息对话框打开文件# must be -defaultextension, -filetypes, -initialdir, -initialfile, -multiple, -parent, -title, or -typevariablefiletypes = [('文本文档', '.txt'), ('所有文件', '.*')]# 打开文件对话框cls.openPath = tkfd.askopenfile(filetypes=filetypes)# 确定打开文件if cls.openPath:openPath = cls.openPath# 窗口初始化cls.text.delete('1.0', 'end')cls.readText = ''# 打开文件读取数据插入到文本域with open(cls.openPath.name, 'r', encoding=cls.openPath.encoding) as file:for i in file:cls.text.insert('end', i)# 更新文本读取数据cls.readText += i# 修改窗口标题cls.base.title(f'{cls.openPath.name.split("/")[-1]} - 记事本')cls.base.event_generate('<Key>')# 数据更新cls.editData.clear()cls.editData.append((cls.textGet, cls.fontSize.get()))cls.editIndex = -1# 新建文本@classmethoddef newText(cls, event=None):ifYes = TruesavePath = True# 判断文本是否编辑过if cls.base.title()[0] == '*':# 从程序打开进入的编辑if not cls.readText:fileName = f'你想将更改保存到 {cls.base.title().split(" ")[0][1::]} 吗?'# 从文件打开进入的编辑else:fileName = f'你想将更改保存到\n{openPath.name}\n吗?'# 在消息对话框选择是否保存当前编辑的文本ifYes = tkmb.askyesnocancel('记事本', fileName)# 确定if ifYes:# 判断保存的文件是否存在if not cls.readText:# 文件保存类型filetypes = [("文本文档", ".txt"), ("所有文件", ".*")]# 保存文件对话框savePath = tkfd.asksaveasfile(defaultextension=".txt", initialfile='*.txt', filetypes=filetypes)# 确定保存if savePath:# 把文本编辑的数据写入文件with open(savePath.name, 'w', encoding=savePath.encoding) as file:file.write(cls.text.get('1.0', 'end')[:-1:])else:# 保存文本数据,写入文件with open(openPath.name, 'w', encoding=openPath.encoding) as file:file.write(cls.text.get('1.0', 'end')[:-1:])# 初始化窗口if ifYes != None and savePath:cls.base.title('无标题 - 记事本')cls.readText = ''cls.text.delete('1.0', 'end')cls.base.event_generate('<Key>')# 编辑Text文本@classmethoddef editText(cls, event=None):print('1editText:', [cls.text.get('1.0', 'end')])data = cls.text.get('1.0', 'end')# 文本编辑时窗口标题前加入'*'if data[:-1:] != cls.readText and data != '\n' and cls.base.title()[0] != '*':cls.base.title('*' + cls.base.title())# 编辑过文本未保存或与原文本相同时窗口标题前去掉'*'elif data[:-1:] == cls.readText or data == '\n':if cls.base.title()[0] == '*':cls.base.title(cls.base.title()[1::])# 如果是键盘Ctrl+O触发打开文件(必须)cls.textGet = cls.text.get('1.0', 'end')# 编辑撤销数据存储if cls.editIndex == -1:cls.editData.append((cls.textGet, cls.fontSize.get()))if len(cls.editData) != 1 and cls.textGet == cls.editData[cls.editIndex-1][0]:cls.editData.pop(-2)else:if cls.textGet != cls.editData[cls.editIndex][0]:cls.editData.insert(len(cls.editData) + cls.editIndex + 1, (cls.textGet, cls.fontSize.get()))buf = cls.editData[:len(cls.editData) + cls.editIndex + 1:]cls.editData.clear()cls.editData.extend(buf)cls.editIndex = -1else:cls.editData.pop(len(cls.editData) + cls.editIndex)cls.editData.insert(len(cls.editData) + cls.editIndex+1, (cls.textGet, cls.fontSize.get()))print('2editText:', [cls.text.get('1.0', 'end')])print('2readText:', [cls.readText])print('2editData:', cls.editData)# 状态栏:更新字体大小百分比@classmethoddef FontSizeEvent(cls, event):# 菜单调整字体大小if event == '放大':cls.base.event_generate('<Control-Key-+>') # 引起键盘触发事件returnelif event == '缩小':cls.base.event_generate('<Control-minus>') # 引起键盘触发事件returnelif event == '默认缩放':cls.base.event_generate('<Control-Key-0>') # 引起键盘触发事件return# 快捷键调整字体大小# 向下滚动if event.delta < 0 or event.keysym == 'minus':# 字体大小范围if cls.fontSize.get() <= 1:return# 缩小字体cls.fontSize.set(cls.fontSize.get() - 1)print('向上滚动,字体大小:', cls.fontSize.get())# 向上滚动else:# 字体大小范围if cls.fontSize.get() >= FONT_SIZE * 5:return# 放大字体cls.fontSize.set(cls.fontSize.get() + 1)# 恢复默认缩放if event.keysym == '0':cls.fontSize.set(FONT_SIZE)print('向下滚动,字体大小:', cls.fontSize.get())# 改变字体大小cls.setFont.config(size=cls.fontSize.get())# 改变底部显示字体大小百分比cls.fontSizeLabel.config(text='{:.0%}'.format(cls.fontSize.get() / FONT_SIZE))# 状态栏:获取Text光标位置@classmethoddef cursorPosition(cls, event):row, column = event.widget.index("insert").split(".")print("光标位置:行", row, "列", int(column) + 1)cls.locationLabel.config(text=f' 第 {row} 行,第 {int(column) + 1} 列')# 勾选自动换行显示与否@classmethoddef setWrap(cls):# 设置自动换行if cls.wrap.get():# 自动换行设置cls.text.config(wrap='word')# 移除底部水平滑动条cls.bottomScrollbar.pack_forget()# 底部框架没有组件显示时移除if not cls.state.get():cls.bottomFrame.pack_forget()# 设置取消自动换行else:# 先移除右侧滚动条,再显示cls.rightScrollbar.pack_forget()# 先移除中间文本域,再显示cls.text.pack_forget()# 显示底部框架cls.bottomFrame.pack(side=tk.BOTTOM, fill='both')# 取消自动换行设置cls.text.config(wrap='none')# 显示底部水平滑动条cls.bottomScrollbar.pack(fill='both')# 再显示右侧滚动条cls.rightScrollbar.pack(side=tk.RIGHT, fill='both')# 再中间文本域cls.text.pack(expand=True, fill='both')# 勾选底部状态栏显示与否@classmethoddef setState(cls):# 底部显示状态栏if cls.state.get():# 先移除右侧滚动条,再显示cls.rightScrollbar.pack_forget()# 先移除中间文本域,再显示cls.text.pack_forget()# 显示底部框架cls.bottomFrame.pack(side=tk.BOTTOM, fill='both')# 显示状态栏cls.stateFrame.pack(side=tk.BOTTOM, fill='both')# 底部移除状态栏else:# 移除状态栏cls.stateFrame.pack_forget()# 底部框架没有组件显示时移除if cls.wrap.get():cls.bottomFrame.pack_forget()# 再显示右侧滚动条cls.rightScrollbar.pack(side=tk.RIGHT, fill='both')# 再中间文本域cls.text.pack(expand=True, fill='both')# Text文本鼠标右键菜单事件@classmethoddef rightKeyEvent(cls, event, object):object.post(event.x_root, event.y_root)# 创建顶级窗口后,根窗口会对其产生影响(比如:打开的对话框是对根窗口打开的,操作的却是顶级窗口)
# 创建新窗口(需要用到线程,否则前面创建的窗口不能运行)
def newWindow(event=None):newBase = tk.Toplevel()UI2 = WindowsUI(newBase)UI2.workFunc() # 项目运行函数# 主窗口
def mainWindow():base = tk.Tk() # 新建主窗口UI = WindowsUI(base)UI.workFunc() # 项目运行函数WindowsUI.mainLoop() # 窗口主循环# 全局变量
openPath = ''# 主函数
def main():mainWindow() # 主窗口# 代码测试
if __name__ == '__main__':main()
else:print(f'导入{__name__}模块')
作者:周华
创作日期:2023/11/23