Monday, December 16, 2013

tcl: find widget recursively

I was doing some testing for multi container application, and same widget could exist in different containers and could have variable parent name, I created a function to find the widget recursively by the last portion of the widget name
The function I initially created
   1: proc findWidgetByName { wname {widget .mainwindow} } {    
   2:     set pattern "*.${wname}" 
   3:     #puts $widget    
   4:     set all "[winfo children $widget]" 
   5:     foreach child $all  { 
   6:         #puts "$child" 
   7:         if { [winfo viewable $child] && [string match $pattern $child] } { 
   8:                 #puts $child 
   9:                 return $child            
  10:         } else { 
  11:             return [findWidgetByName $wname $child]        
  12:         } 
  13:     } 
  14:     return 0 
  15: }

Testing the above function, it was taking the first child in each “for loop” and returning without checking other childs, with the help of my colleague, we changed it to working version

   1: proc findWidgetByName { wname {widget .mainwindow} } {    
   2:     set pattern "*.${wname}" 
   3:     #puts $widget    
   4:     set all "[winfo children $widget]" 
   5:     foreach child $all  { 
   6:         #puts "$child" 
   7:         if { [winfo viewable $child] && [string match $pattern $child] } { 
   8:                 #puts $child 
   9:                 return $child            
  10:         } else { 
  11:             set x [findWidgetByName $wname $child]        
  12:             if {  $x !=0 } { 
  13:             return $x ;            
  14:             } 
  15:         } 
  16:     } 
  17:     return 0 
  18: }

When using for loops with recursion we need to put some condition when to break the for loop execution
I have created also another version to find widgets by label, in case of buttons or labels

   1: proc findWidgetByLabel { label {widget .mainwindow} } {       
   2: set all "[winfo children $widget]"
   3:     foreach child $all  {
   4:         #puts "$child"
   5:         if {([winfo class $child]== "Label" || [winfo class $child]== "Button" ) && [winfo viewable $child] && [$child cget -text]==$label } {
   6:                 #puts $child
   7:                 return $child            
   8:         } else {
   9:             set x [findWidgetByLabel $label $child]        
  10:             if {  $x !=0 } {
  11:                 return $x ;            
  12:             }
  13:         }
  14:     }
  15:     return 0
  16: }

Same can be applied to find widget by any property