Andrey A
Andrey A

Reputation: 13

TkInter: how to get actual text modification?

I want to know where and what was changed by user in tkinter's Text widget.

I've found how to get that text was somehow modified by using <<Modified>>event but I can't get actual changes:

from tkinter import *

def reset_modified():
    global resetting_modified
    resetting_modified = True
    text.tk.call(text._w, 'edit', 'modified', 0)
    resetting_modified = False

def on_change(ev=None):
    if resetting_modified: return
    print ("Text now:\n%s" % text.get("1.0", END))
    if False: # ???? 
        print ("Deleted [deleted substring] from row %d col %d")
    if False: # ????
        print ("Inserted [inserted substring] at row %d col %d")
    reset_modified()

resetting_modified = False
root = Tk()
text = Text(root)
text.insert(END, "Hello\nworld")
text.pack()
text.bind("<<Modified>>", on_change)
reset_modified()
root.mainloop()

For example, if I select 'ello' part from "hello\nworld" in Text widget then I press 'E', then I want to see

"Deleted [ello] from row 0 col 1" followed by "Inserted [E] at row 0 col 1"

is it possible to get such changes (or at least their coordinates) or I have basically to diff text on each keystroke if I want to detect changes run time?

Upvotes: 0

Views: 893

Answers (2)

Bryan Oakley
Bryan Oakley

Reputation: 385980

Catching the low level inserts and deletes performed by the underlying tcl/tk code is the only good way to do what you want. You can use something like WidgetRedirector or you can do your own solution if you want more control.

Writing your own proxy command to catch all internal commands is quite simple, and takes just a few lines of code. Here's an example of a custom Text widget that prints out every internal command as it happens:

from __future__ import print_function
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)

        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):
        # this lets' tkinter handle the command as usual
        cmd = (self._orig, command) + args
        result = self.tk.call(cmd)

        # here we just echo the command and result
        print(command, args, "=>", result)

        # Note: returning the result of the original command
        # is critically important!
        return result

if __name__ == "__main__":
    root = tk.Tk()
    CustomText(root).pack(fill="both", expand=True)
    root.mainloop()

Upvotes: 1

Andrey A
Andrey A

Reputation: 13

After digging around, I've found that idlelib has WidgetRedirector which can redirect on inserted/deleted events:

from tkinter import *
from idlelib.WidgetRedirector import WidgetRedirector

def on_insert(*args):
    print ("INS:", text.index(args[0]))
    old_insert(*args)

def on_delete(*args):
    print ("DEL:", list(map(text.index, args)))
    old_delete(*args)
root = Tk()
text = Text(root)
text.insert(END, "Hello\nworld")
text.pack()
redir = WidgetRedirector(text)
old_insert=redir.register("insert", on_insert)
old_delete=redir.register("delete", on_delete)
root.mainloop()

Though it seems hacky. Is there a more natural way?

Upvotes: 1

Related Questions