在 Tkinter Entry 小部件中撤消和重做?

发布于 2024-10-02 06:31:53 字数 295 浏览 8 评论 0原文

有没有办法在 Tkinter Entry 小部件中添加撤消重做功能,或者我必须使用单行Text小部件对于这种类型的功能?

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

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

Is there a way to add undo and redo capabilities in Tkinter Entry widgets or must I use single line Text widgets for this type of functionality?

If the latter, are there any tips I should follow when configuring a Text widget to act as an Entry widget?

Some features that might need tweaking include trapping the Return KeyPress, converting tab keypresses into a request to change focus, and removing newlines from text being pasted from the clipboard.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

烟酒忠诚 2024-10-09 06:31:53

检查 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

Check the Tkinter Custom Entry. I have added Cut, Copy, Paste context menu, and undo redo functions.

# -*- 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
三生池水覆流年 2024-10-09 06:31:53

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

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

您还可以设置保存条目状态的事件,例如 Return 的压力。

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

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

注意根据需要设置锁。

Disclaimer: these are just thoughts that come into my mind on how to implement it.

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]

Run a thread that every X seconds (0.5?) save the state of the entry:

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

You can also set up events that save the Entry's status too, such as the pressure of Return.

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

or

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

Beware to set locks as needed.

白芷 2024-10-09 06:31:53

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

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

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

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

Update on using this method for Undo/Redo:

I am creating a GUI with lot of frames and each contains at least ten or more 'entry' widgets.
I used the History class and created one history object for each entry field that I had. I was able to store all entry widgets values in a list as done here.
I am using 'trace' method attached to each entry widget which will call 'add' function of History class and store each changes. In this way, I was able to do it without running any thread separately.
But the biggest drawback of doing this is, we cannot do multiple undos/redos with this method.

Issue:
When I trace each and every change of the entry widget and add that to the list, it also 'traces' the change that happens when we 'undo/redo' which means we cannot go more one step back. once u do a undo, it is a change that will be traced and hence the 'undo' value will be added to the list at the end. Hence this is not the right method.

Solution:
Perfect way to do this is by creating two stacks for each entry widget. One for 'Undo' and one for 'redo'. When ever there is a change in entry, push that value into the undo stack. When user presses undo, pop the last stored value from the undo stack and importantly push this one to the 'redo stack'. hence, when the user presses redo, pop the last value from redo stack.

瑾兮 2024-10-09 06:31:53

基于 Evgeny 使用自定义 Entry 的回答,但添加了一个 tkinter StringVar 以及对小部件的跟踪,以更准确地跟踪何时对其内容进行更改(而不仅仅是当任何内容发生更改时)按下按键,这似乎将空的撤消/重做项目添加到堆栈中)。还使用 Python 双端队列添加了最大深度。

如果我们通过代码而不是键盘输入来更改 Entry 的内容,我们可以暂时禁用跟踪(例如,请参阅下面的 undo 方法)。

代码:


class CEntry(tk.Entry):
    def __init__(self, master, **kw):
        super().__init__(master=master, **kw)
        self._undo_stack = deque(maxlen=100)
        self._redo_stack = deque(maxlen=100)
        self.bind("<Control-z>", self.undo)
        self.bind("<Control-y>", self.redo)
        # traces whenever the Entry's contents are changed
        self.tkvar = tk.StringVar()
        self.config(textvariable=self.tkvar)
        self.trace_id = self.tkvar.trace("w", self.on_changes)
        self.reset_undo_stacks()
        # USE THESE TO TURN TRACE OFF THEN BACK ON AGAIN
        # self.tkvar.trace_vdelete("w", self.trace_id)
        # self.trace_id = self.tkvar.trace("w", self.on_changes)

    def undo(self, event=None):  # noqa
        if len(self._undo_stack) <= 1:
            return
        content = self._undo_stack.pop()
        self._redo_stack.append(content)
        content = self._undo_stack[-1]
        self.tkvar.trace_vdelete("w", self.trace_id)
        self.delete(0, tk.END)
        self.insert(0, content)
        self.trace_id = self.tkvar.trace("w", self.on_changes)

    def redo(self, event=None):  # noqa
        if not self._redo_stack:
            return
        content = self._redo_stack.pop()
        self._undo_stack.append(content)
        self.tkvar.trace_vdelete("w", self.trace_id)
        self.delete(0, tk.END)
        self.insert(0, content)
        self.trace_id = self.tkvar.trace("w", self.on_changes)

    def on_changes(self, a=None, b=None, c=None):  # noqa
        self._undo_stack.append(self.tkvar.get())
        self._redo_stack.clear()

    def reset_undo_stacks(self):
        self._undo_stack.clear()
        self._redo_stack.clear()
        self._undo_stack.append(self.tkvar.get())

Based on Evgeny's answer with a custom Entry, but added a tkinter StringVar with a trace to the widget to more accurately track when changes are made to its contents (not just when any Key is pressed, which seemed to add empty Undo/Redo items to the stack). Also added a max depth using a Python deque.

If we're changing the contents of the Entry via code rather than keyboard input, we can temporarily disable the trace (e.g. see in the undo method below).

Code:


class CEntry(tk.Entry):
    def __init__(self, master, **kw):
        super().__init__(master=master, **kw)
        self._undo_stack = deque(maxlen=100)
        self._redo_stack = deque(maxlen=100)
        self.bind("<Control-z>", self.undo)
        self.bind("<Control-y>", self.redo)
        # traces whenever the Entry's contents are changed
        self.tkvar = tk.StringVar()
        self.config(textvariable=self.tkvar)
        self.trace_id = self.tkvar.trace("w", self.on_changes)
        self.reset_undo_stacks()
        # USE THESE TO TURN TRACE OFF THEN BACK ON AGAIN
        # self.tkvar.trace_vdelete("w", self.trace_id)
        # self.trace_id = self.tkvar.trace("w", self.on_changes)

    def undo(self, event=None):  # noqa
        if len(self._undo_stack) <= 1:
            return
        content = self._undo_stack.pop()
        self._redo_stack.append(content)
        content = self._undo_stack[-1]
        self.tkvar.trace_vdelete("w", self.trace_id)
        self.delete(0, tk.END)
        self.insert(0, content)
        self.trace_id = self.tkvar.trace("w", self.on_changes)

    def redo(self, event=None):  # noqa
        if not self._redo_stack:
            return
        content = self._redo_stack.pop()
        self._undo_stack.append(content)
        self.tkvar.trace_vdelete("w", self.trace_id)
        self.delete(0, tk.END)
        self.insert(0, content)
        self.trace_id = self.tkvar.trace("w", self.on_changes)

    def on_changes(self, a=None, b=None, c=None):  # noqa
        self._undo_stack.append(self.tkvar.get())
        self._redo_stack.clear()

    def reset_undo_stacks(self):
        self._undo_stack.clear()
        self._redo_stack.clear()
        self._undo_stack.append(self.tkvar.get())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文