mkWidgets 1.3 Manual, Part 1 of 2

Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted. The author makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. By use of this software the user agrees to indemnify and hold harmless the author from any claims or liability for loss arising out of such use.
 

 CONTENTS

mkWidgets 1.3 - A method for writing metawidgets, and a collection of these. Part 1: How to build metawidgets (Part 2)

Introduction
The Metawidget Command
Special Commands
Tool Commands
Notes
Examples
Installation
Changes
Author

 INTRODUCTION

A metawidget is composed of several standard widgets and behaves like a standard widget itself (well, almost). Since there is no limit in regards to the number of standard widgets inside a metawidget and its overall functionality, metawidgets can be anything from as simple as a combobox to as complex as a treecontrol and more. See Part 2 of this manual for details.

mkWidgets 1.3 is written entirely in Tcl/Tk and does not require any other extensions (unlike version 1.0, where mkGeneric and mkClasses was required). It provides two things:

The main characteristics of a regular widget in Tk are:

The Metawidget package allows for the definition of metawidgets that behave almost the same way as regular Tk widgets. A metawidget’s attributes are:

The Metawidget package is basically a code generator. One can build metawidgets in a rather abstract and simple way by defining new widget commands and options, while the package takes care of namespace handling and Tcl code generation.

A nice new feature in this version is the metawidget export command. It returns the standard Tcl code that is generated by the Metawidget package for a particular metawidget. By simply integrating this code into an application, the dependency to mkWidgets can be eliminated.

 THE METAWIDGET COMMAND

A Metawidget is defined with the "metawidget" command and its subcommands "create", "proc", "option" and "command". It can be deleted with "delete", and information can be retrieved with "info" or "names".

metawidget create ClassName InitProc ExitProc ?-type WidgetType? ?-default WidgetName? ?-command CommandName? ?args?

This command creates a new metawidget class with the name ClassName. According to existing naming conventions, ClassName must start with an uppercase letter. InitProc specifies a script that is evaluated on each instantiation of a metawidget. It usually contains code to create any internal widgets that make up the metawidget, and to initialize options and other state information. The current metawidget is referenced by means of the automatic variable $this. ExitProc is evaluated when a metawidget is destroyed. As with InitProc, the metawidget's name is stored in the variable $this. The ExitProc usually releases any resources such as open channels.

Each metawidget has a widget that encloses all other widgets. This widget is automatically created and is an invisible frame by default. By means of the -type option, a different widget type can be specified, for instants a toplevel widget.

The -default option specifies where calls to the standard metawidget commands configure and cget shall be redirected to, if they are not explicitely defined with "metawidget option". Also, all commands not explicitely defined with "metawidget command" will be redirected to the default widget. WidgetName must be the name of one of the internal widgets, relative to its enclosing widget. If no default widget is specified, the enclosing widget is taken.

The -command option specifies what the name of the new widget command shall be. By default, it is the same as the metawidget's ClassName, but all in lowercase letters.

In addition, an arbitrary number of option-value pairs may be specified. They will directly be applied to the enclosing widget and hence must be allowed option values for a frame or whatever widget type has been specified by means of the -type option.

metawidget proc ClassName ProcName Args Body

This command defines a procedure that is associated with a metawidget. It is defined just like a regular procedure, i.e. it has a name, a list of arguments, and a body. As with the InitProc and the ExitProc from the "metawidget create" command, the automatic variable $this represents the current metawidget. Metawidget procedures can be called only from within another metawidget procedure of the very same widget instance. They are used to implement code that is associated to metawidget options and/or commands.

metawidget command ClassName CommandName ProcName

This defines a metawidget command and associates it with a metawidget procedure. Metawidget commands are identical to regular widget commands and are specified right after the widget name, like in ".mybutton invoke". When a metawidget's command is called, its associated procedure is executed, hereby passing all arguments that might have been specified after the command. Note that even if no metawidget commands are defined, a metawidget will still accept all commands that exist for its default widget.

metawidget option ClassName OptionName ?SetProc? ?GetProc?

This defines a metawidget option. Metawidget options are those that require special processing, i.e. they are in most cases not a real option of any of its internal widgets. As with metawidget commands, a metawidget that has no such metawidget options defined will still accept all options that apply to the metawidget's default widget.

There are three ways to define metawidget options: By means of a widget-variable (see below), by means of a metawidget procedure or with a combination of both:

