Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Tk text tag bind issue with Tcl/Tk 8.6

97 views
Skip to first unread message

Andy Maleh

unread,
Nov 6, 2021, 12:11:10 PM11/6/21
to
I reported this issue at the ActiveState forum and Ruby Tk project:

https://community.activestate.com/t/tk-text-tag-bind-issue-with-activetcl-8-6-occurs-when-using-tk-in-both-ruby-and-python/7665

https://github.com/ruby/tk/issues/40

Basically, using the tk::text widget, I bind '<KeyPress>' to a tag called 'all' covering '1.0' till 'end'. The goal is to track insert mark movement via arrow keys (and page-up/page-down/home/end). It works until I decide to highlight all text, delete it, and enter new text. Afterwards, the binding does not fire any events. But, after I enter a number of newlines, sometimes it starts firing again (unreliable as a workaround).

Help is appreciated. Or perhaps a recommendation for where else to report this if this is not the right place to mention.

Cheers.

Andy Maleh

Rich

unread,
Nov 6, 2021, 12:56:43 PM11/6/21
to
Andy Maleh <and...@gmail.com> wrote:
> Basically, using the tk::text widget, I bind '<KeyPress>' to a tag
> called 'all' covering '1.0' till 'end'. The goal is to track insert
> mark movement via arrow keys (and page-up/page-down/home/end). It
> works until I decide to highlight all text, delete it, and enter new
> text. Afterwards, the binding does not fire any events. But, after
> I enter a number of newlines, sometimes it starts firing again
> (unreliable as a workaround).

As an alternate, you can bind <KeyPress> on the widget itself and then
filter for just the keys you are interested in, ignoring the remainder.
Binding to the widget itself will mean the event should be unaffected
by changes within the text widget contents.

Andy Maleh

unread,
Nov 6, 2021, 2:28:38 PM11/6/21
to
On Saturday, November 6, 2021 at 12:56:43 PM UTC-4, Rich wrote:
Actually, what you suggest is what I tried first, but I noticed that none of the standard events (e.g. <KeyPress>, <ButtonPress>, etc...) work on the text widget directly. Only when binding to a tag, they work.

I guess that is another bug to report: binding to standard events like <KeyPress> does not work on text.

Andy Maleh

unread,
Nov 6, 2021, 2:32:18 PM11/6/21
to
Nevermind, I stand corrected. I just tried binding to '<KeyPress>' directly on the text widget in Python TKinter and it worked!

I guess it is a Ruby-only issue that I cannot bind to standard events on the text widget.

Thank you for the offered workaround.

Well, that just leaves the originally reported issue then.

Rich

unread,
Nov 6, 2021, 2:56:08 PM11/6/21
to
Binding to standard events work just fine on a text text widget:

$ rlwrap wish
% text .t
.t
% pack .t
% focus .t
% proc fire {args} {
puts "binding fired args = $args"
}
% bind .t <KeyPress> [list fire %K]
% binding fired args = a
binding fired args = b
binding fired args = c
binding fired args = d
binding fired args = e
binding fired args = Up
binding fired args = Right
binding fired args = Left
binding fired args = Down
binding fired args = Home
binding fired args = End
binding fired args = Prior
binding fired args = Next

After typing in the 'bind' command, I made the wish window current and
started typing at the wish window, you see the results above.

Rich

unread,
Nov 6, 2021, 3:01:34 PM11/6/21
to
Andy Maleh <and...@gmail.com> wrote:
> Nevermind, I stand corrected. I just tried binding to '<KeyPress>'
> directly on the text widget in Python TKinter and it worked!
>
> I guess it is a Ruby-only issue that I cannot bind to standard events
> on the text widget.

If so, then it is a bug for the Ruby community.

> Well, that just leaves the originally reported issue then.

Which is not an issue. Deleting all the text tagged with a tag means
the tag is no longer associated with any text in the widget. If you
don't retag newly inserted text with that tag, it remains associated
with no text in the widget.

And if the tag is not associated with any text in the widget, then the
bindings on that tag will not fire.



Andy Maleh

