【问题标题】:Undo and Redo in an Tkinter Entry widget?在 Tkinter 条目小部件中撤消和重做?
【发布时间】:2011-05-08 00:19:20
【问题描述】:

有没有办法在 Tkinter Entry 小部件中添加 undoredo 功能,或者我必须使用单行 Text 小部件来实现此类功能?

如果是后者,在将 Text 小部件配置为 Entry 小部件时,我应该遵循哪些提示?

一些可能需要调整的功能包括捕获Return KeyPress、将制表键按键转换为更改焦点的请求以及从剪贴板粘贴的文本中删除换行符。

【问题讨论】:

    标签: python user-interface text tkinter tkinter-entry


    【解决方案1】:

    检查 Tkinter 自定义条目。我添加了剪切、复制、粘贴上下文菜单和撤消重做功能。

    # -*- coding: utf-8 -*-
    from tkinter import *
    
    
    class CEntry(Entry):
        def __init__(self, parent, *args, **kwargs):
            Entry.__init__(self, parent, *args, **kwargs)
    
            self.changes = [""]
            self.steps = int()
    
            self.context_menu = Menu(self, tearoff=0)
            self.context_menu.add_command(label="Cut")
            self.context_menu.add_command(label="Copy")
            self.context_menu.add_command(label="Paste")
    
            self.bind("<Button-3>", self.popup)
    
            self.bind("<Control-z>", self.undo)
            self.bind("<Control-y>", self.redo)
    
            self.bind("<Key>", self.add_changes)
    
        def popup(self, event):
            self.context_menu.post(event.x_root, event.y_root)
            self.context_menu.entryconfigure("Cut", command=lambda: self.event_generate("<<Cut>>"))
            self.context_menu.entryconfigure("Copy", command=lambda: self.event_generate("<<Copy>>"))
            self.context_menu.entryconfigure("Paste", command=lambda: self.event_generate("<<Paste>>"))
    
        def undo(self, event=None):
            if self.steps != 0:
                self.steps -= 1
                self.delete(0, END)
                self.insert(END, self.changes[self.steps])
    
        def redo(self, event=None):
            if self.steps < len(self.changes):
                self.delete(0, END)
                self.insert(END, self.changes[self.steps])
                self.steps += 1
    
        def add_changes(self, event=None):
            if self.get() != self.changes[-1]:
                self.changes.append(self.get())
                self.steps += 1
    

    【讨论】:

    • 这应该是选择的答案。在这种情况下,使用线程有点过度设计......
    【解决方案2】:

    免责声明:这些只是我想到的关于如何实施它的想法。

    class History(object):
    
        def __init__(self):
            self.l = ['']
            self.i = 0
    
        def next(self):
            if self.i == len(self.l):
                return None
            self.i += 1
            return self.l[self.i]
    
        def prev(self):
            if self.i == 0:
                return None
            self.i -= 1
            return self.l[self.i]
    
        def add(self, s):
            del self.l[self.i+1:]
            self.l.append(s)
            self.i += 1
    
        def current(self):
            return self.l[self.i]
    

    运行一个线程,每隔 X 秒(0.5?)保存条目的状态:

    history = History()
    ...
    history.add(stringval.get())
    

    你也可以设置保存Entry状态的事件,比如Return的压力。

    prev = history.prev()
    if prev is not None:
        stringvar.set(prev)
    

    next = history.next()
    if next is not None:
        stringvar.set(next)
    

    注意根据需要设置锁。

    【讨论】:

    • 在这里使用线程是完全没有必要的。小部件可以在更改时轻松通知您。
    • @Bryan Oakley:不是真的,这只是解决问题的不同方法,是众多解决方案之一。
    • @MG:好计划:捕获对小部件的每次更改,将总值存储在历史列表中,撤消时,将历史指针移回一级并获取其值。在重做时,将历史指针向前移动一级并获取它的值。存储用户更改时,请在每次用户更改后清除重做历史记录。我不是线程监控的粉丝——我需要的是等效于值更改事件(或者可能是 trace_var()),以便我可以跟踪确切的更改。我发了一个关于这种事件存在的问题:stackoverflow.com/questions/4165164
    • @Malcom:我知道这不是一个好的解决方案,但正如我所说的只是一个想法,因为起初我不喜欢跟踪每个变量的变化,但在事件循环中你可以设置超时。顺便说一句,它必须只是关于如何实现History 的跟踪,这是最有趣的部分。看看History类的变化,扩展list不是个好主意。
    • @MG:你的思考过程对我帮助很大。谢谢。
    【解决方案3】:

    关于使用此方法进行撤消/重做的更新:

    我正在创建一个包含许多框架的 GUI,每个框架都包含至少十个或更多“入口”小部件。 我使用了 History 类,并为我拥有的每个输入字段创建了一个历史对象。我能够将所有条目小部件值存储在列表中,如此处所做的那样。 我正在使用附加到每个条目小部件的“跟踪”方法,该方法将调用历史类的“添加”功能并存储每个更改。通过这种方式,我能够在不单独运行任何线程的情况下做到这一点。 但是这样做的最大缺点是,我们不能用这种方法进行多次撤消/重做。

    问题: 当我跟踪条目小部件的每一次更改并将其添加到列表中时,它还会“跟踪”当我们“撤消/重做”时发生的更改,这意味着我们不能再退一步了。一旦您进行撤消,它就是将被跟踪的更改,因此“撤消”值将被添加到最后的列表中。因此,这不是正确的方法。

    解决方案: 完美的方法是为每个条目小部件创建两个堆栈。一个用于“撤消”,一个用于“重做”。当条目发生变化时,将该值推入撤消堆栈。当用户按下撤消时,从撤消堆栈中弹出最后存储的值,重要的是将此值推送到“重做堆栈”。因此,当用户按下重做时,从重做堆栈中弹出最后一个值。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 2021-07-29
      • 2011-03-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-01
      相关资源
      最近更新 更多