Python: Tkinter bind("<<Modified>>") works only once

Question:

so i was trying out tkinter Text widget..
and made a small code that highlights the word "print" in the text..
CODE:

from tkinter import *
def get(*arg):
    print("Highlighting...")
    text.tag_remove('found', '1.0', END)
    s = "print"
    if s:
        idx = '1.0'
        while 1:
            idx = text.search(s, idx, nocase=1, stopindex=END)
            if not idx: break
            lastidx = '%s+%dc' % (idx, len(s))
            text.tag_add('found', idx, lastidx)
            idx = lastidx
            text.see(idx)  # Once found, the scrollbar automatically scrolls to the text
            text.bind('<<Modified>>', get)
            break
        text.tag_config('found', foreground='green')

root = Tk()
text = Text(root)
text.grid()
root.bind('<<Modified>>', get)
root.mainloop()

In this, the root.bind('<<Modified>>', get) works only once.
i checked it with the line print("Highlighting...") it just worked once even when i enter thousands of characters..

Am i doing something wrong?
or my approach is bad?

SPECS:
OS: Windows 7 SP1 (i will upgrade only to windows 11)
Python: Python 3.8.10
Arch: Intel x86 

Answers:

Try this:

from tkinter import *

# From: https://stackoverflow.com/a/40618152/11106801
class CustomText(Text):
    def __init__(self, *args, **kwargs):
        """A text widget that report on internal widget commands"""
        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


def get(event):
    text.tag_remove("found", "1.0", "end")
    string = "print"
    if string:
        idx = "1.0"
        while True:
            idx = text.search(string, idx, nocase=1, stopindex=END)
            if not idx:
                break
            lastidx = f"{idx}+{len(string)}c"
            text.tag_add("found", idx, lastidx)
            idx = lastidx
        text.tag_config("found", foreground="green")


root = Tk()
text = CustomText(root)
text.grid()
root.bind("<<TextModified>>", get)
root.mainloop()

This uses the CustomText that @BryanOakley created here.

Also I had to remove the break from the end of your while True loop because it stopped searching for "print".

Answered By: TheLizzard

Found an even simpler way in the excellent New Mexico Tech Tkinter 8.5 reference, you can reset the modified flag with the .edit_modified() method. See example:

import tkinter as tk

root = tk.Tk()
textbox = tk.Text(root, width=30, height=15, padx=10, pady=5, wrap='word')
textbox.pack(pady=10, padx=10)

# Bind changes in textbox to callback function.
def modify_callback(event):
    print('modify_callback')
    textbox.edit_modified(False)    # Reset the modified flag for text widget.
textbox.bind('<<Modified>>', modify_callback)

# Insert text, modifies text widget.
textbox.insert('end', 'Example text.')

root.mainloop()

For some reason the function modify_callback() is called twice for each change in the text widget but I don’t know why.

Description in the reference:

.edit_modified(arg=None)

Queries, sets, or clears the modified flag. This flag is used to track whether the contents of the widget have been changed. For example, if you are implementing a text editor in a Text widget, you might use the modified flag to determine whether the contents have changed since you last saved the contents to a file.

When called with no argument, this method returns True if the modified flag has been set, False if it is clear. You can also explicitly set the modified flag by passing a True value to this method, or clear it by passing a False value.

Any operation that inserts or deletes text, whether by program actions or user actions, or an undo or redo operation, will set the modified flag.

Answered By: figbeam
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.