event binding and component.config

Jeff Epler jepler at unpythonic.net
Mon Aug 18 14:13:08 EDT 2003


I believe that this "problem" lies in the default bindings that Tk
provides for widgets.

You should make yourself familiar with Tk's concept of the "bindtags"
of a widget, and the sequence of bindings that are invoked when a widget
gets an event.  I apologize for not translating these examples into
Python, but getting at the information I want to show is much easier
inside the tcl shell than through Python/Tkinter.  Apologies also for
the length..

A widget W inside a toplevel T of class K gets a default bindtags of
 W K T all
.. and Tk provides many bindings on each class K and a few bindings on
the class "all".

One of the bindings for Button is for <ButtonRelease-1>, and it changes
the widget's relief to a stored relief value seen in the <ButtonPress-1>
event.  Here's some output from wish, the interactive tcl/tk shell:

    % # Create a button
    % button .b
    .b
    % # Show the bindtags of the new button
    % bindtags .b
    .b Button . all
    % # Show the bindings that exist on Button
    % bind Button
    <ButtonRelease-1> <Button-1> <Leave> <Enter> <Key-space>
    % # Show the binding and the command it invokes
    % bind Button <ButtonRelease-1>

        tkButtonUp %W

    % info body tkButtonUp

        global tkPriv
        if {[string equal $w $tkPriv(buttonWindow)]} {
            set tkPriv(buttonWindow) ""
            $w configure -relief $tkPriv(relief)
            if {[string equal $w $tkPriv(window)]  && [string compare [$w cget -state] "disabled"]} {
                uplevel #0 [list $w invoke]
            }
        }

When you create the binding on the component for <ButtonRelease-1>,
it gets added to the tag whose name is the same as the widget path.
Now, when the <ButtonRelease-1> event arrives, the tags are searched in
order, and for each tag the most specific binding (if any) is executed.
If the binding does not "break", then the next tag is searched.

So the first binding sets the relief of the button to sunken, and then
the second binding sets it to the saved value, $tkPriv(relief).

The reason that using command= leaves you with the desired relief can
also be understood: the button is actually invoked after the relief is
reset.

You have a number of options.  First, unless you *want* to get rid of
the normal requirements for what constitutes a button press, you'll have
to rewrite the normal button logic for what constitutes a press.  If you
don't, eventually you'll see a situation where the button sees a
<ButtonRelease-1> event without an earlier <ButtonPress-1> event, and
the behavior will be wrong.  (this can happen if a window from a different
application just above the button destroys itself while button 1 was down,
for instance)

If you can't do this from within command, but want to take advantage of
the normal logic of a button press, you could set the relief to the
"after press" value in the widget's <ButtonPress-1> binding.  This will
then get stored in tkPriv(relief) by Button's <ButtonPress-1> and
restored by Button's <ButtonRelease-1>.  But the altered binding will be
restored wither or not the press was completed (by releasing inside the
button) or canceled (by releasing outside the button), so again it's
probably not what you want.

You can "snoop" inside Tk's internals.  In the above, the relief is
stored in tkPriv(relief), but this is an implementation detail.  In
newer versions of Tk (8.4.x), this is stored in a different location,
probably ::tk::relief or something like that.  But this is clearly
documented to change between releases.

(Another problem with doing things inside the <Button> bindings is that
you also have to duplicate it for keyboard access.  For instance, by
default <Key-Space> invokes the button:
    % bind Button <Key-space>

        tkButtonInvoke %W

    % info body tkButtonInvoke

        if {[string compare [$w cget -state] "disabled"]} {
            set oldRelief [$w cget -relief]
            set oldState [$w cget -state]
            $w configure -state active -relief sunken
            update idletasks
            after 100
            $w configure -state $oldState -relief $oldRelief
            uplevel #0 [list $w invoke]
        }

Finally, you don't explain exactly why you want this behavior,
and I'm not familiar with any common programs that do this (though the
behavior you're describing sounds a little bit like a Tk checkbutton
with indicatoron=0).  You should always ask yourself if departing from
standard behavior of the OS and other applications is worth it, given
that the results are likely to confuse users.

Jeff





More information about the Python-list mailing list