The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Defining a new composite widget class

SYNOPSIS

  package Whatever;
  @ISA = qw(Tk::Frame);  # or Tk::Toplevel
         
  Tk::Widget->Construct('Whatever'); 
         
  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->Delegates(...);
   $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
  }      
         
  1;     
  __END__

DESCRIPTION

The intention behind a composite is to create a higher-level widget, sometime called a "super-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 pTk 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:

        Tk::Widget->Construct('Whatever'); 

Here, Whatever is the package name (aka the widget's class). This will define a constructor method for Whatever, 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

        $cw->InitObject(\%args);

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