当前位置:   article > 正文

Tkinter项目:Python Tkinter PNote记事本开发

tkinter项目

Tkinter项目:Python Tkinter PNote记事本开发学习记录

原项目:[Tkinter项目]Python Tkinter NotePad记事本项目实战
https://www.bilibili.com/video/BV1qk4y1k7N2/?p=4

一、整体功能规划

整体的功能是模仿记事本的一些基本简单设计,但是增加了工具栏的快捷方式。名字为PNote,“P”是指单词“penguin”(企鹅),简而言之,该记事本叫“企鹅记事本”,其icon就是使用一张小企鹅的表情包制成的,增添界面的趣味性。界面功能思维导图如下

提前找好工具栏的图片,图片的格式都为gif,大小一般为32x32或16x16。icon图片来源网站:https://www.iconfont.cn/

,下载的时候把图片后缀改成gif就可以了。

先建一个py文件,随意起一个记事本的名字,py文件和图片文件分开放,图片放在同目录下的img文件中。ico图片转化网站:https://www.uupoop.com/ico/。将小企鹅表情包.jpg图片转换成尺寸大小32x32的icon图

记录工具:CSDN官网上的Markdown编辑器

二、代码实现

根据想法中的思维导图整个应用程序的图形化界面分为四个part:菜单栏、工具栏、文本输入区域、右键弹出菜单,将组件代码分别写在这个四个方法中,其他的功能实现方法则另外处理

PS:此处的代码展示和源码文件的排版并不一致

1.菜单栏

导入类库
#导入tkinter类库
from tkinter import *  
from tkinter import filedialog, messagebox
from tkinter.ttk import Scrollbar, Checkbutton,Label,Button  #tkk里面的组件会有所优化
import os
import sys
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
类PNote:
class PNote(Tk):
    #工具栏所用图片的名称
    icons = ["new_file","open_file","save","cut","copy","paste",
             "undo","redo","find_text"]
    icon_res = []
    # 默认主题使用字典(背景色为白色,字体为黑色)、主题一(背景为企鹅灰,字体为深蓝色)、主题二(背景色为深蓝色、字体为白色)
    theme_color = {"Default":"#000000.#FFFFFF",
                   "Penguin Gray":"#222b34.#e7e4e3",
                   "Night Mode":"#FFFFFF.#222b34"}
		# 初始化操作
    def __init__(self):       
        super().__init__() #继承父类的构造  
        #调用方法
        self.set_window()  #调用界面窗口方法
        self.create_menu_bar() #菜单栏
        self.create_tool_bar()  #工具栏
        self.create_body()  #文本输入区域
        self.create_pop_menu()  #弹出菜单
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
设置窗口界面
    # 设置窗口界面
    def set_window(self):
        self.title("PNote")  #标题
        max_width,max_height = self.maxsize()  #宽高
        # 居中对齐,像素
        align_center = "800x600+%d+%d" % ((max_width-800)/2,(max_height-600)/2)       
        self.geometry(align_center)
        self.iconbitmap("./img/penguin.ico")
   

    #创建菜单栏
    def create_menu_bar(self):
        menu_bar = Menu(self)
        self['menu']=menu_bar
                #添加菜单栏项目

        #文件
        file_menu = Menu(menu_bar,tearoff=0)
        file_menu.add_command(label='新建',accelerator='Ctrl+N',command=self.new_file)
        file_menu.add_command(label='打开',accelerator='Ctrl+O',command=self.open_file)
        file_menu.add_command(label='保存',accelerator='Ctrl+S',command=self.save_file)
        file_menu.add_command(label='另存为',accelerator='Ctrl+Shift+S',command=self.save_as)
        file_menu.add_separator()  #分割符
        file_menu.add_command(label='退出',accelerator='Alt+F4',command=self.exit_notepad)
        menu_bar.add_cascade(label='文件',menu=file_menu)
        
