ActiveTcl User Guide

[ Main table Of Contents | Tcllib Table Of Contents | Tcllib Index ]

snitfaq(n) 0.81.1 "Snit"

NAME

snitfaq - Snit Frequently Asked Questions

SYNOPSIS

package require Tcl 8.3
package require snit ?0.82?

DESCRIPTION

OVERVIEW

What is this document?

This is an atypical FAQ list, in that few of the questions are frequently asked. Rather, these are the questions I think a newcomer to Snit should be asking. This file is not a complete reference to Snit, however; that information is in the snit man page.

What is Snit?

Snit is a framework for defining abstract data types and megawidgets in pure Tcl. The name stands for "Snit's Not Incr Tcl", signifying that Snit takes a different approach to defining objects than does Incr Tcl, the best known object framework for Tcl.

What version of Tcl does Snit require?

Snit requires version Tcl 8.4 or later.

What are Snit's goals?

In developing Snit I had the following goals:

How is Snit different from other OO frameworks?

Snit is 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.

What can I do with Snit?

Using Snit, a programmer can:

OBJECTS

What is an object?

Obviously, a full description of object-oriented programming is beyond the scope of this FAQ. In simple terms, an object is an instance of an abstract data type--a coherent bundle of code and data. There are many ways to represent objects in Tcl/Tk; the best known example are the Tk widgets. A widget is an object; it is represented by a Tcl command. The object's methods are subcommands of the Tcl command. Snit uses the same conventions as Tk widgets do.

What is an abstract data type?

In computer science terms, an abstract data type is a complex data structure along with a set of operations, like a stack, a queue, or a binary tree--that is to say, in modern terms, an object. In systems that include include some form of inheritance the word class is usually used instead of abstract data type, but as Snit doesn't do inheritance, the older term seems more appropriate.

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 option 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, or instance methods of the instance command. For example, to insert text into a Tk text widget, you use the text widget's insert method:

 
    # Create a text widget and insert some text in it.
    text .mytext -width 80 -height 24
    .mytext insert end "Howdy!"


In this example, text is the type command and .mytext is the instance command.

What kinds of abstract data types does Snit provide?

Snit allows you to define three kinds of abstract data types:

What is a snit::type?

A snit::type is a non-GUI abstract data type, e.g., a stack or a queue. snit::types are defined using the snit::type command. For example, if you were designing a kennel management system for a dog breeder, you'd need a dog type.

 
% snit::type dog {
    # ...
}
::dog


This definition defines a new command (::dog, in this case) which can be used to define dog objects.

An instance of a snit::type can have instance methods, instance variables, options, and components. The type itself can have type methods and procs.

What is a snit::widget?

A snit::widget is a Tk megawidget built using Snit; it is very similar to a snit::type. See WIDGETS.

What is a snit::widgetadaptor?

A snit::widgetadaptor uses Snit to wrap an existing widget type (e.g., a Tk label), modifying its interface to a lesser or greater extent. It is very similar to a snit::type. See WIDGET ADAPTORS.

How do I create an instance of a snit::type?

You create an instance of a snit::type by passing the new instance's name to the type's create method. In the following example, we create a dog object called spot.

 
% snit::type dog {
    # ....
}
::dog
% dog create spot
::spot


The create method name can be omitted so long as the instance name doesn't conflict with any defined type methods. So the following example is identical to the previous example:

 
% snit::type dog {
    # ....
}
::dog
% dog spot
::spot


This document generally uses the shorter form.

If the dog type defines options, these can usually be set 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


Either way, the instance name now names a new Tcl command which is used to manipulate the object. For example, the following code makes the dog bark:

 
% spot bark
::spot barks.


How do I refer to an object indirectly?

Some programmers prefer to save the object name in a variable, and reference it that way. For example,

 
% snit::type dog {
    option -breed mongrel
    option -color brown

    method bark {} { return "$self barks." }
}
::dog
% set d [dog spot -breed dalmation -color spotted]
::spot
% $d cget -breed
dalmation
% $d bark
::spot barks.


How can I generate the object name automatically?

If you'd like Snit to generate an object name for you, use the %AUTO% keyword as the requested name:

 
% snit::type dog {
    method bark {} { return "$self barks." }
}
::dog
% set d [dog %AUTO%]
::dog2
% $d bark
::dog2 barks.


Can types be renamed?

Tcl's rename command renames other commands. It's a common technique in Tcl to modify an existing command by renaming it and defining a new command with the original name; the new command usually calls the renamed command.

