15—
Composite Widgets
So far, we have only discussed each basic widget separately. The Perl/Tk distribution also includes several composite widgets. Composite widgets are combinations of widgets that do something specific when they are combined. Here are some examples of composite widgets:
Optionmenu
Based on menubutton widget; it allows the user to select from a list of items on the menu.
LabEntry
Based on frame widget; it is an entry widgetwith a configurable label.
Dialog
Based on toplevel widget; it displays a bitmapand a message to the user.
I chose these examples because they demonstrate a good point about composite widgets.They can be based on a widget (in this case, menubutton), on a frame that contains widgets, or ona toplevel widget that contains other widgets and is a complete window.
When I first started learning about composite widgets, I always felt like I was missingsomething. If I looked at the code out of the corner of my eye, it made sense. Yet if I looked at ithead on, I was suddenly utterly confused and wasn't sure what it was doing. The important thing toremember is that there is quite a bit that goes on behind the scenes that we take advantage ofwhen we are creating a composite widget.
My goal with this chapter isn't for you to write the most complex type of composite widget youcan think of. Simply understanding how composite widgets work is more than enough. You canbuild up slowly from there. The best thing to do is read through this chapter and then look at theexamples already included with the
distribution of Perl/Tk. The composite widgets included with the Tk module are complete, havebeen reviewed by many different people, and will do something when you run them (plus they areusually documented with pod documentation). Rather than show a do-nothing example in thischapter, I will refer you to real code.
Looking at an Example Sideways
I admit it. I like examples. They give me a starting point to come back to when I'm getting intothe nitty-gritty. Since there is quite a bit of nitty-gritty with composite widgets, we'll start simple andwork up from there.
If you look at the code for these composite widgets, the LabEntry has the smallest amount ofcode. Here is the LabEntry.pm widget code:
# Copyright (c) 1995-1997 Nick Ing-Simmons. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
# Class LabeledEntry

package Tk::LabEntry;
require Tk::Frame;
@ISA = qw(Tk::Frame);

Construct Tk::Widget 'LabEntry';

sub Populate
{
 require Tk::Entry;
 # LabeledEntry constructor.
 #
 my($cw, $args) = @_;
 $cw->SUPER::Populate ($args);
 # Advertised subwidgets: entry.
 my $e = $cw->Entry();
 $e->pack(-expand => 1, -fill => 'both');
 $cw->Advertise ('entry' => $e);
 $cw->ConfigSpecs(DEFAULT => [$e]);
 $cw->Delegates(DEFAULT => $e);
 $cw->AddScrollbars ($e) if (exists $args->{-scrollbars});
}

1;
That's the complete set of code, comments and all. You can
tell it's a frame-based composite widget because of the line@ISA = qw(Tk::Frame). We can look inProgramming Perl (O'Reilly, 1997) to find out what the @ISAarray is for: "Within each package a special array called @ISAtells Perl where else to look for a method if it can't find the method in that package."There's a lot more there about
how this implements inheritance, but I wouldn't want to overuse their words just to explain asimple concept: To have your composite widget work, you need this line in your code.* All the otherexplanation is nitty-gritty.**
Next step-how does the entry widget come into play? We know it gets createdbecause if we use a LabEntry, we see one on the screen. You'll notice there's only one subroutinein the whole file; that subroutine is called Populate. You never call itdirectly, but it does get called. The arguments to Populate are twoscalars. The first is a reference to the frame itself, and the second is a reference to a hash thatcontains all the argument pairs you would have used to create the widget. Here's an example ofcreating a LabEntry widget:
$label_entry = $mw->LabEntry(-textvariable => \$text,
                            -label => "Enter Name:",
                            -labelPack => [ -side => 'left' ])->pack();