unread,
Nov 6, 2021, 5:20:34 PM11/6/21
to
On Saturday, November 6, 2021 at 3:01:34 PM UTC-4, Rich wrote:
I was able to resolve the Ruby issue by binding events directly on the text widget. It turned out Ruby had a special nicety whereby I do not have to surround the event by '<>'. Ruby automatically does it for me, so I could simply bind to 'KeyPress' instead of '<KeyPress>'.

Thank you again for your suggestion. It worked in Ruby too eventually!

About the original problem with tag bindings, I actually tried retagging on every text change and that didn't work.

The reason is because the 'all' tag covering '1.0' to 'end' already covers all text from beginning to end for the past, present ,and future of the text widget content. Retagging does nothing to alleviate the problem.

Here is a code example written in Python3 to illustrate:

```python
from tkinter import *
from tkinter import ttk

root = Tk()

text = Text(root)
text.grid()
text.insert('1.0', "Some giberish\nMore giberish\nNot well spelled giberish")
text.tag_add('all', '1.0', 'end')
def print_info(event):
print('key press')
print(event)

def changed(event):
print('modified')
text.edit_modified(0)
text.tag_remove('all', '1.0', 'end')
text.tag_add('all', '1.0', 'end')

text.bind('<<Modified>>', changed)
text.tag_bind('all', '<KeyPress>', print_info)

text.edit_modified(0)

root.mainloop()
```

Andy Maleh

unread,
Nov 6, 2021, 5:26:09 PM11/6/21
to
Here is a blockquoted version of the code (hoping it preserves the Python code indentation this time); it auto-retags on every modification to the text widget content:

> from tkinter import *
> from tkinter import ttk
>
> root = Tk()
>
> text = Text(root)
> text.grid()
> text.insert('1.0', "Some giberish\nMore giberish\nNot well spelled giberish")
> text.tag_add('all', '1.0', 'end')
> def print_info(event):
> print('key press')
> print(event)
>
> def changed(event):
> print('modified')
> text.edit_modified(0)
> text.tag_remove('all', '1.0', 'end')
> text.tag_add('all', '1.0', 'end')
> text.tag_bind('all', '<KeyPress>', print_info)
>
> text.bind('<<Modified>>', changed)
> text.tag_bind('all', '<KeyPress>', print_info)
>
> text.edit_modified(0)
>
> root.mainloop()

The strangest thing is after you enter a number of newlines after clearing text and entering new text, the tag binding sometimes suddenly starts firing again upon key presses.

Rich

unread,
Nov 6, 2021, 11:04:59 PM11/6/21
to
Andy Maleh <and...@gmail.com> wrote:
> On Saturday, November 6, 2021 at 3:01:34 PM UTC-4, Rich wrote:
>> Andy Maleh wrote:
>> > Nevermind, I stand corrected. I just tried binding to '<KeyPress>'
>> > directly on the text widget in Python TKinter and it worked!
>> >
>> > I guess it is a Ruby-only issue that I cannot bind to standard events
>> > on the text widget.
>> If so, then it is a bug for the Ruby community.
>> > Well, that just leaves the originally reported issue then.
>> Which is not an issue. Deleting all the text tagged with a tag means
>> the tag is no longer associated with any text in the widget. If you
>> don't retag newly inserted text with that tag, it remains associated
>> with no text in the widget.
>>
>> And if the tag is not associated with any text in the widget, then the
>> bindings on that tag will not fire.
>
> I was able to resolve the Ruby issue by binding events directly on
> the text widget. It turned out Ruby had a special nicety whereby I
> do not have to surround the event by '<>'. Ruby automatically does
> it for me, so I could simply bind to 'KeyPress' instead of
> '<KeyPress>'.

Ah, that would make a difference then.

>
> About the original problem with tag bindings, I actually tried
> retagging on every text change and that didn't work.
>
> The reason is because the 'all' tag covering '1.0' to 'end' already
> covers all text from beginning to end for the past, present ,and
> future of the text widget content. Retagging does nothing to
> alleviate the problem.

Nope. Line numbers added below manually:

