|
ActiveTcl User Guide
|
|
|
[ Main table Of Contents | Tcllib Table Of Contents | Tcllib Index ]
snit(n) 0.82 "Snit"
snit - Snit's Not Incr Tcl
package require Tcl 8.3
package require snit ?0.82?
Snit is yet another pure Tcl object and megawidget system. It's
unique among Tcl object systems (so far as I know) in that it's a
system based not on inheritance but on delegation. Object systems
based on inheritance only allow you to inherit from classes defined
using the same system, and that's a shame. In Tcl, an object is
anything that acts like an object; it shouldn't matter how the
object was implemented. I designed Snit to help me 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.
The Instance Command
A Snit type or widget's create type method
creates objects of the type; each object has a unique name which 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 defined in the next section, or any instance
method defined in the type definition. The subsequent args depend on the specific method
chosen.
Standard Instance Methods
In addition to any delegated or locally-defined instance methods
in the type's definition, all Snit objects will have at least the
following methods:
- $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.
Two warnings. First, unlike Tk widget options, locally-defined snit::type and snit::widget
options do not have a dbname or classname; Snit
never queries the Tk option database. These fields in the returned
information will be set to the empty string, {}. Second, the
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.
- $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.
- $object cget option
- Returns the option's value.
- $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 the Tk destroy command, just as a normal widget is.
- $object info
type
- Returns the instance's type.
- $object info
vars
- Returns a list of the object's instance variables (excluding
Snit internal variables). The names are fully qualified.
- $object info
typevars
- Returns a list of the object's type's type variables (excluding
Snit internal variables). The names are fully qualified.
- $object info
options
- 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 which could be delegated to the component.
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.
Commands for use in Object Code
Snit defines the following commands for use in object code: 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
are directly available.
- varname 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.
- typevarname 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.
- codename name
- 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.
- 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 a normal (undelegated) option, and defvalue is not specified, then the option's
default value as specified in the type definition will be returned
instead.
- 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-specific code.
However, instance code (e.g., method bodies) can declare such
variables explicitly using the variable command,
if desired; or, instance code can use the variable command to declare instance variables that don't
appear in the type definition.
It's generally best to define all instance variables in the type
definition, and omit declaring them in methods and so forth.
Note that this is not the same as 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 explicitly, if
desired; or, they can use typevariable to
declare type variables that don't appear in the type
definition.
It's generally best to declare all type variables in the type
definition, and omit declaring them in methods, type methods, and
so forth.
- 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.
The command which creates the hull widget usually just passes its
result to installhull as follows:
|
installhull [frame $win options....]
|
Type and Widget Definitions
- 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 appears (usually the global namespace). It
returns the fully qualified type name.
The type name is then a command which is used to create objects of
the new type, along with other activities.
The snit::type definition block is a script
which may contain the following definitions:
- typevariable name ?value?
- Defines a type variable with the specified name, and optionally the specified value. Type variables are shared by all instances of the
type. This definition can be used to define array variables, but
cannot initialize their elements.
- typemethod name arglist body
- Defines a type method with the specified name, argument list,
and body. The variable type is automatically
defined in the body to the type's
fully-qualified name.
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.
Type variables defined in the type definition
are automatically visible in the body of every
type method.
- option name
?defaultValue?
- Defines an option for instances of this type, and optionally
gives it an initial value. (The option's value defaults to the
empty string if no initial value is specified.) The option's name must begin with a hyphen, -.
Options are normally set and retrieved using the standard
configure and cget instance
methods.
An option defined in this way is said to be locally
defined.
- variable name ?value?
- Defines an instance variable, a private variable associated
with each instance of this type, and optionally its initial value.
This definition can be used to define array instance variables, but
cannot initialize their elements.
Note that the delegate statement implicitly
defines an instance variable for the named component.
- 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 standard Tcl argument list, and may
contain default values and the argument names. The arglist is a normal Tcl argument list and may contain
default arguments and the args argument. In
addition, 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 defined in the type definition are automatically visible in all instance
methods. If the type has locally defined options, the
options array is also visible.
- 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.
If the constructor is not defined, it defaults to this:
|
constructor {args} {
$self configurelist $args
}
|
For standard Tk widget behavior (or to achieve the behavior of
previous versions of snit) the argument list should be the single
name args, as shown.
- destructor body
- The destructor is used to code any actions which must take
place when an instance of the type is destroyed: typically, the
destruction of anything created in the constructor.
As with arguments, the parameters type,
self, selfns, and
win, are defined implicitly.
- onconfigure name arglist body
- Every locally-defined option has an onconfigure handler which is called when the option is
set to a new value by the configure or
configurelist instance method.
The arglist may contain exactly one argument
name. As with methods, the arguments type,
self, selfns, and
win, are defined implicitly.
If no explicit onconfigure handler is defined for an option, the
handler is defined as follows:
|
onconfigure name {value} {
set options(name) $value
}
|
If an explicit onconfigure handler is defined, the options array
will be updated with the new value only if the handler so updates
it.
- oncget name
body
- Every locally-defined option has an oncget
handler which is called when the option's value is retrieved.
Although there is no explicit argument list, the arguments
type, self,
selfns, and win, are defined
implicitly, just as they are for methods.
The variables type, self,
selfns, and win are defined as
usual in the handler's body. Whatever the
handler returns will be the return value of the call to the
cget instance method.
If no explicit oncget handler is defined for an option, the
handler is defined as follows:
|
oncget name {
return $options(name)
}
|
- proc name args body
- Defines a new Tcl procedure in the type's namespace. The new
proc differs from a normal Tcl proc in that all type variables
defined in the type definition are automatically
visible.
Although they are not implicitly defined for procs, the argument
names type, self,
selfns, and win should be
avoided.
- delegate
method name to
comp ?as compmethod compargs...?
- Defines a delegated instance method. When instance method name is used with an instance of this type, it will
automatically be delegated to the named component as though the
method were defined as follows:
|
method name {args...} {
$comp mymethod args...
}
|
If desired, the delegated method may target a method with a
different name by using the as clause; it may also
add arguments to the beginning of the argument list. In that case,
it's as though the delegated method were defined as follows:
|
method name {args...} {
$comp compmethod compargs... args...
}
|
If the specified method name is
*, then all unknown method names passed to the
instance will be passed along to the specified comp. In this case, the as clause is not
allowed.
A method cannot be both locally defined and delegated.
- delegate
option name to
comp ?as compoption?
- Defines a delegated option. 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:
|
onconfigure name {value} {
$comp configure compoption $value
}
oncget name {
return [$comp cget compoption]
}
|
If the as clause is omitted, the compoption name is the same as name.
Warning: options can only be delegated to a component if it
supports the configure and cget
instance methods.
- snit::widget name definition
- This command defines a Snit megawidget type with the specified
name. The definition is
defined identically to that for snit::type. A snit::widget differs from a snit::type in these ways:
- Every snit::widget instance has an
automatically-created component called hull, which
is a Tk frame widget. Other widgets created as part of the
megawidget will be created within this frame.
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.
- 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.
The Type Command
A type or widget definition creates a type command, which is
used to create instances of the type. The type command this
form.
- $type typemethod args...
- The typemethod can be any of the standard
type methods defined in the next section, or any type method
defined in the type definition. The subsequent args depend on the specific typemethod
chosen.
Standard Type Methods
In addition to any typemethods in the type's definition, all
types and widgets will have at least the following method:
- $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.
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.
- $type info
typevars
- Returns a list of the type's type variables (excluding Snit
internal variables); all variable names are fully-qualified.
- $type info
instances
- 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.
- $type
destroy
- Destroys the type's instances, the type's namespace, and the
type command itself.
Components and Delegation
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 assign
their names to a instance variable. In the following example, a dog object has a tail
object:
|
snit::type dog {
variable 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.
In the above example, the dog object's
wag method simply calls the tail component's wag method. In OO
circles, this is called delegation. Snit provides an easier way to
do this, as shown:
|
snit::type dog {
delegate method wag to mytail
constructor {args} {
set mytail [tail %AUTO% -partof $self]
$self configurelist $args
}
}
snit::type tail {
option -length 5
option -partof
method wag {} { return "Wag, wag, wag."}
}
|
The delegate statement in the type definition
implicitly defines the instance variable mytail to
hold the component's name; it also defines the dog object's wag method, delegating it
to the tail component.
Please understand that while Snit is already very stable, it is
still early days in Snit's development, and not be too critical. 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.
One particular area to watch is the interaction of Snit with
other megawidget packages. Some widgets in BWidgets for example
place their own <Destroy> binding not on a separate bind-tag,
but on the widget itself. When used as the hull of a snit::widgetadaptor this causes them to be called before
Snit, removing the widget command. A previous version of Snit was
tripped by this and threw errors because it tried to operate on and
with an already deleted widget command. Snit is now able to deal
with this, despite the fact that the ultimate cause is at least bad
behaviour of Bwidget, possibly even a bug. This however does not
preclude that there might be other issues lurking.
So, if you use a snit::widgetadaptor to adapt
somebody else's megawidget, you need to be very careful about
making sure the bindtags are done properly.
There's no way for Snit to take into account all the possible weird
things other megawidget frameworks might do wrong.
- 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 it was
working very well. But on the other hand, it was getting tiresome.
Writing objects in pure Tcl is straightforward, once you figure it
out, but there's a fair amount of boilerplate code to write for
each one, especially if you're trying to create megawidgets or
create objects with options, like Tk widgets have.
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.
I should add, I'm not particularly down on Incr Tcl. But "Snit's
Not Incr Tcl" occurred to me while I was casting about for a name,
and I guess there was a certainly inevitability about it.
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
now 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, and
Michael Cleverly.
BWidget , C++ , Incr
Tcl , adaptors , class , mega widget , object , object oriented , widget , widget adaptors
Copyright © 2003, by William H. Duquette