#文件菜单功能实现
    # 打开文件功能
    def open_file(self,event=None):
        #打开文件并进行类型设置
        input_file = filedialog.askopenfilename(filetypes=[("所有文件","*.*"),("文本文档","*.text")])
        if input_file:
            self.title("{}***NotePad".format(os.path.basename(input_file)))
            self.file_name = input_file
            self.context_text.delete(1.0,END)
            with open(input_file,'r') as _file:
                self.context_text.insert(1.0,_file.read())

    #文件的保存,是保存(替代)原有文本文件的内容,先读写文件
    def write_to_file(self,file_name):
        try:
            content = self.context_text.get(1.0,END)
            with open(file_name,'w') as _file:
                _file.write(content)
            self.title("{}---NotePad".format(os.path.basename(file_name)))
        except IOError:
            messagebox.showerror("错误","文件保存失败!")

    def save_file(self,event=None):
        if not self.file_name:
            self.save_as() #避免保存出来的新建的问题
        else:
            self.write_to_file(self.file_name)

    #新建
    def new_file(self,event=None):
        self.title("新建---PNote")
        self.context_text.delete(1.0,END)
        self.file_name = None

    #另存为
    def  save_as(self):
        input_file = filedialog.askopenfilename(filetypes=[("所有文件","*.*"),("文本文档","*.text")])
        if input_file:
            self.file_name= input_file
            self.write_to_file(self.file_name)

    #退出
    def exit_notepad(self):
        if messagebox.askokcancel("退出","确定退出吗?"):
            self.destroy()

        
if __name__ == '__main__':
    app = PNote()
    app.mainloop()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
编辑菜单

由于编辑的功能和工具栏差不多,所以会其方法放在body部分,但是“查找”功能在此单独说明

#在方法create_menu_bar(self)加入以下代码

        #编辑
        editor_menu = Menu(menu_bar,tearoff=0)
        """撤销,恢复,剪切,复制,粘贴,查找,全选"""
        menu_bar.add_cascade(label='编辑',menu=editor_menu)
        editor_menu.add_command(label='撤销',accelerator='Ctrl+Z',command=lambda:self.handle_menu_action('撤销'))
        editor_menu.add_command(label='恢复',accelerator='Ctrl+Y',command=lambda:self.handle_menu_action('恢复'))
        editor_menu.add_separator()
        editor_menu.add_command(label='剪切',accelerator='Ctrl+X',command=lambda:self.handle_menu_action('剪切'))
        editor_menu.add_command(label='复制',accelerator='Ctrl+C',command=lambda:self.handle_menu_action('复制'))
        editor_menu.add_command(label='粘贴',accelerator='Ctrl+V',command=lambda:self.handle_menu_action('粘贴'))
        editor_menu.add_separator()
        editor_menu.add_command(label='查找',accelerator='Ctrl+F',command=self.find_text_dialog)
        editor_menu.add_command(label='全选',accelerator='Ctrl+A',command=self.select_all)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

查找文本功能

    #设置查找对话框
    def find_text_dialog(self):
        search_dialog = Toplevel(self)
        search_dialog.title('查找文本')
        #居中
        max_width,max_height = self.maxsize()  #宽高
        align_center = "300x80+%d+%d" % ((max_width-300)/2,(max_height-80)/2)       
        search_dialog.geometry(align_center)
        search_dialog.resizable(False,False)
        Label(search_dialog,text='查找全部').grid(row=0,column=0,sticky='e')
        search_text = Entry(search_dialog,width=25)
        search_text.grid(row=0,column=1,padx=2,pady=2,sticky="we")
        search_text.focus_set()
        #忽略大小写
        ignore_case_value = IntVar()
        Checkbutton(search_dialog,text="忽略大小写",variable=ignore_case_value).grid(
            row=1,column=1,sticky='e',padx=2,pady=2
            )
        Button(search_dialog,text='查找',command=lambda:self.search_result(search_text.get(),
                                                                         ignore_case_value.get(),
                                                                         search_dialog,
                                                                         search_text
                                                                         )).grid(row=0,column=2,
                                                                                 sticky="w"+"e",padx=1,
                                                                                 pady=1)
        # 关闭查找文本对话框
        def close_search_dialog():
            self.context_text.tag_remove('match',1.0,END)
            search_dialog.destroy()

        search_dialog.protocol("WM_DELETE_WINDOW",close_search_dialog)
        return "break"
    # 查找的方法
    def search_result(self,key,ignore_case,search_dialog,search_box):
        self.context_text.tag_remove('match',1.0,END)
        matches_found = 0
        if key:
            start_pos = 1.0
            while True:
                start_pos = self.context_text.search(key,start_pos,
                                                     nocase=ignore_case,
                                                     stopindex=END)
                if not start_pos:
                    break
                end_pos = "{}+{}c".format(start_pos,len(key))
                self.context_text.tag_add('match',start_pos,end_pos)
                matches_found +=1
                start_pos = end_pos
            self.context_text.tag_config('match',foreground='white',
                                         background='green')
        search_box.focus_set()
        search_dialog.title("发现了%d个匹配项" % matches_found)
    # 全选
    def select_all(self):
        self.context_text.tag_add('sel',1.0,END)
        return "break"        
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
视图、关于菜单

