【tkinter制作文本编辑器(3)】编辑菜单选项栏事件和右键菜单功能实现(撤销、还原、复制、粘贴、剪切、查找及全选)

1. 编辑菜单选项栏事件功能实现步骤

上一个博客完成了“文件”菜单栏下关联内容的功能设置,接着该进行“编辑”菜单栏下的关联内容的功能设置。

Tk里面已经包含了很多的event事件,上一个博客的自定义函数时候会发现有个参数event = None(没有使用自带的event事件),这里可以使用event_generate这个方法用来产生相应的事件(就不需要分别自定义函数了),具体包含的event事件种类见官网。具体的设置内容如下

1.1 撤销

撤销就是返回上一步的意思,取消对刚刚操作的执行。为了方便调用,这里直接将下面所有的功能封装在一个函数中,通过一个参数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("<<Undo>>")

→ 输出的结果为:(撤销功能设置完毕,快捷键Tk自带有为Ctrl+z/Z)
在这里插入图片描述

1.2 恢复

恢复功能和上面的撤销功能相反,设置代码如下

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("<<Redo>>")

→ 输出的结果为:(恢复功能设置完毕,快捷键Tk自带有为Ctrl+y/Y)
在这里插入图片描述

1.3 剪切

剪切就是将所选中的文本数据内容,默认然后移动到目标位置(原位置的内容消失),设置如下

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("<<Redo>>")

→ 输出的结果为:(剪切功能设置完毕,快捷键Tk自带有为Ctrl+x/X)
在这里插入图片描述

1.4 复制

复制就是将所选中的文本数据内容,默认然后移动到目标位置(原位置的内容不消失),设置如下

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("<<Copy>>")

→ 输出的结果为:(剪切功能设置完毕,快捷键Tk自带有为Ctrl+c/C)
在这里插入图片描述

1.5 粘贴

粘贴就是复制或者剪切文本后要将文本放置在一个位置,以上都是默认使用了粘贴,设置如下

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("<<Paste>>")

→ 输出的结果为:(粘贴功能设置完毕,快捷键Tk自带有为Ctrl+v/V)
在这里插入图片描述

1.6 全选

全选功能就是对于文本栏中所有的文本数据进行选中,然后可以进行其他功能的操作,比如复制,剪切等,设置如下

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("<<SelectAll>>")

→ 输出的结果为:(全选功能设置完毕,快捷键Tk自带有为Ctrl+a/A)
在这里插入图片描述

1.7 查找

查找功能就是在文本栏中进行相关信息的匹配,最后输出满足的数据所在的位置。这个事件在Tk里面并没有现成的可以直接加载,因此需要和之前一样进行自定义函数顺带绑定一下快捷键(没有快捷键,就需要自己设置),设置如下

edit_menu.add_command(label='查找', accelerator='Ctrl+F', command=self.find_text)
self.content_text.bind('<Control-f>', self.find_text)#该语句是在_create_body_函数内部
self.content_text.bind('<Control-F>', self.find_text)#该语句是在_create_body_函数内部

核心重点在于find_text函数的设置,注意事项:

① 使用查找命令后就会弹出一个小窗体,而且是要显示在主窗口之上的

② 查找窗口的布局设置,采用网格式布局的方式较为简洁(简单)

③ 如何实现文本数据的查找,这里单独再定义一个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)  #在标题上显示匹配结果

→ 输出的结果为:(文本查找的功能满足)
在这里插入图片描述

2. 快捷键绑定

由上面的设置可知,撤销、回复、剪切、复制、粘贴、全选,在创建文本栏的时候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函数在下面的右键弹出设置)
在这里插入图片描述

3. 右键弹出菜单设置

在文本编辑器中,右键弹出菜单功能可以简单的将上面实现的功能进行融合,方便在输入时候进行快速调用,这里主要的注意事项是:

① 右键弹出是要在鼠标选定的位置弹出

② 参数传递的过程(不用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('<Button-3>',
						   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对象

→ 输出的结果为:(右键弹出菜单设置完毕)
在这里插入图片描述

4. 全部代码

该部分实现功能的全部代码

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('<Control-F>', self.find_text)
	self.content_text.bind('<Control-f>', 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('<Button-3>',
						   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("<<Undo>>")
	elif action_type == "恢复":
		self.content_text.event_generate("<<Redo>>")
	elif action_type == "剪切":
		self.content_text.event_generate("<<Cut>>")
	elif action_type == "复制":
		self.content_text.event_generate("<<Copy>>")
	elif action_type == "粘贴":
		self.content_text.event_generate("<<Paste>>")
	elif action_type == "全选":
		self.content_text.event_generate("<<SelectAll>>")

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)
原创文章 159 获赞 93 访问量 4万+

猜你喜欢

转载自blog.csdn.net/lys_828/article/details/105360079