Some options do not require immediate processing. It is rather sufficient to store their value in a variable for later reference. An example is the -command option of a regular button: When this option is set, nothing happens. A metawidget option of that kind is created without specifying SetProc and GetProc. The metawidget will simply store the value in a widget-variable that has the same name as the option.

Many times, when an option is set, it requires code to be executed. For instants, there must be some code that actually changes a button's background color, when its value for the -background option is changed. If that piece of code eventually stores the new color in a variable, then the option's current value can always be looked up there. This is equivalent to creating a metawidget option where SetProc is a metawidget procedure. Every time this option is set, SetProc is called with the new option value as its only argument. If the option's value is retrieved by means of the cget command, the actual value is taken from its widget-variable. Therefore SetProc must set this widget-variable to the option's new value.

If, in addition, a GetProc is specified, then it is called every time the option's value is requested with the cget command. GetProc must return the option's value, in whatever way it is retrieved, and is called without arguments.

metawidget delete ClassName

The delete subcommand deletes a metawidget class. This should not be called if instances of that class still exist.

metawidget names

This simply returns all currently defined metawidgets.

metawidget info ClassName procs | commands | options

This returns the procedures, commands and options defined for the specified class. It always returns a list where each element is a sublist of the arguments that were used during the definition of the procs, commands or options.

metawidget export ClassName

The code as generated by the Metawidget package is returned. It is standard Tcl with no dependencies to the package itself. Consequently, by simply pasting the code into a standard application, metawidgets can be used without their definition and without having to source the Metawidget package.

 SPECIAL COMMANDS

A metawidget instance is basically like an object in the object oriented programming world: It has a constructor (InitProc), a destructor (ExitProc), methods (the commands), and properties (the options). Consequently, it has something like private variables, here called widget-variables, and also class-static variables, called class-variables.

A widget-variable is specific to an instance of a metawidget. Widget-variables can be set and retrieved in any metawidget procedure and remain state in-between calls. They are neither really global nor local, and can have a different value in each instance. Widget-variables are controlled by a set of "my" commands.

A class-variable is specific to a metawidget's class. As with widget-variables class-variables can be set and retrieved in any metawidget procedure and remain state in-between calls. They exist exactly once for all instances of a class and are therefore suited to exchange data between objects and to store information that is identical for all instances of a class. Widget-variables are controlled by a set of "our" commands.

my WidgetVar ?NewValue?

Sets or retrieves a widget-variable. Similar to the standard "set" command, it can be called with the name of a variable to retrieve the value of this variable or to set it to a new value.

unmy WidgetVar ?WidgetVar ...?

Unsets widget-variables. Although the command's name seems nonsense, it is a nice equivalent to the standard command "unset".

myarray option WidgetArray ?args?

Not only scalar widget-variables can be defined, but also arrays. This command is just like the standard "array" command, except that it applies to widget-arrays.

myinfo class, myinfo vars, myinfo exists WidgetVar

Either returns the metawidget's class name, or all currently defined widget-variables, or a flag if the specified WidgetVar exists.

our ClassVar ?NewValue?

Sets or retrieves a class-variable. Similar to the standard "set" command, it can be called with the name of a variable to retrieve the value of this variable or to set it to a new value.

unour ClassVar ?ClassVar ...?

Unsets class-variables. Although the command's name seems nonsense, it is a nice equivalent to the standard command "unset".

ourarray option ClassArray ?args?

Not only scalar class-variables can be defined, but also arrays. This command is just like the standard "array" command, except that it applies to class-arrays.

ourinfo vars, ourinfo exists ClassVar

Either returns all currently defined class-variables, or a flag if the specified ClassVar exists.

 TOOL COMMANDS

Some tool commands are also imported into every metawidget class. They all start with "mkw." and are not really necessary, but make some things easier (they are merely Tcl-only rip-offs of my favorites from the mkGeneric package).

mkw.lassign List ?VarName ...?

Assigns all elements in List to the given VarNames from left to right. I.e. the first element in List is assigned to the first VarName, the second element to the second VarName and so on. If there are more VarNames than elements in List, the remaining VarNames are left unchanged.

mkw.lextend List ?Value ...?

If Value is not yet an element of List, Value is appended to the list and the extended list is returned. Otherwise, the command simply returns List.

mkw.lshrink List ?Value ...?

If Value is an element of List, Value is deleted from the list and the reduced list is returned. Otherwise, the command simply returns List.

mkw.lchange List OldValue NewValue

If OldValue is an element of List, OldValue is replaced by NewValue and the modified list is returned. Otherwise, the command simply returns List.

mkw.decode Expr List ?DefaultValue?

