|
Description:
If you're creating a mega-widget (aka composite widget) that you want to be extensible, but you aren't quite ready to take the plunge into a full Tcl class extension (or haven't found one quite for you), the following is a simple "next best thing".
Source: Text Source
package provide MegaWidget 1.0
proc MegaWidget { hWnd } {
variable widgetClasses
set NS [uplevel namespace current]
if {[info exist widgetClasses($hWnd)]} {
set widgetClasses($hWnd) [linsert $widgetClasses($hWnd) 0 $NS]
return
}
set widgetClasses($hWnd) $NS
rename ::$hWnd [namespace current]::mega$hWnd
set template {
if {[string match %W @HWND@]} {
namespace eval @MYNS@ array unset widgetClasses %W
rename %W {}
}
}
regsub -all {@HWND@} $template $hWnd template
regsub -all {@MYNS@} $template [namespace current] template
bind $hWnd <Destroy> $template
set template {
global errorInfo errorCode
variable widgetClasses
set hWnd @HWND@
foreach NS $@MYNS@::widgetClasses($hWnd) {
if {[namespace inscope $NS info proc $command] == $command} {
set rc [catch { uplevel [set NS]::$command $hWnd $args } result]
set ei $errorInfo
set ec $errorCode
break
}
}
if {![info exist rc]} {
set rc [catch { uplevel @MYNS@::mega$hWnd $command $args } result]
set ei $errorInfo
set ec $errorCode
}
return -code $rc -errorinfo $ei -errorcode $ec $result
}
regsub -all {@HWND@} $template $hWnd template
regsub -all {@MYNS@} $template [namespace current] template
proc ::$hWnd { command args } $template
}
# .mw dosomethingelse -option value ...
The license for this recipe is available here.
Discussion:
This MegaWidget procedure allows you to treat namespaces and widgets loosely as extensible classes. The class name is defined by the namespace from which the MegaWidget command was called (MyWidget in the example), and the specific class instance is named by the main widget name.
When MegaWidget is called, it is passed the path of some widget that you want to turn into a mega widget. The namespace of the caller is added to a search list (list of namespaces from which MegaWidget was called on the named widget) and the widget's command (provided by Tk) is renamed and replaced. When this replacement command is called, it will scan through the search list, checking each namespace stored for a procedure with the same name as the first argument. If found, then it's called, with the widget name inserted as the first argument. (In the above example, ".mw dosomething" calls "MyWidget::dosomething with .mw passed in the hWnd parameter).
Note: this assumes that the procedure will already have been defined so that it will be visible via "info proc". If the procedure hasn't been auto-loaded, it might call the wrong layer.
If no procedure is found in any of the namespaces of the search list, then the command is passed on to the widget command its self as if it were not a mega-widget.
The MegaWidget function provides some basic inheritence mechanisms. You can call it multiple times from different namespaces to add or override basic functionality. To call a specific parent-class's version of a function, you just need to call the function directly, passing the widget path as the first argument. e.g., MegaWidget::dosomething .mw ?arg arg ...?.
See http://aspn.activestate.com/ASPN/Cookbook/Tcl/Recipe/122548 for a working mega-widget based on this function.
Aug 08, 2003 Edit - changed evals in widget procs to uplevels so that upvar can be used in the widget proc.
|