In Snit, as in Tk, a type is a command that creates instances--objects--that belong to the type. Most types define some number of "options" that can be set at creation time, and usually can be changed later.
Further, an instance is also a Tcl command--a command that gives access to the operations that are defined for that abstract data type. Conventionally, the operations are defined as subcommands, or instance methods of the instance command. For example, to insert text into a Tk text widget, you use the text widget's "insert" method:
# Create a text widget and insert some text in it. text .mytext -width 80 -height 24 .mytext insert end "Howdy!"In this example, "text" is the type command and ".mytext" is the instance command.
snit::type
snit::widget
snit::widgetadaptor
snit::type
is a non-GUI abstract data type, e.g., a
stack or a queue. snit::types
are defined using the
snit::type
command. For example, if you were designing
a kennel management system for a dog breeder, you'd need a dog type.
% snit::type dog { # ... } ::dog %This definition defines a new command (
::dog
, in this
case) that can be used to define dog objects.
An instance of a snit::type
can have
instance methods,
instance variables, options, and
components. The type itself
can have type methods and procs.
snit::widget
is a Tk megawidget built using Snit; it is
very similar to a snit::type
. See WIDGETS.
snit::widgetadaptor
uses Snit to wrap an existing
widget type (e.g., a Tk label), modifying its interface to a lesser or
greater extent. It is very similar to a snit::widget
. See
WIDGETADAPTORS.
snit::type
by passing the new
instance's name to the type's create method. In the following
example, we create a dog
object called
spot
.
% snit::type dog { # .... } ::dog % dog create spot ::spot %
The "create" method name can be omitted so long as the instance name doesn't conflict with any defined type methods. So the following example is identical to the previous example:
% snit::type dog { # .... } ::dog % dog spot ::spot %
This document generally uses the shorter form.
If the dog
type defines options, these can
be given defaults at creation time:
% snit::type dog { option -breed mongrel option -color brown method bark {} { return "$self barks." } } ::dog % dog create spot -breed dalmation -color spotted ::spot % spot cget -breed dalmation % spot cget -color spotted %
Either way, the instance name now names a new Tcl command that is used to manipulate the object. For example, the following code makes the dog bark:
% spot bark ::spot barks. %
% snit::type dog { option -breed mongrel option -color brown method bark {} { return "$self barks." } } ::dog % set d [dog spot -breed dalmation -color spotted] ::spot % $d cget -breed dalmation % $d bark ::spot barks. %
% snit::type dog { method bark {} { return "$self barks." } } ::dog % set d [dog %AUTO%] ::dog2 % $d bark ::dog2 barks. %The "%AUTO%" keyword can be embedded in a longer string:
% set d [dog dog%AUTO%] ::dogdog5 % $d bark ::dogdog5 barks. %
rename
" command renames other commands. It's a
common technique in Tcl to modify an existing command by renaming it
and defining a new command with the original name; the new command
usually calls the renamed command.
snit::type
's, however, should never be renamed; to do so
breaks the connection between the type and its objects.
rename
" command renames other commands. It's a
common technique in Tcl to modify an existing command by renaming it
and defining a new command with the original name; the new command
usually calls the renamed command.All Snit objects (including widgets and widgetadaptors) can be renamed, though this flexibility has some consequences:
[list $self methodname args...]
You'll get an error if this command is called after your object is renamed.
[mymethod methodname args...]
The mymethod
command returns code that will call
the desired method safely; the caller of the callback can
safely add additional arguments to the end of the command as
usual.
For example, one could use this code to call a method when a Tk button is pushed:
.btn configure -command [list $self buttonpress]
This will break if your instance is renamed. Here's the safe way to do it:
.btn configure -command [mymethod buttonpress]
snit::widget
method bodies, etc., as "$win". This value is constant for the
life of the object. When creating child windows, it's best to
use "$win.child" rather than "$self.child" as the name of the
child window.
snit::type
has a destroy
method:
% snit::type dog { method bark {} { return "$self barks." } } ::dog % dog spot ::spot % spot bark ::spot barks. % spot destroy % info commands ::spot %Snit megawidgets (i.e., instances of
snit::widget
and
snit::widgetadaptor
) are destroyed like any other widget:
by using the Tk destroy
command on the widget or on one
of its ancestors in the window hierarchy.
In addition, any Snit object of any type can be destroyed by renaming it to ""
using the Tcl rename
command.
% snit::type dog { method bark {} { return "$self barks." } method chase {thing} { return "$self chases $thing." } } ::dog %A dog can bark, and it can chase things.
The "method" statement looks just like a normal Tcl "proc", except
that it appears in a snit::type
definition.
Notice that every instance method gets an implicit argument called
"self
"; this argument contains the object's name.
% dog spot ::spot % spot bark ::spot barks. % spot chase cat ::spot chases cat. %
self
".Suppose, for example, that our dogs never chase anything without barking at them:
% snit::type dog { method bark {} { return "$self barks." } method chase {thing} { return "$self chases $thing. [$self bark]" } } ::dog % dog spot ::spot % spot bark ::spot barks. % spot chase cat ::spot chases cat. ::spot barks. %
configure
, configurelist
, cget
,
destroy
, and info
.
Snit doesn't implement any access control on instance methods, so all methods are de facto public. Conventionally, though, the names of public methods begin with a lower-case letter, and the names of private methods begin with an upper-case letter.
For example, suppose our simulated dogs only bark in response to other stimuli; they never bark just for fun. So the "bark" method could be private:
% snit::type dog { # Private by convention: begins with uppercase letter. method Bark {} { return "$self barks." } method chase {thing} { return "$self chases $thing. [$self Bark]" } } ::dog % dog fido ::fido % fido chase cat ::fido chases cat. ::fido barks. %
type
,
selfns
, win
, and self
.
type
" contains the fully qualified
name of the object's type:
% snit::type thing { method mytype {} { return $type } } ::thing % thing something ::something % something mytype ::thing %
self
" contains the object's fully
qualified name.
If the object's command is renamed, then "$self
" will
change to match in subsequent calls. Thus, your code should not
assume that "$self
" is constant unless you know for sure
that the object will never be renamed.
% snit::type thing { method myself {} { return $self } } ::thing % thing mutt ::mutt % mutt myself ::mutt % rename mutt jeff % jeff myself ::jeff %
$selfns
" is the name of this
namespace; it never changes, and is constant for the life of the
object, even if the object's name changes:
% snit::type thing { method myNameSpace {} { return $selfns } } ::thing % thing jeff ::jeff % jeff myNameSpace ::thing::Snit_inst3 % rename jeff mutt % mutt myNameSpace ::thing::Snit_inst3 %The above example reveals how Snit names an instance's private namespace; however, you should not write code that depends on the specific naming convention, as it might change in future releases.
win
" is defined for all Snit
methods, including those of widgets and
widgetadaptors, though it makes sense mostly for the latter
two kinds. "$win
" is simply the original name of the
object, whether it's been renamed or not. For widgets and
widgetadaptors, it is also therefore the name of a Tk window.
When a snit::widgetadaptor
is used to modify the
interface of a widget or megawidget, it must rename the widget's
original command and replace it with its own. Thus, using
"$win
" whenever the Tk window name is called for means
that a snit::widget
or snit::widgetadaptor
can be adapted by a snit::widgetadaptor
.
See WIDGETS for more information.
dog
object named fido
, and I want
fido
to bark when a Tk button is pressed. In this case,
I pass the instance method in the normal way, as a subcommand of
fido
:
button .bark -text "Bark!" -command [list fido bark]In typical Tcl style, we use a callback to hook two independent components together. But what if the
dog
object
itself, passing one of its own instance methods to another object (one
of its components, say)? The obvious thing to do is this:
% snit::widget dog { constructor {args} { #... button $win.barkbtn -text "Bark!" -command [list $self bark] #... } } ::dog %(Note that in this example, our
dog
becomes a
snit::widget
, because it has GUI behavior. See
WIDGETS for more.) Thus, if we create a dog
called .spot
, it will create a Tk button called
.barkbtn
and pass it "$self bark
" as the
command.
Now, this will work--provided that .spot
is never
renamed. But why should .spot
be renamed? Surely
renaming widgets is abnormal? And so it is--unless .spot
is the hull component of a
snit::widgetadaptor
.
If it is, then it will be renamed, and .spot
will name the
snit::widgetadaptor
object. When the button is pressed,
the command "$self bark
" will be handled by the
snit::widgetadaptor
, which might or might not do the
right thing.
There's a safer way to do it, and it looks like this:
% snit::widget dog { constructor {args} { #... button $win.barkbtn -text "Bark!" -command [mymethod bark] #... } } ::dog %The command
mymethod
can be used like list
to build up a callback command; the only difference is that
mymethod
returns a form of the command that won't change
if the instance's name changes.
variable
statement. You can simply name it, or you can
initialize it with a value:
snit::type mytype { # Define variable "greeting" and initialize it with "Howdy!" variable greeting "Howdy!" }
variable
command; however, you can't initialize them
using the variable command. Typically, they get initialized in the
constructor:
snit::type mytype { # Define array variable "greetings" variable greetings constructor {args} { set greetings(formal) "Good Evening" set greetings(casual) "Howdy!" } }
First, every Snit object has a built-in instance variable called "options", which should never be redefined.
Second, all names beginning with "Snit_" or "snit_" are reserved for use by Snit internal code.
Third, instance variable names with the namespace delimiter ("::") in them are likely to cause great confusion.
-textvariable
option which names the
variable which will contain the widget's text. This allows the
program to update the label's value just by assigning a new value to
the variable.
If you naively pass the instance variable name to the label widget,
you'll be confused by the result; Tk will assume that the name names a
global variable. Instead, you need to provide a fully-qualified
variable name. From within an instance method or a constructor, you
can fully qualify the variable's name using the varname
command:
snit::widget mywidget { variable labeltext "" constructor {args} { # ... label $win.label -textvariable [varname labeltext] # ... } }
Snit's implementation of options follows the Tk model fairly exactly,
except that snit::type
objects can't interact with Tk's
option database; snit::widget
and
snit::widgetadaptor
objects, on the other hand, can and
do.
option
statement. Consider the following type, to be
used in an application that manages a list of dogs for a pet store:
% snit::type dog { option -breed mongrel option -color brown option -akc 0 option -shots 0 } ::dog %According to this, a dog has four notable properties, or options: a breed, a color, a flag that says whether it's pedigreed with the American Kennel Club, and another flag that says whether it has had its shots. The default dog, evidently, is a brown mutt.
If no default value is specified, the option's value defaults to the empty string, {}.
The Snit man page refers to these as "locally defined" options.
% dog spot -breed beagle -color "mottled" -akc 1 -shots 1 ::spot % dog fido -shots 1 ::fido %So ::spot is a pedigreed beagle; ::fido is a typical mutt, but his owners evidently take care of him, because he's had his shots.
NOTE: If the type defines a constructor, it can specify a different object-creation syntax. See CONSTRUCTORS for more information.
cget
method:
% spot cget -color mottled % fido cget -breed mongrel %
configure
instance method. Suppose that closer inspection
shows that ::fido is a rare Arctic Boar Hound of a lovely dun color:
% fido configure -color dun -breed "Arctic Boar Hound" % fido cget -color dun % fido cget -breed Arctic Boar Hound %Alternatively, the
configurelist
method takes a list of
options and values; this is sometimes more convenient:
% set features [list -color dun -breed "Arctic Boar Hound"] -color dun -breed {Arctic Boar Hound} % fido configurelist $features % fido cget -color dun % fido cget -breed Arctic Boar Hound %
configure
and cget
methods, as shown below:
% snit::type dog { option -weight 10 method gainWeight {} { set wt [$self cget -weight] incr wt $self configure -weight $wt } } ::dog % dog fido ::fido % fido cget -weight 10 % fido gainWeight % fido cget -weight 11 %Alternatively, Snit provides a built-in array instance variable called
options
. The indices are the option names; the values
are the option values. The method given above can thus be rewritten
as follows:
method gainWeight { incr options(-weight) }As you can see, using the
options
variable involves
considerably less typing. If you define onconfigure
or
oncget
handlers, as described in the following answers,
you might wish to use the configure
and cget
methods anyway, just so that any special processing you've implemented
is sure to get done.
onconfigure
handler.
onconfigure
handler is a special kind of instance
method that's called whenever the related option is given a new value
via the configure
or configurelist
instance
methods. The handler can validate the new value, pass it to some other
object, and anything else you'd like it to do.
An onconfigure
handler is defined by an
onconfigure
statement in the type definition. Here's
what the default configuration behavior would look like if written as
an onconfigure
handler:
snit::type dog { option -color brown onconfigure -color {value} { set options(-color) $value } }The name of the handler is just the option name. The argument list must have exactly one argument; it can be called almost anything, but conventionally it is called "value". Within the handler, the argument is set to the new value; also, all instance variables are available, just as in an instance method.
Note that if your handler doesn't put the value in the
options
array, it doesn't get updated.
oncget
handler.
oncget
handler is a special kind of instance method that's
called whenever the related option's value is queried via the
cget
instance method. The handler can compute the value,
retrieve it from a database, or anything else you'd like it to do.
An oncget
handler is defined by an
oncget
statement in the type definition. Here's
what the default behavior would look like if written as
an oncget
handler:
snit::type dog { option -color brown oncget -color { return $options(-color) } }The handler takes no arguments, and so has no argument list; however, all instance variables are available, just as they are in normal instance methods.
typevariable
statement. You can simply name it, or you can
initialize it with a value:
snit::type mytype { # Define variable "greeting" and initialize it with "Howdy!" typevariable greeting "Howdy!" }Every object of type
mytype
now has access to a single
variable called "greeting".
typevariable
command; however, you can't initialize them
that way, just as you can't initialize array variables using Tcl's
standard variable
command.
Type constructors are the usual way to initialize
array-valued type variables.
-textvariable
option which names the
variable which will contain the widget's text. This allows the
program to update the label's value just by assigning a new value to
the variable.
If you naively pass a type variable name to the label widget,
you'll be confused by the result; Tk will assume that the name names a
global variable. Instead, you need to provide a fully-qualified
variable name. From within an instance method or a constructor, you
can fully qualify the type variable's name using the typevarname
command:
snit::widget mywidget { typevariable labeltext "" constructor {args} { # ... label $win.label -textvariable [typevarname labeltext] # ... } }
Alternatively, you can publicize the variable's name in your documentation and clients can access it directly. For example,
snit::type mytype { typevariable myvariable } set ::mytype::myvariable "New Value"As shown, type variables are stored in the type's namespace, which has the same name as the type itself.
snit::type dog { # List of pedigreed dogs typevariable pedigreed typemethod pedigreedDogs {} { return $pedigreed } # ... }Suppose the
dog
type maintains a list of the names of the
dogs that have pedigrees. The pedigreedDogs
type method
returns this list.
The "typemethod" statement looks just like a normal Tcl "proc", except
that it appears in a snit::type
definition. It defines
the method name, the argument list, and the
body of the method.
snit::type dog { option -pedigreed 0 # List of pedigreed dogs typevariable pedigreed typemethod pedigreedDogs {} { return $pedigreed } # ... } dog spot -pedigreed 1 dog fido foreach dog [dog pedigreedDogs] { ... }
create
and info
.
Snit doesn't implement any access control on type methods; by convention, the names of public methods begin with a lower-case letter, and the names of private methods begin with an upper-case letter.
Alternatively, a Snit "proc" can be used as a private type method; see PROCS.
snit::type dog { typemethod pedigreedDogs {} { ... } typemethod printPedigrees {} { foreach obj [$type pedigreedDogs] { ... } } }
$type
". For example, suppose we want to print
a list of pedigreed dogs when a Tk button is pushed:
button .btn -text "Pedigrees" -command [list dog printPedigrees] pack .btn
snit::type mytype { # Pops and returns the first item from the list stored in the # listvar, updating the listvar proc pop {listvar} { ... } # ... }
By convention, proc names, being private, begin with a capital letter.
snit::type mytype { # Pops and returns the first item from the list stored in the # listvar, updating the listvar proc Pop {listvar} { ... } variable requestQueue {} # Get one request from the queue and process it. method processRequest {} { set req [Pop requestQueue] } }
codename
command returns the proc's fully qualified
name.
A type can have at most one type constructor.
typeconstructor
statement in the type definition. For
example, suppose the type uses an array-valued type variable as a
look up table:
% snit::type mytype { typevariable lookupTable typeconstructor { array set lookupTable {key value...} } } ::mytype %
snit::type
command's create method and can then do whatever it likes. That might
include computing instance variable values, reading data from files,
creating other objects, updating type variables, and so forth.The constructor doesn't return anything.
constructor
statement in the type definition. Suppose that it's desired to keep a
list of all pedigreed dogs. The list can be maintained in a type
variable and retrieved by a type method. Whenever a dog is created,
it can add itself to the list--provided that it's registered with the
American Kennel Club.
% snit::type dog { option -akc 0 typevariable akcList {} constructor {args} { $self configurelist $args if {$options(-akc)} { lappend akcList $self } } typemethod akclist {} { return $akcList } } ::dog % dog spot -akc 1 ::spot % dog fido ::fido % dog akclist ::spot %
% snit::type dog { option -breed mongrel option -color brown option -akc 0 constructor {args} { $self configurelist $args } } ::dog % dog spot -breed dalmatian -color spotted -akc 1 ::spot %When the constructor is called, "args" will be set to the list of arguments that follow the object's name. The constructor is allowed to interprete this list any way it chooses; the normal convention is to assume that it's a list of option names and values, as shown in the example above. If you simply want to save the option values, you should use the
configurelist
method, as shown.
% snit::type dog { variable breed option -color brown option -akc 0 constructor {theBreed args} { set breed $theBreed $self configurelist $args } method breed {} { return $breed } } ::dog % dog spot dalmatian -color spotted -akc 1 ::spot % spot breed dalmatian %The drawback is that this creation syntax is non-standard, and may limit the compatibility of your new type with other people's code. For example, Snit generally assumes that components use the standard creation syntax.
type
,
selfns
, win
, and self
.
destructor
statement
in the type definition. Suppose we're maintaining a list of pedigreed
dogs; then we'll want to remove dogs from it when they are
destroyed.
% snit::type dog { option -akc 0 typevariable akcList {} constructor {args} { $self configurelist $args if {$options(-akc)} { lappend akcList $self } } destructor { set ndx [lsearch $akcList $self] if {$ndx != -1} { set akcList [lreplace $akcList $ndx $ndx] } } typemethod akclist {} { return $akcList } } ::dog % dog spot -akc 1 ::spot % dog fido -akc 1 ::fido % dog akclist ::spot ::fido % fido destroy % dog akclist ::spot %
type
,
selfns
, win
, and self
.
For a Snit megawidget (snit::widgets
and
snit::widgetadaptors
), any widget components
created by it will be destroyed automatically when the megawidget is
destroyed, in keeping with normal Tk behavior (destroying a parent
widget destroys the whole tree).
On the other hand, all non-widget components of a Snit megawidget, and
all components of a normal snit::type
object, must be
destroyed explicitly in a destructor.
But Snit also has a more precise meaning for "component". The components of a Snit object are those objects created by it to which methods and options can be delegated. See DELEGATION for more information about delegation.
dog
object creates a tail
object (the better to wag with, no
doubt). The tail
object will have some command name, but
you tell Snit about it using its role name, as follows:
% snit::type dog { # Define component name as an instance variable variable mytail constructor {args} { # Create and save the component's command install mytail using tail %AUTO% -partof $self $self configurelist $args } method wag {} { $mytail wag } } ::dogAs shown here, it doesn't matter what the
tail
object's
real name is; the dog
object refers to it by its
component name.The above example shows one way to delegate the "wag" method to the "mytail" component; see DELEGATION for an easier way.
In the example in the previous FAQ, the component name is "mytail"; the "mytail" component's object name is chosen automatically by Snit since %AUTO% was used when the component object was created.
install
command creates the component using the
specified command (tail %AUTO% -partof $self
), and
assigns the result to the mytail
variable. For
snit::types
, the install
command shown
above is equivalent to the following command:
set mytail [tail %AUTO% -partof $self]For
snit::widgets
and snit::widgetadaptors
,
however, the install
command also queries
the Tk option database and initializes the component's
options accordingly. For consistency, it's a good idea to get in the
habit of using install
for all components.
snit::widget
and snit::widgetadaptor
have a special component called the hull
component; thus,
the name hull
should be used for no other purpose.Component names are in fact instance variable names, and so follow the rules for instance variables.
snit::type tail { ... } snit::type dog { delegate method wag to mytail constructor {} { install mytail using tail mytail } }This code uses the component name, "mytail", as the component object name. This is not good, and here's why: Snit instance code executes in the Snit type's namespace. In this case, the mytail component is created in the ::dog:: namespace, and will thus have the name ::dog::mytail.
Now, suppose you create two dogs. Both will have a mytail component called ::dog::mytail. In other words, you've got two dogs with one tail between them. This is very bad. Here are a couple of ways to avoid it:
First, if the component type is a snit::type
you can
specify %AUTO% as its name, and be guaranteed to get a unique name.
This is the safest thing to do:
install mytail using tail %AUTO%If the component type isn't a
snit::type
you can base the
component's object name on the type's name in some way:
install mytail using tail $self.mytailThis isn't as safe, but should usually work out okay.
snit::widget
or snit::widgetadaptor
you
don't need to destroy any components that are widgets.
Any non-widget components, however, and all components of a
snit::type
object, must be destroyed explicitly. This is
true whether you assign them a component name or not.
% snit::type dog { variable mytail constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } destructor { $mytail destroy } } ::dog %Note that this code assumes that
tail
is also a
snit::type
; if not, it might need to be destroyed in some
other way.
However, there are times when it's appropriate, not to mention simpler, just to make the entire component part of your type's public interface.
expose
statement. For example, suppose you're
creating a combobox megawidget which owns a listbox widget, and you
want to make the listbox's entire interface public. You can do this:
snit::widget combobox { expose listbox constructor {args} { install listbox using listbox $win.listbox .... #... } #... } combobox .mycombo .mycombo listbox configure -width 30The
expose
statement exposes the named component by
defining a method of the same name. The method's arguments are passed
along to the component. Thus, the above code sets the listbox
component's "-width" to 30.If called with no arguments, the method returns the component's object name:
% .mycombo listbox .mycombo.listboxUsually you'll let the method name be the same as the component name; however, you can rename it if necessary. The code in the following listing exposes the same interface as the previous example:
snit::widget combobox { expose mylistbox as listbox constructor {args} { install mylistbox using listbox $win.mylistbox .... #... } #... } combobox .mycombo .mycombo listbox configure -width 30
dog
object can delegate its wag
method and its -taillength
option to its
tail
component.
% snit::type dog { variable mytail option -taillength onconfigure -taillength {value} { $mytail configure -length $value } oncget -taillength { $mytail cget -length } constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } method wag {} { $mytail wag } } ::dog % snit::type tail { option -length 5 option -partof method wag {} { return "Wag, wag, wag."} } ::tail % dog spot -taillength 7 ::spot % spot cget -taillength 7 % spot wag Wag, wag, wag. %This is the hard way to do it, by it demonstrates what delegation is all about. See the following answers for the easy way to do it.
Note that the constructor calls the configurelist
method
after it creates its tail
; otherwise, if
-taillength
appeared in the list of args
we'd get an error.
delegate
statement in the type definition. (See
COMPONENTS for more information about component names.)
For example, here's a much better way to delegate the dog
object's wag
method:
% snit::type dog { delegate method wag to mytail constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } } ::dog % snit::type tail { option -length 5 option -partof method wag {} { return "Wag, wag, wag."} } ::tail % dog spot ::spot % spot wag Wag, wag, wag. %This code has the same affect as the code shown under the previous question: when a
dog
's wag
method is called,
the call and its arguments are passed along automatically to the
tail
object.
Note that when a component is mentioned in a delegate
statement, the component's instance variable is defined implicitly.
Note also that you can define a method name using the method
statement, or you can define it using delegate
; you can't
do both.
tail
object has a wiggle
method
instead of a wag
method, and you want to delegate the
dog
's wag
method to the tail
's
wiggle
method. It's easily done:
% snit::type dog { delegate method wag to mytail as wiggle constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } } ::dog % snit::type tail { option -length 5 option -partof method wiggle {} { return "Wag, wag, wag."} } ::tail % dog spot ::spot % spot wag Wag, wag, wag. %
tail
object has a wag
method
that takes as an argument the number of times the tail should be
wagged. You want to delegate the dog
's wag
method to the tail
's wag
method, specifying
that the tail should be wagged three times. It's easily done:
% snit::type dog { delegate method wag to mytail as {wag 3} constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } } ::dog % snit::type tail { option -length 5 option -partof method wag {count} { return [string repeat "Wag " $count] } } ::tail % dog spot ::spot % spot wag Wag Wag Wag %
tail
object has a
-length
option; we want to allow the creator of a
dog
object to set the tail's length. We can do this:
% snit::type dog { delegate option -length to mytail constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } } ::dog % snit::type tail { option -partof option -length 5 } ::tail % dog spot -length 7 ::spot % spot cget -length 7 %This produces nearly the same result as the
oncget
and
onconfigure
handlers shown under the first question in
this section: whenever a dog
object's
-length
option is set or retrieved, the underlying
tail
object's option is set or retrieved in turn.
Note that you can define an option name using the option
statement, or you can define it using delegate
; you can't
do both.
dog
's
-length
option down to its tail
. This is,
of course, wrong. The dog has a length, and the tail has a length,
and they are different. What we'd really like to do is give the
dog
a -taillength
option, but delegate it to
the tail
's -length
option:
% snit::type dog { delegate option -taillength to mytail as -length constructor {args} { set mytail [tail %AUTO% -partof $self] $self configurelist $args } } ::dog % snit::type tail { option -partof option -length 5 } ::tail % dog spot -taillength 7 ::spot % spot cget -taillength 7 %
snit::widgetadaptors
, for example, where we wish to
slightly the modify the behavior of an existing widget. To carry on
with our dog
example, however, suppose that we have a
snit::type
called animal
that implements a
variety of animal behaviors--moving, eating, sleeping, and so forth.
We want our dog
objects to inherit these same behaviors,
while adding dog-like behaviors of its own. Here's how we can give a
dog
methods and options of its own while delegating all
other methods and options to its animal
component:
% snit::type dog { delegate option * to animal delegate method * to animal option -akc 0 constructor {args} { install animal using animal %AUTO% -name $self $self configurelist $args } method wag {} { return "$self wags its tail" } } ::dog %That's it. A
dog
is now an animal
which has
a -akc
option and can wag
its tail.
Note that we don't need to specify the full list of method names or
option names which animal
will receive. It gets anything
dog
doesn't recognize--and if it doesn't recognize it
either, it will simply throw an error, just as it should.
dog
is an
animal
by delegating all unknown methods and options to
the animal
component. But what if the
animal
type has some methods or options that we'd like
to suppress?
One solution is to explicitly delegate all the options and methods,
and forgo the convenience of delegate method *
and
delegate option *
. But if we wish to suppress only a few
options or methods, there's an easier way:
% snit::type dog { delegate option * to animal except -legs delegate method * to animal except {fly climb} # ... constructor {args} { install animal using animal %AUTO% -name $self -legs 4 $self configurelist $args } # ... } ::dog %Dogs have four legs, so we specify that explicitly when we create the
animal
component, and explicitly exclude
-legs
from the set of delegated options. Similarly,
dogs can neither fly nor climb, so we exclude those
animal
methods as shown.
snit::widget
is the Snit version of what Tcl
programmers usually call a "megawidget": a widget-like object usually
consisting of one or more Tk widgets all contained within a Tk frame.
A snit::widget
is also a special kind of
snit::type
. Just about everything in this FAQ list that
relates to snit::types
also applies to
snit::widgets
.
snit::widgets
are defined using the
snit::widget
command, just as snit::types
are defined by the snit::type
command.The body of the definition can contain all of the same kinds of statements, plus a couple of others which will be mentioned below.
snit::type
can be any
valid Tcl command name, in any namespace. The name of an
instance of a snit::widget
must be a valid Tk
widget name, and its parent widget must already exist.
snit::type
can be destroyed by
calling its destroy
method.
Instances of a snit::widget
have no destroy
method; use the Tk destroy
command instead.
snit::widget
has one
predefined component called its hull
component.
The hull is a Tk frame
or toplevel
widget; any other widgets created as
part of the snit::widget
will usually be contained within
this frame or toplevel.
snit::widgets
can have their options receive
default values from the Tk option database.
snit::widget
must be wrapped around
a genuine Tk widget; this Tk widget is called the hull
component. Snit effectively piggybacks the behavior you define
(methods, options, and so forth) on top of the hull component so that
the whole thing behaves like a standard Tk widget.
For snit::widgets
the hull component must be a
Tk frame
or toplevel
widget; any other
widgets created as part of the snit::widget
will
be contained within this frame or toplevel.
snit::widgetadaptors
differ from
snit::widgets
chiefly in that any kind of widget can be
used as the hull component; see WIDGETADAPTORS.
snit::widget
's hull component will usually be a Tk
frame
widget; however, it may also be a
toplevel
widget. You can explicitly choose one or the
other by including the hulltype
command in the widget
definition:
snit::widget mytoplevel { hulltype toplevel # ... }If no
hulltype
command appears, the hull will be a
frame
.
snit::widget
is
first created, its instance name, $self
, is a Tk window
name; however, if the snit::widget
is used as the hull
component by a snit::widgetadaptor
its instance name will
be changed to something else. For this reason, every
snit::widget
method, constructor, destructor, and so
forth is passed another implicit argument, $win
, which is
the window name of the megawidget. Any children must be named using
$win
as the root.Thus, suppose you're writing a toolbar widget, a frame consisting of a number of buttons placed side-by-side. It might look something like this:
snit::widget toolbar { delegate option * to hull constructor {args} { button $win.open -text Open -command [mymethod open] button $win.save -text Save -command [mymethod save] # .... $self configurelist $args } }See also the question on renaming objects, toward the top of this file.
snit::widgetadaptor
is a kind of
snit::widget
. Whereas a snit::widget
's hull
is automatically created and is always a Tk frame, a
snit::widgetadaptor
can be based on any Tk widget--or on
any Snit megawidget, or even (with luck) on megawidgets defined using
some other package.It's called a "widget adaptor" because it allows you to take an existing widget and customize its behavior.
snit::widgetadaptor
command. The definition
for a snit::widgetadaptor
looks just like that for a
snit::type
or snit::widget
, except that the
constructor must create and install the hull component.For example, the following code creates a read-only text widget by the simple device of turning its "insert" and "delete" methods into no-ops. Then, we define new methods, "ins" and "del", which get delegated to the hull component as "insert" and "delete". Thus, we've adapted the text widget and given it new behavior while still leaving it fundamentally a text widget.
% ::snit::widgetadaptor rotext { constructor {args} { # Create the text widget; turn off its insert cursor installhull using text -insertwidth 0 # Apply any options passed at creation time. $self configurelist $args } # Disable the text widget's insert and delete methods, to # make this readonly. method insert {args} {} method delete {args} {} # Enable ins and del as synonyms, so the program can insert and # delete. delegate method ins to hull as insert delegate method del to hull as delete # Pass all other methods and options to the real text widget, so # that the remaining behavior is as expected. delegate method * to hull delegate option * to hull } ::rotext %The most important part is in the constructor. Whereas
snit::widget
creates the hull for you,
snit::widgetadaptor
cannot--it doesn't know what kind of
widget you want. So the first thing the constructor does is create
the hull component (a Tk text widget in this case), and then installs
it using the installhull
command.
Note: There is no instance command until you create one by
installing a hull component. Any attempt to pass methods to $self
prior to calling installhull
will fail.
At times, it can be convenient to adapt a widget created by another
party. For example, the Bwidget PagesManager
widget
manages a set of frame
widgets, only one of which is
visible at a time. The application chooses which frame
is visible. These frames
are created by the
PagesManager
itself, using its add
method.
In a case like this, the Tk widget will already exist when the
snit::widgetadaptor
is created. Snit provides an
alternate form of the installhull
command for this purpose:
snit::widgetadaptor pageadaptor { constructor {args} { # The widget already exists; just install it. installhull $win # ... } }
Full details about the Tk option database are beyond the scope of this document; both Practical Programming in Tcl and Tk by Welch, Jones, and Hobbs, and Effective Tcl/Tk Programming by Harrison and McClennan., have good introductions to it.
Snit is implemented so that most of the time it will simply do the right thing with respect to the option database, provided that the widget developer does the right thing by Snit. The body of this section goes into great deal about what Snit requires. The following is a brief statement of the requirements, for reference.
widgetclass
statement in the
widget definition.
installhull using
command to create
and install the hull for snit::widgetadaptors
.
install
command to create and install all
other components.
Only snit::widgets
and snit::widgetadaptors
query the option database.
button
widget is
"Button".
Similarly, the widget class of a snit::widget
defaults
to the unqualified type name with the first letter capitalized. For
example, the widget class of
snit::widget ::mylibrary::scrolledText { ... }is "ScrolledText".
The widget class can also be set explicitly using
the widgetclass
statement within the
snit::widget
definition:
snit::widget ::mylibrary::scrolledText { widgetclass Text # ... }The above definition says that a
scrolledText
megawidget
has the same widget class as an ordinary text
widget.
This might or might not be a good idea, depending on how the rest of
the megawidget is defined, and how its options are delegated.
snit::widgetadaptor
is just the
widget class of its hull widget; Snit has no control over this.
Note that the widget class can be changed only for frame
and
toplevel
widgets, which is why these are the valid hull
types for snit::widgets
.
Try to use snit::widgetadaptors
only to make small
modifications to another widget's behavior. Then, it will usually
not make sense to change the widget's widget class anyway.
configure
and cget
commands.
The resource and class names are used to initialize option
default values by querying the option database.
The resource name is usually just the option
name minus the hyphen, but may contain uppercase letters at word
boundaries; the class name is usually just the resource
name with an initial capital, but not always. For example, here are
the option, resource, and class names for several Tk text
widget options:
-background background Background -borderwidth borderWidth BorderWidth -insertborderwidth insertBorderWidth BorderWidth -padx padX PadAs is easily seen, sometimes the resource and class names can be inferred from the option name, but not always.
delegate
option *
, the resource and class names will be exactly those
defined by the component. The configure
method returns
these names, along with the option's default and current values:
% snit::widget mytext { delegate option * to text constructor {args} { install text using text .text # ... } # ... } ::mytext % mytext .text .text % .text configure -padx -padx padX Pad 1 1 %For all other options (whether locally defined or explicitly delegated), the resource and class names can be defined explicitly, or they can be allowed to have default values.
By default, the resource name is just the option name minus the hyphen; the the class name is just the option name with an initial capital letter. For example, suppose we explicitly delegate "-padx":
% snit::widget mytext { option -myvalue 5 delegate option -padx to text delegate option * to text constructor {args} { install text using text .text # ... } # ... } ::mytext % mytext .text .text % .text configure -mytext -mytext mytext Mytext 5 5 % .text configure -padx -padx padx Padx 1 1 %Here the resource and class names are chosen using the default rules. Often these rules are sufficient, but in the case of "-padx" we'd most likely prefer that the option's resource and class names are the same as for the built-in Tk widgets. This is easily done:
% snit::widget mytext { delegate option {-padx padX Pad} to text # ... } ::mytext % mytext .text .text % .text configure -padx -padx padX Pad 1 1 %
option add *Mywidget.texture pebbled snit::widget mywidget { option -texture smooth # ... } mywidget .mywidget -texture greasyHere, "-texture" would normally default to "smooth", but because of the entry added to the option database it defaults to "pebbled". However, the caller has explicitly overridden the default, and so the new widget will be "greasy".
snit::widget
's hull is a widget, and given that its
class has been set it is expected to query the option database for
itself. The only exception concerns options that are delegated to it
with a different name. Consider the following code:
option add *Mywidget.borderWidth 5 option add *Mywidget.relief sunken option add *Mywidget.hullbackground red option add *Mywidget.background green snit::widget mywidget { delegate option -borderwidth to hull delegate option -hullbackground to hull as -background delegate option * to hull # ... } mywidget .mywidget set A [.mywidget cget -relief] set B [.mywidget cget -hullbackground] set C [.mywidget cget -background] set D [.mywidget cget -borderwidth]The question is, what are the values of variables A, B, C and D?
The value of A is "sunken". The hull is a Tk frame which has been given the widget class "Mywidget"; it will automatically query the option database and pick up this value. Since the -relief option is implicitly delegated to the hull, Snit takes no action.
The value of B is "red". The hull will automatically pick up the value "green" for its -background option, just as it picked up the -relief value. However, Snit knows that -hullbackground is mapped to the hull's -background option; hence, it queries the option database for -hullbackground and gets "red" and updates the hull accordingly.
The value of C is also "red", because -background is implicitly delegated to the hull; thus, retrieving it is the same as retrieving -hullbackground. Note that this case is unusual; the -background option should probably have been excluded using the delegate statement's "except" clause, or (more likely) delegated to some other component.
The value of D is "5", but not for the reason you think. Note that as it is defined above, the resource name for -borderwidth defaults to "borderwidth", whereas the option database entry is "borderWidth", in accordance with the standard Tk naming for this option. As with -relief, the hull picks up its own "-borderwidth" option before Snit does anything. Because the option is delegated under its own name, Snit assumes that the correct thing has happened, and doesn't worry about it any further. To avoid confusion, the -borderwidth option should have been delegated like this:
delegate option {-borderwidth borderWidth BorderWidth} to hullFor
snit::widgetadaptors
, the case is somewhat altered.
Widget adaptors retain the widget class of their hull, and the
hull is not created automatically by Snit. Instead, the
snit::widgetadaptor
must call
installhull
in its
constructor. The normal way to do this is as follows:
snit::widgetadaptor mywidget { # ... constructor {args} { # ... installhull using text -foreground white # } #... }In this case, the
installhull
command will create the hull using a command like this:
set hull [text $win -foreground white]The hull is a
text
widget, so its widget class
is "Text". Just as with snit::widget
hulls,
Snit assumes that it will pick up all of its normal option values
automatically, without help from Snit. Options delegated from a
different name are initialized from the option database in the same
way as described above.
In earlier versions of Snit, snit::widgetadaptors
were
expected to call installhull
like this:
installhull [text $win -foreground white]This form still works--but Snit will not query the option database as described above.
A component widget remains a widget still, and is therefore
initialized from the option database in the usual way. A
text
widget remains a text
widget whether
it is a component of a megawidget or not, and will be created as such.
But then, the option database is queried for all options delegated
to the component, and the component is initialized
accordingly--provided that the install
command is used to create it.
Before option database support was added to Snit, the usual way to create a component was to simply create it in the constructor and assign its command name to the component variable:
snit::widget mywidget { delegate option -background to myComp constructor {args} { set myComp [text $win.text -foreground black] } }The drawback of this method is that Snit has no opportunity to initialize the component properly. Hence, the following approach is now used:
snit::widget mywidget { delegate option -background to myComp constructor {args} { install myComp using text $win.text -foreground black } }The
install
command does the
following:
install
command--in this
case, -foreground.
configure
method to receive a
list of all of the component's options. From this Snit builds
a list of options implicitly delegated to the component which
were not explicitly included in the
install
command. For all
such options, Snit queries the option database and configures
the component accordingly.
install
to install your components, and Snit will try to
do the right thing.
snit::type
never queries the option database.
However, a snit::widget
can have non-widget
components. And if options are delegated to those components, and if
the install
command is used to
install those components, then they will be initialized from the
option database just as widget components are.
However, when used within a megawidget, install
assumes
that the created component uses a reasonably standard widget-like
creation syntax. If it doesn't, don't use install
.
Copyright © 2003, by William H. Duquette. All rights reserved.