Creating Custom Widgets
by Steve Lidie
|
Pages: 1, 2
Since we are a Tk::DialogBox widget at heart, set up a
default Cancel button to ensure our superclass' Populate()
has a chance to process the option list, then withdraw the window
until it's shown.
$args->{'-buttons'} = ['Cancel'] unless defined $args->{'-buttons'};
$self->SUPER::Populate($args);
$self->withdraw;
Create the canvas with its photo, and store the canvas reference and the image id as instance variables. We'll need access to both later.
$self->{can} = $self->Canvas(
-width => $cray_w,
-height => $cray_h,
)->pack;
$self->{iid} = $self->{can}->createImage(0, 0,
-anchor => 'nw',
-image => $crayons,
);
Define the canvas callback that fetches and returns an RGB
triplet. The CanvasBind() method operates on the entire
canvas, unlike the canvas' bind() method that operates on
an individual canvas tag or id.
$self->{can}->CanvasBind('<buttonrelease-1>' => [\&pick_color, $self]);
|
Related Reading |
Next, create the tiny transparent trapezoids that cover the tip
of the 64 crayons, and define the balloon help. When specifying balloon
help for one or more canvas items, the balloon widget expects its -msg
option to be a reference to a hash, where the hash keys are canvas tags or
ids, and the hash values are the balloon help text.
So, we first create an instance variable that references an empty
anonymous hash, then invoke the private method
make_balloon_items() to do the dirty work. The method
creates the canvas polygon items and populates the hash pointed to by
$self->{col}. Then, we create the balloon widget, and
attach the canvas and help messages. The ballon text appears next to
the cursor.
$self->{col} = {}; # anonymous hash indexes colors by id
$self->make_balloon_items;
$self->{bal} = $self->Balloon;
$self->{bal}->attach($self->{can},
-balloonposition => 'mouse',
-msg => $self->{col},
);
} # end Populate
Here's the class private method make_balloon_items(), which simply
makes 64 calls to make_poly().
The 64-crayon Crayola box in divided into 4 sections of 16 crayons each. Each section contains two rows of eight crayons. These subroutine calls create each section, starting with the section's background row, followed by the section's foreground row.
We create the polygons items from back to front so that the canvas stacking order is back to front. This ensures that the balloon help of foreground polygons items takes precedence over background items.
For obvious brevity, most of the make_poly() calls have
been removed.
sub make_balloon_items {
my ($self) = @_;
# 16 northwest crayons.
$self->make_poly(132, 8, 'red');
# 16 northeast crayons.
$self->make_poly(306, 61, 'gray');
# 16 southwest crayons.
$self->make_poly(107, 97, 'brick red');
# 16 southeast crayons.
$self->make_poly(270, 157, 'tumbleweed');
} # end make_balloon_items
Given the coordinates of the point of a crayon, the class private
method make_poly() creates a transparent polygon over the
tip so we can attach a balloon message to it. The message is the
crayon's color, and is stored in the hash pointed to by
$self->{col}, indexed by polygon canvas id.
The transparent stipple is important, as it allows balloon events to be seen. The fill color is irrelevant; we just need something to fill the polygon items so events are registered.
If we remove the stipple, then the polygon items covering the crayon tips become visible, as shown in Figure 2.
sub make_poly {
my ($self, $x, $y, $color) = @_;
my $id = $self->{can}->createPolygon(
$x-3, $y, $x+3, $y, $x+11, $y+38, $x-11, $y+38, $x-3, $y,
-fill => 'yellow',
-stipple => 'transparent',
);
$self->{col}->{$id} = $color;
} # end make_poly
Figure 2. |
Subroutine pick_color() is our last class private method. It
demonstrates a rather dubious object oriented programming technique -
meddling with the internals of its superclass! But we do this out of
necessity, as a workaround for the "balloons do not work with a grab"
bug.
We want to override Tk::DialogBox::Show, so we need to know what its
waitVariable() is waiting for. It's this variable that the dialog
buttons set when we click on them, and it turns out to be
$self->{'selected_button'}.
We make pick_color() set the same variable when returning
a pixel's RGB values, thus unblocking the waitVariable()
and returning the RGB data to the user.
In case you're interested, early-on in the coding I determined the coordinates of each crayon's point by printing $x and $y in this callback.
sub pick_color {
my ($canvas, $self) = @_;
my ($x, $y) = ($Tk::event->x, $Tk::event->y);
my $i = $canvas->itemcget($self->{iid}, -image);
$self->{'selected_button'} = $i->get($x, $y);
} # end pick_color
Here is our only class public method, Show(). We can't
use the standard DialogBox Show() method because the grab
interferes with balloon help. So we roll our own, forgoing the modal
approach. Control passes from waitVariable() in one of
two ways: 1) a color is selected (see pick_color()
above), or, 2), the Cancel button is activated.
sub Show {
my ($self) = @_;
$self->Popup;
$self->waitVariable(\$self->{'selected_button'});
$self->withdraw;
return $self->{'selected_button'};
} # end Show
And that's it. Until next time ... use Tk;
You can download the class module, associated .GIF file and a test program that uses the new class.
O'Reilly & Associates recently released (January 2002) Mastering Perl/Tk.
Sample Chapter 15, Anatomy of the MainLoop, is available free online.
You can also look at the Table of Contents, the Index, and the Full Description of the book.
For more information, or to order the book, click here.


