【问题标题】:Tkinter - Undo and Redo the formatting's made to a text widgetTkinter - 撤消和重做文本小部件的格式设置
【发布时间】:2021-07-29 15:53:44
【问题描述】:

有什么方法可以撤消和重做对 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 中实现这一点?

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

【问题讨论】:

  • 我认为没有办法。你可能想看看this
  • 我认为undoredo 背后的想法是回归事件。我猜,通过代码突出显示不被认为是用户完成的事件。
  • 有办法。所做的每个动作都必须注册到列表或某事中,甚至标记文本。所以标记文本的函数应该在列表中附加一些东西,所以在撤消时执行添加的东西或某事
  • 我认为链接文档中的第一行回答了这个问题。
  • @CoolCloud 好的,我会的。首先,我会做更多的谷歌搜索来尝试找到解决方法。

标签: python tkinter undo-redo


【解决方案1】:

就像 TheLizzard 所说的,这对于 Text 小部件的内置撤消/重做机制是不可能的。但是,您可以实现自己的撤消/重做机制来处理格式化。

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

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

每次修改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()

【讨论】:

  • 很好的答案,但为什么你有.pop(-1) 而不仅仅是.pop()
  • @TheLizzard 是的,我忘记了 -1 是 pop 中的默认索引
  • 非常感谢您的帮助。我真的惊呆了!我很难选择我应该接受哪个答案,因为他们都帮了很多忙。再次感谢大家的帮助!
  • @Lenovo360 这个答案更好。对我来说,你必须安装东西,然后不能保证它会起作用。这个答案更简单、更干净。
  • @TheLizzard:没问题!我已经通过您的回答了解了撤消和重做机制实际上是如何工作的。非常感谢您对编程初学者的巨大帮助!我赞成您的回答,因为您花时间帮助我!非常感谢!
【解决方案2】:

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

也许可以安装this 之类的东西,但这不是我的专业知识。在there 中,它谈到了撤消机制和标签,我认为这就是您要寻找的。更具体地说,修改后的文本小部件添加了一个新的immediately 参数:“如果指定了 -immediately 选项,则将立即推送分隔符;如果标记或标记操作应该被分隔,这是必需的。” 因此,它可能会在标签添加之间添加分隔符。

【讨论】:

  • immediatelyText() 还是edit_separator() 的参数?我尝试对它们都使用immediately 参数,但它给出了错误unknown option "-immediately"
  • @Lenovo360 只有在您安装了that revised tkinter text widget 时才有效。你将不得不使用<tk.Text widget>.tk.call(<tk.Text widget>._w, "edit", "separator", "immediately")
猜你喜欢
  • 2011-05-08
  • 2023-03-12
  • 2011-03-09
  • 2016-10-28
  • 2021-06-16
  • 2021-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多