视图的下拉列表包括显示行号和显示高亮当前行、主题选择,前两者用checkbutton组件实现图形化界面,后者使用radiobutton组件实现图形化界面


# 在方法create_menu_bar(self)加入以下代码
        #视图菜单
        view_menu = Menu(menu_bar,tearoff=0)
        menu_bar.add_cascade(label='视图',menu=view_menu)
        
        """显示行号"""
        self.is_show_line_num = IntVar()
        self.is_show_line_num.set(1)
        view_menu.add_checkbutton(label="显示行号",
                                  onvalue=0,offvalue=1,
                                  variable=self.is_show_line_num,
                                  command=self.update_line_num
                                  )

        """显示高亮当前行"""
        self.is_heighlight_line = IntVar()
        view_menu.add_checkbutton(label="高亮当前行",
                                  variable=self.is_heighlight_line,
                                  command=self.toggle_highlight)

        """主题"""
        
        themes_menu = Menu(menu_bar,tearoff=0)
        view_menu.add_separator()
        view_menu.add_cascade(label="主题",menu=themes_menu)
        #主题选择
        #默认主题
        self.theme_choice = StringVar()
        self.theme_choice.set("Default")
        for k in sorted(self.theme_color):
            themes_menu.add_radiobutton(label=k,
                                        variable=self.theme_choice,
                                        command=self.change_theme)
    
        #关于菜单
        about_menu = Menu(menu_bar,tearoff=0)
        about_menu.add_command(label="关于",command=lambda:self.show_messagebox("关于"))
        about_menu.add_command(label="帮助",command=lambda:self.show_messagebox("帮助"))
        menu_bar.add_cascade(label='关于',menu=about_menu)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

“关于”菜单的下拉列表都是使用messagebox弄两个对话框,简单化,如图所示

在这里插入图片描述

视图和菜单的功能实现
    # 主题的切换
    def change_theme(self):
        selected_theme = self.theme_choice.get()
        fg_bg = self.theme_color.get(selected_theme)
        fg_color,bg_color = fg_bg.split('.')
        self.context_text.config(bg=bg_color,fg=fg_color)

    # 关于菜单
    def show_messagebox(self,type):
        if type == "帮助":
            messagebox.showinfo("帮助","这是帮助文档",icon="question")
        else:
            messagebox.showinfo("关于","这是一个简单的记事本程序")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.工具栏

工具栏界面的实现

工具栏的,设计上是新建、打开、保存、剪切、复制、粘贴、撤销、重做、查找文本的icon逐一排列,和弹出菜单、菜单栏的功能是有些重复,是常用的功能快捷方式

    def create_tool_bar(self):
        tool_bar = Frame(self,height=25,background="#ffffff") # 容器Frame,白色
        # 填充x轴
        tool_bar.pack(fill="x")
        # 生成图片文件放到对应的位置
        for icon in self.icons:
            tool_icon = PhotoImage(file="./img/%s.gif" % (icon,)) # 因为是元组所以有个逗号
            tool_btn = Button(tool_bar,image=tool_icon,command=self.tool_bar_action(icon))
            tool_btn.pack(side="left")  # 图片左对齐
            # 将tool_icon添加到icon_res里
            self.icon_res.append(tool_icon)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
工具栏的命令处理
   def tool_bar_action(self,action_type):        
        def handle():
            if action_type == 'open_file':
                self.open_file()
            elif action_type == "save":
                self.save_file()
            elif action_type == "new_file":
                self.new_file()
            elif action_type == "cut":
                self.handle_menu_action("剪切")
            elif action_type == "copy":
                self.handle_menu_action("复制")
            elif action_type == "paste":
                self.handle_menu_action("粘贴")
            elif action_type == "undo":
                self.handle_menu_action("撤销")
            elif action_type == "redo":
                self.handle_menu_action("恢复")
            elif action_type == "find_text":
                self.find_text_dialog()
        
        # handle返回处理
        return handle
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