snit::type's, however, should never be renamed; to do so breaks the connection between the type and its objects.

Can objects be renamed?

Tcl's rename command renames other commands. It's a common technique in Tcl to modify an existing command by renaming it and defining a new command with the original name; the new command usually calls the renamed command.

All Snit objects (including widgets and widgetadaptors) can be renamed, though this flexibility has some consequences:

How do I destroy a Snit object?

Every instance of a snit::type has a destroy method:

 
% snit::type dog {
    method bark {} { return "$self barks." }
}
::dog
% dog spot
::spot
% spot bark
::spot barks.
% spot destroy
% info commands ::spot


Snit megawidgets (i.e., instances of snit::widget and snit::widgetadaptor) are destroyed like any other widget: by using the Tk destroy command on the widget or on one of its ancestors in the window hierarchy. In addition, any Snit object of any type can be destroyed by renaming it to the empty string using the Tcl rename command.

INSTANCE METHODS

What is an instance method?

An instance method is a procedure associated with a specific object.

How do I define an instance method?

Instance methods are defined in the type definition using the method statement. Consider the following code that might be used to add dogs to a computer simulation:

 
% 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.

How does a client call an instance method?

The method name becomes a subcommand of the object. For example, let's put a simulated dog through its paces:

 
% dog spot
::spot
% spot bark
::spot barks.
% spot chase cat
::spot chases cat.


How does an instance method call another instance method?

If method A needs to call method B on the same object, it does so just as a client does: it calls method B as a subcommand of the object itself, using the object name stored in self.

Suppose, for example, that our dogs never chase anything without barking at them:

 
% snit::type dog {
    method bark {} {
        return "$self barks."
    }

    method chase {thing} {
        return "$self chases $thing.  [$self bark]"
    }
}
::dog
% dog spot
::spot
% spot bark
::spot barks.
% spot chase cat
::spot chases cat.  ::spot barks.


Are there any limitations on instance method names?

Not really, so long as you avoid the standard instance method names: configure, configurelist, cget, destroy, and info.

How do I make an instance method private?

It's often useful to define private methods, that is, instance methods intended to be called only by other methods of the same object.

Snit doesn't implement any access control on instance methods, so all methods are de facto public. Conventionally, though, the names of public methods begin with a lower case letter, and the names of private methods begin with an upper case letter.

For example, suppose our simulated dogs only bark in response to other stimuli; they never bark just for fun. So the bark method could be private:

 
% snit::type dog {
    # Private by convention: begins with uppercase letter.
    method Bark {} {
        return "$self barks."
    }

    method chase {thing} {
        return "$self chases $thing. [$self Bark]"
    }
}
::dog
% dog fido
::fido
% fido chase cat
::fido chases cat. ::fido barks.


Are there any limitations on instance method arguments?

Method argument lists are defined just like normal Tcl proc argument lists; they can include default values, and the args argument. However, every method is called with 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.

What implicit arguments are passed to each instance method?

The arguments implicitly passed to every method are type, selfns, win, and self.

What is $type?

The implicit argument type contains the fully qualified name of the object's type:

 
% snit::type thing {
    method mytype {} {
        return $type
    }
}
::thing
% thing something
::something
% something mytype
::thing


What is $self?

The implicit argument self contains the object's fully qualified name.

If the object's command is renamed, then self will change to match in subsequent calls. Thus, your code should not assume that self is constant unless you know for sure that the object will never be renamed.

 
% snit::type thing {
    method myself {} {
        return $self
    }
}
::thing
% thing mutt
::mutt
% mutt myself
::mutt
% rename mutt jeff
% jeff myself
::jeff


What is $selfns

Each Snit object has a private namespace in which to store its instance variables and options. The implicit argument selfns is the name of this namespace; it never changes, and is constant for the life of the object, even if the object's name changes:

 
% snit::type thing {
    method myNameSpace {} {
        return $selfns
    }
}
::thing
% thing jeff
::jeff
% jeff myNameSpace
::thing::Snit_inst3
% rename jeff mutt
% mutt myNameSpace
::thing::Snit_inst3


The above example reveals how Snit names an instance's private namespace; however, you should not write code that depends on the specific naming convention, as it might change in future releases.

What is $win

