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
}
}
::dog
As 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 30
The 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 greasy
Here, "-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 hull
For 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.