tkinter中改写Entry增加撤销和恢复功能

学习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没得反应!求牛人解惑

TechWhizKid参考GPT回答:

img


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

  • 以下回答由chatgpt基于相关博客总结生成:

    我可以尝试给出一个解决方案,但需要注意的是,因为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。