The implicit argument win is defined for all Snit methods, including those of widgets and widgetadaptors, though it makes sense mostly for the latter two kinds. win is simply the original name of the object, whether it's been renamed or not. For widgets and widgetadaptors, it is also therefore the name of a Tk window. When a snit::widgetadaptor is used to modify the interface of a widget or megawidget, it must rename the widget's original command and replace it with its own. Thus, using win whenever the Tk window name is called for means that a snit::widget or snit::widgetadaptor can be adapted by a snit::widgetadaptor. See WIDGETS for more information.

How do I pass an instance method as a callback?

It depends on the context. Suppose in my application I have a dog object named fido, and I want fido to bark when a Tk button is pressed. In this case, I pass the instance method in the normal way, as a subcommand of fido:

 
    button .bark -text "Bark!" -command [list fido bark]


In typical Tcl style, we use a callback to hook two independent components together. But what if the dog object itself, passing one of its own instance methods to another object (one of its components, say)? The obvious thing to do is this:

 
% snit::widget dog {
    constructor {args} {
        #...
        button $win.barkbtn -text "Bark!"  -command [list $self bark]
        #...
    }
}
::dog


(Note that in this example, our dog becomes a snit::widget, because it has GUI behavior. See WIDGETS for more.) Thus, if we create a dog called .spot, it will create a Tk button called .barkbtn and pass it $self bark as the command.

Now, this will work--provided that .spot is never renamed. But why should .spot be renamed? Surely renaming widgets is abnormal? And so it is--unless .spot is the hull component of a snit::widgetadaptor. If it is, then it will be renamed, and .spot will name the snit::widgetadaptor object. When the button is pressed, the command $self bark will be handled by the snit::widgetadaptor, which might or might not do the right thing.

There's a safer way to do it, and it looks like this:

 
% snit::widget dog {
    constructor {args} {
        #...
        button $win.barkbtn -text "Bark!"  -command [mymethod bark]
        #...
    }
}
::dog


The command mymethod can be used like list to build up a callback command; the only difference is that mymethod inserts the object's name automatically, and does so in a way that later name changes won't affect the command's execution.

How do I delegate instance methods to a component?

See DELEGATION.

INSTANCE VARIABLES

What is an instance variable?

An instance variable is a private variable associated with some particular Snit object. Instance variables can be scalars or arrays.

How is a scalar instance variable defined?

Scalar instance variables are defined in the type definition using the variable statement. You can simply name it, or you can initialize it with a value:

 
snit::type mytype {
    # Define variable "greeting" and initialize it with "Howdy!"
    variable greeting "Howdy!"
}


How is an array instance variable defined?

Array instance variables are also defined using the variable command; however, you can't initialize them using the variable command. Typically, they get initialized in the constructor:

 
snit::type mytype {
    # Define array variable "greetings"
    variable greetings

    constructor {args} {
        set greetings(formal) "Good Evening"
        set greetings(casual) "Howdy!"
    }
}


Are there any limitations on instance variable names?

Just a couple. First, every Snit object has a built-in instance variable called options, which should never be redefined; second, instance variable names with the namespace delimiter (::) in them are likely to cause great confusion.

Do I need to declare instance variables before using them?

No. Once you've defined an instance variable in the type definition, it can be used in any instance code without declaration. This differs from normal Tcl practice, in which all non-local variables in a proc need to be declared.

How do I pass an instance variable's name to another object?

In Tk, it's common to pass a widget a variable name; for example, Tk label widgets have a -textvariable option which names the variable which will contain the widget's text. This allows the program to update the label's value just by assigning a new value to the variable.

If you naively pass the instance variable name to the label widget, you'll be confused by the result; Tk will assume that the name names a global variable. Instead, you need to provide a fully-qualified variable name. From within an instance method or a constructor, you can fully qualify the variable's name using the varname command:

 
snit::widget mywidget {
    variable labeltext ""

    constructor {args} {
        # ...

        label $win.label -textvariable [varname labeltext]

        # ...
    }
}


How do I make an instance variable public?

Practically speaking, you don't. Instead, you'll implement public variables as options. Alternatively, you can write instance methods to set and get the variable's value.

OPTIONS

What is an option?

A type's options are the equivalent of what other object-oriented languages would call public member variables or properties: they are data values which can be retrieved and (usually) set by the clients of an object. If a type is to be used a record type, it's possible that options are all that's needed.

Snit's implementation of options follows the Tk model fairly exactly, except that Snit doesn't interact with Tk's option database.

How do I define an option?