3. 主体body部分(文本输入、行号、高亮、滚动条)

    def create_body(self):
        # 左:行号;右:滚动条;中:文本编辑区
        # 行号区域
        self.line_number_bar = Text(self,width=3, padx=3,takefocus=0,border=0,
                                    background="#f0f0f0",state="disable"
                                    ) #state="disable"不能编辑状态
        #左边填充整个y轴
        self.line_number_bar.pack(side='left',fill='y')
        # 文本编辑区
        # undo=True是否具备文本取消功能,wrap:如何换行,word:按照单词自动换行,expand:可以拉伸
        self.context_text = Text(self,wrap="word",undo=True)
        # 热键绑定
        self.context_text.bind("<Control-o>",self.open_file)
        self.context_text.bind("<Control-O>",self.open_file)
        self.context_text.bind("<Control-s>",self.save_file)
        self.context_text.bind("<Control-S>",self.save_file)
        self.context_text.bind("<Control-n>",self.new_file)
        self.context_text.bind("<Control-N>",self.new_file)
        self.context_text.bind('<Any-KeyPress>',lambda e:self.update_line_num())
        self.context_text.pack(fill='both',expand="yes")
        
        # 设置文本输入区
        self.context_text.tag_config("active_line",background="#ffffff")
        
        # 滚动条
        scroll_bar = Scrollbar(self.context_text)
        scroll_bar['command'] = self.context_text.yview
        self.context_text["yscrollcommand"] = scroll_bar.set
        scroll_bar.pack(side="right",fill="y")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
增加行号
    # 行号处理
    def update_line_num(self):
        if self.is_show_line_num.get():
            # 获取所有行
            row,col = self.context_text.index(END).split('.')
            # 列举每行的行号
            line_num_content = "\n".join([str(i) for i in range(1,int(row))])
            self.line_number_bar.config(state="normal")
            self.line_number_bar.delete(1.0,END)
            self.line_number_bar.insert(1.0,line_num_content)
            self.line_number_bar.config(state='disable')
        else:
            self.line_number_bar.config(state='normal')
            self.line_number_bar.delete(1.0,END)
            self.line_number_bar.config(state='disable')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
高亮当前行

高亮的颜色默认是白色的,所以在

    # 高亮当前行
    def toggle_highlight(self):
        if self.is_heighlight_line.get():
            self.context_text.tag_remove('active_line',1.0,END)
            # 设置高亮
            self.context_text.tag_add("active_line","insert linestart",
                                      "insert lineend+1c")
            # 通过递归的方式进行处理,会很耗资源,想想有没有别的办法
            self.context_text.after(200,self.toggle_highlight)
        else:
            self.context_text.tag_remove("active_line",1.0,END)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.弹出菜单

弹出菜单的功能选项有:剪切、复制、粘贴、撤销、恢复、全选

    #弹出菜单(也叫右键菜单)
    def create_pop_menu(self):
        pop_menu = Menu(self.context_text,tearoff=0)
        for item1,item2 in zip(['剪切','复制','粘贴','撤销','恢复'],
                               ['cut','copy','paste','undo','redo',]):
            
            pop_menu.add_command(label=item1,compound='left',command=self.tool_bar_action(item2))
        pop_menu.add_separator() #分割
        pop_menu.add_command(label="全选",command=self.select_all)
        #绑定
        self.context_text.bind("<Button-3>",lambda event:pop_menu.tk_popup(event.x_root,event.y_root))
        
    #右键菜单的处理
    def handle_menu_action(self,action_type):
        if action_type == "撤销":
            self.context_text.event_generate("<<Undo>>")
        elif action_type == "恢复":
            self.context_text.event_generate("<<Rndo>>")
        elif action_type == "剪切":
            self.context_text.event_generate("<<Cut>>")
        elif action_type == "复制":
            self.context_text.event_generate("<<Copy>>")
        elif action_type == "粘贴":
            self.context_text.event_generate("<<Paste>>")

    #防止事件传递
        return "break"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

三、展示效果

版本如以下动图所示:

在这里插入图片描述

在这里插入图片描述

四、打包程序

网上找的教程python打包生成exe可执行文件
打包使用的是auto -py-to-exe
先安装pip install auto-py-to-exe,安装成功后输入auto-py-to-exe回车会弹出一个窗口
在这里插入图片描述
根据网上的教程为了便于打包,修改了图片的位置,所以修改了代码中图片的路径表达,

        self.iconbitmap("D:\\PNote\\img\\penguin.ico")
  • 1
		tool_icon = PhotoImage(file="D:\\PNote\\img\\%s.gif" % (icon,)) 
  • 1

最后上传文件和图片
在这里插入图片描述
最中得到PNote.exe,测试使用无误
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/847338
推荐阅读
相关标签
  

闽ICP备14008679号