01 $ rlwrap wish
02 % text .t
03 .t
04 % pack .t
05 % .t insert end "The quick brown fox jumped over the lazy dog\n"
06 % .t insert end "Mary had a little lamb, it's fleece was white as snow\n"
07 % .t tag add all 0.0 end
08 % .t tag ranges all
09 1.0 4.0
10 % .t delete 0.0 end
11 % .t tag ranges all
12 % .t insert end "The quick brown fox jumped over the lazy dog\n"
13 % .t insert end "Mary had a little lamb, it's fleece was white as snow\n"
14 % .t tag ranges all
15 %

At line 7, a tag named all is added covering the entirety of the text.
The 'tag ranges' command shows it is attached to the text in the
widget.

Line 10 deletes all the text.

Line 11 shows that the 'all' tag is no longer associated to any text
within the widget.

Lines 12 and 13 reinsert the text (I just reused lines 5 and 6).

Line 14 shows that the all tag is unchanged, it is associated with no
text within the widget. To have a tag binding fire that tag must be
associated with some text in the widget. A tag with no text associated
to it will not fire its binding scripts.

> Here is a code example written in Python3 to illustrate:
>
> ```python
> from tkinter import *
> from tkinter import ttk
>
> root = Tk()
>
> text = Text(root)
> text.grid()
> text.insert('1.0', "Some giberish\nMore giberish\nNot well spelled giberish")
> text.tag_add('all', '1.0', 'end')
> def print_info(event):
> print('key press')
> print(event)
>
> def changed(event):
> print('modified')
> text.edit_modified(0)
> text.tag_remove('all', '1.0', 'end')
> text.tag_add('all', '1.0', 'end')
>
> text.bind('<<Modified>>', changed)
> text.tag_bind('all', '<KeyPress>', print_info)
>
> text.edit_modified(0)
>
> root.mainloop()
> ```

And..., if you were to delete all of the text in the widget (by say
'selecting all' and pressing "delete", you will end up with the all tag
not associated with any text. Subsequent typing of text after deleting
on the keyboard will insert untagged text, which will not be associated
to the "all" tag.

Here's another illustration:

$ rlwrap wish
% text .t
.t
% pack .t
% .t insert end "The quick brown fox jumped over the lazy dog\n"
% .t insert end "Mary had a little lamb, it's fleece was white as snow\n"
% .t tag add all 0.0 end
% .t tag configure all -background yellow
% # now type something at the end, it is in yellow
% .t get 0.0 end
The quick brown fox jumped over the lazy dog
Mary had a little lamb, it's fleece was white as snow
this is some new text

% # now delete the text in the widget using select all and delete key
% .t get 0.0 end


% .t tag ranges all
% # now type new text - it will not have a yellow background
% .t get 0.0 end
this is even newer text

% .t tag ranges all
%

The 'yellow' background just makes what is, or is not, tagged very
visible in the text widget.

Deleting everything by selecting all (control+/) and deleting (Delete)
results in an all tag with no ranges. And subsequent typing does not
enter text with a yellow background. That newly typed text is
untagged, because the select all plus delete by the keyboard
disassociated the tag with any text in the widget.

Rich

unread,
Nov 6, 2021, 11:15:09 PM11/6/21
to
I made two one tiny changes to your Python while rewriting it as plain
Tcl. One, the tag includes a yellow highlight to "see" where it
exists. Two, instead of rebinding the all tag to the <KeyPress> event
on every modification, I bound it once at the outer level. I.e., I
changed it to this:

>> def changed(event):
>> print('modified')
>> text.edit_modified(0)
>> text.tag_remove('all', '1.0', 'end')
>> text.tag_add('all', '1.0', 'end')
>>
>> text.tag_bind('all', '<KeyPress>', print_info)
>> text.bind('<<Modified>>', changed)
>> text.tag_bind('all', '<KeyPress>', print_info)

And with Tcl, with that change, your script works perfectly. The text
stay's tagged, even if I delete everything.

So, try binding the tag once, at outer level, and see if that works for
you.

Andy Maleh

unread,
Nov 7, 2021, 1:40:07 PM11/7/21
to
On Saturday, November 6, 2021 at 11:15:09 PM UTC-4, Rich wrote:
Wow! I would look forward to having a version that works.

Question: the code you shared is in Python (and it also repeats the tag binding twice at the outer level, once before binding <<Modified>> and once afterwards). It actually resembles my original version that had the problem (I was originally only tag binding once; I only tried retagging and rebinding in the modified event handler as a failed workaround)

Do you have the Tcl code that worked for you? Even if I'm more of a Rubyist (Python is not even my language, I only used it to illustrate that the problem is not constrainted to the Ruby bindings), I could take a stab at understanding the Tcl code.

Thank you,

Andy

Rich

unread,
Nov 7, 2021, 2:34:02 PM11/7/21
to
Andy Maleh <and...@gmail.com> wrote:
> On Saturday, November 6, 2021 at 11:15:09 PM UTC-4, Rich wrote:
>> So, try binding the tag once, at outer level, and see if that works
>> for you.
>
> Wow! I would look forward to having a version that works.
>
> Question: the code you shared is in Python (and it also repeats the
> tag binding twice at the outer level, once before binding
> <<Modified>> and once afterwards).

What I tried to share was a snippet of your python, with an attempt to
show the change I made. It is possible my editing of the python did
not go as planned.

> Do you have the Tcl code that worked for you?

No, as I just typed it into a REPL. It is a pretty straightforward
translation from your python you posted.

Here is a working variant, recreated (note that I don't actually use
the "args" on changed, so it could have been defined as
"proc changed {} {"):

text .t
pack .t
.t insert end "Some giberish\nMore giberish\nNot well spelled giberish"
.t tag add all 0.0 end
.t tag configure all -background yellow

proc key {args} {
puts stderr "keypress args='$args'"
}

proc changed {args} {
puts stderr "modified"
.t edit modified 0
.t tag add all 0.0 end
}

bind .t <<Modified>> [list changed]
.t tag bind all <KeyPress> [list key %K]
.t edit modified 0


> Even if I'm more of a Rubyist (Python is not even my language, I only
> used it to illustrate that the problem is not constrainted to the
> Ruby bindings), I could take a stab at understanding the Tcl code.

