Updated 2013-01-18 22:01:11 by pooryorick

In a text widget, bindings can be made to the entire widget. Bindings can also be made to tagged characters.

When a break command is executed in a binding to a tagged character, this does not prevent bindings to the entire widget from firing. See the c.l.t discussion at [1].

Unfortunately, the word 'tag' has two meanings:

(1) a 'binding tag' is one of the identifiers (widget name, widget toplevel name, widget class, or the keyword 'all') that bind a script to an entire widget: see bindtags for details.

(2) a 'tag' is a type of annotation that can be applied to characters in a text widget, to give those characters a particular font, style etc. This 'tag' annotation can also give those characters a binding, which is described below as 'tagged-character binding' or 'text binding', to distinguish it from a 'widget binding' to the entire widget.

Here is a session that shows break doesn't stop the widget and class bindings from firing:
% text .t
% pack .t
% .t insert 1.0 "some text" myTextTag
% bind Text <1> "puts Text"
% bind .t <1> "puts .t"
% .t tag bind myTextTag <1> "puts myTextTag; break"
% # Now click on the text

This behavior is undesirable in cases where the text tagged-character binding changes some state (such as the insertion cursor location) and then the widget's Text class binding changes the same state, thereby undoing what the tagged-character binding did.

One workaround is to add an entry to the binding chain before the Text class that will execute break if the event happened at an index containing the tagged character. The following shows how to block the Text class binding for a mouse button-1 event that happens on text characters with a tag called myTextTag:
bind mySpecialBindings <1> {if {[lsearch [%W tag names @%x,%y] myTextTag] >= 0} break}
bindtags .t {.t mySpecialBindings Text . all}

jdc Alternatively, use a callback to test which binding must be triggered. You can do this using a global variable to interlock (tag bindign will be triggered first) or with an arguments passed to the callback indicating if the binding was triggered from the widget or from a tag. If triggered from the widget, check if the pointer is not above a tag:
# Create a text widget and insert some dat
pack [text .txt]
.txt insert end "a" "" "b" myTag "c" ""

# Add the bindings
bind .txt <1> [list my_callback 1 %x %y]
.txt tag bind myTag <1> [list my_callback 0 %x %y]

proc my_callback {from_widget x y} {
    # If binding triggered from widget, check if myTag is under the mouse pointer.
    # If it is, break so the tag binding can have its way.
    if {$from_widget && "myTag" in [.txt tag names @$x,$y]} {
        return -code break
    puts "Callback $from_widget @$x,$y"

ulis, 2003-01-01:

Here is my explanation of what occurred. Can somebody correct it if it's not correct?

  • Before, I change slightly the code to add a second text binding:
text .t
pack .t
.t insert 1.0 "some text" {myTextTag1 myTextTag2}
bind Text <1> "puts Text"
bind .t <1> "puts .t"
.t tag bind myTextTag1 <1> "puts myTextTag1; break"
.t tag bind myTextTag2 <1> "puts myTextTag2"

  • Then, I put here the relevant sections of the Tk 8.4 manual.

Text binding:

It is possible for the current character to have multiple tags, and for each of them to have a binding for a particular event sequence. When this occurs, one binding is invoked for each tag, in order from lowest-priority to highest priority. If there are multiple matching bindings for a single tag, then the most specific binding is chosen (see the manual entry for the bind command for details). continue and break commands within binding scripts are processed in the same way as for bindings created with the bind command.

If bindings are created for the widget as a whole using the bind command, then those bindings will supplement the tag bindings. The tag bindings will be invoked first, followed by bindings for the window as a whole.

Widget binding:

It is possible for several bindings to match a given X event. If the bindings are associated with different tag's, then each of the bindings will be executed, in order. By default, a binding for the widget will be executed first, followed by a class binding, a binding for its toplevel, and an all binding. The bindtags command may be used to change this order for a particular window or to associate additional binding tags with the window.

The continue and break commands may be used inside a binding script to control the processing of matching scripts. If continue is invoked, then the current binding script is terminated but Tk will continue processing binding scripts associated with other tag's. If the break command is invoked within a binding script, then that script terminates and no other scripts will be invoked for the event.

  • Here is my explanation.

.t has two widget binding tags that match <1>: Text and .t. By default, .t bindtags list is: {.t Text . all}. At the occurrence of <1>, the fired widget binding tags sequence will be: .t, then Text.

"some text" has two text binding tags: myTextTag1 and myTextTag2, in that order. At the occurrence of <1> inside "some text", the fired text binding tags sequence will be: myTextTag1, then myTextTag2.

The text bindings will be fired BEFORE the widget bindings. (if the <1> is fired for .t; that is: is not intercepted before)

So, at the <1> event inside "some text":

  1. we should see the result of the binding of the myTextTag1 text tag,
  2. we shouldn't see the result of the binding of the myTextTag2 text tag (because of the break inside the script associated to the myTextTag1 text binding tag),
  3. we should see the result of the binding of the .t widget tag,
  4. we should see the result of the binding of the Text widget tag.

A break inside the script associated with the .t widget tag would prevent the firing of the script associated to the Text widget tag.

It appears that a break inside the script of a text binding tag DOES NOT prevent the firing of a script associated to the widget.

SRT, 2005-12-16:

The same for Perl/Tk:
$text->bindtags(["myTextTag", $text->bindtags]);
$text->bind("myTextTag", "<1>",
    [sub {
        my($w,$x,$y) = @_;
        if (grep { /^jump/ } $w->tagNames("\@$x,$y")) {
    }, Ev("x"), Ev("y")]);