学习tkinter 中发现了一个问题,其中Entry输入框不能进行ctrl+Z,和ctrl+Y操作!后面想对其进行完善写了如下代码:
import tkinter as tk
class MyEntry(tk.Entry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._undo = []
self._redo = []
self.bind("<Control-z>", self._undo_callback)
self.bind("<Control-y>", self._redo_callback)
def _undo_callback(self, event=None):
try:
self._redo.append(self.get())
text = self._undo.pop()
self.delete('0', 'end')
self.insert('end', text)
except IndexError:
pass
def _redo_callback(self, event=None):
try:
self._undo.append(self.get())
text = self._redo.pop()
self.delete('0', 'end')
self.insert('end', text)
except IndexError:
pass
def insert(self, index, s):
self._undo.append(self.get())
super().insert(index, s)
self._redo = []
def delete(self, index1, index2=None):
self._undo.append(self.get())
super().delete(index1, index2)
self._redo = []
root = tk.Tk()
e = MyEntry(root)
e.pack()
root.mainloop()
可以基本实现ctrl+y和ctrl+z;但是又发现了一个问题就是 先ctrl+z后按ctrl+y没得反应!求牛人解惑
import tkinter as tk
class MyEntry(tk.Entry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._undo = []
self._redo = []
self._is_callback = False # 新增标志位
self.bind("<Control-z>", self._undo_callback)
self.bind("<Control-y>", self._redo_callback)
def _undo_callback(self, event=None):
try:
self._is_callback = True
self._redo.append(self.get())
text = self._undo.pop()
self.delete('0', 'end')
self.insert('end', text)
self._is_callback = False
except IndexError:
pass
def _redo_callback(self, event=None):
try:
self._is_callback = True
self._undo.append(self.get())
text = self._redo.pop()
self.delete('0', 'end')
self.insert('end', text)
self._is_callback = False
except IndexError:
pass
def insert(self, index, s):
self._undo.append(self.get())
super().insert(index, s)
if not self._is_callback: # 如果不是在执行回调,就清空_redo列表
self._redo = []
def delete(self, index1, index2=None):
self._undo.append(self.get())
super().delete(index1, index2)
if not self._is_callback: # 如果不是在执行回调,就清空_redo列表
self._redo = []
root = tk.Tk()
e = MyEntry(root)
e.pack()
root.mainloop()
我可以尝试给出一个解决方案,但需要注意的是,因为tkinter的撤销和恢复功能需要对输入框内的文本进行操作,所以需要用到Text控件,而不是Entry控件。下面是实现撤销和恢复功能的代码:
from tkinter import *
from tkinter import messagebox
class EditableText(Text):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bind('<Control-z>', self.undo)
self.bind('<Control-y>', self.redo)
self.bind('<Key>', self.record)
self.undo_stack = []
self.redo_stack = []
def record(self, event=None):
if event and (event.keysym == 'Left' or event.keysym == 'Right'
or event.keysym == 'BackSpace' or event.keysym == 'Delete'):
return
text = self.get('1.0', 'end-1c')
self.undo_stack.append(text)
self.redo_stack = []
def undo(self, event=None):
if not self.undo_stack:
messagebox.showwarning('Warning', 'Nothing to undo')
return
self.redo_stack.append(self.get('1.0', 'end-1c'))
text = self.undo_stack.pop()
self.delete('1.0', 'end')
self.insert('1.0', text)
def redo(self, event=None):
if not self.redo_stack:
messagebox.showwarning('Warning', 'Nothing to redo')
return
self.undo_stack.append(self.get('1.0', 'end-1c'))
text = self.redo_stack.pop()
self.delete('1.0', 'end')
self.insert('1.0', text)
# 创建一个窗口
root = Tk()
root.geometry("500x500")
text = EditableText(root, font=('Arial', 14))
text.pack(fill=BOTH, expand=True)
root.mainloop()
在这个代码中,我们新建了一个EditableText类,它继承自Text类,并重写了构造函数和三个方法:
__init__
:绑定了 <Control-z>
和 <Control-y>
按键事件,以及普通输入事件 <Key>
。同时,声明了两个栈用于保存撤销和恢复的文本内容。record
:被普通输入事件 <Key>
调用,当监听到一次普通的文本改变输入时,会将修改前的文本内容保存到撤销栈中。undo
:被快捷键流程 <Control-z>
调用,当你尝试执行一次撤销操作时,会将当前文本内容保存到恢复栈中,从撤销栈中取出上一次的文本内容,重新渲染到Text控件中。redo
:被快捷键流程 <Control-y>
调用,当你尝试执行一次恢复操作时,会将当前文本内容保存到撤销栈中,从恢复栈中取出上一次的文本内容,重新渲染到Text控件中。这里重写的原因是,我们需要在普通的文本输入时,将当前文本内容保存到撤销栈中,以便于执行撤销操作时能够回退到上一个版本。
最后,我们把EditableText实例添加至tk的窗口中,end。