Python 使用tkinter复刻Windows记事本UI和菜单功能(三)

news2024/11/15 21:48:19

上一篇: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1243871.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么 x86 操作系统从 0x7c00 处开始

0x00&#xff1a;x86 架构 BIOS 引导加载程序中的"0x7C00"之谜 你知道 x86 操作系统中的"0x7C00"这个神奇数字吗 ? "0x7C00" 是BIOS加载MBR&#xff08;主引导记录&#xff0c;磁盘中的第一个扇区&#xff09;的内存地址。操作系统或引导加载…

思维模型 等待效应

本系列文章 主要是 分享 思维模型 &#xff0c;涉及各个领域&#xff0c;重在提升认知。越是等待&#xff0c;越是焦虑。 1 等待效应的应用 1.1 等待效应在管理中的应用 西南航空公司是一家美国的航空公司&#xff0c;它在管理中运用了等待效应。西南航空公司鼓励员工在工作中…

XUbuntu22.04之解决gpg keyserver receive failed no data(一百九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Axios 通过a标签下载文件 跨域下载

<!-- a标签占位 --><a ref"down" ></a>getTest() {this.$axios.request({url: https://cnv13.55.la/download?file_key3695fa9461a0ae59cf3148581e4fe339&handle_typeexcel2pdf,method: get,responseType: blob, // 切记类型 blob}).then(re…

【Java并发】聊聊线程池原理以及实际应用

线程其实对于操作系统来说是宝贵的资源&#xff0c;java层面的线程其实本质还是依赖于操作系统内核的线程进行处理任务&#xff0c;如果频繁的创建、使用、销毁线程&#xff0c;那么势必会非常浪费资源以及性能不高&#xff0c;所以池化技术&#xff08;数据库连接池、线程池&a…

3D火山图绘制教程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 本期教程内容 **注&#xff1a;**本教程详细内容 Volcano3D绘制3D火山图 一、前言 火山图是做差异分析中最常用到的图形&#xff0c;在前面的推文中&#xff0c;我们也推出了好几期火山图的绘制教程&#xff0…

Linux下载工具XDM下载安装与使用

Windows上IDM多线程下载非常强大&#xff0c;即能捕捉页面上的视频、图片、音频&#xff0c;又能作为浏览器下载器使用&#xff0c;但是IDM无法在Linux下使用&#xff0c;除非使用wine。不过我们可以在Linux中用XDM(Xtreme Download Manager)代替IDM。 1、XDM下载 Xtreme Dow…

从Discord的做法中学习 — 使用Golang进行请求合并

正如你可能之前看到的&#xff0c;Discord去年发布了一篇有价值的文章&#xff0c;讨论了他们成功存储了数万亿条消息。虽然有很多关于这篇文章的YouTube视频和文章&#xff0c;但我认为这篇文章中一个名为“数据服务为数据服务”的部分没有得到足够的关注。在这篇文章中&#…

如何在AD的PCB板做矩形槽孔以及如何倒圆弧角

Altium Designer 22下载安装教程-CSDN博客 如何在AD上创建完整的项目-CSDN博客 开始前&#xff0c;请先安装后AD&#xff0c;并创建好项目。 目录 1. 如何在AD的PCB板做矩形槽孔 2. 如何在AD的PCB板倒圆弧角 1. 如何在AD的PCB板做矩形槽孔 首先&#xff0c;我们进入上面创…

普通话考试相关(一文读懂)

文章目录&#xff1a; 一&#xff1a;相关常识 1.考试报名时间 2.报名地方 费用 证件 3.考试流程 4.普通话等级说明 二&#xff1a;题型 三&#xff1a;技巧 1.前三题 2.命题说话 四&#xff1a;普通话考试题库 1.在线题库 2.下载题库 一&#xff1a;相关常识 …

【工具栏】热部署不生效

目录 配置热部署&#xff1a; 解决热部署不生效&#xff1a; 首先检查&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 配置热部署&#xff1a; https://blog.csdn.net/m0_67930426/article/details/133690559 解决热部署不…

OmniGraffle

安装 在mac上安装OmniGraffle&#xff0c;找一个正版或者啥的都行&#xff0c;安装好后&#xff0c;可以直接在网上找一个激活码&#xff0c;然后找到软件的许可证&#xff0c;进行添加即可。 使用 新建空白页 然后图形啥的看一眼工具栏就知道了&#xff0c;颜色形状还是挺…

ELK企业级日志分析平台——ES集群监控

启用xpack认证 官网&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/7.6/configuring-tls.html#node-certificates 在elk1上生成证书 [rootelk1 ~]# cd /usr/share/elasticsearch/[rootelk1 elasticsearch]# bin/elasticsearch-certutil ca[rootelk1 ela…

九、ffmpeg命令转封装

开了几天小差&#xff0c;今天继续学习ffmpeg。 准备测试使用的视频&#xff0c;并查看其信息 # 查看视频信息。使用Mediainfo也可以 ffprobe test.mp4 视频格式的信息如下。 保持编码格式&#xff1a;ffmpeg -i test.mp4 -vcodec copy -acodec copy test_copy.tsffmpeg -i…

读书笔记——《黑猩猩的政治》

前言 弗朗斯德瓦尔&#xff08;Frans de Waal)的代表作《黑猩猩政治》成书于1982年&#xff0c;是它的首部书籍作品&#xff0c;也是美国国会新任议员的被推荐读物。之前看的他另一部作品的《万智有灵》是2016年的作品&#xff0c;时间跨度居然这么大。《万智有灵》介绍了许多…

6.2.SDP协议

那今天呢&#xff1f;我们来介绍一下sdp协议&#xff0c;那实际上呢&#xff1f;sdp协议非常的简单。我们如果拿到一个stp的文档去看的话&#xff0c;那你要分阅里边的所有的内容会觉得很枯燥&#xff0c;但实际上呢&#xff0c;如果我们按照这张图所展示的结构去看stp的话。你…

消息中间件——RabbitMQ(四)命令行与管控台的基本操作!

前言 在前面的文章中我们介绍过RabbitMQ的搭建&#xff1a;RabbitMQ的安装过以及各大主流消息中间件的对比&#xff1a;&#xff0c;本章就主要来介绍下我们之前安装的管控台是如何使用以及如何通过命令行进行操作。 1. 命令行操作 1.1 基础服务的命令操作 rabbitmqctl sto…

Linux快速显示文件行号并跳转

有时候&#xff0c;想要在线上直接查看日志文件&#xff0c;搜索到关键词后&#xff0c;如果一直按n找下去&#xff0c;很麻烦&#xff0c;我们可以先显示出行号&#xff0c;确定好我们要找内容对应的行号&#xff0c;直接跳转过去。 esc进入命令模式&#xff0c;输入:set nu命…

【神印王座】龙皓晨美妆胜过月夜,魔神皇识破无视,撮合月夜阿宝

Hello,小伙伴们&#xff0c;我是拾荒君。 《神印王座》国漫第82集已更新&#xff0c;拾荒君和大多数人一样&#xff0c;更新就去看了。魔神皇枫秀&#xff0c;威严凛然&#xff0c;突然空降月魔宫&#xff0c;整个宫殿都在这股无与伦比的强大气息中颤栗。为了顺利躲避魔神皇的…

筑牢思想防线——建行驻江门市分行纪检组举办2023年清廉合规大讲堂

为推动廉洁教育打通“最后一公里”&#xff0c;近日&#xff0c;建行驻江门市分行纪检组举办江门市分行2023年清廉合规大讲堂。 本次大讲堂检察官结合一线办案经历&#xff0c;从防范化解金融风险、预防金融从业人员犯罪等方面对全辖员工进行了深入浅出地的讲解&#xff0c;引导…