Options are defined in the type definition using the option statement. Consider the following type, to be used in an application that manages a list of dogs for a pet store:

 
% snit::type dog {
    option -breed mongrel
    option -color brown
    option -akc 0
    option -shots 0
}
::dog


According to this, a dog has four notable properties, or options: a breed, a color, a flag that says whether it's pedigreed with the American Kennel Club, and another flag that says whether it has had its shots. The default dog, evidently, is a brown mutt.

If no default value is specified, the option's value defaults to the empty string.

How can a client set options at object creation?

The normal convention is that the client may pass any number of options and their values after the object's name at object creation. For example, the ::dog command defined in the previous answer can now be used to define individual dogs. Any or all of the options may be set at creation time.

 
  % 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.

How can a client retrieve an option's value?

Retrieve option values using the cget method:

 
% spot cget -color
mottled
% fido cget -breed
mongrel


How can a client set options after object creation?

Any number of options may be set at one time using the configure instance method. Suppose that closer inspection shows that ::fido is a rare Arctic Boar Hound of a lovely dun color:

 
  % fido configure -color dun -breed "Arctic Boar Hound"
  % fido cget -color
  dun
  % fido cget -breed
  Arctic Boar Hound


Alternatively, the configurelist method takes a list of options and values; this is some times 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


How should an instance method access an option value?

There are two ways an instance method can set and retrieve an option's value. One is to use the configure and cget methods, as shown below:

 
% snit::type dog {
    option -weight 10

    method gainWeight {} {
        set wt [$self cget -weight]
        incr wt
        $self configure -weight $wt
    }
}
::dog
% dog fido
::fido
% fido cget -weight
10
% fido gainWeight
% fido cget -weight
11


Alternatively, Snit provides a built-in array instance variable called options. The indices are the option names; the values are the option values. The method given above can thus be rewritten as follows:

 
    method gainWeight {
        incr options(-weight)
    }


As you can see, using the options variable involves considerably less typing. If you define onconfigure or oncget handlers, as described in the following answers, you might wish to use the configure and cget methods anyway, just so that any special processing you've implemented is sure to get done.

How can I catch changes to an option's value?

Use an onconfigure handler.

What is an onconfigure handler?

An onconfigure handler is a special kind of instance method that's called whenever the related option is given a new value via the configure or configurelist instance methods. The handler can validate the new value, pass it to some other object, and anything else you'd like it to do.

An onconfigure handler is defined by an onconfigure statement in the type definition. Here's what the default configuration behavior would look like if written as an onconfigure handler:

 
snit::type dog {
    option -color brown

    onconfigure -color {value} {
        set options(-color) $value
    }
}


The name of the handler is just the option name. The argument list must have exactly one argument; it can be called almost anything, but conventionally it's called value. Within the handler, the argument is set to the new value; also, all instance variables are available, just as in an instance method.

Note that if your handler doesn't put the value in the options array, it doesn't get updated.

How can I catch accesses to an option's value?

Use an oncget handler.

What is an oncget handler?

An oncget handler is a special kind of instance method that's called whenever the related option's value is queried via the cget instance method. The handler can compute the value, retrieve it from a database, or anything else you'd like it to do.

An oncget handler is defined by an oncget statement in the type definition. Here's what the default behavior would look like if written as an oncget handler:

 
snit::type dog {
    option -color brown

    oncget -color {
        return $options(-color)
    }
}


The handler takes no arguments, and so has no argument list; however, all instance variables are available, just as they are in normal instance methods.

TYPE VARIABLES

What is a type variable?

A type variable is a private variable associated with a Snit type rather than with a particular instance of the type. In C++ and Java, the equivalent of type variables are called static member variables. Type variables can be scalars or arrays.

How is a scalar type variable defined?

Scalar type variables are defined in the type definition using the typevariable statement. You can simply name it, or you can initialize it with a value:

 
snit::type mytype {
    # Define variable "greeting" and initialize it with "Howdy!"
    typevariable greeting "Howdy!"
}


Every object of type mytype now has access to a single variable called greeting.

How is an array type variable defined?

Array-valued type variables are also defined using the typevariable command; however, you can't initialize them that way. In fact, at present there's no particularly good way to initialize an array-valued type variable. Snit really needs something like Java's static initializers.

Are there any limitations on type variable names?

Type variable names have the same minimal restrictions as instance variable names.

Do I need to declare type variables before using them?