The above snippet is Tcl, that does what I believe you are expecting.
However, binding to the text to grab keystrokes will likely be superior
than trying to use a <<Modified>> event to 'retag' the entire text.
Performance of the bind to the text itself will likely be better,
possibly much better if the text widget contains a large amount of
text.

Andy Maleh

unread,
Nov 9, 2021, 8:50:08 AM11/9/21
to
On Sunday, November 7, 2021 at 2:34:02 PM UTC-5, Rich wrote:
Hi,

Thank you for providing the Tcl example that worked for you.

It does not work for me when I convert to Ruby or Python.

Here is the Ruby counterpart (which I adjusted away from Ruby idioms to make it look closer to the Tcl code):

require 'tk'

root = Tk::Root.new

text = Tk::Text.new(root)
text.pack
text.insert 'end', "Some giberish repeated many times\n"*20
text.tag_add('all', '0.0', 'end')
text.tag_configure('all', 'background', 'yellow')

text.bind('<Modified>') { |event|
print "modified\n"
text.modified = false
text.tag_add('all', '0.0', 'end')
}
text.tag_bind('all', 'KeyPress') { |event|
print "key press: #{event.inspect}\n"
}
text.modified = false

root.mainloop

One thing I noticed is you used 0.0 for the start index instead of 1.0. According to the Tcl/Tk docs (https://tkdocs.com/tutorial/text.html), I believe 1.0 is the correct beginning since lines are 1-based even though characters are 0-based, but I digress.

I do see the first line colored with a yellow background after clearing out all the text (e.g. CMD+A & DELETE), but still it does not fire binding changes upon key presses after adding new text in the yellow area.

Andy Maleh

unread,
Nov 9, 2021, 8:58:03 AM11/9/21
to
I just tried your Tcl code using tkcon, and I still got the same problem. I get printouts when navigating by arrow keys before clearing text. After clearing, if I enter a few characters on the first line and then navigate with arrow keys, nothing prints. I have to enter a few newlines before printouts happen again (it is a bit random when they would happen again)

Rich

unread,
Nov 9, 2021, 9:54:57 AM11/9/21
to
In Tcl, the binding here is <<Modified>> (the double brackets are
meaningful). Does Ruby auto-insert a pair of < >'s?

>> print "modified\n"
>> text.modified = false
>> text.tag_add('all', '0.0', 'end')
>> }
>> text.tag_bind('all', 'KeyPress') { |event|
>> print "key press: #{event.inspect}\n"
>> }
>> text.modified = false
>>
>> root.mainloop
>>
>> One thing I noticed is you used 0.0 for the start index instead of
>> 1.0. According to the Tcl/Tk docs
>> (https://tkdocs.com/tutorial/text.html), I believe 1.0 is the
>> correct beginning since lines are 1-based even though characters are
>> 0-based, but I digress.

Even so, 0.0 is before 1.0 so also means "first line".

>> I do see the first line colored with a yellow background after
>> clearing out all the text (e.g. CMD+A & DELETE), but still it does
>> not fire binding changes upon key presses after adding new text in
>> the yellow area.
>
> I just tried your Tcl code using tkcon, and I still got the same
> problem. I get printouts when navigating by arrow keys before
> clearing text. After clearing, if I enter a few characters on the
> first line and then navigate with arrow keys, nothing prints. I have
> to enter a few newlines before printouts happen again (it is a bit
> random when they would happen again)

Trying your specific steps above, I get the same result. If I
Control+/ and then Delete, typing produces no keypresses until after I
enter a first (or occasionally more) newline(s). I had not seen this
before because I did not know your specific steps above that seem to
trigger it.

So some kind of weird binding bug exists here.

Andy Maleh

unread,
Nov 9, 2021, 10:07:24 AM11/9/21
to
On Tuesday, November 9, 2021 at 9:54:57 AM UTC-5, Rich wrote:
Yes, Ruby does auto-insert a pair of < >, so "<Modified>" is what is required in Ruby for that binding to work.

Thank you for testing and confirming.

Best regards,

Andy

Francois Vogel

unread,
Nov 12, 2021, 12:06:23 PM11/12/21
to
Le 09/11/2021 à 15:54, Rich a écrit :
> So some kind of weird binding bug exists here.

Follow-up to:

https://core.tcl-lang.org/tk/tktview/631a0b2d95

Regards,
Francois

Francois Vogel

unread,
Nov 12, 2021, 4:41:01 PM11/12/21
to
Le 12/11/2021 à 18:06, Francois Vogel a écrit :
> Le 09/11/2021 à 15:54, Rich a écrit :
>> So some kind of weird binding bug exists here.
>
> Follow-up to:
>
>     https://core.tcl-lang.org/tk/tktview/631a0b2d95

I have dropped my analysis in that ticket. In a nutshsell: there is no
bug, it works as designed.

The man page however could (and probably should) explain all this much
better however (any suggestions for a better text, someone?)

Regards,
Francois

Mike Griffiths

unread,
Nov 12, 2021, 6:54:34 PM11/12/21
to
While I agree that the manpage *could* be interpreted that way, I would still consider it a bug, or at the very least a feature so useless it should be classed the same way; I can't see any benefit to having keyboard-related bindings on a text-based widget which only respond when the mouse pointer is located inside the widget. And if desired, that's a behaviour that's really easy to code with winfo containing/winfopointerxy anyway.

It's also worth noting that, while "current" may not be updated when the mouse is outside the widget, it still exists (with [$widget index current] always returning 1.0) so technically from how the manpage is worded I don't think there's anything to say the bindings shouldn't still fire.

Francois Vogel

unread,
Nov 13, 2021, 10:53:08 AM11/13/21
to
Le 13/11/2021 à 00:54, Mike Griffiths a écrit :
> While I agree that the manpage *could* be interpreted that way, I would still consider it a bug

The man page AND the code are consistent. The intent is clear in the code.

That said, improvement is possible. See the ticket for a proposal.

> It's also worth noting that, while "current" may not be updated when the mouse is outside the widget, it still exists (with [$widget index current] always returning 1.0) so technically from how the manpage is worded I don't think there's anything to say the bindings shouldn't still fire.

In my implementation I decided that, for key events, 'insert' is a
better mark to consider than 'current'.

Regards,
Francois






0 new messages