WhoAmI? -Letting proceedurally added widgets know their names

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) there is no guarantee 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 precedence 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 widgets are built procedurally and pathnames
 # are not defined explicitly.
 #
 #----------------------------------------------------------------

 # set global array for where the last 2 B1 events occurred
 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 It's 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 much 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 within text embedded windows. See Text Collapser.

WJG (24/03/08) Here's another way of tackling this problem.

  #---------------
  # whoami.tcl
  #---------------
  # Created by William J Giddings
  # March, 2008
  #---------------
  # Description:
  # Enable procedurally created widgets know who they are.
  #
  # Notes:
  # Inclusion of 'info level 0' will return the name and #
  # argument of the currently interpreted procedure. 
  # In Tcl "..everything is a string". [https://wiki.tcl-lang.org/3018]
  #---------------

  #!/bin/sh/ 
  #\
  exec tclsh "$0" "$@"
  package require Tk 

  # create a few buttons and return their names
  foreach i {apple banana cherry damson} {

  # create and pack some buttons
  set tmp [button .$i -text "I'm $i\nClick ME!" ]
  pack $tmp -fill both -expand 1

   # make some custom button handlers
   $tmp configure -command WIDGET$tmp
        
  # create a command proc on the fly..
   proc WIDGET$tmp {args} { 
                # get the line that called this proc, then process the string..
                set w ".[lindex [split [lindex [info level 0] 0] .] 1]"
                puts "I am $w"
                }
  }

In gnocl, widgets always know who they are... WhoAmI? -Gnocl widgets always know who they are