如何使用不会在 copy/paste 上崩溃的代理来跟踪 tkinter 文本小部件是否已被修改?
How do I track whether a tkinter text widget has been modified using a proxy that doesn't crash on copy/paste?
我使用了 中的以下解决方案来跟踪 tkinter 文本小部件是否已被修改:
import tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()
但是,将其集成到我自己的代码中后,我发现我的代码和上面的裸解决方案都有问题。如果您试图粘贴到文本小部件中,整个程序将崩溃。终端给出以下错误:
Traceback (most recent call last):
File "Test.py", line 39, in <module>
root.mainloop()
File "AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1277, in mainloop
self.tk.mainloop(n)
File "Test.py", line 16, in _proxy
result = self.tk.call(cmd)
_tkinter.TclError: text doesn't contain any characters tagged with "sel"
(我删除了上面文件路径中的识别信息,但其他信息是直接从控制台复制的。)
经过一些测试后发现,只要在粘贴时有一些文本 selected/highlighted,就可以 粘贴而不会导致程序崩溃。
此行为不会发生在未修改的文本小部件中;您可以像往常一样在不事先选择文本的情况下粘贴,不会崩溃。
所以,我的问题是,我怎样才能修改上面的解决方案,使粘贴不会崩溃?我不熟悉Tcl/Tk,所以我不知道如何开始调查这个。这是在 Python 3.6.3.
(我也想直接联系这段代码的原作者,结果发现这里没有私信功能,我不能作为新用户发表评论。)
编辑:我现在有了工作代码,尽管解决方案感觉像是用胶带固定在一起,而不是真正解决潜在的问题。我改变了 CustomText class 如下:
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
self.bind("<<Paste>>", self.Paste)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
def Paste(self, event):
tagranges = self.tag_ranges("sel")
if tagranges:
selectionstart = self.index(tk.SEL_FIRST)
selectionend = self.index(tk.SEL_LAST)
self.delete(selectionstart, selectionend)
self.mark_set(tk.INSERT, selectionstart)
self.insert(tk.INSERT, root.clipboard_get())
self.see(tk.INSERT)
return "break"
通过将 "<<Paste>>"
绑定到以 return "break"
结尾的函数,我可以阻止小部件将事件传递给导致崩溃的任何原因,并且修改事件仍会按预期触发。有趣的是,我可以在 return "break"
行之前编写自己的粘贴函数,并且它的功能从一开始就符合我的预期。
仍然不知道是什么原因导致了这个问题,只是它显然是一个 Windows 问题(感谢您查看,Bryan Oakley)。
您可以绑定事件 "<Control-v>"
和 "<Control-c>"
。
这意味着如果用户使用其中任何一个,您可以设置一个特殊条件来以不同方式处理它。
from tkinter import *
root = Tk()
text = Text(root)
text.pack()
def callback(*args):
print(True)
text.bind("<Control-v>", callback)
root.mainloop()
这更像是一种变通方法而不是解决方案,因为这会让您面临我们不知道的其他键盘组合的潜在崩溃。
除非有人知道更好的解决方案,否则我会在这里留下我的答案。
我使用了
import tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()
但是,将其集成到我自己的代码中后,我发现我的代码和上面的裸解决方案都有问题。如果您试图粘贴到文本小部件中,整个程序将崩溃。终端给出以下错误:
Traceback (most recent call last):
File "Test.py", line 39, in <module>
root.mainloop()
File "AppData\Local\Programs\Python\Python36-32\lib\tkinter\__init__.py", line 1277, in mainloop
self.tk.mainloop(n)
File "Test.py", line 16, in _proxy
result = self.tk.call(cmd)
_tkinter.TclError: text doesn't contain any characters tagged with "sel"
(我删除了上面文件路径中的识别信息,但其他信息是直接从控制台复制的。)
经过一些测试后发现,只要在粘贴时有一些文本 selected/highlighted,就可以 粘贴而不会导致程序崩溃。
此行为不会发生在未修改的文本小部件中;您可以像往常一样在不事先选择文本的情况下粘贴,不会崩溃。
所以,我的问题是,我怎样才能修改上面的解决方案,使粘贴不会崩溃?我不熟悉Tcl/Tk,所以我不知道如何开始调查这个。这是在 Python 3.6.3.
(我也想直接联系这段代码的原作者,结果发现这里没有私信功能,我不能作为新用户发表评论。)
编辑:我现在有了工作代码,尽管解决方案感觉像是用胶带固定在一起,而不是真正解决潜在的问题。我改变了 CustomText class 如下:
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
self.bind("<<Paste>>", self.Paste)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
def Paste(self, event):
tagranges = self.tag_ranges("sel")
if tagranges:
selectionstart = self.index(tk.SEL_FIRST)
selectionend = self.index(tk.SEL_LAST)
self.delete(selectionstart, selectionend)
self.mark_set(tk.INSERT, selectionstart)
self.insert(tk.INSERT, root.clipboard_get())
self.see(tk.INSERT)
return "break"
通过将 "<<Paste>>"
绑定到以 return "break"
结尾的函数,我可以阻止小部件将事件传递给导致崩溃的任何原因,并且修改事件仍会按预期触发。有趣的是,我可以在 return "break"
行之前编写自己的粘贴函数,并且它的功能从一开始就符合我的预期。
仍然不知道是什么原因导致了这个问题,只是它显然是一个 Windows 问题(感谢您查看,Bryan Oakley)。
您可以绑定事件 "<Control-v>"
和 "<Control-c>"
。
这意味着如果用户使用其中任何一个,您可以设置一个特殊条件来以不同方式处理它。
from tkinter import *
root = Tk()
text = Text(root)
text.pack()
def callback(*args):
print(True)
text.bind("<Control-v>", callback)
root.mainloop()
这更像是一种变通方法而不是解决方案,因为这会让您面临我们不知道的其他键盘组合的潜在崩溃。
除非有人知道更好的解决方案,否则我会在这里留下我的答案。