As you glance through the code, you know an entry widget is created because you see this line: my $e = $cw->Entry(). Then a bunch of weird stuff happens with Advertising, ConfigSpecs, and Delegates. For now let's just say that these functions allow the entry widget to behave as you would expect an entry widget to behave.
The LabEntry's label is created automatically because we use the - label option when we create it. If we look back to Chapter 12, Frames, we know that if we use the -label option with a frame, a label will be created for us. So what makes this a simple composite widget is that it takes advantage of the label alreadyincluded with a frame widget.
Location of Files
When you create your own composite widgets, you create a file that has the same name(including capitalization) of your widget and has a .pm suffix. For instance, if you wantedto create a new composite widget called ListButton, you would place the code for it in a file calledListButton.pm.
In the code that uses your new widget, includeuse ListButton after theuse Tk at the top of your code, assuming you keep yourcomposite widget files (such as ListButton.pm) in the same directory as the rest of yourapplication code. If not, before any use or require statements, add:
use lib ("dirl", "dir2");
pointing to whatever directory you're using for ListButton.pm.
* You really don't need to inherit from a frame, but most people do, and itmakes things a little simpler because you have an automatic container for your composite widget.
** The nitty-gritty would involve tracing through all the Perl/Tk code to seewhat gets called where, but we don't need that level of detail here.
Creating a Composite Widget Based on Frame
There are slight differences between creating a composite widget based on a frame andcreating one based on a toplevel. I will include a short example for each to give you an idea of whatyou can do.
Assuming you're making a composite widget called MyWidget,the first five lines you absolutely must have in your new composite widget file are:
package MyWidget;
require Tk::Frame;
@ISA = qw (Tk::Frame);
Construct Tk::Widget 'MyWidget';
sub Populate
{
  ...
}
You must declare your new widget as its own package, hence thepackage MyWidget line. (If you were going to have asubdirectory for your widgets, you would use DirName::MyWidget.
The next two lines are simple: require Tk::Frame tomake sure you have loaded the information necessary to use a frame widget, and then addTk::Frame to the @ISA variable. The next line calls theConstruct method from Tk::Widget (you could also write this asTk::Widget->Construct ("MyWidget") withthe name of your widget. In this call to Construct you do not add thename of the directory in which your widget resides.
By calling Construct, you create a constructor method for yournew MyWidget widget. This allows you to create a new MyWidget by calling theMyWidget method:
$newwidget = $mw->MyWidget(...);
You are creating a composite widget based on a frame, so you need to usePopulate to create your subwidgets and do any other necessaryconfiguration.
Inside Populate
It is a good idea to add a require statement for any other widgets you want to create in your composite widget. In the LabEntry code, we saw a require Tk::Entry because LabEntrycreates an entry widget.
Populate is called with two arguments: a reference to thecomposite widget and a reference to a hash. Assign these arguments to variables so you can usethem later:
my ($cw, $args) = @_;
The next thing you should do is deal with any specific options that apply to your entirecomposite widget. Do this by getting them out of the $args hashreference and then seeing if the value was defined:
$option_value = delete $args->{"-flag"};
if (defined $option_value) {
  ...
}
Let's say you want to use the option -filename, which will get thevalue associated with the -filename option into$filename:
$filename = delete $args-> {"filename"};
if (defined $filename) {
  #Open file...
  ...
}
After dealing with all the arguments that you want to pull out directly, it is a good idea to callSUPER::Populate like this:
$cw->SUPER::Populate ($args);
Next you should create the widgets you want in your composite widget. For instance, if youwant to create a listbox with several buttons, call the appropriate methods for each one. If you wantthe user to be able to manipulate those widgets, you should callAdvertise for each one.
Calling Advertise
The Advertise method allows you to use thesubwidget method to get directly at that widget later on in the program.For example, after you create the LabEntry, you can get a reference to the entry widget:
$label_entry = $mw->LabEntry (-textvariable => \$text,
                             -label => "Enter Name:",
                             -labelPack => [ -side => 'left' ])->pack();
$entry = $label_entry->Subwidget ("entry");
$entered = $entry->get();
When you create a composite widget remember to add another call toAdvertise for each widget. For example, if you create an entry and abutton, you'll have two calls to Advertise:
$cw->Advertise('entry' => $e);
$cw->Advertise('button' => $button);
Calling Delegates
When you create a composite widget, you are essentially combining two or three widgets into one. When you invoke methods on the composite widget, you have to define what methods are actually called. Do so by using the Delegates method and sending it a reference to the widget you want to use:
$cw->Delegates (DEFAULT => $e);
All other subwidget methods of the composite will have to be accessed by using thesubwidget method and then invoking methods from there.
You can also use Delegates to call a method on a subwidget as follows:
$cw Delegates ('insert' => $scrolled_listbox,
               'delete' => $scrolled_listbox,
               DEFAULT => $e);
In this example, if the user calls $composite-insert(...)the method call will be passed along to the $scrolled_listbox-insertmethod. You cannot pass along any methods that your composite already defines. If you composite uses its own insert method, you would have to manually pass control to the subwidget yourself.
Calling ConfigSpecs
When you create a composite widget, you want to be able to callconfigure on it. You can use ConfigSpecs to do so. There are three different ways to call ConfigSpecs: create an option and a way to handle it, alias an option to another option, or specify a default widget that will handle all of the configure calls.
A simple composite widget such as LabEntry will callConfigSpecs just to set a default widget to handle all of the configuration. It called ConfigSpecs like this:
$cw->ConfigSpecs(DEFAULT => [$e]);
Specify DEFAULT as the first parameter, and then specify an anonymous list containing the widget to use as the second parameter. This way, anytime you callconfigure and use the composite widget reference, you'll be configuring the entry widget.
Creating an alias
You can use ConfigSpecs to create an alias for an option, possibly to make a short and long version of the same option. If you want to use-file and -file-name to mean exactly thesame thing, call ConfigSpecs like this:
$cw ConfigSpecs('-file' => '-filename');
Specify the alias first and the equivalent option second.
Defining options
To define an option and associate some action with it, callConfigSpecs like this:
$cw->ConfigSpecs(-newoption => [ <action>, "newOption",
                                 "NewOption", <fallbackvalue> ]);
The option you are creating the action for is listed first, and the second argument is an anonymous list consisting of four items. The first item is the action you want to take and should be"DESCENDANTS","ADVERTISED","SELF","CHILDREN","PASSIVE","METHOD","CALLBACK", or a$reference to a subwidget. The second and third items in the list have to do with the option database and can be left blank if you prefer. The fourth item is the default value of that option, usally undef or"" or whatever you want the default value of that option to be if the user doesn't specify it.
The action part of the list defines what happens. Each possible value is defined as follows:
DESCENDANTS
The configure for that option will be applied recursively to all descendants.
ADVERTISED
The configure will be applied to all advertised subwidgets.
SELF
The configure will be applied to the base widget (in this case, a frame, but the base widget can also be another composite widget).
CHILDREN
The configure will beapplied to all children.
PASSIVE
The value will be stored in$args. This is the way you would useConfigSpecs on any options that can be used at create time or bycustom methods of your composite widget.
METHOD
The method with the samename as the option will be called. For instance, if you call $cw->ConfigSpecs(-newoption => ["METHOD", "", "", undef]) and then the user uses the-newoption option, the method newoption(which you still have to define in the file somewhere) will be invoked. When you cannot define anoption with one of the other settings, you can use METHOD.
CALLBACK
Invokes a method insideyour composite widget if that option is configured or sent when the widget is created. For instance,$cw->ConfigSpecs(-myopt => ["CALLBACK", "myMethod", "MyMethod", undef]) would call the
subroutine myMethod when the option-myopt is used (also see BrowseEntry.pm'sConfigSpecs below for an example).
$reference
Forces a call to$reference->configure(-option => value) for thatoption. Usually $reference is a subwidget of the composite widget (forexample, an entry widget).
ConfigSpecs example
Here is the ConfigSpecs call from the Tk8.0 version ofBrowseEntry.pm:
$w->ConfigSpecs( -listwidth
=>
[qw/PASSIVE
listWidth
ListWidth/,
undef],
-listcmd
=>
[qw/CALLBACK
listCmd
ListCmd/,
undef],
-browscmd
=>
[qw/CALLBACK
browseCmd
BrowseCmd/,
undef],
-choices
=>
[qw/METHOD
choices
Choices/,
undef],
-state
=>
qw/METHOD
State
State
normal/],
-arrowimage
=>
 MR:3>[{-image => $b), qw/arrowImage ArrowImage/,undef],
variable
=>
"-textvariable",
   
DEFAULT
=>
[$e] );
   

As you can see, you can send multiple pairs of information toConfigSpecs. In this example, there is onePASSIVE option, two CALLBACK options,and two METHOD options. Any other calls toconfigure with different options will be directed to the subwidget$e. Take a look at the complete code to see what the methods pointedto in ConfigSpecs do.
Frame-Based Widget Review
Just to sum up, here's some pseudocode to show you how to create your own frame-basedcomposite widget:
$package NewWidget;
@ISA = qw(Tk::Frame);
Tk: :Widget->Construct('NewWidget');

sub Populate ()
{
  my ($cw, $args) = @_;

  # Handle any creation only options
  my $value = delete $args->{-option};
  if (defined $value) {
    ...
  }

  # Create any subwidgets you want to...
  $widget = $cw->Widget (...);
 
  $cw->Delegates();
  $cw->ConfigSpecs( ... );

}

sub myoption {
  ...
}

1;
Toplevel-Based Composite Widgets
There is one small difference between a composite widget based on a frame instead of atoplevel. If you want to be able to use ->new() to create your window,define InitObject instead of Populate. Mostof the composite widgets included with the Tk distribution do not do this, however. Look atColorEditor.pm and DialogBox.pm for examples of how to create a toplevel-based composite widget. All the rules for using ConfigDefaults are thesame.