List must consist of an even number of elements. Each odd-numbered element in List is compared against Expr. If it matches, the element on the right of the matching element in List is returned. If there is no match and a DefaultValue is specified, it is returned. Otherwise the command returns nothing. (This is similar to "string map", which is not available in Tcl 8.0).

mkw.complete Expr List

Expr is compared against each of the elements in List to find a match. A match occurs if Expr is identical to one of the elements, or if it is a unique abbreviation for exactly one of them. In this case the matching element is returned, otherwise an error is created.

mkw.options List ?OptionSpec ...?

This is a simple option processing routine. options can have no arguments (like with lsort -dictionary) or one argument (like fconfigure ... -blocking 1). List is expected to consist of any combination of zero- or one-argument options, e.g. '-dictionary -command mySort -index 1 -decreasing'. Allowed options are specified with an arbitrary number of option specifications. For options with no argument, the OptionSpec is simply the name of the option, e.g. -dictionary. For options with a value, an OptionSpec consists of two or three elements: The first is the option name, the second either a list of allowed values or an asterisk * for any value. The third element is optional and specifies a default value if the option is not found in List.

For each option found, a variable with the same name as the option is set in the calling procedure's context. This variable's value is either a 1 for options without argument, or the option's value. Option names can be abbreviated, as well as option values that map to a list of allowed values in the OptionSpec. The command will return an error message, if an unknown option was found, or, if one of the OptionSpecs is a plain *, return all elements in List that could not be resolved.

 NOTES

Bindings

The bindtags of a metawidget are modified, so that the widget type of the enclosing widget is replaced by the class name of the metawidget. The default class binding for the event <Destroy> is redefined and now calls the metawidget's ExitProc. Besides that, any binding to a metawidget will apply to its enclosing widget.

If a metawidget procedure needs to be called from within a binding script, then this procedure has to be "exported" via a metawidget command. Binding scripts are evaluated from within the global namespace and therefore don't know any of the metawidget's procedures.

Geometry Management

Many metawidgets are "final". That is, they won’t have child widgets other than their internal widgets (those that constitute the metawidget). A combobox, for instants, would not like to have other widgets packed inside it. However, some metawidgets like the Textframe example below, make only sense with other widgets inside them. Other metawidgets of that kind are Document, Pane and Tabcontrol.

