Python tkinter 文本小部件“修改”事件似乎无法正确触发

问题描述 投票:0回答:2

我想监视 tkinter

Text
小部件中的文本何时被修改,以便用户可以保存他们输入的任何新数据。然后按“保存”我想重置它。

我将

Text
小部件的
<<Modified>>
事件绑定到一个函数,以便对文本进行任何更改都会将“保存”按钮从
'disabled'
更新为
'normal'
状态。点击“保存”按钮后,我运行了一个函数,该函数重置
modified
标志并再次禁用“保存”按钮,直到进行进一步的更改。

但我发现它似乎只触发了一次事件。点击“保存”并没有将按钮重置为

'disabled'
状态,并且在第一次之后编辑文本似乎也没有影响“保存”按钮的状态。

下面是一个最小的示例,显示标志似乎没有被重置。

import tkinter as tk

root = tk.Tk()

def text_modified(event=None):
    status_label.config(text="Modified = True")

def reset():
    if not text_widget.edit_modified():
        return
    status_label.config(text="Modified = False")
    text_widget.edit_modified(False)

text_widget = tk.Text(root, width=30, height=5)
text_widget.pack()
text_widget.bind("<<Modified>>", text_modified)

status_label = tk.Label(root, text="Modified = False")
status_label.pack()

reset_btn = tk.Button(root, text="Reset", command=reset)
reset_btn.pack()

root.mainloop()
python python-3.x tkinter events tkinter-text
2个回答
3
投票

事实证明,将

<<Modified>>
事件绑定到函数意味着该函数不会在
Text
小部件文本更改时运行,而是在
modified
标志更改时运行 - 无论它更改为
True
还是更改为
False
。因此,我的“保存”按钮正在保存数据,禁用自身,并将
modified
标志重置为
False
,并且此标志更改触发了
<<Modified>>
事件,该事件绑定到再次启用“保存”按钮的函数。

这是一个最小的示例,显示了正在发生的事情。我们只需要调整我们绑定

<<Modified>>
事件的函数,以便它也能处理
modified
False
的情况:

import tkinter as tk

root = tk.Tk()

def modified_flag_changed(event=None):
    if text_widget.edit_modified():
        status_label.config(text="Modified = True")
        print("Text modified")
    else:
        print("Modified flag changed to False")

def reset():
    if not text_widget.edit_modified():
        print("Doesn't need resetting")
        return
    status_label.config(text="Modified = False")
    text_widget.edit_modified(False)
    print('Reset')

text_widget = tk.Text(root, width=30, height=5)
text_widget.pack()
text_widget.bind("<<Modified>>", modified_flag_changed)

status_label = tk.Label(root, text="Modified = False")
status_label.pack()

reset_btn = tk.Button(root, text="Reset", command=reset)
reset_btn.pack()

root.mainloop()

0
投票

使用 tkinter 解决像“...监视 tkinter

Text
小部件中的文本何时被修改...”这样的基本问题可能具有挑战性。

文本小部件的内置事件类型可以实现一些功能,但并不完全是我们想要的。首先,有

<Key>
<KeyPress>
事件类型,但这些事件类型会导致在按键修改文本框内容之前运行回调,这在大多数用例中是无用的。在回调中执行
.get("1.0", "end")
会产生旧内容,而不会因按键而造成修改。其次,有
<KeyRelease>
事件类型,但在这种类型中,对于大多数用例来说,回调运行得太晚,并且用户的键盘打字体验可能不够。第三,有
<<Modified>>
事件类型,但此事件类型仅导致回调有时运行,正如您在问题中所描述的那样。

一个解决方案可能是实现两个回调链,该链包括:第一个回调处理

<Key>
事件并执行指向第二个回调的
after_idle
命令;以及处理修改后的文本的第二个回调。

import tkinter as tk
from tkinter.scrolledtext import ScrolledText


class TextBox(ScrolledText):
    def __init__(self, *args, **kwargs) -> None:
        ScrolledText.__init__(self, *args, **kwargs)

        # The next two lines are needed for the two callbacks.
        self._execution_id = ""  # this one is needed for the first callback
        self.edit_modified(False)  # and this one is need for the second callback

        self.bind("<Key>", self.on_key)  # Binding to <KeyPress> may work too.

    # First callback:
    def on_key(self, event: tk.Event) -> None:
        # Firstly, we start by cancelling any scheduled executions of the second
        # callback that have not yet been executed.
        if self._execution_id:
            self.after_cancel(self._execution_id)
        # Secondly, we schedule an execution of the second callback using the
        # `after_idle` command. (see https://tcl.tk/man/tcl8.5/TclCmd/after.htm#M9)
        self._execution_id = self.after_idle(self.on_modified, event)

    # Second callback:
    def on_modified(self, event: tk.Event) -> None:
        # Firstly, we use the built-in `edit_modified` method to check if the
        # content of the text widget has changed. (see https://tcl.tk/man/tcl8.5/TkCmd/text.htm#M93)
        # Otherwise, we abort the second callback. This is necessary to filter out
        # key presses that do not change the contents of the Text widget, such as
        # pressing arrow keys, Ctrl, Alt, etc.
        if not self.edit_modified():
            return
        # Secondly, we do whatever we want with the content of the Text widget.
        content = self.get("1.0", "end")
        print("! on_modified:", repr(content))
        # Thirdly, we set the built-in modified flag of the Text widget to False.
        self.edit_modified(False)


if __name__ == "__main__":
    root = tk.Tk()
    text = TextBox(root, height=10, width=50)
    text.pack(fill="both", expand=True, padx=10, pady=10)
    text.focus_set()
    root.mainloop()

这个两个回调链 (C2C) 方法适用于基本文本编辑按键,例如按字母键。重要的是(至少对于我的用例),当您在文本小部件中按 Ctrl+V 某些内容时(如果在文本小部件中设置了文本选择,则独立),此方法可以正常工作,不会引发丑陋的异常。我尝试过其他方法(自称为“Tk voodoo”),这些方法使用

rename
命令并通过代理生成虚拟事件,但这些方法无法处理 Ctrl+V,并且我的源很快变得难以理解的混乱(或在至少比平常快!)。

这种 C2C 方法需要很少的代码,而且它使用 tkinter 的公共方法,而不需要太深入 tkinter 的内部机制,这非常好。缺点可能是它需要

self._execution_id
属性,这可能会污染 TextBox 类的命名空间。也许,有更好的方法。

© www.soinside.com 2019 - 2024. All rights reserved.