Scope in Itcl

  1. Scoping problems
  2. Using the "bind" command
  3. Using the variable "this"
  4. Using "info which command"
  5. Using the "code" command

Scoping problems

One of the trickiest parts about using Itcl is to properly manage namespaces and scope. A key problem that arises repeatedly is how to construct a command that is bound to an event (like a key press), bound to a button, executed in the background using "after," or executed in some scope different from where the command is defined. In general, the problem is that a command has a namespace context. There are two ways in Itcl that this namespace context can be represented:

    ::namespace::commandname
    {@scope ::namespace commandname}
Either of these can be executed as a command. (One of the unfortunately features of Itcl is that there are several ways to accomplish just about everything related to namespaces and scope, and the subtle differences between them are not always clear.)

Within an Itcl object, the variable "this" has the first of these two forms, as a fully qualified command name. The "info which" command can be used to obtain the first form for any command ("this" only works for an object). The "code" or the "scope" commands can be used to obtain the second form.

In Itcl, all commands that are executed as a consequence of a keyboard or window event are executed at the global scope (in namespace "::"). Also, commands executed in the background using "after" are executed at the global scope. To verify this, we can use the "info context" command, which returns a null string at the global scope. For example:

namespace foo {
   ::tycho::inform "Inside context: [info context]"
   after idle {
      ::tycho::inform "after context: [info context]"
   }
}
Above, we find that the context inside the namespace is reported as "::foo", but in the "after" command, it is reported as an empty string, meaning the global context. Button commands work similarly:
namespace foo {
   catch {destroy .x}
   toplevel .x
   button .x.b -text pushme -command {
      ::tycho::inform "button context: [info context]"
      destroy .x
   }
   pack .x.b
}
Again, the button command executes at the global scope. Thus, the challenge when building Itcl objects within namespaces is to be sure that commands that will execute at global scope can in fact execute at global scope. There are a number of ways to do this.

Using the "bind" command

Bindings to key or mouse events, like the button presses above, execute at the global scope. Itcl has provided two particularly useful additions to the special symbols that the bind command understands:

%q
Returns the fully-qualified command name of object receiving the event.
%Q
Returns the fully-qualified command name of megawidget receiving the event

These can be used as in the following example:

namespace foo {
   catch {delete class bar}
   class bar {
      inherit ::itk::Toplevel
      constructor {args} {
         itk_component add f {
            canvas $itk_interior.f
         } {}
         pack $itk_component(f)
         bind $itk_component(hull) <a> \
            {::tycho::inform "expansion of %%q: %q"}
         bind $itk_component(hull) <b> \
            {delete object %q}
      }
   }
   bar .y   
}
Here, we define a class "bar" inside namespace "foo" derived from an Itk top-level window class. In the constructor, we pack into the window a canvas widget. We bind the key "a" to simply report the expansion for "%q", which will be "::foo::.y". The key "b" uses this expansion to delete the object. Notice that the window specified for the bindings is "$itk_component(hull)", which will be the top-level window.

The "%Q" directive is similar, except that the full expanded name of an Itk megawidget will be reported.

The "winfo command windowname" command returns the fully-qualified command name of an object given a Tk top-level window name. For example, if the canvas window from the above example is still present, try:

    ::tycho::inform [winfo command .y]
Notice that this can execute in the global scope. The window name ".y" is visible to Tk in any scope because Tk has only a flat name space. The Itk object ".y", however, is not visible in the global scope.

The "winfo megawidget windowname" command is similar, except that it returns the name of an Itk megawidget rather than a top-level window object. The specified window name can be any window inside the megawidget.

Using the variable "this"

The variable "this" inside a class context has as its value the fully qualified command name associated with the object (yes, it could have been used instead of %q in the above example). Consider the following:

namespace foo {
   catch {delete class bar}
   class bar {
      constructor {args} {
         ::tycho::inform "Constructing object: $this"
         ::tycho::inform "Current context: [info context]"
         after idle "$this x"
      }
      method x {} {::tycho::inform {works}}
   }
   bar y
}
The value of the variable "this" is reported as "::foo::y", which is the fully qualified command name associated with the object "y". The context inside the constructor is "::foo::bar", a namespace associated with the class. The command "$this x" or "::foo::y x" can be executed at the global scope, so the "after" directive works. Notice that it would not have worked to say:
         after idle {$this x}
because the "$this" will not be evaluated until the "after" command triggers, and this will happen at the global scope where no variable "this" is defined.

If in a binding or delayed invocation using "after" you wish to access methods in a widget contained in the calling widget, use syntax like that in the following example:

   after idle "$this component name method arg1 arg2 ... "
Here, "name" is the name of the Itk component, "method" is the name of its method, and the argument list is the arguments to pass to the method.

Using "info which command"

The "info which" command takes one argument, which is the name of a command in the current scope, and returns a fully qualified command with namespace information. Thus, for example,

namespace foo {
   proc x {} {::tycho::inform {works}}
   ::tycho::inform [info which x]
   after idle [info which x]
}
The first line defines a procedure "x", the second informs us of its fully qualified name "::foo::x", and the third executes the fully qualified name as a command at the global scope. The following, by contrast, yields an error:
namespace foo {
   proc x {} {::tycho::inform {works}}
   after idle "x"
}
The same principle can be used in class definitions. Consider the following:
namespace foo {
   catch {delete class bar}
   class bar {
      constructor {args} {
         ::tycho::inform "Scheduling command: [info which z]"
         after idle [info which z]
      }
      proc z {} {::tycho::inform {works}}
   }
   bar y
}
Notice that "z" here is a procedure, not a method. A procedure does not need an instance of an object to be invoked. Indeed, the command that we schedule using "after" is "::foo::bar::z", which does not make any reference to any particular instance of the class "bar". Thus, "info which" should not be used to access methods, but can be used to access procedures.

Using the "code" command

The "code" command returns a scoped reference in the second form described above, beginning with "@scope". For example:

namespace foo {
   proc x {} {::tycho::inform {works}}
   ::tycho::inform [code x]
   after idle [code x]
}
In this case, the "[code x]" directive returns the list "@scope ::foo x", which can be executed as a command at the global scope. It can also be used in class definitions. For example:
namespace foo {
   catch {delete class bar}
   class bar {
      constructor {args} {
         ::tycho::inform "Scheduling command: [code z]"
         after idle [code z]
      }
      proc z {} {::tycho::inform {works}}
   }
   bar y
}
The command that is scheduled by "after" is "@scope ::foo::bar z".

Tycho Home Page


Copyright © 1996, The Regents of the University of California. All rights reserved.
Last updated: 96/11/06, comments to: eal@eecs.berkeley.edu