I used the following solution from this earlier question to track whether a tkinter text widget has been modified:
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()
However, after integrating it into my own code, I discovered a problem in both my code and the bare solution above. If you attempt to paste into the text widget, the entire program will crash. The terminal gives the following error:
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"
(I removed identifying information in the file paths above, but otherwise that's copied straight from the console.)
After a bit of testing, it turns out that you can paste without crashing the program as long as you have some text selected/highlighted when you paste.
This behavior does not occur in the unmodified text widget; you can paste without selecting text beforehand as normal, no crashing.
So, my question is, how can I modify the above solution such that pasting does not crash it? I am not familiar with Tcl/Tk, so I don't know how to start investigating this. This is in Python 3.6.3.
(I also would have contacted the original author of this code directly, but it turns out there's no private messaging feature here and I can't leave a comment as a new user.)
Edit: I have working code now, though the solution feels like it's held together with duct tape instead of actually addressing the underlying problem. I altered the CustomText class as follows:
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"
By binding "<<Paste>>"
to a function that ends in return "break"
, I can stop the widget from passing the event along to whatever is causing the crash, and the modification event still fires as expected. Amusingly, I can code my own paste function that precedes the return "break"
line, and it functions as I believe it should have from the start.
Still no idea what causes this issue, except that it's apparently a Windows problem (thanks for checking it out, Bryan Oakley).