scwoop

(Simple Composite Widget Object Oriented Package)

Scwoop is a composite widget (also known as mega widget) extension to the great Tk widget library. Scwoop is entirely written in Tcl using the stooop (Simple Tcl Only Object Oriented Programming) extension (Stooop provides object oriented facilities modeled after the C++ programming language while following the Tcl language philosophy).

Contents

About this document

This document contains general information, reference information and examples designed to help the programmer understand and use the scwoop Tk extension.

A working knowledge of the stooop extension is required to understand this document.

Introduction

If you have ever tried to build a composite widget using native Tk widgets, you probably found yourself drifting away from the Tk widgets implementation philosophy as you tried to add new configuration options or new features, and adding child widgets or sub-layers would rapidly become unmanageable.

Clearly, what is needed is a way to create composite widgets that have a configuration interface as close as possible to the Tk widgets interface. Moreover, a composite widget should be able to be made of Tk native widgets as well as other composite widgets, in a transparent fashion for the programmer. For example, one should be able to embed a combo-box composite widget in a file selector composite widget.

Scwoop is an attempt at answering these requirements, using the object oriented composite pattern, which insures that simple and composite objects behave the same.

Simple example

Let us start with a code sample that will give you some feeling on how scwoop works. Let us create a simple text viewer with 2 scrollbars:


source stooop.tcl
source scwoop.tcl

proc viewer::viewer {this parentPath args} composite {
    [new frame $parentPath] $args
} {
    composite::manage $this\
        [new text $widget($this,path) -state disabled -wrap none] text\
        [new scrollbar $widget($this,path)] vertical\
        [new scrollbar $widget($this,path) -orient horizontal] horizontal

    widget::configure $composite($this,text)\
        -yscrollcommand "$composite($this,vertical,path) set"\
        -xscrollcommand "$composite($this,horizontal,path) set"
    set textPath $composite($this,text,path)
    widget::configure $composite($this,vertical) -command "$textPath yview"
    widget::configure $composite($this,horizontal) -command "$textPath xview"

    grid $textPath -sticky nsew
    grid $composite($this,vertical,path) -column 1 -row 0 -sticky nsew
    grid $composite($this,horizontal,path) -row 1 -sticky nsew
    grid rowconfigure $widget($this,path) 0 -weight 1
    grid columnconfigure $widget($this,path) 0 -weight 1

    composite::complete $this
}

proc viewer::~viewer {this} {}

proc viewer::options {this} {
    return {{-file file File {}} {-text text Text {}}}
}

proc viewer::set-text {this text} {
    widget::configure $composite($this,text) -state normal
    $composite($this,text,path) delete 1.0 end
    $composite($this,text,path) insert 1.0 $text
    widget::configure $composite($this,text) -state disabled
}

proc viewer::set-file {this name} {
    if {[string length $name]==0} return
    set file [open $name]
    viewer::set-text $this [read $file]
    close $file
}

set view [new viewer . -text "please wait..."]
pack $widget($view,path) -fill both -expand 1
update
widget::configure $view -file /etc/termcap

First we define the viewer constructor. The viewer class must derive from the composite class. The constructor arguments are the Tk parent widget path and optional configuration options, such as "-text foo". The composite base class arguments are the viewer base widget object, in this case a frame, followed by the configuration options. Note that we use the frame class which is a simple wrapper around the native Tk widget frame command.

Next, we let the composite layer manage 1 text object and 2 scrollbar objects. Note that we assign the text, vertical and horizontal names to those widgets, names that we can later use to retrieve their object identifier and Tk path (from here on, the word path refers to the Tk native widget pathname).

Then we configure the text and scrollbar widgets so that they actually work together. Note the use of the widget class configure method which provides a Tk configure command like interface (there is also a cget method). Also note how we use the widget object identifier (such as $composite($this,text)) for configuration purposes and the widget path (such as $composite($this,vertical,path)) for other purposes.

Next, we arrange the widgets using the grid command on the different widget paths.

Finally, we tell the composite layer that it is complete, meaning that no other child widgets will be managed by it.

In this case we built the composite viewer using Tk native widget wrapper objects (text and scrollbar classes), but we could have used any other composite widget class just as easily (for example, the text widget could have been replaced by a htmlText class object for HTML document viewing).

The viewer destructor does nothing as the composite layer takes care of destroying the widgets that it manages.

The options method is mandatory (it is declared as a pure virtual at the composite level). It must return a list describing all of the available options, very much like the result of a Tk widget configure command with no arguments, except that the current values are omitted (see Tk_configureInfo manual entry for more information).

In this case, we chose to handle only the -text and -file switches for demonstration purposes (a real implementation should probably also handle options such as -background, -font, -foreground, ...).

