Tkinter - 撤消和重做对文本小部件的格式化

Tkinter - Undo and Redo the formatting's made to a text widget

有什么方法可以撤消和重做对 tkinter 文本小部件所做的格式化吗?

代码如下:

from tkinter import *

root = Tk()

text = Text(root, width=65, height=20, undo=True, font="consolas 14")
text.pack()

undo_button = Button(root, text="Undo", command=text.edit_undo)
undo_button.pack()

redo_button = Button(root, text="Redo", command=text.edit_redo)
redo_button.pack()

text.insert('1.0', "Hello world")
text.tag_add('test', '1.0', '1.5')
text.tag_config('test', background='yellow')

mainloop()

在这里,我给文本小部件添加了一个标签,但问题是,当我点击撤消或重做按钮时,只有文本小部件中的文本被修改,而不是格式化它。

我想要的是当我按下 undo_button 首先 应该删除 'test' 标签,因为添加该标签是我最后一件事对我的文本小部件做了。

有什么方法可以在 tkinter 中实现吗?

如果有人能帮助我就太好了。

我认为默认的 tkinter 安装是不可能的,因为 tcl 只在 undoing/redoing 操作时才考虑文本。它不关心标签。这就是为什么(在您的示例代码中),如果您尝试重做撤消,文本不会突出显示。重写了 tcl 如何处理 undos/redos suggested in the past,但我找不到关于这些建议的任何进展。

可能可以安装类似 this but it is out of my expertise. In there 的东西,它讨论撤消机制和标签,我认为这就是您要找的东西。更具体地说,修改后的文本小部件添加了一个新的 immediately 参数:“如果指定选项 -immediately,则将立即推送分隔符;如果标记或标签操作应该分隔,则这是必需的。”。 =18=] 因此,它可能会在标记添加之间添加分隔符。

正如 TheLizzard 所说,Text 小部件的内置 undo/redo 机制无法做到这一点。但是,您可以实施自己的 undo/redo 格式设置机制。

想法是有一个撤销堆栈和一个重做堆栈。我对两者都使用了列表。当您调用撤消函数时,您获取撤消堆栈中的最后一项,将其附加到重做堆栈并使用该项目中的信息来撤消修改。它通常是一个 (undo_args, redo_args) 元组。然后对于重做函数,你做同样的事情,但从重做堆栈中取出项目并将其附加到撤消堆栈。

但是,您需要在每次发生修改时将所需的 (undo_args, redo_args) 附加到撤消堆栈,并清除重做堆栈。 为此,我从 Brayn Oakley 的回答 (关于自动更新行号)中改编了代理机制。

每次 Text 小部件发生修改时,都会调用 _proxy 方法。如果此修改是文本插入、文本删除、标记添加或标记删除,则 (undo_args, redo_args) 元组附加到撤消堆栈。因此可以通过调用 self.tk.call((self._orig,) + undo_args) 撤消修改并使用 self.tk.call((self._orig,) + redo_args).

重做
import tkinter as tk

class MyText(tk.Text):

    def __init__(self, master=None, **kw):
        tk.Text.__init__(self, master, undo=False, **kw)
        self._undo_stack = []
        self._redo_stack = []
        # create proxy
        self._orig = self._w + "_orig"
        self.tk.call("rename", self._w, self._orig)
        self.tk.createcommand(self._w, self._proxy)


    def _proxy(self, *args):
        if args[0] in ["insert", "delete"]:
            if args[1] == "end":
                index = self.index("end-1c")
            else:
                index = self.index(args[1])
            if args[0] == "insert":
                undo_args = ("delete", index, "{}+{}c".format(index, len(args[2])))
            else:  # args[0] == "delete":
                undo_args = ("insert", index, self.get(*args[:1]))
            self._redo_stack.clear()
            self._undo_stack.append((undo_args, args))
        elif args[0] == "tag":
            if args[1] in ["add", "remove"] and args[2] != "sel":
                indexes = tuple(self.index(ind) for ind in args[3:])
                undo_args = ("tag", "remove" if args[1] == "add" else "add", args[2]) + indexes
                self._redo_stack.clear()
                self._undo_stack.append((undo_args, args))
        result = self.tk.call((self._orig,) + args)
        return result

    def undo(self):
        if not self._undo_stack:
            return
        undo_args, redo_args = self._undo_stack.pop()
        self._redo_stack.append((undo_args, redo_args))
        self.tk.call((self._orig,) + undo_args)

    def redo(self):
        if not self._redo_stack:
            return
        undo_args, redo_args = self._redo_stack.pop()
        self._undo_stack.append((undo_args, redo_args))
        self.tk.call((self._orig,) + redo_args)


root = tk.Tk()

text = MyText(root, width=65, height=20, font="consolas 14")
text.pack()

undo_button = tk.Button(root, text="Undo", command=text.undo)
undo_button.pack()

redo_button = tk.Button(root, text="Redo", command=text.redo)
redo_button.pack()

text.insert('end', "Hello world")
text.tag_add('test', '1.0', '1.5')
text.tag_config('test', background='yellow')
root.mainloop()