上一篇: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 = None
leftVar = None
rightVar = None
topVar = None
bottomVar = None
headerVar = None
footerVar = None
# (打印)页面设置
@classmethod
def 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 webbrowser
headerFooterWeb.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() # 窗口循环
# (打印纸张)页面设置确定与取消
@classmethod
def confirmCancel(cls, option, win=None):
print(option)
if option == '确定':
# 修改的数值保存到文件
# 发出警告声音
win.bell()
pass
# 关闭当前窗口
win.destroy()
# 获取输入框的数据
@classmethod
def 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_VAR
LEFT_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_VAR
HEADER_VAR = cls.headerVar.get()
FOOTER_VAR = cls.footerVar.get()
# (打印纸张)方向选择
@classmethod
def orientOption(cls):
global PAPER_ORIENT
PAPER_ORIENT = cls.orientVar.get()
# (打印纸张)方向选择
if PAPER_ORIENT == 1:
print('方向:纵向')
elif PAPER_ORIENT == 2:
print('方向:横向')
# 纸张选择
@classmethod
def paperOption(cls, option):
global PAPER_VAR
PAPER_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 = '' # 读取文本数据
@classmethod
def __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 = None
text = None
fontSize = None
setFont = None
fontSizeLabel = None
locationLabel = None
wrap = None
bottomScrollbar = None
rightScrollbar = None
state = None
bottomFrame = None
stateFrame = None
editMenu = None
textGet = None
@classmethod
def mainLoop(cls):
cls.base.mainloop() # 窗口主循环
# 项目运行函数
@classmethod
def 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)
# 编辑剪切
# 编辑复制
# 编辑粘贴
# 编辑删除
# 编辑撤销菜单状态
@classmethod
def 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
@classmethod
def 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:
return
cls.editIndex -= 1
# 快捷键触发
elif event.keysym == 'Z':
# 限制条件
if cls.editIndex >= -1:
return
cls.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))
# 文件另存为
@classmethod
def 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
# 保存文件
@classmethod
def 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:]
# 打开文件
@classmethod
def openFile(cls, keyTrigger=None):
global openPath
ifYes = True
savePath = 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 -typevariable
filetypes = [('文本文档', '.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
# 新建文本
@classmethod
def newText(cls, event=None):
ifYes = True
savePath = 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文本
@classmethod
def 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 = -1
else:
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)
# 状态栏:更新字体大小百分比
@classmethod
def FontSizeEvent(cls, event):
# 菜单调整字体大小
if event == '放大':
cls.base.event_generate('<Control-Key-+>') # 引起键盘触发事件
return
elif event == '缩小':
cls.base.event_generate('<Control-minus>') # 引起键盘触发事件
return
elif 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光标位置
@classmethod
def cursorPosition(cls, event):
row, column = event.widget.index("insert").split(".")
print("光标位置:行", row, "列", int(column) + 1)
cls.locationLabel.config(text=f' 第 {row} 行,第 {int(column) + 1} 列')
# 勾选自动换行显示与否
@classmethod
def 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')
# 勾选底部状态栏显示与否
@classmethod
def 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文本鼠标右键菜单事件
@classmethod
def 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