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"
.
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.
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
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"
.
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.