This can be a problem, if these widgets are true children of the metawidget, for a) the metawidget has already some children (or it wouldn't be a metawidget...), and b) there is no control of where the new children will be placed, gridded or packed together with the metawidget's own children.

One solution is to simply document the particular child of the metawidget where other children may be legally placed into. E.g. in the case of the Document metawidget, this would be $this.work. This is a document's work area, and other widgets should be put inside it. These widgets may or may not be logical children of $this.work.

Another solution is to define the metawidget commands pack, place and grid. They would accept the usual arguments for these three native commands, but would also ensure that widgets appear in the right spot, by sort of redirecting the widgets into the work area. Obviously, that is not so nice as using pack, place and grid in the usual way. However, it's not really new either: Complex widgets like canvas and text also place other objects inside them by using e.g. .canvas create ... or .text window ...

Option Database

The option database is not supported in this version (quote from "Tcl and the Tk Toolkit": "I suggest that you use it as little as possible").

Configure Command

Calling a metawidget with the configure command but without additional arguments will only return the options list of the default widget but not the metawidget options.

Command and Option Abbreviation

Commands and options can be abbreviated, but the way they are being resolved is not quite clean. As described above, a command (or option) that is not specified with "metawidget command" (or "metawidget option") will be applied to the default widget. An abbreviated command (or option) would first be resolved within the metawidget commands (or metawidget options), and then within the default widget commands (or options). So, if there's a metawidget option "-compute" and the default widget is a button, then the string "-com" would apply to both "-compute" and "-command". However, instead of raising an error, "-compute" will be called.

Backward Compatibility

The current version 1.3 introduces a minor incompatibility that was necessary to make metawidgets behave a bit more like native widgets: The -type option of the metawidget command now only accepts the values frame and toplevel. Other widget types are not allowed anymore. The benefit of this is that a call to winfo class metawidget now returns the correct class name of that metawidget (which is achieved with the -class option only available for frame and toplevel window types).

Document metawidget: If you pack, place or grid a widget inside a document metawidget, and this widget is a logical child of the document metawidget, then it will get an binding which will reset the mouse cursor whenever it enters this widget. If the widget is not a child of the document metawidget, then the binding will not be applied because it is not required for a correct mouse cursor behavior. The new bind script would overwrite other bind scripts that you might have defined. Conversively, you'd have to be careful not to overwrite this bind script.

 EXAMPLES

The collection of metawidgets that comes with this package provides a good source of examples. Each file contains a procedure "test" that creates an instance of the related metawidget.

Nevertheless, here is closer look on a metawidget called Textframe. It is nothing more but a decorative frame with a title text embedded in the upper part.

So, in the first approach, it would consist of a frame widget and a label widget. But since the label sticks out of the decorative frame in both directions, this frame cannot be the enclosing object. Also, all external widgets that shall be put inside the textframe, would probably overlap or otherwise interfere with the label widget. Therefore, two additional, invisible frames must be defined: One is the enclosing widget, the other one is used for packing, placing or gridding all external widgets inside it. In between these two frames is the decorative, visible frame and the label.

  +-----------------+
  | %% LABEL %%%%%% |--- enclosing frame
  | % +---------+ % |
  | % | ....... | %----- decorative frame
  | % | ....... | % |
  | % | ....... |------- inner frame
  | % +---------+ % |
  | %%%%%%%%%%%%%%% |
  +-----------------+
  
  package require Metawidget

  # metawidget textframe
  # creates a frame with a title text. other widgets can be packed inside
  # the textframe. The textframe can be configured like a regular frame
  # widget. Extra options are -text for the title text and -font
  # for the title font.

  metawidget create Textframe {
    # create all internal widgets
    frame $this._frm_ -border 2 -relief groove  ;# decorative frame
    frame $this._inn_ -border 0                 ;# inner frame
    label $this._txt_ -border 0 -padx 0 -pady 0 ;# label

    # position all internal widgets.
    # only one here, the other two are packed in the -font member.
    place $this._txt_ -in $this._frm_ -x 12 -y 0 -anchor w

    # preset all metawidget options.
    -text {}
    -font [$this._txt_ cget -font]
  } {} -default _frm_

  # option -text
  # since the default widget is the decorative frame, all options for
  # the label widget must be explicitely redirected to it.

  metawidget proc Textframe -text { sText } {
    $this._txt_ configure -text $sText
    my -text $sText
  }

  # option -font
  # this is more complex. the font cannot just be set and that's it.
  # the decorative frame needs to cross the label vertically centered.
  # the new height of the label determines the padding of the decorative
  # and the inner frames, requiring them to be repacked each time the
  # font is changed.

  metawidget proc Textframe -font { sFont } {
    $this._txt_ configure -font $sFont
    my -font $sFont

    update

    set iPad [expr [winfo height $this._txt_] / 2 + 1]
    pack $this._frm_ -fill both -expand 1 -padx $iPad -pady $iPad
    pack $this._inn_ -in $this._frm_ -fill both -expand 1 -padx $iPad -pady $iPad
  }

  # command pack
  # command for packing other widgets inside the text frame. for grid and
  # place this can be implemented in a similar way. the option -in
  # $this._inn_ takes care that everything ends up in the inner frame.

  metawidget proc Textframe pack_ { sWidget args } {
    eval pack $sWidget -in $this._inn_ $args
  }

  # define metawidget options and commands with their associated procs

  metawidget option  Textframe -text -text
  metawidget option  Textframe -font -font
  metawidget command Textframe pack  pack_

  # test it

  textframe .t -text { Title }
  button .b -text Destroy -command { destroy .t }

  pack .t -fill both -expand 1
  pack .b -fill x -side bottom

  array set aFont {0 {Helv 9} 1 {Courier 15 bold}}
  array set aFrame {0 groove 1 solid}

  checkbutton .c1 -text {Other Font} -variable iC1 -command { .t config -font $aFont($iC1) }
  checkbutton .c2 -text {Other Frame} -variable iC2 -command { .t config -relief $aFrame($iC2) }

  .t pack .c1 -side top -anchor w
  .t pack .c2 -side top -anchor w
  

 INSTALLATION

mkWidgets is written in pure Tcl/Tk (version 8) and does not contain any other package dependencies.

To install, place the directory "mkWidgets1.3" in one of the directories contained in the global Tcl variable "auto_path". For a standard Tcl/Tk installation, this is commonly "c:/program files/tcl/lib" (Windows) and "/usr/local/lib" (Unix).

 CHANGES

Changes from 1.0 to 1.2

Changes from 1.2 to 1.3

 AUTHOR

Michael Kraus
mailto:michael@kraus5.de
http://mkextensions.sourceforge.net