No. Once you've defined a type variable in the type definition, it can be used in instance methods or type methods without declaration. This differs from normal Tcl practice, in which all non-local variables in a proc need to be declared.

How do I pass a type variable's name to another object?

In Tk, it's common to pass a widget a variable name; for example, Tk label widgets have a -textvariable option which names the variable which will contain the widget's text. This allows the program to update the label's value just by assigning a new value to the variable.

If you naively pass a type variable name to the label widget, you'll be confused by the result; Tk will assume that the name names a global variable. Instead, you need to provide a fully-qualified variable name. From within an instance method or a constructor, you can fully qualify the type variable's name using the typevarname command:

 
snit::widget mywidget {
    typevariable labeltext ""

    constructor {args} {
        # ...

        label $win.label -textvariable [typevarname labeltext]

        # ...
    }
}


How do I make a type variable public?

There are two ways to do this. The preferred way is to write a pair of type methods to set and query the variable's value.

Alternatively, you can publicize the variable's name in your documentation and clients can access it directly. For example,

 
snit::type mytype {
    typevariable myvariable
}

set ::mytype::myvariable "New Value"


As shown, type variables are stored in the type's namespace, which has the same name as the type itself.

TYPE METHODS

What is a type method?

A type method is a procedure associated with the type itself rather than with any specific instance of the type.

How do I define a type method?

Type methods are defined in the type definition using the typemethod statement:

 
snit::type dog {
    # List of pedigreed dogs
    typevariable pedigreed

    typemethod pedigreedDogs {} {
        return $pedigreed
    }

    # ...
}


Suppose the dog type maintains a list of the names of the dogs that have pedigrees. The pedigreedDogs type method returns this list.

The typemethod statement looks just like a normal Tcl proc, except that it appears in a snit::type definition. It defines the method name, the argument list, and the body of the method.

How does a client call a type method?

The method name becomes a subcommand of the type's command. For example,

 
snit::type dog {
    option -pedigreed 0

    # List of pedigreed dogs
    typevariable pedigreed

    typemethod pedigreedDogs {} {
        return $pedigreed
    }

    # ...
}

dog spot -pedigreed 1
dog fido

foreach dog ::pedigreedDogs { ... }


Are there any limitations on type method names?

Not really, so long as you avoid the standard type method names:

create and info.

How do I make a type method private?

It's sometimes useful to define private type methods, that is, type methods intended to be called only by other type or instance methods of the same object.

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.

Are there any limitations on type method arguments?

Method argument lists are defined just like normal Tcl proc argument lists; they can include default values, and the args argument. 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.

How does an instance or type method call a type method?

If an instance or type method needs to call a type method, it should use type to do so:

 
snit::type dog {

    typemethod pedigreedDogs {} { ... }

    typemethod printPedigrees {} {
        foreach obj [$type pedigreedDogs] { ... }
    }
}


How do I pass a type method as a callback?

It's common in Tcl to pass a snippet of code to another object, for it to call later. Because types cannot be renamed, the thing to do is just use the type name, or, if the callback is registered from within a type method, type. For example, suppose we want to print a list of pedigreed dogs when a Tk button is pushed:

 
  button .btn -text "Pedigrees" -command [list dog printPedigrees]
  pack .btn


PROCS

What is a proc?

A Snit proc is really just a Tcl proc defined within the type's namespace. You can use procs for private code that isn't related to any particular instance. For example, I often find myself writing a proc to pop the first item off of a list stored in a variable.

How do I define a proc?

Procs are defined by including a proc statement in the type definition:

 
snit::type mytype {
    # Pops and returns the first item from the list stored in the
    # listvar, updating the listvar
   proc pop {listvar} { ... }

   # ...
}


Are there any limitations on proc names?

Any name can be used, so long as it does not begin with Snit_; names beginning with Snit_ are reserved for Snit's own use. However, the wise programmer will avoid proc names like set, list , if, and so forth that would shadow standard Tcl command names.

By convention, proc names begin with a capital letter.

How does a method call a proc?

Just like it calls any Tcl command. For example,

 
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]
    }
}


How can I pass a proc to another object as a callback?

I tend to use type or instance methods for this purpose and ignore procs altogether. But if you really need to, the codename command returns the proc's fully qualified name.

CONSTRUCTORS

What is a constructor?

In object-oriented programming, an object's constructor is responsible for initializing the object completely at creation time. The constructor receives the list of options passed to the snit::type command's create method and can then do whatever it likes. That might include computing instance variable values, reading data from files, creating other objects, updating type variables, and so forth.

