Documentation generated from fossil trunk
snitfaq -
Snit Frequently Asked Questions
The primary purpose of Snit is to be object glue--to help you compose diverse objects from diverse sources into types and megawidgets with clean, convenient interfaces so that you can more easily build your application.
Snit isn't about theoretical purity or minimalist design; it's about being able to do powerful things easily and consistently without having to think about them--so that you can concentrate on building your application.
Snit isn't about implementing thousands of nearly identical carefully-specified lightweight thingamajigs--not as individual Snit objects. Traditional Tcl methods will be much faster, and not much more complicated. But Snit is about implementing a clean interface to manage a collection of thousands of nearly identical carefully-specified lightweight thingamajigs (e.g., think of the text widget and text tags, or the canvas widget and canvas objects). Snit lets you hide the details of just how those thingamajigs are stored--so that you can ignore it, and concentrate on building your application.
Snit isn't a way of life, a silver bullet, or the Fountain of Youth. It's just a way of managing complexity--and of managing some of the complexity of managing complexity--so that you can concentrate on building your application.
Note that you can achieve the effect of inheritance using COMPONENTS and DELEGATION--and you can inherit from anything that looks like a Tcl object.
Snit 1.3, on the other hand, lacks Snit 2.2's optimizations, but requires only Tcl 8.3 and later.
In short, if you're targetting Tcl 8.3 or 8.4 you should use Snit 1.3. If you can afford to target Tcl 8.5, you should definitely use Snit 2.2. If you will be targetting both, you can use Snit 1.3 exclusively, or (if your code is unaffected by the minor incompatibilities between the two versions) you can use Snit 1.3 for Tcl 8.4 and Snit 2.2 for Tcl 8.5.
package require snit 1.3To always use Snit 2.2 (or a later version of Snit 2.x), say this instead:
package require snit 2.2Note that if you request Snit 2.2 explicitly, your application will halt with Tcl 8.4, since Snit 2.2 is unavailable for Tcl 8.4.
If you wish your application to always use the latest available version of Snit, don't specify a version number:
package require snitTcl will find and load the latest version that's available relative to the version of Tcl being used. In this case, be careful to avoid using any incompatible features.
There are four specific incompatibilities between Snit 1.3 and Snit 2.2.
dog spot ;# Explicit naming set obj1 [dog %AUTO%] ;# Automatic naming set obj2 [dog] ;# Implicit namingIn Snit 2.2, type commands are defined using the namespace ensemble mechanism; and namespace ensemble doesn't allow an ensemble command to be called without a subcommand. In short, using namespace ensemble there's no way to support implicit naming.
pragma -hastypemethods 0
method {tag configure} {tag args} { ... } method {tag cget} {tag option} {...}Here we've implicitly defined a tag method which has two subcommands, configure and cget.
$obj tag cget -myoption ;# The good way $obj {tag cget} -myoption ;# The weird wayIn the second call, we see that a hierarchical method or type method is simply one whose name contains multiple words.
namespace import [namespace parent]::*in a type constructor. This is less useful, however, as it picks up only those commands which have already been exported by the parent namespace at the time the type is defined.
A Tk widget is an object; it is represented by a Tcl command. The object's methods are subcommands of the Tcl command. The object's properties are options accessed using the configure and cget methods. Snit uses the same conventions as Tk widgets do.
In Snit, as in Tk, a type is a command that creates instances -- objects -- which belong to the type. Most types define some number of options which 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 which are defined for that abstract data type. Conventionally, the operations are defined as subcommands of the instance command. For example, to insert text into a Tk text widget, you use the text widget's insert subcommand:
# 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.
In Snit, object subcommands are generally called INSTANCE METHODS.
% 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, TYPE VARIABLES, TYPE COMPONENTS, and PROCS.
% snit::type dog { # .... } ::dog % dog create spot ::spot %
In general, the create method name can be omitted so long as the instance name doesn't conflict with any defined TYPE METHODS. (See TYPE COMPONENTS for the special case in which this doesn't work.) 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 usually 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 %
Once created, 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 { ... } ::dog % set d [dog spot -breed dalmation -color spotted] ::spot % $d cget -breed dalmation % $d bark ::spot barks. %
If you prefer this style, you might prefer to have Snit generate the instance's name automatically.
% snit::type dog { ... } ::dog % set d [dog %AUTO%] ::dog2 % $d bark ::dog2 barks. %
The %AUTO% keyword can be embedded in a longer string:
% set d [dog obj_%AUTO%] ::obj_dog4 % $d bark ::obj_dog4 barks. %
snit::type commands, however, should never be renamed; to do so breaks the connection between the type and its objects.
All Snit objects (including widgets and widgetadaptors) can be renamed, though this flexibility has some consequences:
.btn configure -command [list $self ButtonPress]You'll get an error if .btn calls your command after your object is renamed.
.btn configure -command [mymethod ButtonPress]The mymethod command returns code that will call the desired method safely; the caller of the callback can add additional arguments to the end of the command as usual.
Snit megawidgets (i.e., instances of snit::widget and snit::widgetadaptor) can be destroyed like any other widget: by using the Tk destroy command on the widget or on one of its ancestors in the window hierarchy.
Every instance of a snit::type has a destroy method:
% snit::type dog { ... } ::dog % dog spot ::spot % spot bark ::spot barks. % spot destroy % spot barks invalid command name "spot" %
Finally, every Snit type has a type method called destroy; calling it destroys the type and all of its instances:
% snit::type dog { ... } ::dog % dog spot ::spot % spot bark ::spot barks. % dog destroy % spot bark invalid command name "spot" % dog fido invalid command name "dog" %
% 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. (There's more on implicit method arguments below.)
% dog spot ::spot % spot bark ::spot barks. % spot chase cat ::spot chases cat. %
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. %
snit::widget mytext { method {tag configure} {tag args} { ... } method {tag cget} {tag option} {...} }Note that there is no explicit definition for the tag method; it is implicit in the definition of tag configure and tag cget. If you tried to define tag explicitly in this example, you'd get an error.
% mytext .text .text % .text tag configure redtext -foreground red -background black % .text tag cget redtext -foreground red %
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 becomes Bark to indicate that it is 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. %
However, every method also has a number of implicit arguments provided by Snit in addition to those explicitly defined. The names of these implicit arguments may not used to name explicit arguments.
% snit::type thing { method mytype {} { return $type } } ::thing % thing something ::something % something mytype ::thing %
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 %
% 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.
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.
Suppose in my application I have a dog object named fido, and I want fido to bark when a Tk button called .bark is pressed. In this case, I create the callback command in the usual way, using list:
button .bark -text "Bark!" -command [list fido bark]
In typical Tcl style, we use a callback to hook two independent components together. But suppose that the dog object has a graphical interface and owns the button itself? In this case, the dog must pass one of its own instance methods to the button it owns. 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 .spot.barkbtn; when pressed, the button will call $self bark.
Now, this will work--provided that .spot is never renamed to something else. But 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 become the name of 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 takes any number of arguments, and 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 even if the instance's name changes.
On the other hand, you might prefer to allow a widgetadaptor to override a method such that your renamed widget will call the widgetadaptor's method instead of its own. In this case, using [list $self bark] will do what you want...but this is a technique which should be used only in carefully controlled circumstances.
snit::type mytype { # Define variable "greeting" and initialize it with "Howdy!" variable greeting "Howdy!" }
snit::type mytype { # Define array variable "greetings" variable greetings -array { formal "Good Evening" 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_" are reserved for use by Snit internal code.
Third, instance variable names containing the namespace delimiter (::) are likely to cause great confusion.
There is a speed penalty to having all instance variables implicitly available in all instance code. Even though your code need not declare the variables explicitly, Snit must still declare them, and that takes time. If you have ten instance variables, a method that uses none of them must still pay the declaration penalty for all ten. In most cases, the additional runtime cost is negligible. If extreme cases, you might wish to avoid it; there are two methods for doing so.
The first is to define a single instance variable, an array, and store all of your instance data in the array. This way, you're only paying the declaration penalty for one variable--and you probably need the variable most of the time anyway. This method breaks down if your instance variables include multiple arrays; in Tcl 8.5, however, the dict command might come to your rescue.
The second method is to declare your instance variables explicitly in your instance code, while not including them in the type definition:
snit::type dog { constructor {} { variable mood set mood happy } method setmood {newMood} { variable mood set mood $newMood } method getmood {} { variable mood return $mood } }This allows you to ensure that only the required variables are included in each method, at the cost of longer code and run-time errors when you forget to declare a variable you need.
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 myvar command:
snit::widget mywidget { variable labeltext "" constructor {args} { # ... label $win.label -textvariable [myvar labeltext] # ... } }
Snit's implementation of options follows the Tk model fairly exactly, except that snit::type objects usually don't interact with THE TK OPTION DATABASE; snit::widget and snit::widgetadaptor objects, on the other hand, always do.
snit::type dog { option -breed -default mongrel option -color -default brown option -akc -default 0 option -shots -default 0 }
According to this, a dog has four notable properties: 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.
There are a number of options you can specify when defining an option; if -default is the only one, you can omit the word -default as follows:
snit::type dog { option -breed mongrel option -color brown option -akc 0 option -shots 0 }
If no -default value is specified, the option's default value will be the empty string (but see THE TK OPTION DATABASE).
The Snit man page refers to options like 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.
% spot cget -color mottled % fido cget -breed mongrel %
% 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; occasionally this is 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 %
In Tcl 8.5, the * keyword can be used with configure in this case:
% set features [list -color dun -breed "Arctic Boar Hound"] -color dun -breed {Arctic Boar Hound} % fido configure {*}$features % fido cget -color dun % fido cget -breed Arctic Boar Hound %
The results are the same.
% 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 gainWeight can thus be rewritten as follows:
method gainWeight {} { incr options(-weight) }
As you can see, using the options variable involves considerably less typing and is the usual way to do it. But if you use -configuremethod or -cgetmethod (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. Also, if the option is delegated to a component then configure and cget are the only way to access it without accessing the component directly. See DELEGATION for more information.
Suppose you've got an option that determines how instances of your type are constructed; it must be set at creation time, after which it's constant. For example, a dog never changes its breed; it might or might not have had its shots, and if not can have them at a later time. -breed should be read-only, but -shots should not be.
% snit::type dog { option -breed -default mongrel -readonly yes option -shots -default no } ::dog % dog fido -breed retriever ::fido % fido configure -shots yes % fido configure -breed terrier option -breed can only be set at instance creation %
Here's what the default behavior would look like if written using a -cgetmethod:
snit::type dog { option -color -default brown -cgetmethod GetOption method GetOption {option} { return $options($option) } }
Any instance method can be used, provided that it takes one argument, the name of the option whose value is to be retrieved.
Here's what the default configuration behavior would look like if written using a -configuremethod:
snit::type dog { option -color -default brown -configuremethod SetOption method SetOption {option value} { set options($option) $value } }
Any instance method can be used, provided that it takes two arguments, the name of the option and the new value.
Note that if your method doesn't store the value in the options array, the options array won't get updated.
For example, suppose an option always takes a Boolean value. You can ensure that the value is in fact a valid Boolean like this:
% snit::type dog { option -shots -default no -validatemethod BooleanOption method BooleanOption {option value} { if {![string is boolean -strict $value]} { error "expected a boolean value, got \"$value\"" } } } ::dog % dog fido % fido configure -shots yes % fido configure -shots NotABooleanValue expected a boolean value, got "NotABooleanValue" %Note that the same -validatemethod can be used to validate any number of boolean options.
Any method can be a -validatemethod provided that it takes two arguments, the option name and the new option 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.
snit::type mytype { # Define typearray variable "greetings" typevariable greetings -array { formal "Good Evening" casual "Howdy!" } }
Type variables are subject to the same speed/readability tradeoffs as instance variables; see Do I need to declare my instance variables in my methods?
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 mytypevar command:
snit::widget mywidget { typevariable labeltext "" constructor {args} { # ... label $win.label -textvariable [mytypevar labeltext] # ... } }
Type variables are stored in the type's namespace, which has the same name as the type itself. Thus, you can also publicize the type variable's name in your documentation so that clients can access it directly. For example,
snit::type mytype { typevariable myvariable } set ::mytype::myvariable "New Value"
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. Notice that every type method gets an implicit argument called type, which contains the fully-qualified type name.
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] { ... }
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.
However, every type method is called with an implicit argument called type that contains the name of the type command. In addition, type methods should by convention avoid using the names of the arguments implicitly defined for INSTANCE METHODS.
snit::type dog { typemethod pedigreedDogs {} { ... } typemethod printPedigrees {} { foreach obj [$type pedigreedDogs] { ... } } }
button .btn -text "Pedigrees" -command [list dog printPedigrees] pack .btnAlternatively, from a method or type method you can use the mytypemethod command, just as you would use mymethod to define a callback command for INSTANCE METHODS.
snit::type mytype { # Pops and returns the first item from the list stored in the # listvar, updating the listvar proc pop {listvar} { ... } # ... }
proc names, being private, should begin with a capital letter according to convention; however, as there are typically no public procs in the type's namespace it doesn't matter much either way.
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] } }
A type can have at most one type constructor.
% snit::type mytype { typevariable lookupTable typeconstructor { array set lookupTable {key value...} } }
The constructor's return value is ignored (unless it's an error, of course).
% 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 { constructor {args} { $self configurelist $args } }
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 interpret 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 syntax is non-standard, and may limit the compatibility of your new type with other people's code. For example, Snit assumes that it can create COMPONENTS using the standard creation syntax.
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 } }
Any Tk widgets created by a snit::widget or snit::widgetadaptor will be destroyed automatically by Tk when the megawidget is destroyed, in keeping with normal Tk behavior (destroying a parent widget destroys the whole tree).
Components of normal snit::types, on the other hand, are never destroyed automatically, nor are non-widget components of Snit megawidgets. If your object creates them in its constructor, then it should generally destroy them in its destructor.
For example, a dog might create a tail component; the component will need to be destroyed. But suppose there's an error while processing the creation options--the destructor will be called, and there will be no tail to destroy. The simplest solution is generally to catch and ignore any errors while destroying components.
snit::type dog { component tail constructor {args} { $self configurelist $args set tail [tail %AUTO%] } destructor { catch {$tail destroy} } }
But Snit also has a more precise meaning for COMPONENT. The components of a Snit object are those objects to which methods or options can be delegated. (See DELEGATION for more information about delegation.)
For example, suppose your dog object creates a tail object (the better to wag with, no doubt):
snit::type dog { component mytail constructor {args} { # Create and save the component's command set mytail [tail %AUTO% -partof $self] $self configurelist $args } method wag {} { $mytail wag } }
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 question, 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.
Otherwise, since component names are in fact instance variable names they must follow the rules for INSTANCE VARIABLES.
As stated above, a component is an object to which our object can delegate methods or options. Under this definition, our object will usually create its component objects, but not necessarily. Consider the following: a dog object has a tail component; but tail knows that it's part of the dog:
snit::type dog { component mytail constructor {args} { set mytail [tail %AUTO% -partof $self] $self configurelist $args } destructor { catch {$mytail destroy} } delegate method wagtail to mytail as wag method bark {} { return "$self barked." } } snit::type tail { component mydog option -partof -readonly yes constructor {args} { $self configurelist $args set mydog $options(-partof) } method wag {} { return "Wag, wag." } method pull {} { $mydog bark } }Thus, if you ask a dog to wag its tail, it tells its tail to wag; and if you pull the dog's tail, the tail tells the dog to bark. In this scenario, the tail is a component of the dog, and the dog is a component of the tail, but the dog owns the tail and not the other way around.
snit::type dog { component mytail constructor {args} { # set mytail [tail %AUTO% -partof $self] install mytail using tail %AUTO% -partof $self $self configurelist $args } }In a snit::type's code, the install command shown above is equivalent to the set mytail command that's commented out. In a snit::widget's or snit::widgetadaptor's, code, however, the install command also queries THE TK OPTION DATABASE and initializes the new component's options accordingly. For consistency, it's a good idea to get in the habit of using install for all owned components.
Component objects which are Tk widgets or megawidgets must have valid Tk window names.
Component objects which are not widgets or megawidgets must have fully-qualified command names, i.e., names which include the full namespace of the command. Note that Snit always creates objects with fully qualified names.
Next, the object names of components and owned by your object must be unique. This is no problem for widget components, since widget names are always unique; but consider the following code:
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 dogs will attempt to create a tail called ::dog::mytail. The first will succeed, and the second will fail, since Snit won't let you create an object if its name is already a command. Here are two ways to avoid this situation:
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 create the component in the object's instance namespace:
install mytail using tail ${selfns}::mytail
Make sure you pick a unique name within the instance namespace.
If your object is an instance of snit::type, though, none of its owned components will be destroyed automatically, nor will be non-widget components of a snit::widget be destroyed automatically. All such owned components must be destroyed explicitly, or they won't be destroyed at all.
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.
For example, supposed you've written a combobox megawidget which owns a listbox widget, and you want to make the listbox's entire interface public. You can do it like this:
snit::widget combobox { component listbox -public listbox constructor {args} { install listbox using listbox $win.listbox .... } } combobox .mycombo .mycombo listbox configure -width 30
Your comobox widget, .mycombo, now has a listbox method which has all of the same subcommands as the listbox widget itself. Thus, the above code sets the listbox component's width to 30.
Usually you'll let the method name be the same as the component name; however, you can name it anything you like.
Once you understand COMPONENTS and DELEGATION, type components are just more of the same.
Suppose in your model you've got many dogs, but only one veterinarian. You might make the veterinarian a type component.
snit::type veterinarian { ... } snit::type dog { typecomponent vet # ... }
You'll usually install type components in the type constructor, as shown here:
snit::type veterinarian { ... } snit::type dog { typecomponent vet typeconstructor { set vet [veterinarian %AUTO%] } }
snit::type dog { variable mytail option -taillength -configuremethod SetTailOption -cgetmethod GetTailOption method SetTailOption {option value} { $mytail configure $option $value } method GetTailOption {option} { $mytail cget $option } method wag {} { $mytail wag } constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } }
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.
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 {} { install mytail using tail %AUTO% } } ::dog % snit::type tail { method wag {} { return "Wag, wag, wag."} } ::tail % dog spot ::spot % spot wag Wag, wag, wag.
This code has the same effect 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. However, it's still good practice to declare it explicitly using the component statement.
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.
snit::type dog { delegate method wagtail to mytail as wag constructor {args} { install mytail using tail %AUTO% -partof $self $self configurelist $args } }
snit::type dog { delegate method wagtail to mytail as {wag 3} # ... } snit::type tail { method wag {count} { return [string repeat "Wag " $count] } # ... }
Suppose your dog simulation stores dogs in a database, each dog as a single record. The database API you're using provides a number of commands to manage records; each takes the record ID (a string you choose) as its first argument. For example, saverec saves a record. If you let the record ID be the name of the dog object, you can delegate the dog's save method to the saverec command as follows:
snit::type dog { delegate method save using {saverec %s} }The %s is replaced with the instance name when the save method is called; any additional arguments are the appended to the resulting command.
The using clause understands a number of other %-conversions; in addition to the instance name, you can substitute in the method name (%m), the type name (%t), the instance namespace (%n), the Tk window name (%w), and, if a component or typecomponent name was given in the delegate statement, the component's object command (%c).
Naturally, you can't delegate a type method to an instance component...Snit wouldn't know which instance should receive it.
% 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 -configuremethod and -cgetmethod 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.
snit::type dog { delegate option -taillength to mytail as -length constructor {args} { set mytail [tail %AUTO% -partof $self] $self configurelist $args } }
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" } }
That's it. A dog is now an animal that 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 that 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.
You can also delegate all unknown type methods to a type component using delegate typemethod *.
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 -numlegs delegate method * to animal except {fly climb} # ... constructor {args} { install animal using animal %AUTO% -name $self -numlegs 4 $self configurelist $args } # ... }
Dogs have four legs, so we specify that explicitly when we create the animal component, and explicitly exclude -numlegs from the set of delegated options. Similarly, dogs can neither fly nor climb, so we exclude those animal methods as shown.
snit::type tail { method wag {} {return "Wag, wag"} method droop {} {return "Droop, droop"} } snit::type dog { delegate method {tail wag} to mytail delegate method {tail droop} to mytail # ... constructor {args} { install mytail using tail %AUTO% $self configurelist $args } # ... }
Unrecognized hierarchical methods can also be delegated; the following code delegates all subcommands of the "tail" method to the "mytail" component:
snit::type dog { delegate method {tail *} to mytail # ... }
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.
The body of the definition can contain all of the same kinds of statements, plus a couple of others which will be mentioned below.
For snit::widgets the hull component must be a Tk widget that defines the -class option.
snit::widgetadaptors differ from snit::widgets chiefly in that any kind of widget can be used as the hull component; see WIDGET ADAPTORS.
snit::widget mytoplevel { hulltype toplevel # ... }
If no hulltype command appears, the hull will be a frame.
By default, Snit recognizes the following hull types: the Tk widgets frame, labelframe, toplevel, and the Tile widgets ttk::frame, ttk::labelframe, and ttk::toplevel. To enable the use of some other kind of widget as the hull type, you can lappend the widget command to the variable snit::hulltypes (always provided the widget defines the -class option. For example, suppose Tk gets a new widget type called a prettyframe:
lappend snit::hulltypes prettyframe snit::widget mywidget { hulltype prettyframe # ... }
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.
It's called a widget adaptor because it allows you to take an existing widget and customize its behavior.
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 }
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 pre-existing widget instead of creating your own. 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. All of the These frames are created by the PagesManager itself, using its add method. It's convenient to adapt these frames to do what we'd like them to do.
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.
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.
If you create an instance of a snit::type as a component of a snit::widget or snit::widgetadaptor, on the other hand, and if any options are delegated to the component, and if you use install to create and install it, then the megawidget will query the option database on the snit::type's behalf. This might or might not be what you want, so take care.
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.
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.
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 Pad
As is easily seen, sometimes the resource and class names can be inferred from the option name, but not always.
% 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 -myvalue -myvalue myvalue Myvalue 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".
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:
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.
For example, the following code uses DELEGATION to create a work-alike for the standard string command:
snit::type ::mynamespace::mystringtype { delegate method * to stringhandler constructor {} { set stringhandler string } } ::mynamespace::mystringtype mystringWe create the type in a namespace, so that the type command is hidden; then we create a single instance with the desired name-- mystring, in this case.
This method has two drawbacks. First, it leaves the type command floating about. More seriously, your shiny new ensemble command will have info and destroy subcommands that you probably have no use for. But read on.
For example, the following code uses DELEGATION to create a work-alike for the standard string command:
snit::type mystring { delegate typemethod * to stringhandler typeconstructor { set stringhandler string } }Now the type command itself is your ensemble command.
This method has only one drawback, and though it's major, it's also surmountable. Your new ensemble command will have create, info and destroy subcommands you don't want. And worse yet, since the create method can be implicit, users of your command will accidentally be creating instances of your mystring type if they should mispell one of the subcommands. The command will succeed--the first time--but won't do what's wanted. This is very bad.
The work around is to set some PRAGMAS, as shown here:
snit::type mystring { pragma -hastypeinfo no pragma -hastypedestroy no pragma -hasinstances no delegate typemethod * to stringhandler typeconstructor { set stringhandler string } }Here we've used the pragma statement to tell Snit that we don't want the info typemethod or the destroy typemethod, and that our type has no instances; this eliminates the create typemethod and all related code. As a result, our ensemble command will be well-behaved, with no unexpected subcommands.
snit::type dog { pragma -hastypeinfo no # ... }Snit will refrain from defining the info type method.
snit::type dog { pragma -hastypedestroy no # ... }Snit will refrain from defining the destroy type method.
snit::type dog { pragma -hasinstances no # ... }Snit will refrain from defining the create type method; if you call the type command with an unknown method name, you'll get an error instead of a new instance of the type.
This is useful if you wish to use a snit::type to define an ensemble command rather than a type with instances.
Pragmas -hastypemethods and -hasinstances cannot both be false (or there'd be nothing left).
snit::type dog { pragma -hastypemethods no #... } # Creates ::spot dog spot # Tries to create an instance called ::create dog create spotPragmas -hastypemethods and -hasinstances cannot both be false (or there'd be nothing left).
snit::type dog { ... } dog procYou now have a new dog named "proc", which is probably not something that you really wanted to do. As a result, Snit now throws an error if your chosen instance name names an existing command. To restore the old behavior, set the -canreplace pragma to yes:
snit::type dog { pragma -canreplace yes # ... }
Snit 1.x method dispatch is both flexible and fast, but the flexibility comes with a price. If your type doesn't require the flexibility, the -simpledispatch pragma allows you to substitute a simpler dispatch mechanism that runs quite a bit faster. The limitations are these:
It's easily done. Outside of any type definition, define a macro that returns 1 if the extension is available, and 0 otherwise:
if {$gotFastExtension} { snit::macro fastcode {} {return 1} } else { snit::macro fastcode {} {return 0} }Then, use your macro in your type definition:
snit::type dog { if {[fastcode]} { # Fast methods method bark {} {...} method wagtail {} {...} } else { # Slow methods method bark {} {...} method wagtail {} {...} } }
snit::widget mywidget { option -background -default white -configuremethod PropagateBackground method PropagateBackground {option value} { $comp1 configure $option $value $comp2 configure $option $value $comp3 configure $option $value } }For one option, this is fine; if you've got a number of options, it becomes tedious and error prone. So package it as a macro:
snit::macro propagate {option "to" components} { option $option -configuremethod Propagate$option set body "\n" foreach comp $components { append body "\$$comp configure $option \$value\n" } method Propagate$option {option value} $body }Then you can use it like this:
snit::widget mywidget { option -background default -white option -foreground default -black propagate -background to {comp1 comp2 comp3} propagate -foreground to {comp1 comp2 comp3} }
If you're using Snit macros in your application, go ahead and name them in the global namespace, as shown above. But if you're using them to define types or widgets for use by others, you should define your macros in the same namespace as your types or widgets. That way, they won't conflict with other people's macros.
If my fancy snit::widget is called ::mylib::mywidget, for example, then I should define my propagate macro as ::mylib::propagate:
snit::macro mylib::propagate {option "to" components} { ... } snit::widget ::mylib::mywidget { option -background default -white option -foreground default -black mylib::propagate -background to {comp1 comp2 comp3} mylib::propagate -foreground to {comp1 comp2 comp3} }
class, object oriented, object, C++, Incr Tcl, BWidget, widget, adaptors, widget adaptors, mega widget