For each option that the composite widget implements, there must be a corresponding set-option method defined at the derived class (here viewer) level. The set-option methods are automatically called as the composite widget is configured. For example, the set-file procedure is invoked with /etc/termcap as name argument when the last line of the example script is evaluated.

After the set-text and set-file methods implementation, we start by creating a viewer object using the default Tk toplevel as parent. We pass a -text configuration option at construction time, just like we could do with a native Tk widget. Next we pack the widget using its path. Finally, we display a text file using the -file configuration option switch, again just like we would proceed to configure a native Tk widget.

Class hierarchy

                                widget
            ______________________|__________ _ _ ________
           |         |      |      |     |         |      |
       composite   button canvas entry frame  ...  text toplevel
   ________|______________ _ _
  |       |          |
viewer combobox fileselector ...

The composite class and the Tk widgets wrapper classes (such as button, label, ...) all derive from the widget base class. Composite widget implementation classes (such as viewer, combobox, ...) derive from the composite class.

Widget class

The widget base class provides the configuration interface and the path for both native widget wrappers and composite widgets:

widget

The widget class should never be instantiated, that is no widget object should be created directly with the new operator.

The configure member procedure is used to query or modify the configuration options of the widget object, very much like a native Tk widget is configured. For example:


$ wish
% source viewer.tcl
% set view [new viewer . -text "please wait..."]
1
% widget::configure $view
{-file file File {}} {-text text Text {} please wait...}
% widget::configure $view -file /etc/termcap
% widget::configure $view -file
-file file File {} /etc/termcap
%

Except for the stooop object specific syntax, the configure member procedure behaves exactly like any native Tk widget configure command. In the case of any Tk widget wrapper class, the valid accessible options are identical to those of the corresponding native Tk widget. For the composite widgets, the valid options are specified in the options procedure (see below).

The cget member procedure is used to query an option current value, as shown below:


$ wish
% source stooop.tcl
% source scwoop.tcl
% set l [new label .]
1
% widget::cget $l -font
-Adobe-Helvetica-Bold-R-Normal--*-120-*-*-*-*-*-*
%

Except for the stooop object specific syntax, the cget member procedure behaves exactly like any native Tk widget cget command.

The path data member is the Tk pathname for the widget object. It is read-only and is used with Tk geometry managers (such as pack, grid, ...) and other Tk commands, such as bind, ...


$ wish
% set l [new label .]
1
% pack $widget($l,path)
%

Tk widgets wrapper classes

For each Tk native widget command, there is a corresponding scwoop wrapper class that accepts exactly the same options as the original Tk widget.

Before the optional configuration options, the constructor takes the Tk parent path as first parameter, as in:


$ wish
% source stooop.tcl
% source scwoop.tcl
% toplevel .top
% set l [new frame .top -relief sunken -background white]
1
%

As with all scwoop widgets, configuration modification or query is done through the configure and cget member procedures.

Composite class

The composite class serves as base class for the user built composite widgets. It provides facilities for managing child widgets and options through a simple to use interface.

The composite class constructor takes the composite widget base widget as first parameter, followed by the usual optional configuration option / value pairs. The base widget is usually a frame or a toplevel class object that serves as parent of all the child widgets that make the composite widget. For example:


proc labelledEntry::labelledEntry {this parentPath args} composite {
    [new frame $parentPath -borderwidth 0] $args
} {
    composite::manage $this\
        [new label $widget($this,path)] label\
        [new entry $widget($this,path)] entry
    ...
}

The frame parent is accessed using the widget layer path data member.

Note that the base widget can be of any scwoop class. For example, it could be a scrollableArea composite widget.

The composite class members available to the widget programmer are:

composite

The manage procedure is used to register one or more child widgets at the composite layer level. It takes one or more widget / name pairs as argument. The name associated with each widget object identifier can be any name as long as it is unique. It is later used to retrieve the corresponding child widget object identifier and path, as in the following example:


proc passwordEntry::passwordEntry {this parentPath args} composite {
    [new frame $parentPath -borderwidth 0] $args
} {
    composite::manage $this\
        [new label $widget($this,path)] string\
        [new entry $widget($this,path)] input
    pack $composite($this,string,path) $composite($this,input,path)
    widget::configure $composite($this,string) -text "password :"
    widget::configure $composite($this,input) -show *
    ...
}
After managing a label and an entry widget, we pack them using theirs paths, then configure them using their object identifiers.

The complete procedure is used to tell the composite layer that no more child widgets will be managed, meaning that all the elements necessary to build the composite widget have been created. At this time, the initial configuration of the composite widget occurs, using default option values (see options procedure) eventually overridden by the construction time options, passed to the new operator. The complete procedure must be called once only, usually around or at the end of the composite widget constructor, as in the following example:


proc passwordEntry::passwordEntry {this parentPath args} composite {
    [new frame $parentPath -borderwidth 0] $args
} {
    composite::manage $this\
        [new label $widget($this,path)] string\
        [new entry $widget($this,path)] input
    ...
    composite::complete $this
}

The options procedure must return the configuration description for all options that the composite widget will accept. It is a pure virtual member procedure and therefore its implementation is mandatory in the derived class layer. The format of the list of configuration option descriptions is similar to the format of the list returned by the native Tk widget configure command with no arguments, except that the current value must be omitted in each description list member. For example:


proc arrowButton::options {this} {
    return {\
        {-activebackground activeBackground Foreground #ececec}\
        {-background background Background #d9d9d9}\
        {-command command Command {}}\
        {-highlightbackground highlightBackground HighlightBackground #d9d9d9}\
        {-highlightcolor highlightColor HighlightColor Black}\
        {-state state State normal}\
        {-troughcolor troughColor Background #c3c3c3}\
        {-width width Width 15}\
    }
}

The options default values are important as they are used for the composite widget initial configuration that occurs when the widget is complete (see complete procedure).

The set-options procedures may be viewed as dynamic pure virtual functions. There must be one implementation per supported option, as returned by the options procedure. For example:


proc arrowButton::options {this} {
    return {\
        ...
        {-background background Background #d9d9d9}\
        ...
    }
}

proc arrowButton::set-background {this value} {
    ...
}

Since the -background option was listed in the options procedure, a set-background procedure implementation is provided, which of course would proceed to set the background of some or all of the widgets that make the arrowButton composite widget.

As you add a supported option in the list returned by the options procedure, the corresponding set-option procedure will be called as soon as the composite widget is complete, which occurs when the composite level complete procedure is called. This way, and as a side effect, an automatic test of all set-option procedures is done with default values as specified in the options procedure, as long as they were not overridden with construction time options. For example:


proc arrowButton::arrowButton {this parentPath args} composite {
    ...
} {
    ...
    composite::complete $this
}

...

proc arrowButton::options {this} {
    return {\
        {-background background Background #d9d9d9}\
        {-command command Command {}}\
        {-state state State normal}\
        {-width width Width 15}\
    }
}

...

proc arrowButton::set-background {this value} {
    ...
}

proc arrowButton::set-command {this value} {
    ...
}

proc arrowButton::set-state {this value} {
    ...
}
proc arrowButton::set-width {this value} {
    ...
}

new arrowButton .

In this case, a new arrowButton is created with no options, which causes the arrowButton constructor to be called, which in turns calls the composite level complete procedure after all child components have been created. At this point, the set-background procedure is called with its default value of #d9d9d9 as parameter, followed by the set-command call with {} value, set-state with normal value and finally with set-width with 15 as parameter.

The composite layer checks that an option is valid (that is, listed in the options procedure) but obviously does not check the validity of the value passed to the set-option procedure, which must throw an error (most likely using the Tcl error command) if the value is invalid.

The composite layer also keeps track of the options current values, so that a set-option procedure is called only when the corresponding option value passed as parameter is different from the current value (see -option data members description). There is one exception to this rule, which occurs when the complete composite layer procedure is called. In this case, all the options are configured with their default values overridden by the construction time option values.

The try procedure provides a very simple way of supporting an option. It will try to apply the option and its value (passed as parameter) to all the components of the composite widget (base and child widgets). However, no error checking is done since all errors are caught in the try procedure implementation. For example, setting the background color of all elements of a composite widget can be done as follows:


proc passwordEntry::set-background {this value} {
    composite::try $this -background $value
}

The base data member is the base widget object identifier, the widget that is passed to the composite constructor when the composite widget is created, as in the following example:


proc viewer::viewer {this parentPath args} composite {
    [new frame $parentPath] $args
} {
    ...
}

...

proc viewer::set-relief {this value} {
    widget::configure $composite($this,base) -relief $value
}

In this case, the set-relief procedure configures the frame base widget of the viewer composite widget. Reminder: the composite base widget path can be accessed using the path member at the widget class layer.

There is one child object identifier data member for each child widget that is managed by the composite widget. The data member name used is the one passed to the manage procedure when a child widget is managed. It is a read-only data member. For example:


proc viewer::viewer {this parentPath args} composite {
    [new frame $parentPath] $args
} {
    composite::manage $this\
        [new text $widget($this,path) -state disabled -wrap none] text\
        [new scrollbar $widget($this,path)] vertical\
        [new scrollbar $widget($this,path) -orient horizontal] horizontal
    ...
}

...

proc viewer::set-background {this value} {
    widget::configure $composite($this,text) -background $value
    widget::configure $composite($this,vertical) -background $value
    widget::configure $composite($this,horizontal) -background $value
}

Here, when the background color is changed, the text, vertical and horizontal data members are used to change the corresponding widgets background color.

There is also one child,path widget Tk path data member for each child widget that is managed by the composite widget. It is a read-only data member. The data member first part (before the comma) used is the name passed to the manage procedure when a child widget is managed. The second part (after the comma) is always path. This data member is used for non-configuration Tk commands, as in the following example:


proc scrollList::scrollList {this parentPath args} composite {
    [new button $parentPath -borderwidth 0 -state disabled -takefocus 0] $args
} {
    composite::manage $this\
        [new listbox $widget($this,path) -width 0 -highlightthickness 0] listbox
    ...
    bind $widget($this,path) <FocusIn> "focus $composite($this,listbox,path)"
    ...
}

Here, we pass the focus on to the listbox child widget when the composite widget gets the focus. The listbox Tk widget wrapper path is used for this purpose.

The -option data member is the option current value. There is one for each option listed in the options procedure. It is a read-only value which the composite layer checks against when an option is changed. It is rarely used at the composite derived layer, except in the few cases, such as in the following example:


...

proc arrowButton::options {this} {
    return {\
        ...
        {-command command Command {}}\
        ...
    }
}

proc arrowButton::set-command {this value} {}

proc arrowButton::invokeCommand {this} {
    eval $composite($this,-command)
}

In this case, the command to be executed when the button is activated is stored at the composite layer level (this is why the set-command has nothing to do) and later retrieved in the invokeCommand method, itself invoked by appropriate bindings, for example.

Frequently Asked Questions

There are many options, such as background, foreground, ... that are fastidious to implement. Isn't there an easier way?

Yes. For those options that are not critical, such as color options, the composite layer try procedure can be used. It will simply try the option and its value on all the components of the composite widget. For example, non-critical options handling could be implemented as follows:


foreach option {-background -highlightbackground -highlightcolor -troughcolor} {
    proc arrowButton::set$option {this value} "
        composite::try \$this $option \$value
    "
}

I want my composite widget to handle the focus as a whole, how do I do it?

Use a button as base widget. By setting its -state option to disabled and its -takefocus option to 0, it will react properly when focus is set through keyboard traversal, that is it will show a highlighted border and pass the focus to the next widget in the focus order, probably a child widget within your composite widget.

In order to completely handle the focus, especially when it is set directly through the focus command on the composite widget base, a binding needs to be setup so the focus is passed to the child widget that you chose. For example, the scrollList implementation is as follows:


proc scrollList::scrollList {this parentPath args} composite {
    [new button $parentPath -borderwidth 0 -state disabled -takefocus 0] $args
} {
    ...
    bind $widget($this,path) <FocusIn> "focus $composite($this,listbox,path)"
    ...
}

I want to create a dialog box composite widget, is it possible to use a toplevel widget as base widget?

Yes. There is absolutely no restriction for the class of your composite base widget. It can be a Tk native widget wrapper, such as a toplevel or another composite widget that you created.

I noticed that the Tk commands bind, pack, ... act on the widget object path instead of the widget identifier, as do the configure and cget methods. Why this difference?

The configure and cget are widget commands at the Tk level, that is they actually are an argument to the native Tk widget command, so they map nicely to a class method, whereas the bind, pack, ... are full Tk commands that either do not always take a Tk widget as argument or may take more than one Tk widget as arguments.

How do I implement a default value for an option?

Easy. Just specify it in the corresponding option list in the options procedure and it will be automatically used when the complete procedure is called.

The Tk native widget wrappers or the composite widgets implementations take a Tk widget path as construction argument. Why not a widget object identifier?

Otherwise it would be impossible to use a scwoop widget in a Tk native widget only environment. It would be impossible to find a widget object as parent.

Does scwoop provide child widget access for configuration?

No. It could but the responsability is left to the composite widget designer, as there is no guarantee that child widget access should be allowed in all cases. However, it is very easy to implement a child widget configuration method, as the following example shows:


proc scrollList::scrollList {this parentPath args} composite {
    ...
} {
    ...
    composite::manage $this\
        [new listbox $widget($this,path) -width 0 -highlightthickness 0] listbox
    ...
}

proc scrollList::configure-listbox {this args} {
    eval widget::configure $composite($this,listbox) $args
}

set l [new scrollList .]
scrollList::configure-listbox $l -selectmode extended -height 5

How do I choose the default values in the options procedure?

Just use Tk native widgets default values when the option is supported by Tk, but be aware that for some options, notably color related ones, default values depend on the platform (Windows, UNIX, Mac Intosh, ...).

The Tk option database command is not supported, why?

Support is planned in the future. For the time being, as the option Tk command is not supported on Windows platforms and may change in the future (see Tk 4.1 release ToDo file), I feel there are good reasons to wait a bit.

Send your comments, complaints, ... to

Jean-Luc Fontaine