The constructor doesn't return anything.

How do I define a constructor?

A constructor is defined by using the constructor statement in the type definition. Suppose that it's desired to keep a list of all pedigreed dogs. The list can be maintained in a type variable and retrieved by a type method. Whenever a dog is created, it can add itself to the list--provided that it's registered with the American Kennel Club.

 
% snit::type dog {
    option -akc 0

    typevariable akcList {}

    constructor {args} {
        $self configurelist $args

        if {$options(-akc)} {
            lappend akcList $self
        }
    }

    typemethod akclist {} {
        return $akcList
    }
}
::dog
% dog spot -akc 1
::spot
% dog fido
::fido
% dog akclist
::spot


What does the default constructor do?

If you don't provide a constructor explicitly, you get the default constructor, which looks like this:

 
% snit::type dog {
    option -breed mongrel
    option -color brown
    option -akc 0

    constructor {args} {
        $self configurelist $args
    }
}
::dog
% dog spot -breed dalmatian -color spotted -akc 1
::spot


When the constructor is called, args will be set to the list of arguments that follow the object's name. The constructor is allowed to interprete this list any way it chooses; the normal convention is to assume that it's a list of option names and values, as shown in the example above. If you simply want to save the option values, you should use the configurelist method, as shown.

Can I choose different command line arguments for the constructor?

Yes, you can. For example, suppose we wanted to be sure that the breed was explicitly stated for every dog at creation time, and couldn't be changed thereafter. One way to do that is as follows:

 
% 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


Are there any limitations on constructor arguments?

Constructor argument lists are defined just like normal Tcl proc argument list; they can include default values, and the args argument. However, the constructor is called with 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.

What implicit arguments are passed to the constructor?

The constructor gets the same implicit arguments that are passed to instance methods: type, selfns, win, and self.

DESTRUCTORS

What is a destructor?

A destructor is a special kind of method that's called when an object is destroyed. It's responsible for doing any necessary clean-up when the object goes away: destroying components, closing files, and so forth.

How do I define a destructor?

Destructors are defined by using the destructor statement in the type definition. Suppose we're maintaining a list of pedigreed dogs; then we'll want to remove dogs from it when they are destroyed.

 
% snit::type dog {
    option -akc 0

    typevariable akcList {}

    constructor {args} {
        $self configurelist $args

        if {$options(-akc)} {
            lappend akcList $self
        }
    }

    destructor {
        set ndx [lsearch $akcList $self]

        if {$ndx != -1} {
            set akcList [lreplace $akcList $ndx $ndx]
        }
    }

    typemethod akclist {} {
        return $akcList
    }
}
::dog
% dog spot -akc 1
::spot
% dog fido -akc 1
::fido
% dog akclist
::spot ::fido
% fido destroy
% dog akclist
::spot


Are there any limitations on destructor arguments?

Yes; a destructor has no explicit arguments.

What implicit arguments are passed to the destructor?

The destructor gets the same implicit arguments that are passed to instance methods: type, selfns, win, and self.

Must components be destroyed explicitly?

Yes and no.

For a Snit megawidget (snit::widgets and snit::widgetadaptors), any widget components created by it will be destroyed automatically when the megawidget is destroyed, in keeping with normal Tk behavior (destroying a parent widget destroys the whole tree). On the other hand, all non-widget components of a Snit megawidget, and all components of a normal snit::type object, must be destroyed explicitly in a destructor.

COMPONENTS

What is a component?

Often an object will create and manage a number of other objects. One example is a Snit megawidget that composes a number of Tk widgets. These objects are part of the main object and are thus are called components of it.

But Snit also has a more precise meaning for component. The components of a Snit object are those objects created by it to which methods and options can be delegated. See DELEGATION for more information about delegation.

How do I create a component?

