Defining a new - composite widget class


  package Whatever;
  @ISA = qw(Tk::Frame);  # or Tk::Toplevel
  sub Populate 
   my ($cw,$args) = @_;
   my $flag = delete $args->{-flag};
   if (defined $flag)
     # handle -flag => xxx which can only be done at create time
     # the delete above ensures that new() does not try and
     # do $cw->configure(-flag => xxx)
   $w = $cw->Component(...);
   $cw->ConfigSpecs('-cursor' =>     [SELF,cursor,Cursor,undef],
                    '-something'  => [METHOD,dbName,dbClass,'default'],
                    '-text'       => [$label,dbName,dbClass,'default'],
                    '-heading'    => [Tk::Config->new($head,-text),heading,Heading,'My Heading']
  sub something
   my ($cw,$value) = @_;
   if (@_ > 1)
     # set it 
   return # current value


The intention behind a composite is to create a higher-level widget, sometimes called a "super-widget" or "meta-widget". Most often, a composite will be built upon other widgets by using them, as opposed to specializing on them. For example, the supplied composite widget LabEntry is made of an Entry and a Label; it is neither a kind-of Label nor is it a kind-of Entry.

Most of the work of a composite widget consist in creating subwidgets, arrange to dispatch configure options to the proper subwidgets and manage composite-specific configure options.

Composite widget creation
Since perl/Tk is heavilly using an object-oriented approach, it is no suprise that creating a composite goes through a new() method. However, the composite does not normally define a new() method itself: it is usually sufficient to simply inherit it from Tk::Widget.

This is what happens when the composite use

@ISA = qw(Tk::Frame); # or Tk::Toplevel
to specify its inheritance chain. To complete the initialisation of the widget, it must call the Construct method from class Widget. That method accepts the name of the new class to create, i.e. the package name of your composite widget:

Here, Whatever is the package name (aka the widget's class). This will define a constructor method for Whatever, normally named after the widget's class. Instanciating that composite in client code would the look like:

$top = MainWindow->new(); # Creates a top-level main window

$cw = $top->Whatever(); # Creates an instance of composite widget
# 'Whatever'
Whenever a composite is instanciated in client code, Tk::Widget::new() will be invoked via the widget's class constructor. That new method will call

where %args is the arguments passed to the widget's constructor. Note that InitObject receives a reference to the hash array containing all arguments.

For composite widgets that needs an underlying frame, InitObject will typically be inherited from Tk::Frame, that is, no method of this name will appear in the composite package. For composites that don't need a frame, InitObject will typically be defined in the composite class (package). Compare the LabEntry composite with Optionmenu: the former is Frame based while the latter is Widget based.

In Frame based composites, Tk::Frame::InitObject() will call Populate(), which should be defined to create the characteristic subwidgets of the class.

Widget based composites don't need an extra Populate layer; they typically have their own InitObject method that will create subwidgets.

Creating Subwidgets
Subwidget creation happens usually in Populate() (Frame based) or InitObject() (Widget based). The composite usually calls the subwidget's constructor method either directly, for "private" subwidgets, or indirectly through the Component method for subwidgets that should be advertised to clients.

Populate may call Delegates to direct calls to methods of chosen subwidgets. For simple composites, typically most if not all methods are directed to a single subwidget - e.g. ScrListbox directs all methods to the core Listbox so that $composite->get\|(...) calls $listbox->get\|(...).

Further steps for Frame based composites

Populate should also call ConfigSpecs() to specify the way that configure-like options should be handled in the composite. Once Populate returns, method Tk::Frame::ConfigDefault walks through the ConfigSpecs entries and populates %$args hash with defaults for options from X resources (.Xdefaults, etc).

When InitObject() returns to Tk::Widget::new(), a call to $cw->configure\|(%$args) is made which sets *all* the options.