上一个博客完成了“文件”菜单栏下关联内容的功能设置,接着该进行“编辑”菜单栏下的关联内容的功能设置。
Tk
里面已经包含了很多的event
事件,上一个博客的自定义函数时候会发现有个参数event = None
(没有使用自带的event
事件),这里可以使用event_generate
这个方法用来产生相应的事件(就不需要分别自定义函数了),具体包含的event事件种类见官网。具体的设置内容如下
撤销就是返回上一步的意思,取消对刚刚操作的执行。为了方便调用,这里直接将下面所有的功能封装在一个函数中,通过一个参数action_type
进行传递,可以节省大量代码,减少工作量
注意:这里调用的函数中含有参数,可以使用lambda
函数进行参数传递
edit_menu.add_command(label='撤销', accelerator='Ctrl+Z', command=lambda: self.handle_menu_action('撤销'))
def handle_menu_action(self, action_type):
if action_type == "撤销":
self.content_text.event_generate("<>" )
→ 输出的结果为:(撤销功能设置完毕,快捷键Tk
自带有为Ctrl+z/Z
)
恢复功能和上面的撤销功能相反,设置代码如下
edit_menu.add_command(label='恢复',accelerator = 'Ctrl+Y',command = lambda : self.handle_menu_action('恢复'))
def handle_menu_action(self, action_type):
if action_type == "恢复":
self.content_text.event_generate("<>" )
→ 输出的结果为:(恢复功能设置完毕,快捷键Tk
自带有为Ctrl+y/Y
)
剪切就是将所选中的文本数据内容,默认然后移动到目标位置(原位置的内容消失),设置如下
edit_menu.add_command(label='剪切',accelerator = 'Ctrl+X',command = lambda : self.handle_menu_action('剪切'))
def handle_menu_action(self, action_type):
if action_type == "剪切":
self.content_text.event_generate("<>" )
→ 输出的结果为:(剪切功能设置完毕,快捷键Tk
自带有为Ctrl+x/X
)
复制就是将所选中的文本数据内容,默认然后移动到目标位置(原位置的内容不消失),设置如下
edit_menu.add_command(label='复制',accelerator = 'Ctrl+C',command = lambda : self.handle_menu_action('复制'))
def handle_menu_action(self, action_type):
if action_type == "复制":
self.content_text.event_generate("<>" )
→ 输出的结果为:(剪切功能设置完毕,快捷键Tk
自带有为Ctrl+c/C
)
粘贴就是复制或者剪切文本后要将文本放置在一个位置,以上都是默认使用了粘贴,设置如下
edit_menu.add_command(label='粘贴',accelerator = 'Ctrl+V',command = lambda : self.handle_menu_action('粘贴'))
def handle_menu_action(self, action_type):
if action_type == "粘贴":
self.content_text.event_generate("<>" )
→ 输出的结果为:(粘贴功能设置完毕,快捷键Tk
自带有为Ctrl+v/V
)
全选功能就是对于文本栏中所有的文本数据进行选中,然后可以进行其他功能的操作,比如复制,剪切等,设置如下
edit_menu.add_command(label='全选',accelerator = 'Ctrl+A',command = lambda : self.handle_menu_action('全选'))
def handle_menu_action(self, action_type):
if action_type == "全选":
self.content_text.event_generate("<>" )
→ 输出的结果为:(全选功能设置完毕,快捷键Tk
自带有为Ctrl+a/A
)
查找功能就是在文本栏中进行相关信息的匹配,最后输出满足的数据所在的位置。这个事件在Tk
里面并没有现成的可以直接加载,因此需要和之前一样进行自定义函数顺带绑定一下快捷键(没有快捷键,就需要自己设置),设置如下
edit_menu.add_command(label='查找', accelerator='Ctrl+F', command=self.find_text)
self.content_text.bind('' , self.find_text)#该语句是在_create_body_函数内部
self.content_text.bind('' , self.find_text)#该语句是在_create_body_函数内部
核心重点在于find_tex
t函数的设置,注意事项:
① 使用查找命令后就会弹出一个小窗体,而且是要显示在主窗口之上的
② 查找窗口的布局设置,采用网格式布局的方式较为简洁(简单)
③ 如何实现文本数据的查找,这里单独再定义一个search_result
搜索结果的函数,直接调用
④ 搜索框的关闭,因为是子窗口,在任务结束之后需要关闭的
def find_text(self,event=None):
search_toplevel = Toplevel(self) #创建一个顶级窗口
search_toplevel.title('查找文本') #窗口命名
search_toplevel.transient(self) #总是让搜索框显示在主程序窗口之上
search_toplevel.geometry('340x60+700+500') #设置查找框所在的位置
search_toplevel.resizable(False, False) #窗口不可变
Label(search_toplevel,text='查找全部:').grid(row=0,column=0,sticky='e') #设置标签提醒
search_entry_widget = Entry(search_toplevel,width=25) #设置输入框
search_entry_widget.grid(row=0,column=1,padx=2,pady=2,sticky='we') #布局
search_entry_widget.focus_set() #这里就是输入的焦点,如果没有的话就没有提示输入的一闪一闪的竖杠
ignore_case_value = IntVar() #这里的整型变量只在这个函数内部使用所以不需要转化为实例属性
Checkbutton(search_toplevel, text='忽略大小写', variable=ignore_case_value).grid(
row=1, column=1, sticky='e', padx=2, pady=2) #设置是否忽略大小写的按钮
Button(search_toplevel, text="查找", command=lambda: self.search_result(
search_entry_widget.get(), ignore_case_value.get(), search_toplevel, search_entry_widget)
).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2) #设置查找按钮,但是要回事件的,中间常常的参数是要传入到search_result函数中的变量
def close_search_window():
self.content_text.tag_remove('match', '1.0', "end") #首先移除所有的标记效果,因为是选择文本内容是要标记出来选中的内容,所以在退出窗口之前,选中的标记需要先去除掉
search_toplevel.destroy() #然后再销毁窗口
search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window)
#最后这个窗口也需要关闭,只是不需要弹出消息对话框,所以需要重新定一个函数,这个函数在find_text内部定义的话,较为简单,
#如果和主窗口退出时定义的那样需要将search_toplevel变量转化为实例属性,这样就可以在另外的函数中使用了
return "break" #防止一直被使用,每次运行之后要退出
search_result
函数讲解,传入的四个参数,分别为:搜索的关键词,是否忽略大小写,搜索窗口(用来在标题处显示搜索成功的结果),搜索栏(用来显示输入的焦点)。注意事项
① 匹配文本后,做标记(颜色设置)
② 匹配文本计数(大小写的问题)
③ 多文本数据匹配(不是一个字符数据的匹配)
def search_result(self, key, ignore_case, search_toplevel, search_box):
self.content_text.tag_remove('match', '1.0', "end") #每次进行匹配文本内容查找时候都要把上一次匹配的标记给去掉
#print(ignore_case)#不勾选的话就是0,默认不忽略
matches_found = 0 #匹配成功计数
if key: #如果输入了要匹配的数据,进行接下来的操作
start_pos = '1.0' #将匹配开始的位置设置在文本开始的位置
while True:
# search返回第一个匹配上的结果的开始索引,返回空则没有匹配的(nocase:忽略大小写)
start_pos = self.content_text.search(key, start_pos, nocase=ignore_case, stopindex="end")
#这里直接使用文本栏的搜索功能,需要输入要匹配的数据,开始和结束的位置,以及是否忽略大小写,可以使用01代替,这也就是之前为啥要进行整型变量的赋值了
if not start_pos:
break #这两个组合就是,如果匹配到了就继续往下,没有匹配到就退出while循环
end_pos = '{}+{}c'.format(start_pos, len(key)) #经过上面的另个语句,可知匹配到内容了,这时候就要把匹配结束的位置记下来
self.content_text.tag_add('match', start_pos, end_pos)#这里就用到了上面的位置,对匹配到的内容进行贴标签
matches_found += 1 #匹配的内容计数+1
start_pos = end_pos #这时候要为下一次循环做准备,所以先把开始的位置定在上一次匹配成功的数据结束的位置,继续进行下去
self.content_text.tag_config('match', foreground='red', background='yellow') #进行标签的配色
search_box.focus_set() #显示焦点
search_toplevel.title('发现%d个匹配的' % matches_found) #在标题上显示匹配结果
由上面的设置可知,撤销、回复、剪切、复制、粘贴、全选,在创建文本栏的时候Tk中是默认快捷键的,而且这种快捷键也是我们日常生活中经常使用到的,关于自定义的find_text
函数,上面也进行了快捷的设置,因此关于快捷键的设置这里相当于完成了。这里的快捷键设置,是对应之前设置的快捷菜单栏的,也就是点击小图标可以响应对应的时间,设置如下
def _create_shortcut_bar_(self):
shortcut_bar = Frame(self, height=25, background='#20b2aa')
shortcut_bar.pack(fill='x')
for i, icon in enumerate(ICONS):
tool_icon = PhotoImage(file='img/%s.gif' % (icon,))
tool_btn = Button(shortcut_bar, image=tool_icon,
command=self._shortcut_action(icon))
#和之前的区别在于这里添加一个函数回调 ,这个函数在下面邮件弹出菜单设置的时候也有用到,总共九个功能
tool_btn.pack(side='left')
self.icon_res.append(tool_icon)
→ 输出的结果为:(_shortcut_action
函数在下面的右键弹出设置)
在文本编辑器中,右键弹出菜单功能可以简单的将上面实现的功能进行融合,方便在输入时候进行快速调用,这里主要的注意事项是:
① 右键弹出是要在鼠标选定的位置弹出
② 参数传递的过程(不用lambda
了,减少代码量,可以使用函数递包的方式)
③ 需要在类的初始化创建self._create_right_popup_menu()
函数
def _create_right_popup_menu(self):
popup_menu = Menu(self.content_text, tearoff=0) #有了之间创建菜单的经历,这里就是依托的对象不同
for it1, it2 in zip(['剪切', '复制', '粘贴', '撤销', '恢复'],
['cut', 'copy', 'paste', 'undo', 'redo']):
popup_menu.add_command(label=it1, compound='left',
command=self._shortcut_action(it2))
#注意这里,使用的label是汉字的,传到函数中的参数是英文的,这里其实可以直接全部使用中文的
popup_menu.add_separator() #为了美观,将全选这个功能单独提取出来
popup_menu.add_command(label='全选', command = lambda: self.handle_menu_action("全选"))
self.content_text.bind('' ,
lambda event: popup_menu.tk_popup(event.x_root, event.y_root))
#实现在右键点击后出现弹窗
函数递包(这里没有再使用lambda
传参,可以像设置‘全选’功能键一样,将其他的功能键进行类似设置),实现不用lambda
就可以传递参数
def _shortcut_action(self, type):
def handle():
if type == "new_file":
self.new_file()
elif type == "open_file":
self.open_file()
elif type == "save":
self.save()
elif type == "cut":
self.handle_menu_action("剪切")
elif type == "copy":
self.handle_menu_action("复制")
elif type == "paste":
self.handle_menu_action("粘贴")
elif type == "undo":
self.handle_menu_action("撤销")
elif type == "redo":
self.handle_menu_action("恢复")
elif type == "find_text":
self.find_text()
return handle #最后返回的是就是handle对象
该部分实现功能的全部代码
def _create_menu_bar_(self):
menu_bar = Menu(self)
edit_menu = Menu(menu_bar,tearoff = 0) #基于菜单栏实例化“编辑”关联选项栏对象
edit_menu.add_command(label='撤销',accelerator = 'Ctrl+Z',command = lambda : self.handle_menu_action('撤销'))
edit_menu.add_command(label='恢复',accelerator = 'Ctrl+Y',command = lambda : self.handle_menu_action('恢复'))
edit_menu.add_separator()
edit_menu.add_command(label='剪切',accelerator = 'Ctrl+X',command = lambda : self.handle_menu_action('剪切'))
edit_menu.add_command(label='复制',accelerator = 'Ctrl+C',command = lambda : self.handle_menu_action('复制'))
edit_menu.add_command(label='粘贴',accelerator = 'Ctrl+V',command = lambda : self.handle_menu_action('粘贴'))
edit_menu.add_separator()
edit_menu.add_command(label='全选',accelerator = 'Ctrl+A',command = lambda : self.handle_menu_action('全选'))
edit_menu.add_separator()
edit_menu.add_command(label='查找',accelerator = 'Ctrl+F',command = self.find_text)
menu_bar.add_cascade(label='编辑',menu=edit_menu)
def _create_body_(self):
self.content_text.bind('' , self.find_text)
self.content_text.bind('' , self.find_text)
def _create_right_popup_menu(self):
popup_menu = Menu(self.content_text, tearoff=0)
for it1, it2 in zip(['剪切', '复制', '粘贴', '撤销', '恢复'],
['cut', 'copy', 'paste', 'undo', 'redo']):
popup_menu.add_command(label=it1, compound='left',
command=self._shortcut_action(it2))
popup_menu.add_separator()
popup_menu.add_command(label='全选', command = lambda: self.handle_menu_action("全选"))
self.content_text.bind('' ,
lambda event: popup_menu.tk_popup(event.x_root, event.y_root))
def _shortcut_action(self, type):
def handle():
if type == "cut":
self.handle_menu_action("剪切")
elif type == "copy":
self.handle_menu_action("复制")
elif type == "paste":
self.handle_menu_action("粘贴")
elif type == "undo":
self.handle_menu_action("撤销")
elif type == "redo":
self.handle_menu_action("恢复")
elif type == "find_text":
self.find_text()
return handle
def handle_menu_action(self, action_type):
if action_type == "撤销":
self.content_text.event_generate("<>" )
elif action_type == "恢复":
self.content_text.event_generate("<>" )
elif action_type == "剪切":
self.content_text.event_generate("<>" )
elif action_type == "复制":
self.content_text.event_generate("<>" )
elif action_type == "粘贴":
self.content_text.event_generate("<>" )
elif action_type == "全选":
self.content_text.event_generate("<>" )
def find_text(self,event=None):
search_toplevel = Toplevel(self) #创建一个顶级窗口
search_toplevel.title('查找文本') #窗口命名
search_toplevel.transient(self) #总是让搜索框显示在主程序窗口之上
search_toplevel.geometry('340x60+700+500')
search_toplevel.resizable(False, False) #窗口不可变
Label(search_toplevel,text='查找全部:').grid(row=0,column=0,sticky='e')
search_entry_widget = Entry(search_toplevel,width=25)
search_entry_widget.grid(row=0,column=1,padx=2,pady=2,sticky='we')
search_entry_widget.focus_set()
ignore_case_value = IntVar()
Checkbutton(search_toplevel, text='忽略大小写', variable=ignore_case_value).grid(
row=1, column=1, sticky='e', padx=2, pady=2)
Button(search_toplevel, text="查找", command=lambda: self.search_result(
search_entry_widget.get(), ignore_case_value.get(), search_toplevel, search_entry_widget)
).grid(row=0, column=2, sticky='e' + 'w', padx=2, pady=2)
def close_search_window():
self.content_text.tag_remove('match', '1.0', "end")
search_toplevel.destroy()
search_toplevel.protocol('WM_DELETE_WINDOW', close_search_window)
return "break"
def search_result(self, key, ignore_case, search_toplevel, search_box):
self.content_text.tag_remove('match', '1.0', "end")
print(ignore_case)#不勾选的话就是0,默认不忽略
matches_found = 0
if key:
start_pos = '1.0'
while True:
# search返回第一个匹配上的结果的开始索引,返回空则没有匹配的(nocase:忽略大小写)
start_pos = self.content_text.search(key, start_pos, nocase=ignore_case, stopindex="end")
if not start_pos:
break
end_pos = '{}+{}c'.format(start_pos, len(key))
self.content_text.tag_add('match', start_pos, end_pos)
matches_found += 1
start_pos = end_pos
self.content_text.tag_config('match', foreground='red', background='yellow')
search_box.focus_set()
search_toplevel.title('发现%d个匹配的' % matches_found)