First, you must decide what role a component plays within your object, and give the role a name. For example, suppose your dog object creates a tail object (the better to wag with, no doubt). The tail object will have some command name, but you tell Snit about it using its role name, as follows:

 
% snit::type dog {
    # Define component name as an instance variable
    variable mytail

    constructor {args} {
        # Create and save the component's command
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    method wag {} {
        $tail wag
    }
}
::dog


As shown here, it doesn't matter what the tail object's real name is; the dog object refers to it by its component name.

The above example shows one way to delegate the wag method to the mytail component; see DELEGATION for an easier way.

Are there any limitations on component names?

Yes. snit::widget and snit::widgetadaptor have a special component called the hull component; thus, the name hull should be used for no other purpose.

Component names are in fact instance variable names, and so follow the rules for instance variables.

Must I destroy the components I create?

That depends. When a parent widget is destroyed, all child widgets are destroyed automatically. Thus, if your object is a snit::widget or snit::widgetadaptor you don't need to destroy any components that are widgets.

Any non-widget components, however, and all components of a snit::type object, must be destroyed explicitly. This is true whether you assign them a component name or not.

 
% snit::type dog {
    variable mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    destructor {
        $tail destroy
    }
}
::dog


Note that this code assumes that tail is also a snit::type; if not, it might need to be destroyed in some other way.

DELEGATION

What is delegation?

Delegation, simply put, is when you pass a task you've been given to one of your assistants. (You do have assistants, don't you?) Snit objects can do the same thing. The following example shows one way in which the dog object can delegate its wag method and its -taillength option to its tail component.

 
% snit::type dog {
    variable mytail

    option -taillength

    onconfigure -taillength {value} {
         $mytail configure -length $value
    }

    oncget -taillength {
         $mytail cget -length
    }

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }

    method wag {} {
        $mytail wag
    }
}
::dog
% snit::type tail {
    option -length 5
    option -partof
    method wag {} { return "Wag, wag, wag."}
}
::tail
% dog spot -taillength 7
::spot
% spot cget -taillength
7
% spot wag
Wag, wag, wag.


This is the hard way to do it, by it demonstrates what delegation is all about. See the following questions 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.

How can I delegate a method to a component object?

Delegation occurs frequently enough that Snit makes it easy. Any method can be delegated to any component by placing a single delegate statement in the type definition. (See COMPONENTS for more information about component names.)

For example, here's a much better way to delegate the dog object's wag method:

 
% snit::type dog {
    delegate method wag to mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }
}
::dog
% snit::type tail {
    option -length 5
    option -partof
    method wag {} { return "Wag, wag, wag."}
}
::tail
% dog spot
::spot
% spot wag
Wag, wag, wag.


This code has the same affect as the code shown under the previous question: when a dog's wag method is called, the call and its arguments are passed along automatically to the tail object.

Note that when a component is mentioned in a delegate statement, the component's instance variable is defined implicitly.

Note also that you can define a method name using the method statement, or you can define it using delegate; you can't do both.

Can I delegate to a method with a different name?

Suppose the tail object has a wiggle method instead of a wag method, and you want to delegate the dog's wag method to the tail's wiggle method. It's easily done:

 
% snit::type dog {
    delegate method wag to mytail as wiggle

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }
}
::dog
% snit::type tail {
    option -length 5
    option -partof
    method wiggle {} { return "Wag, wag, wag."}
}
::tail
% dog spot
::spot
% spot wag
Wag, wag, wag.


How can I delegate an option to a component object?

The first question in this section shows one way to delegate an option to a component; but this pattern occurs often enough that Snit makes it easy. For example, every tail object has a -length option; we want to allow the creator of a dog object to set the tail's length. We can do this:

 
% snit::type dog {
    delegate option -length to mytail

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }
}
::dog
% snit::type tail {
    option -partof
    option -length 5
}
::tail
% dog spot -length 7
::spot
% spot cget -length
7


This produces nearly the same result as the oncget and onconfigure handlers shown under the first question in this section: whenever a dog object's -length option is set or retrieved, the underlying tail object's option is set or retrieved in turn.

Note that you can define an option name using the option statement, or you can define it using delegate; you can't do both.

Can I delegate to an option with a different name?

In the previous answer we delegated the dog's -length option down to its tail. This is, of course, wrong. The dog has a length, and the tail has a length, and they are different. What we'd really like to do is give the dog a -taillength option, but delegate it to the tail's -length option:

 
% snit::type dog {
    delegate option -taillength to mytail as -length

    constructor {args} {
        set mytail [tail %AUTO% -partof $self]
        $self configurelist $args
    }
}
::dog
% snit::type tail {
    option -partof
    option -length 5
}
::tail
% dog spot -taillength 7
::spot
% spot cget -taillength
7


How can I delegate any unrecognized method or option to a component object?

