|
ActiveTcl User Guide
|
|
|
[ Main table Of Contents | Tcllib Table Of Contents | Tcllib Index ]
snit(n) 0.97 "Snit"
snit - Simple Now In Tcl
TABLE OF
CONTENTS
SYNOPSIS
DESCRIPTION
REFERENCE
Type and Widget Definitions
The Type Command
Standard Type Methods
The Instance Command
Standard Instance Methods
Commands for use in Object
Code
Components and Delegation
Type Components and
Delegation
The Tk Option Database
Macros and Meta-programming
CAVEATS
KNOWN BUGS
HISTORY
CREDITS
KEYWORDS
COPYRIGHT
package require Tcl 8.4
package require snit ?0.97?
Snit is a pure Tcl object and megawidget system. It's unique
among Tcl object systems in that it's based not on inheritance but
on delegation. Object systems based on inheritance only allow you
to inherit from classes defined using the same system, which is
limiting. In Tcl, an object is anything that acts like an object;
it shouldn't matter how the object was implemented. Snit is
intended to help you build applications out of the materials at
hand; thus, Snit is designed to be able to incorporate and build on
any object, whether it's a hand-coded object, a Tk
widget, an Incr Tcl object, a
BWidget or almost anything else.
This man page is intended to be a reference only; see the
accompanying snitfaq
for a gentler, more tutorial introduction to Snit concepts.
Snit provides the following commands for defining new types:
- snit::type name definition
- Defines a new abstract data type called name. If name is not a fully qualified
command name, it is assumed to be a name in the namespace in which
the snit::type command was called (usually the
global namespace). It returns the fully qualified name of the new
type.
The type name is then a command that is used to create objects of
the new type, along with other activities.
The snit::type definition
block is a script that may contain the following definitions:
- typevariable name ?-array? ?value?
- Defines a type variable with the specified name, and optionally the specified value. Type variables are shared by all instances of the
type. If the -array option is included, then value should be a list of keyword/value pairs; it
will be assigned to the variable in the manner of array set.
- typemethod name arglist body
- Defines a type method, a subcommand of the new type command,
with the specified name, argument list, and body. The arglist is a normal Tcl argument list and may contain
default arguments and the args argument; however,
it may not contain the argument names type,
self, selfns, or
win.
The variable type is automatically defined in the
body to the type's fully-qualified name. In
addition, type variables are automatically visible in the body of every type method.
If the name consists of two or more tokens,
Snit handles it specially:
|
typemethod {a b} {} { ... }
|
The following two calls to this type method are equivalent:
In other words, a becomes a subcommand of
$type, and b becomes a subcommand
of a. This makes it possible to define a
hierarchical command structure; see method,
below, for more examples.
- typeconstructor body
- The type constructor's body is executed once
when the type is first defined; it is typically used to initialize
array-valued type variables and to add entries to The Tk Option Database.
The variable type is automatically defined in the
body, and contains the type's fully-qualified
name. In addition, type variables are automatically visible in the
body of the type constructor.
A type may define at most one type constructor.
- variable name
?-array? ?value?
- Defines an instance variable, a private variable associated
with each instance of this type, and optionally its initial value.
If the -array option is included, then value should be a list of keyword/value pairs; it will be
assigned to the variable in the manner of array
set.
- method name
arglist body
- Defines an instance method, a subcommand of each instance of
this type, with the specified name, argument list and body. The arglist is a normal Tcl argument list and may
contain default arguments and the args
argument.
The method is implicitly passed the following arguments as well:
type, which contains the fully-qualified type
name; self, which contains the current instance
command name; selfns, which contains the name of
the instance's private namespace; and win, which
contains the original instance name. Consequently, the arglist may not contain the argument names
type, self,
selfns, or win.
An instance method defined in this way is said to be locally
defined.
Type and instance variables are automatically visible in all
instance methods. If the type has locally defined options, the
options array is also visible.
If the name consists of two or more tokens,
Snit handles it specially:
The following two calls to this type method are equivalent:
In other words, a becomes a subcommand of
$self, and b becomes a subcommand
of a. This makes it possible to define a
hierarchical command structure. For example,
|
% snit::type dog {
method {tail wag} {} {return "Wag, wag"}
method {tail droop} {} {return "Droop, droop"}
}
::dog
% dog spot
::spot
% spot tail wag
Wag, wag
% spot tail droop
Droop, droop
%
|
What we've done is implicitly defined a "tail" method with
subcommands "wag" and "droop". Consequently, it's an error to
define "tail" explicitly.
- option namespec ?defaultValue?
- option namespec ?options...?
- Defines an option for instances of this type, and optionally
gives it an initial value. The initial value defaults to the empty
string if no defaultValue is specified.
An option defined in this way is said to be locally
defined.
The namespec is a list defining the option's
name, resource name, and class name, e.g.:
|
option {-font font Font} {Courier 12}
|
The option name must begin with a hyphen, and must not contain any
upper case letters. The resource name and class name are optional;
if not specified, the resource name defaults to the option name,
minus the hyphen, and the class name defaults to the resource name
with the first letter capitalized. Thus, the following statement is
equivalent to the previous example:
|
option -font {Courier 12}
|
See The Tk Option Database for
more information about resource and class names.
Options are normally set and retrieved using the standard instance
methods configure and cget;
within instance code (method bodies, etc.), option values are
available through the options array:
|
set myfont $options(-font)
|
If the type defines any option handlers (e.g.,
-configuremethod), then it should probably use
configure and cget to access its
options to avoid subtle errors.
The option statement may include the following
options:
- -default defvalue
- Defines the option's default value; the option's default value
will be "" otherwise.
- -readonly flag
- The flag can be any Boolean value recognized
by Tcl. If flag is true, then the option is
readonly--it can only be set using configure or
configurelist at creation time, i.e., in the
type's constructor.
- -cgetmethod methodName
- Every locally-defined option may define a
-cgetmethod; it is called when the option's value
is retrieved using the cget method. Whatever the
method's body returns will be the return value
of the call to cget.
The named method must take one argument, the option name. For
example, this code is equivalent to (though slower than) Snit's
default handling of cget:
|
option -font -cgetmethod GetOption
method GetOption {option} {
return $options($option)
}
|
Note that it's possible for any number of options to share a
-cgetmethod.
- -configuremethod methodName
- Every locally-defined option may define a
-configuremethod; it is called when the option's
value is set using the configure or
configurelist methods. It is the named method's
responsibility to save the option's value; in other words, the
value will not be saved to the options() array
unless the method saves it there.
The named method must take two arguments, the option name and its
new value. For example, this code is equivalent to (though slower
than) Snit's default handling of configure:
|
option -font -configuremethod SetOption
method GetOption {option value} {
set options($option) $value
}
|
Note that it's possible for any number of options to share a
single -configuremethod.
- -validatemethod methodName
- Every locally-defined option may define a
-validatemethod; it is called when the option's
value is set using the configure or
configurelist methods, just before the
-configuremethod (if any). It is the named
method's responsibility to validate the option's new value, and to
throw an error if the value is invalid.
The named method must take two arguments, the option name and its
new value. For example, this code verifies that
-flag's value is a valid Boolean value:
|
option -font -validatemethod CheckBoolean
method CheckBoolean {option value} {
if {![string is boolean -strict $value]} {
error "option $option must have a boolean value."
}
}
|
Note that it's possible for any number of options to share a
single -validatemethod.
- constructor arglist body
- The constructor definition specifies a body
of code to be executed when a new instance is created. The arglist is a normal Tcl argument list and may
contain default arguments and the args
argument.
As with methods, the arguments type,
self, selfns, and
win are defined implicitly, and all type and
instance variables are automatically visible in its body.
If the definition doesn't explicitly define the
constructor, Snit defines one implicitly. If the type declares at
least one option (whether locally or by delegation), the default
constructor will be defined as follows:
|
constructor {args} {
$self configurelist $args
}
|
For standard Tk widget behavior, the argument list should be the
single name args, as shown.
If the definition defines neither a constructor
nor any options, the default constructor is defined as follows:
- destructor body
- The destructor is used to code any actions that must take place
when an instance of the type is destroyed: typically, the
destruction of anything created in the constructor.
The destructor takes no explicit arguments; as with methods, the
arguments type, self,
selfns, and win, are defined
implicitly, and all type and instance variables are automatically
visible in its body.
- proc name args body
- Defines a new Tcl procedure in the type's namespace.
The defined proc differs from a normal Tcl proc in that all type
variables are automatically visible. The proc can access instance
variables as well, provided that it is passed
selfns (with precisely that name) as one of its
arguments.
Although they are not implicitly defined for procs, the argument
names type, self, and
win should be avoided.
- delegate
method name to
comp ?as target?
- Delegates method name to component comp. That is, when method name is
called on an instance of this type, the method and its arguments
will be passed to the named component's command instead. That is,
the following statement
|
delegate method wag to tail
|
is roughly equivalent to this explicitly defined method:
|
method wag {args} {
uplevel $tail wag $args
}
|
As with methods, the name may have multiple
tokens; in this case, the last token of the name is assumed to be
the name of the component's method.
The optional as clause allows you to specify the
delegated method name and possibly add some arguments:
|
delegate method wagtail to tail as "wag briskly"
|
A method cannot be both locally defined and delegated.
Note: All forms of delegate
method can delegate to both instance components and type
components.
- delegate
method name
?to comp?
using pattern
- In this form of the delegate statement, the
using clause is used to specify the precise form
of the command to which method name name is
delegated. In this form, the to clause is
optional, since the chosen command might not involve any particular
component.
The value of the using clause is a list that may
contain any or all of the following substitution codes; these codes
are substituted with the described value to build the delegated
command prefix. Note that the following two statements are
equivalent:
|
delegate method wag to tail
delegate method wag to tail using "%c %m"
|
Each element of the list becomes a single element of the delegated
command--it is never reparsed as a string.
Substitutions:
- %%
- This is replaced with a single "%". Thus, to pass the string
"%c" to the command as an argument, you'd write "%%c".
- %c
- This is replaced with the named component's command.
- %m
- This is replaced with the final token of the method name; if the method name has one
token, this is identical to %M.
- %M
- This is replaced by the method name; if the
name consists of multiple tokens, they are
joined by space characters.
- %j
- This is replaced by the method name; if the
name consists of multiple tokens, they are
joined by underscores ("_").
- %t
- This is replaced with the fully qualified type name.
- %n
- This is replaced with the name of the instance's private
namespace.
- %s
- This is replaced with the name of the instance command.
- %w
- This is replaced with the original name of the instance
command; for Snit widgets and widget adaptors, it will be the Tk
window name. It remains constant, even if the instance command is
renamed.
- delegate
method * ?to comp? ?using pattern? ?except exceptions?
- The form delegate method * delegates all
unknown method names to the specified component.
The except clause can be used to specify a list of
exceptions, i.e., method names that will not be
so delegated. The using clause is defined as given
above. In this form, the statement must contain the
to clause, the using clause, or
both.
In fact, the "*" can be a list of two or more tokens whose last
element is "*", as in the following example:
|
delegate method {tail *} to tail
|
This implicitly defines the method tail whose
subcommands will be delegated to the tail
component.
- delegate
option namespec
to comp
- delegate
option namespec
to comp as target
- delegate
option * to comp
- delegate
option * to comp except exceptions
- Defines a delegated option; the namespec is
defined as for the option statement. When the
configure, configurelist, or
cget instance method is used to set or retrieve
the option's value, the equivalent configure or
cget command will be applied to the component as
though these onconfigure and oncget handlers were defined, where name is the option name from the namespec:
|
onconfigure name {value} {
$comp configure name $value
}
oncget name {
return [$comp cget name]
}
|
If the as clause is specified, then the target option name is used in place of name.
The form delegate option * delegates all
unknown method names to the specified component.
The except clause can be used to specify a list of
exceptions, i.e., option names that will not be
so delegated.
Warning: options can only be delegated to a component if it
supports the configure and cget
instance methods.
Note that an option cannot be both locally defined and
delegated.
- component comp ?-public method?
?-inherit flag?
- Explicitly declares a component called comp,
and automatically defines the component's instance variable.
If the -public option is specified, then the
option is made public by defining a method whose
subcommands are delegated to the component e.g., specifying
-public mycomp is equivalent to the following:
|
component mycomp
delegate method {mymethod *} to mycomp
|
If the -inherit option is specified, then flag must be a Boolean value; if flag is true then all unknown methods and options will be
delegated to this component. The name -inherit
implies that instances of this new type inherit, in a sense, the
methods and options of the component. That is, -inherit
yes is equivalent to:
|
component mycomp
delegate option * to mycomp
delegate method * to mycomp
|
- delegate
typemethod name
to comp ?as target?
- Delegates type method name to type component
comp. That is, when type method name is called on this type, the type method and its
arguments will be passed to the named type component's command
instead. That is, the following statement
|
delegate typemethod lostdogs to pound
|
is roughly equivalent to this explicitly defined method:
|
typemethod lostdogs {args} {
uplevel $pound lostdogs $args
}
|
As with type methods, the name may have
multiple tokens; in this case, the last token of the name is
assumed to be the name of the component's method.
The optional as clause allows you to specify the
delegated method name and possibly add some arguments:
|
delegate typemethod lostdogs to pound as "get lostdogs"
|
A type method cannot be both locally defined and delegated.
- delegate
typemethod name
?to comp?
using pattern
- In this form of the delegate statement, the
using clause is used to specify the precise form
of the command to which type method name name is
delegated. In this form, the to clause is
optional, since the chosen command might not involve any particular
type component.
The value of the using clause is a list that may
contain any or all of the following substitution codes; these codes
are substituted with the described value to build the delegated
command prefix. Note that the following two statements are
equivalent:
|
delegate typemethod lostdogs to pound
delegate typemethod lostdogs to pound using "%c %m"
|
Each element of the list becomes a single element of the delegated
command--it is never reparsed as a string.
Substitutions:
- %%
- This is replaced with a single "%". Thus, to pass the string
"%c" to the command as an argument, you'd write "%%c".
- %c
- This is replaced with the named type component's command.
- %m
- This is replaced with the final token of the type method name; if the type method name
has one token, this is identical to %M.
- %M
- This is replaced by the type method name; if
the name consists of multiple tokens, they are
joined by space characters.
- %j
- This is replaced by the type method name; if
the name consists of multiple tokens, they are
joined by underscores ("_").
- %t
- This is replaced with the fully qualified type name.
- delegate
typemethod * ?to
comp? ?using pattern? ?except exceptions?
- The form delegate typemethod * delegates all
unknown type method names to the specified type component. The
except clause can be used to specify a list of exceptions, i.e., type method names that will not
be so delegated. The using clause is defined as
given above. In this form, the statement must contain the
to clause, the using clause, or
both.
Note: By default, Snit interprets $type foo, where foo is not a defined
type method, as equivalent to $type create foo,
where foo is the name of a new instance of the
type. If you use delegate typemethod *, then the
create type method must always be used
explicitly.
The "*" can be a list of two or more tokens whose last element is
"*", as in the following example:
|
delegate typemethod {tail *} to tail
|
This implicitly defines the type method tail
whose subcommands will be delegated to the tail
type component.
- typecomponent comp ?-public typemethod? ?-inherit flag?
- Explicitly declares a type component called comp, and automatically defines the component's type
variable. A type component is an arbitrary command to which type
methods and instance methods can be delegated; the command's name
is stored in a type variable.
If the -public option is specified, then the type
component is made public by defining a typemethod whose subcommands are delegated to the type
component, e.g., specifying -public mytypemethod
is equivalent to the following:
|
typecomponent mycomp
delegate typemethod {mytypemethod *} to mycomp
|
If the -inherit option is specified, then flag must be a Boolean value; if flag is true then all unknown type methods will be
delegated to this type component. (See the note on "delegate
typemethod *", above.) The name -inherit implies
that this type inherits, in a sense, the behavior of the type
component. That is, -inherit yes is equivalent to:
|
typecomponent mycomp
delegate typemethod * to mycomp
|
- pragma ?options...?
- The pragma statement provides control over
how Snit generates a type. It takes the following options; in each
case, flag must be a Boolean value recognized by
Tcl, e.g., 0, 1,
yes, no, and so on.
By setting the -hastypeinfo,
-hastypedestroy, and
-hasinstances pragmas to false and defining
appropriate type methods, you can create an ensemble command
without any extraneous behavior.
- -canreplace flag
- If false (the default) Snit will not create an instance of a snit::type that has the same name as an existing
command; this prevents subtle errors. Setting this pragma to true
restores the behavior of Snit V0.93 and earlier versions.
- -hastypeinfo flag
- If true (the default), the generated type will have a type
method called info that is used for type
introspection; the info type method is
documented below. If false, it will not.
- -hastypedestroy flag
- If true (the default), the generated type will have a type
method called destroy that is used to destroy
the type and all of its instances. The destroy
type method is documented below. If false, it will not.
- -hastypemethods flag
- If true (the default), the generated type's type command will
have subcommands (type methods) as usual. If false, the type
command will serve only to create instances of the type; the first
argument is the instance name.
This pragma and -hasinstances cannot both be set
false.
- -hasinstances flag
- If true (the default), the generated type will have a type
method called create that is used to create
instances of the type, along with a variety of instance-related
features. If false, it will not.
This pragma and -hastypemethods cannot both be
set false.
- -hasinfo flag
- If true (the default), instances of the generated type will
have an instance method called info that is used
for instance introspection; the info method is
documented below. If false, it will not.
- -simpledispatch flag
- This pragma is intended to make simple, heavily-used abstract
data types (e.g., stacks and queues) more efficient.
If false (the default), instance methods are dispatched normally.
If true, a faster dispatching scheme is used instead. The speed
comes at a price; with -simpledispatch yes you get
the following limitations:
- Methods cannot be delegated.
- uplevel and upvar do not
work as expected: the caller's scope is two levels up rather than
one.
- The option-handling methods (cget, configure, and configurelist) are very
slightly slower.
- expose comp
- expose comp
as method
- Deprecated. To expose component comp publicly, use component's
-public option.
- onconfigure name arglist body
- Deprecated. Define option's
-configuremethod option instead.
As of version 0.95, the following definitions,
|
option -myoption
onconfigure -myoption {value} {
# Code to save the option's value
}
|
are implemented as follows:
|
option -myoption -configuremethod _configure-myoption
method _configure-myoption {_option value} {
# Code to save the option's value
}
|
- oncget name
body
- Deprecated. Define option's
-cgetmethod option instead.
As of version 0.95, the following definitions,
|
option -myoption
oncget -myoption {
# Code to return the option's value
}
|
are implemented as follows:
|
option -myoption -cgetmethod _cget-myoption
method _cget-myoption {_option} {
# Code to return the option's value
}
|
- snit::widget name definition
- This command defines a Snit megawidget type with the specified
name. The definition is
defined as for snit::type. A snit::widget differs from a snit::type
in these ways:
- Every instance of a snit::widget has an
automatically-created component called hull, which
is normally a Tk frame widget. Other widgets created as part of the
megawidget will be created within this widget.
The hull component is initially created with the requested widget
name; then Snit does some magic, renaming the hull component and
installing its own instance command in its place. The hull
component's new name is saved in an instance variable called
hull.
- The name of an instance must be valid Tk window name, and the
parent window must exist.
A snit::widget definition can include any of
statements allowed in a snit::type definition,
and may also include the following:
- widgetclass name
- Sets the snit::widget's widget class to name, overriding the default. See The Tk Option Database for more
information.
- hulltype type
- Determines the kind of widget used as the snit::widget's hull. The type may be
frame (the default) or
toplevel.
- snit::widgetadaptor name definition
- This command defines a Snit megawidget type with the specified
name. It differs from snit::widget in that the
instance's hull component is not created
automatically, but is created in the constructor and installed
using the installhull command. Once the hull is
installed, its instance command is renamed and replaced as with
normal snit::widgets. The original command is
again accessible in the instance variable
hull.
Note that in general it is not possible to change the widget
class of a snit::widgetadaptor's hull
widget.
See The Tk Option Database for
information on how snit::widgetadaptors interact
with the option database.
- snit::typemethod type name arglist
body
- Defines a new type method (or redefines an existing type
method) for a previously existing type.
- snit::method type name arglist
body
- Defines a new instance method (or redefines an existing
instance method) for a previously existing type.
Note that delegated instance methods can't be redefined.
- snit::macro name arglist body
- Defines a Snit macro with the specified name, arglist, and body. Macros are used to define new type and widget
definition statements in terms of the statements defined in this
man page.
A macro is simply a Tcl proc that is defined in the slave
interpreter used to compile type and widget definitions. Thus,
macros have access to all of the type and widget definition
statements. See Macros and
Meta-programming for more details.
The macro name cannot be the same as any
standard Tcl command, or any Snit type or widget definition
statement, e.g., you can't redefine the method
or delegate statements, or the standard set , list , or string commands.
- snit::compile which type body
- Snit defines a type, widget, or widgetadaptor by "compiling"
the definition into a Tcl script; this script is then evaluated in
the Tcl interpreter, which actually defines the new type.
This command exposes the "compiler". Given a definition body for the named type, where which is type,
widget, or widgetadaptor, snit::compile returns a list of two elements. The
first element is the fully qualified type name; the second element
is the definition script.
snit::compile is useful when additional
processing must be done on the Snit-generated code--if it must be
instrumented, for example, or run through the TclDevKit compiler.
In addition, the returned script could be saved in a ".tcl" file
and used to define the type as part of an application or library,
thus saving the compilation overhead at application start-up. Note
that the same version of Snit must be used at run-time as at
compile-time.
A type or widget definition creates a type command, which is
used to create instances of the type. The type command has this
form:
- $type typemethod args...
- The typemethod can be any of the Standard Type Methods (e.g.,
create), or any type method defined in the type
definition. The subsequent args depend on the
specific typemethod chosen.
The type command is most often used to create new instances of the
type; hence, the create method is assumed if the
first argument to the type command doesn't name a valid type
method, unless the type definition includes delegate
typemethod * or the -hasinstances pragma is
set to false.
Furthermore, Snit type commands can be called with no arguments at
all; in this case, the type command creates an instance with an
automatically generated name. In other words, provided that the
type has instances, the following commands are equivalent:
|
snit::type dog { ... }
set mydog [dog create %AUTO%]
set mydog [dog %AUTO%]
set mydog [dog]
|
This doesn't work for Snit widgets, for obvious reasons.
In addition to any type methods in the type's definition, all
type and widget commands will usually have at least the following
subcommands:
- $type create
name ?option value ...?
- Creates a new instance of the type, giving it the specified name and calling the type's constructor.
For snit::types, if name is
not a fully-qualified command name, it is assumed to be a name in
the namespace in which the call to snit::type
appears. The method returns the fully-qualified instance name.
For snit::widgets and snit::widgetadaptors, name must be a
valid widget name; the method returns the widget name.
So long as name does not conflict with any
defined type method name the create keyword may be
omitted, unless the type definition includes delegate typemethod * or the
-hasinstances pragma is set to false.
If the name includes the string
%AUTO%, it will be replaced with the string
$type$counter where $type is the
type name and $counter is a counter that
increments each time %AUTO% is used for this
type.
By default, any arguments following the name
will be a list of option names and their values; however, a type's constructor can specify a
different argument list.
As of Snit V0.95, create will throw an error if
the name is the same as any existing
command--note that this was always true for snit::widgets and snit::widgetadaptors. You can restore the previous
behavior using the -canreplace pragma.
- $type info
typevars ?pattern?
- Returns a list of the type's type variables (excluding Snit
internal variables); all variable names are fully-qualified.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
- $type info
typemethods ?pattern?
- Returns a list of the names of the type's type methods. If the
type definition includes delegate typemethod *,
the list will include only the names of those implicitly delegated
type methods that have been called at least once and are still in
the type method cache.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
- $type info
instances ?pattern?
- Returns a list of the type's instances. For snit::types, it will be a list of fully-qualified
instance names; for snit::widgets, it will be a
list of Tk widget names.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
- $type
destroy
- Destroys the type's instances, the type's namespace, and the
type command itself.
A Snit type or widget's create type method
creates objects of the type; each object has a unique name that is
also a Tcl command. This command is used to access the object's
methods and data, and has this form:
- $object method args...
- The method can be any of the Standard Instance Methods, or any
instance method defined in the type definition. The subsequent args depend on the specific method chosen.
In addition to any delegated or locally-defined instance methods
in the type's definition, all Snit objects will have at least the
following subcommands:
- $object
configure ?option? ?value? ...
- Assigns new values to one or more options. If called with one
argument, an option name, returns a list
describing the option, as Tk widgets do; if called with no
arguments, returns a list of lists describing all options, as Tk
widgets do.
Warning: This information will be available for delegated options
only if the component to which they are delegated has a
configure method that returns this same kind of
information.
Note: Snit defines this method only if the type has at least one
option.
- $object
configurelist optionlist
- Like configure, but takes one argument, a list
of options and their values. It's mostly useful in the type
constructor, but can be used anywhere.
Note: Snit defines this method only if the type has at least one
option.
- $object cget
option
- Returns the option's value.
Note: Snit defines this method only if the type has at least one
option.
- $object
destroy
- Destroys the object, calling the destructor
and freeing all related memory.
Note: The destroy method isn't defined
for snit::widget or snit::widgetadaptor objects; instances of these are
destroyed by calling Tk's destroy command, just as normal widgets are.
- $object info
type
- Returns the instance's type.
- $object info
vars ?pattern?
- Returns a list of the object's instance variables (excluding
Snit internal variables). The names are fully qualified.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
- $object info
typevars ?pattern?
- Returns a list of the object's type's type variables (excluding
Snit internal variables). The names are fully qualified.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
- $object info
typemethods ?pattern?
- Returns a list of the names of the instance's type's type
methods. If the type definition includes delegate
typemethod *, the list will include only the names of those
implicitly delegated type methods that have been called at least
once and are still in the type method cache.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
- $object info
options ?pattern?
- Returns a list of the object's option names. This always
includes local options and explicitly delegated options. If unknown
options are delegated as well, and if the component to which they
are delegated responds to $object configure like
Tk widgets do, then the result will include all possible unknown
options that can be delegated to the component.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
Note that the return value might be different for different
instances of the same type, if component object types can vary from
one instance to another.
- $object info
methods ?pattern?
- Returns a list of the names of the instance's methods. If the
type definition includes delegate method *, the
list will include only the names of those implicitly delegated
methods that have been called at least once and are still in the
method cache.
If pattern is given, it's used as a string match pattern; only names that match the pattern
are returned.
Snit defines the following commands for use in your object code:
that is, for use in type methods, instance methods, constructors,
destructors, onconfigure handlers, oncget handlers, and procs. They
do not reside in the ::snit:: namespace; instead, they are created
with the type, and can be used without qualification.
- mymethod name ?args...?
- The mymethod command is used for formatting
callback commands to be passed to other objects. It returns a
command that when called will invoke method name
with the specified arguments, plus of course any arguments added by
the caller. In other words, both of the following commands will
cause the object's dosomething method to be called
when the $button is pressed:
|
$button configure -command [list $self dosomething myargument]
$button configure -command [mymethod dosomething myargument]
|
The chief distinction between the two is that the latter form will
not break if the object's command is renamed.
- mytypemethod name ?args...?
- The mytypemethod command is used for
formatting callback commands to be passed to other objects. It
returns a command that when called will invoke type method name with the specified arguments, plus of course
any arguments added by the caller. In other words, both of the
following commands will cause the object's
dosomething type method to be called when $button is pressed:
|
$button configure -command [list $type dosomething myargument]
$button configure -command [mytypemethod dosomething myargument]
|
Type commands cannot be renamed, so in practice there's little
difference between the two forms. mytypemethod
is provided for parallelism with mymethod.
- myproc name
?args...?
- The myproc command is used for formatting
callback commands to be passed to other objects. It returns a
command that when called will invoke the type proc name with the specified arguments, plus of course any
arguments added by the caller. In other words, both of the
following commands will cause the object's
dosomething proc to be called when $button is pressed:
|
$button configure -command [list ${type}::dosomething myargument]
$button configure -command [myproc dosomething myargument]
|
- myvar name
- Given an instance variable name, returns the fully qualified
name. Use this if you're passing the variable to some other object,
e.g., as a -textvariable to a Tk label widget.
- mytypevar name
- Given an type variable name, returns the fully qualified name.
Use this if you're passing the variable to some other object, e.g.,
as a -textvariable to a Tk label widget.
- from argvName option ?defvalue?
- The from command plucks an option value from
a list of options and their values, such as is passed into a type's
constructor. argvName must be
the name of a variable containing such a list; option is the name of the specific option.
from looks for option in the
option list. If it is found, it and its value are removed from the
list, and the value is returned. If option
doesn't appear in the list, then the defvalue is
returned. If the option is locally-defined option, and defvalue is not specified, then the option's default
value as specified in the type definition will be returned
instead.
- install compName using objType objName args...
- Creates a new object of type objType called
objName and installs it as component compName, as described in Components and Delegation. Any
additional args... are passed along with the
name to the objType command. If this is a snit::type, then the following two commands are
equivalent:
|
install myComp using myObjType $self.myComp args...
set myComp [myObjType $self.myComp args...]
|
Note that whichever method is used, compName
must still be declared in the type definition using component, or must be referenced in at least one delegate statement.
If this is a snit::widget or snit::widgetadaptor, and if options have been delegated
to component compName, then those options will
receive default values from the Tk option database. Note that it
doesn't matter whether the component to be installed is a widget or
not. See The Tk Option Database
for more information.
install cannot be used to install type
components; just assign the type component's command name to the
type component's variable instead.
- installhull
using widgetType args...
- installhull name
- The constructor of a snit::widgetadaptor
must create a widget to be the object's hull component; the widget
is installed as the hull component using this command. Note that
the installed widget's name must be $win. This
command has two forms.
The first form specifies the widgetType and the
args... (that is, the hardcoded option list) to
use in creating the hull. Given this form, installhull creates the hull widget, and initializes any
options delegated to the hull from the Tk option database.
In the second form, the hull widget has already been created; note
that its name must be "$win". In this case, the Tk option database
is not queried for any options delegated to the hull. The
longer form is preferred; however, the shorter form allows the
programmer to adapt a widget created elsewhere, which is sometimes
useful. For example, it can be used to adapt a "page" widget
created by a BWidgets tabbed notebook or pages
manager widget.
See The Tk Option Database for
more information about snit::widgetadaptors and
the option database.
- variable name
- Normally, instance variables are defined in the type definition
along with the options, methods, and so forth; such instance
variables are automatically visible in all instance code (e.g.,
method bodies). However, instance code can use the variable command to declare instance variables that don't
appear in the type definition, and also to bring variables from
other namespaces into scope in the usual way.
It's generally clearest to define all instance variables in the
type definition, and omit declaring them in methods and so
forth.
Note that this is an instance-specific version of the standard Tcl
::variable command.
- typevariable name
- Normally, type variables are defined in the type definition,
along with the instance variables; such type variables are
automatically visible in all of the type's code. However, type
methods, instance methods and so forth can use typevariable to declare type variables that don't appear
in the type definition.
It's generally clearest to declare all type variables in the type
definition, and omit declaring them in methods, type methods,
etc.
- varname name
- Deprecated. Use myvar
instead.
Given an instance variable name, returns the fully qualified name.
Use this if you're passing the variable to some other object, e.g.,
as a -textvariable to a Tk label widget.
- typevarname name
- Deprecated. Use mytypevar
instead.
Given a type variable name, returns the fully qualified name. Use
this if you're passing the type variable to some other object,
e.g., as a -textvariable to a Tk label widget.
- codename name
- Deprecated. Use myproc
instead. Given the name of a proc (but not a type or instance
method), returns the fully-qualified command name, suitable for
passing as a callback.
When an object includes other objects, as when a toolbar
contains buttons or a GUI object contains an object that references
a database, the included object is called a component. The standard
way to handle component objects owned by a Snit object is to
declare them using component, which creates a
component instance variable. In the following example, a dog object has a tail object:
|
snit::type dog {
component mytail
constructor {args} {
set mytail [tail %AUTO% -partof $self]
$self configurelist $args
}
method wag {} {
$mytail wag
}
}
snit::type tail {
option -length 5
option -partof
method wag {} { return "Wag, wag, wag."}
}
|
Because the tail object's name is stored in
an instance variable, it's easily accessible in any method.
The install command provides an alternate way
to create and install the component:
|
snit::type dog {
component mytail
constructor {args} {
install mytail using tail %AUTO% -partof $self
$self configurelist $args
}
method wag {} {
$mytail wag
}
}
|
For snit::types, the two methods are
equivalent; for snit::widgets and snit::widgetadaptors, the install
command properly initializes the widget's options by querying The Tk Option Database.
In the above examples, the dog object's
wag method simply calls the tail component's wag method. In OO
jargon, this is called delegation. Snit provides an easier way to
do this:
|
snit::type dog {
delegate method wag to mytail
constructor {args} {
install mytail using tail %AUTO% -partof $self
$self configurelist $args
}
}
|
The delegate statement in the type definition
implicitly defines the instance variable mytail to
hold the component's name (though it's good form to use component to declare it explicitly); it also defines the
dog object's wag method,
delegating it to the mytail component.
If desired, all otherwise unknown methods can be delegated to a
specific component:
|
snit::type dog {
delegate method * to mytail
constructor {args} {
set mytail [tail %AUTO% -partof $self]
$self configurelist $args
}
method bark { return "Bark, bark, bark!" }
}
|
In this case, a dog object will handle its
own bark method; but wag will be
passed along to mytail. Any other method, being
recognized by neither dog nor tail, will simply raise an error.
Option delegation is similar to method delegation, except for
the interactions with the Tk option database; this is described in
The Tk Option Database.
The relationship between type components and instance components
is identical to that between type variables and instance variables,
and that between type methods and instance methods. Just as an
instance component is an instance variable that holds the name of a
command, so a type component is a type variable that holds the name
of a command. In essence, a type component is a component that's
shared by every instance of the type.
Just as delegate method can be used to
delegate methods to instance components, as described in Components and Delegation, so delegate typemethod can be used to delegate type
methods to type components.
Note also that as of Snit 0.95 delegate
method can delegate methods to both instance components and
type components.
This section describes how Snit interacts with the Tk option
database, and assumes the reader has a working knowledge of the
option database and its uses. The book Practical Programming in
Tcl and Tk by Welch et al has a good introduction to the
option database, as does Effective Tcl/Tk Programming.
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.
- If the snit::widget's default widget class
is not what is desired, set it explicitly using widgetclass in the widget definition.
- When defining or delegating options, specify the resource and
class names explicitly when if the defaults aren't what you
want.
- Use installhull using to install the hull
for snit::widgetadaptors.
- Use install to install all other
components.
The interaction of Tk widgets with the option database is a
complex thing; the interaction of Snit with the option database is
even more so, and repays attention to detail.
Setting the widget class: Every Tk widget has a
widget class. For Tk widgets, the widget class name is the just the
widget type name with an initial capital letter, e.g., the widget
class for button widgets 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.
Note that only frame and toplevel widgets allow the user to change the widget
class name, which is why they are the only allowable hull types for
snit::widgets.
The widget class of a snit::widgetadaptor is
just the widget class of its hull widget; this cannot be changed
unless the hull widget is a frame or toplevel, in which case it will usually make more sense
to use snit::widget rather than snit::widgetadaptor.
Setting option resource names and classes: In
Tk, every option has three names: the option name, the resource
name, and the class name. The option name begins with a hyphen and
is all lowercase; it's used when creating widgets, and with the configure and cget commands.
The resource and class names are used to initialize option
default values by querying the Tk 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 text widget options:
|
-background background Background
-borderwidth borderWidth BorderWidth
-insertborderwidth insertBorderWidth BorderWidth
-padx padX Pad
|
As is easily seen, sometimes the resource and class names can be
inferred from the option name, but not always.
Snit options also have a resource name and a class name. By
default, these names follow the rule given above: the resource name
is the option name without the hyphen, and the class name is the
resource name with an initial capital. This is true for both
locally-defined options and explicitly delegated options:
|
snit::widget mywidget {
option -background
delegate option -borderwidth to hull
delegate option * to text
# ...
}
|
In this case, the widget class name is "Mywidget". The widget
has the following options: -background, which is
locally defined, and -borderwidth, which is
explicitly delegated; all other widgets are delegated to a
component called "text", which is probably a Tk text widget. If so, mywidget has all
the same options as a text widget. The option,
resource, and class names are as follows:
|
-background background Background
-borderwidth borderwidth Borderwidth
-padx padX Pad
|
Note that the locally defined option,
-background, happens to have the same three names
as the standard Tk -background option; and
-pad, which is delegated implicitly to the
text component, has the same three names for mywidget as it does for the text
widget. -borderwidth, on the other hand, has
different resource and class names than usual, because the internal
word "width" isn't capitalized. For consistency, it should be; this
is done as follows:
|
snit::widget mywidget {
option -background
delegate option {-borderwidth borderWidth} to hull
delegate option * to text
# ...
}
|
The class name will default to "BorderWidth", as expected.
Suppose, however, that mywidget also
delegated -padx and -pady to the
hull. In this case, both the resource name and the class name must
be specified explicitly:
|
snit::widget mywidget {
option -background
delegate option {-borderwidth borderWidth} to hull
delegate option {-padx padX Pad} to hull
delegate option {-pady padY Pad} to hull
delegate option * to text
# ...
}
|
Querying the option database: If you set your
widgetclass and option names as described above, Snit will query
the option database when each instance is created, and will
generally do the right thing when it comes to querying the option
database. The remainder of this section goes into the gory
details.
Initializing locally defined options: When an
instance of a snit::widget is created, its locally defined options
are initialized as follows: each option's resource and class names
are used to query the Tk option database. If the result is
non-empty, it is used as the option's default; otherwise, the
default hardcoded in the type definition is used. In either case,
the default can be overridden by the caller. For example,
|
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".
Initializing options delegated to the hull: A
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 that 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;
in practice, -background would probably be
explicitly 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". 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.
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; options delegated from a different name are
initialized from the option database in the same way.
Initializing options delegated to other
components: Non-hull components are matched against the
option database in two ways. First, a component widget remains a
widget still, and therefore is initialized from the option database
in the usual way. Second, 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:
- Builds a list of the options explicitly included in the install command -- in this case,
-foreground.
- Queries the option database for all options delegated
explicitly to the named component.
- Creates the component using the specified command, after
inserting into it a list of options and values read from the option
database. Thus, the explicitly included options
(-foreground) will override anything read from the
option database.
- If the widget definition implicitly delegated options to the
component using delegate option *, then Snit
calls the newly created component's 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 that were not explicitly included in the install command. For all such options, Snit queries the
option database and configures the component accordingly.
Non-widget components: The option database is
never queried for snit::types, since it can only
be queried given a Tk widget name. However, snit::widgets 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.
The snit::macro command enables a certain
amount of meta-programming with Snit classes. For example, suppose
you like to define properties: instance variables that have set/get
methods. Your code might look like this:
|
snit::type dog {
variable mood happy
method getmood {} {
return $mood
}
method setmood {newmood} {
set mood $newmood
}
}
|
That's nine lines of text per property. Or, you could define the
following snit::macro:
|
snit::macro property {name initValue} {
variable $name $initValue
method get$name {} "return $name"
method set$name {value} "set $name \$value"
}
|
Note that a snit::macro is just a normal Tcl
proc defined in the slave interpreter used to compile type and
widget definitions; as a result, it has access to all the commands
used to define types and widgets.
Given this new macro, you can define a property in one line of
code:
|
snit::type dog {
property mood happy
}
|
Within a macro, the commands variable and proc refer to the Snit type-definition commands,
not the standard Tcl commands. To get the standard Tcl commands,
use _variable and _proc.
Because a single slave interpreter is used for compiling all
Snit types and widgets in the application, there's the possibility
of macro name collisions. If you're writing a reuseable package
using Snit, and you use some snit::macros,
define them in your package namespace:
|
snit::macro mypkg::property {name initValue} { ... }
snit::type dog {
mypkg::property mood happy
}
|
This leaves the global namespace open for application authors.
Please understand that while Snit is well-tested and fairly
stable, it is still evolving (we have not yet reached Snit 1.0). If
you have problems, find bugs, or new ideas you are hereby cordially
invited to submit a report of your problem, bug, or idea at the
SourceForge trackers for tcllib, which can be found at http://sourceforge.net/projects/tcllib/.
The relevant category is snit.
Additionally, you might wish to join the Snit mailing list; see
http://www.wjduquette.com/snit
for details.
One particular area to watch is using snit::widgetadaptor to adapt megawidgets created by other
megawidget packages; correct widget destruction depends on the
order of the <Destroy> bindings. The wisest course is simply
not to do this.
- Error stack traces returned by Snit are extremely ugly and
typically contain far too much information about Snit
internals.
- Also see the SourceForge Trackers at http://sourceforge.net/projects/tcllib/,
category snit.
During the course of developing Notebook (See http://www.wjduquette.com/notebook),
my Tcl-based personal notebook application, I found I was writing
it as a collection of objects. I wasn't using any particular
object-oriented framework; I was just writing objects in pure Tcl
following the guidelines in my Guide to Object Commands (see http://www.wjduquette.com/tcl/objects.html),
along with a few other tricks I'd picked up since. And though it
was working well, it quickly became tiresome because of the amount
of boilerplate code associated with each new object type.
So that was one thing--tedium is a powerful motivator. But the
other thing I noticed is that I wasn't using inheritance at all,
and I wasn't missing it. Instead, I was using delegation: objects
that created other objects and delegated methods to them.
And I said to myself, "This is getting tedious...there has got
to be a better way." And one afternoon, on a whim, I started
working on Snit, an object system that works the way Tcl works.
Snit doesn't support inheritance, but it's great at delegation, and
it makes creating megawidgets easy.
If you have any comments or suggestions (or bug reports!) don't
hesitate to send me e-mail at will@wjduquette.com. In addition, there's
a Snit mailing list; you can find out more about it at the Snit
home page (see http://www.wjduquette.com/snit).
Snit has been designed and implemented from the very beginning
by William H. Duquette. However, much credit belongs to the
following people for using Snit and providing me with valuable
feedback: Rolf Ade, Colin McCormack, Jose Nazario, Jeff Godfrey,
Maurice Diamanti, Egon Pasztor, David S. Cargo, Tom Krehbiel,
Michael Cleverly, Andreas Kupries, Marty Backe, Andy Goth, Jeff
Hobbs, and Brian Griffin. If I've forgotten anyone, my apologies;
let me know and I'll add your name to the list.
BWidget , C++ , Incr
Tcl , Snit , adaptors , class , mega widget , object , object oriented , type , widget , widget adaptors
Copyright © 2003-2004, by William H. Duquette