Version 10 of WhoAmI? -Letting proceedurally added widgets know their names

Updated 2005-09-02 09:51:04

WJG (1st September 2005) There's probably something described in the small print of the Tcl man pages that will do something similar but... If I add new functionality to an app I like to pack buttons 'n' stuff on the fly in which case there is no guarentee as to what a widget name might be. Widget bindings allow us to know which item was clicked although the button -command switch has no such choice. To cross this gap we have to work with references. Because the 'bind all' take presidence over the individual button command we can use a binding to set a global variable which subsequent commands can access.

The following code example allows us to create buttons and then allows the button command to make adjustments to itself.

 #----------------------------------------------------------------
 # whoami.tcl
 #----------------------------------------------------------------
 #
 # Making a button aware of just who it is.
 # This can be useful if wigets are built proceedurally and pathnames
 # are not defined explicitly.
 #
 #----------------------------------------------------------------

 # set global array for where the last 2 B1 events occured
 array set ::widget {
   active ""
   last ""
 }

 bind all <Button-1> {
  set ::widget(last) $::widget(active)
  set ::widget(active) %W
 }

 #----------------------------------------------------------------
 proc whoami {} {
  if {[$::widget(active) cget -text] == "Clickme!"} {
    $::widget(active) config -text "Clicked!"
    } else {
    $::widget(active) config -text "Clickme!"
    }
  puts "I am \"[winfo name ${::widget(active)}]\" and I live in \"[winfo parent ${::widget(active)}]\"."
 }

 #----------------------------------------------------------------

 console show 
 expr srand(1)
 set base .t2 
 toplevel $base

 button .b1 -text Clickme! -command {
   whoami
   # use randomly created path names
   set i [string trimleft [expr rand()] 0.]
   pack [button $base.b$i -text "Button $i" -command whoami]
 }
 pack .b1

 bind .b1 <Button-1> {puts "I am %W -hello from my unique <B1> binding."}

Brian Theado - The value passed to -command isn't limited to being just a command name--arguments can be passed as well (see example on button page). It should work to add an argument to your button procedure:

 proc whoami button {...}

and the pass the window name when -command is configured:

 pack [button $base.b$i -text "Button $i" -command [list whoami $i]]

WJG Is not a matter of passing arguments -this I'm fully aware of. In the example that you give only the value of i is passed. My intention is to pass the whole widget path. The problem I've set myelf is to be able to copy and paste (using a private buffer) embedded windows between text widgets. As the embedded windows have additional associated tags, I need as nuch as possible enable button command invoked procs to know where they were called from. Later I plan to add some sort of file save and loading of embedded windows.

Brian Theado - My mistake. I meant to pass the full widget path -- [list whoami $base.b$i], not just $i. Doing that, you would be able to replace ::widget(active) with the input argument "button" in "whoami". Maybe that still isn't what you are after.


Bryan Oakley - as has already been pointed out, you can include the widget path, or anything else, as part of the -command of a widget. It's generally considered good form to always have bindings call a proc, and to build that call with list. To wit:

 button .b1 -text Clickme! -command {
   whoami .b1
   # use randomly created path names
   set i [string trimleft [expr rand()] 0.]
   pack [button $base.b$i -text "Button $i" -command [list whoami $base.b$i]
 }

Given that rule of thumb, even better (arguably) is this implementation:

  button .b1 -text Clickme! -command [list newButton .b1]
  proc newButton {w} {
     whoami $w
     set i [string trimleft [expr rand()] 0.]
     pack [button $base.b$i -text "Button $i" -command [list whoami $base.b$i]
  }

Personally, I feel this is a much better way to let a callback know who called it than to rely on global variables and bindings.

Also, you wrote "...the 'bind all' take presidence over the individual button command", but that is incorrect. "all" bindings pretty much have the lowest precedence of all. What you don't seem to realize is that the -command is called on a ButtonRelease event but your "all" binding is on a ButtonPress, so you'll see the ButtonPress event first. It's not a matter of precedence, only a matter of which events have which bindings, and press events always come before release events.


WJG Bryan, thanks for the really useful comments on the bindings. In fact all this 'button know itself stuff' has floated out of my attempts to expand your ttd package to allow me to save off buttons contained with text embedded windows. See Text Collapser.

Category Widget