It may happen that a Snit object gets most of its behavior from one of its components. This often happens with snit::widgetadaptors, for example, where we wish to slightly the modify the behavior of an existing widget. To carry on our dog example, however, suppose that we have a snit::type called animal that implements a variety of animal behaviors--moving, eating, sleeping, and so forth. We want our dog objects to inherit these same behaviors. Here's how we can give a dog methods and options of its own while delegating all other methods and options to its animal component:

 
% snit::type dog {
    delegate option * to animal
    delegate method * to animal

    option -akc 0

    constructor {args} {
        set animal is [animal %AUTO% -name $self]
        $self configurelist $args
    }

    method wag {} {
        return "$self wags its tail"
    }
}
::dog


That's it. A dog is now an animal which has a -akc option and can wag its tail.

Note that we don't need to specify the full list of method names or option names which animal will receive. It gets anything dog doesn't recognize--and if it doesn't recognize it either, it will simply throw an error, just as it should.

WIDGETS

What is a snit::widget?

A snit::widget is the Snit version of what Tcl programmers usually call a megawidget: a widget-like object usually consisting of one or more Tk widgets all contained within a Tk frame.

A snit::widget is also a special kind of snit::type. Just about everything in this FAQ list that relates to snit::types also applies to snit::widgets.

How do I define a snit::widget?

snit::widgets are defined using the snit::widget command, just as snit::types are defined by the snit::type command.

The body of the definition can contain all of the same kinds of statements.

How do snit::widgets differ from snit::types?

How should I name widgets which are components of a snit::widget?

Every widget, whether a genuine Tk widget or a Snit megawidget, has to have a valid Tk window name. When a snit::widget is first created, its instance name, self, is a Tk window name; however, if the snit::widget is used as the hull component by a snit::widgetadaptor its instance name will be changed to something else. For this reason, every snit::widget method, constructor, destructor, and so forth is passed another implicit argument, win, which is the window name of the megawidget. Any children must be named using win as the root.

Thus, suppose you're writing a toolbar widget, a frame consisting of a number of buttons placed side-by-side. It might look something like this:

 
snit::widget toolbar {
    delegate option * to hull

    constructor {args} {
        button $win.open -text Open -command [mymethod open]
        button $win.save -text Save -command [mymethod save]

        # ....

        $self configurelist $args

    }
}


See also the question on renaming objects, toward the top of this file.

WIDGETADAPTORS

What is a snit::widgetadaptor?

A snit::widgetadaptor is a kind of snit::widget. Whereas a snit::widget's hull is automatically created and is always a Tk frame, a snit::widgetadaptor can be based on any Tk widget--or on any Snit megawidget, or even (with luck) on megawidgets defined using some other package.

It's called a widget adaptor because it allows you to take an existing widget and customize its behavior.

How do I define a snit::widgetadaptor?

Using the snit::widgetadaptor command. The definition for a snit::widgetadaptor looks just like that for a snit::type or snit::widget, except that the constructor must create and install the hull component.

For example, the following code creates a read-only text widget by the simple device of turning its insert and delete methods into no-ops. Then, we define new methods, ins and del, which get delegated to the hull component as insert and delete. Thus, we've adapted the text widget and given it new behavior while still leaving it fundamentally a text widget.

 
% ::snit::widgetadaptor rotext {

    constructor {args} {
        # Create the text widget; turn off its insert cursor
        installhull [text $win -insertwidth 0]

        # Apply any options passed at creation time.
        $self configurelist $args
    }

    # Disable the text widget's insert and delete methods, to
    # make this readonly.
    method insert {args} {}
    method delete {args} {}

    # Enable ins and del as synonyms, so the program can insert and
    # delete.
    delegate method ins to hull as insert
    delegate method del to hull as delete
    
    # Pass all other methods and options to the real text widget, so
    # that the remaining behavior is as expected.
    delegate method * to hull
    delegate option * to hull
}
::rotext


The most important part is in the constructor. Whereas snit::widget creates the hull for you, snit::widgetadaptor cannot -- it doesn't know what kind of widget you want. So the first thing the constructor does is create the hull component (a Tk text widget in this case), and then installs it using the installhull command.

Note: There is no instance command until you create one by installing a hull component. Any attempt to pass methods to $self prior to calling installhull will fail.

Can I adapt widgets from other megawidget packages?

Yes.

However, 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.

For example, some widgets in BWidgets 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.

KEYWORDS

BWidget , C++ , Incr Tcl , adaptors , class , mega widget , object , object oriented , widget , widget adaptors

COPYRIGHT

Copyright © 2003, by William H. Duquette

Copyright © 2003 for compilation: ActiveState