Getting Started with Perl/Tk
By: Stephen O. Lidie
A favorite Perl module of mine is the Tk widget toolkit. With this
collection of widgets, you can write sophisticated graphical
applications, or just jazz-up your old command line program. Oh, you
don't know what a widget is? How about a gadget, or a control?
Actually, they're all just names that refer to a graphical element,
such as this button:
You don't often see a naked button - usually it's embedded in a
window, as part of a complete program. When you push the button it
reacts and does something. The point here is to realize that widgets
are just discrete entities that you arrange to make a whole graphical
application. Tk has lots of other widgets, like labels, menus,
dialogs, scrollbars, listboxes, and so on, as well as geometry
managers to arrange them as you desire. Of course, there's more to a
graphical application than just presenting a pretty window - it must
interact with a life form (humanoid only, currently). As we'll see,
things known as events help mediate the application-human conversation.
Tk was developed by John Ousterhout, originally for use with his
scripting language, Tcl. That's where I first discovered it, and I
found I could write my GUIs considerably faster that ever before. The
Tk programs were also one tenth the size, had fewer bugs, and were
more robust than an Xlib version.
I was hooked on Tk, but I faced a dilemma, because I was also hooked on Perl!
I sometimes found myself writing a program in both Tcl/Tk (for the GUI) and
Perl (for the systems stuff) - a sad state of affairs, indeed. Then Nick
Ing-Simmons arrived on the scene, and, after a lot of work, molded Tk version
4 into a Perl extension. But Nick didn't stop there, he expanded Perl/Tk by
adding new widgets, incorporating Tix and user contributed widgets, and
making the package object oriented. Throughout the years Perl/Tk has been
updated, tweaked and tuned, and now corresponds to Tcl/Tk version 8.0.
The Perl/Tk version I base this article on is Perl 5.005_02 and Tk800.013,
both available on the CPAN (for Unix), or http://www.ActiveState.com (for Win32).
CPAN stands for Comprehensive Perl Archive Network, 100 or so machines
spread around the globe, each a repository of everything that's Perl.
I presume you have Perl installed and running somewhere, and, if it's
not at least version 5.005_02, you'll update it. I do not presume you
have Perl/Tk, so in a nutshell, here's how you install it.
Assuming you're on Unix, use a web browser and go to
http://www.perl.com and click on the
"Nearest CPAN Site" link. Then link to "authors", "Nick Ing-Simmons".
Grab the latest tar file, presumably "Tk800.013.tar.gz" and save it
somewhere. Like all well behaved Perl modules, the following steps should
suffice for installation:
gunzip Tk800.013.tar.gz
tar -xvf Tk800.013.tar
cd Tk800.013
perl Makefile.PL
make
make test
make install
If you do not have the proper permissions to install Tk, you'll have
to fetch and install Perl in your own file space before you can
install Tk.
For Win32 users, install the latest Perl from the ActiveState site
mentioned above. Then, at a DOS prompt, run PPM (the Perl Package
Manager program), and install Tk:
ppm
PPM>install Tk
PPM>quit
To verify that Tk was installed properly, run the "widget" program from
a Unix or DOS prompt. This program demonstrates most of the Perl/Tk
widgets, and lets you view the code, modify it, and rerun
demonstrations with your changes.
Writing a GUI is fundamentally different than the classical line mode program
- you use a technique called event driven programming. Essentially, it's
your job to write subroutines, or callbacks, that are automatically called
when certain events occur. Interesting events might be a button press, a
file becoming readable, the expiration of a timer, or typing a keyboard
character, and it's the subroutine's job to handle the event appropriately.
To illustrate the concepts, we'll examine the typical "Hello World" program -
actually, I'll show two versions. Here's the first, which produced Figure 2:
1 #!/usr/local/bin/perl -w
2 #
3 # hiworld1 - "Hello World" with a Label and explicit event binding.
4
5 use Tk;
6 use strict;
7
8 my $mw = MainWindow->new;
9
10 my $label = $mw->Label(-text => 'Hi World');
11 $label->pack;
12 $label->bind('<Button-1>' => sub {$mw->destroy});
13
14 MainLoop;
Note lines 1 and 6, where we specify run with warning enabled (-w) and
strict programming style (use strict). I highly recommend you too
write all your Perl/Tk program this way - it will find subtle (and
sometimes stupid!) errors.
Line 3 is a comment, telling us that the program displays a Label
widget. Labels (usually) draw simple text strings, in a particular
font, size and color. Labels are also the simplest Tk widget - about
the only other thing you can do with them is create explicit event
bindings. More on this shortly.
Line 5 imports all the Tk widgets, definitions, variables and
subroutines.
Line 8 is required by all Perl/Tk programs - it opens the program's first
(main) window, the container within which (most) all your other widgets are
arranged. This widget arrangement, or layout, is performed by a geometry
manager. The geometry manager determines the actual location and size of a
widget in the main window, relative to all other widgets. A widget never
appears in the main window until it has been managed by a geometry
manager.
Line 8 also demonstrates the OO (object oriented) nature of Perl/Tk. Here
the scalar variable $mw is initialized with an object reference to a new
instance of a MainWindow. We'll learn some basic OO concepts as we go
along, but you needn't be an OO guru to use Perl/Tk, as long as you're not
building new Perl/Tk widgets -and we aren't.
Line 10 creates our first widget, an instance of a Label, which
displays the string "Hi World". (There may be many widgets of
class Label in a program, and each may have a different text
string, font, font size and color, but any particular one is known as
an instance.) This line demonstrates typical Perl/Tk widget creation
syntax: an object ($mw) has inherent operations called methods -
Label() is one, a constructor method, because is constructs a new
instance of a label widget. The methods are really subroutines that
may have parameters, specified as keyword/value pairs. All keywords,
like -text, start with a dash. Note that we use the => operator as
the argument list separator, not to be confused with the -> operator
used to invoke a method on an object.
Line 11 tells the pack() geometry manager to manage the label in the
main window, which then displays it (Figure 2).
Line 12 is how we exit the program. It defines an anonymous subroutine
that sends a "destroy" message to the object $mw, the main window. destroy()
is a method which frees all memory associated with a widget and removes it
from the display. When $mw is destroyed, everything contained in it is
destroyed as well, and the program ends.
Line 12 uses the bind() method to associate an event with a
subroutine. Subroutines of this type are termed callbacks, because
the code is called back when the event in question (here, pressing
Button-1 on the mouse) occurs.
Line 14 kick-starts the program by entering the Tk event loop. The
event loop dispatches events to callbacks (yours and Tk's), and runs
continuously until the main window goes away. Forget this statement
and your Perl/Tk program thinks a while, then exits without displaying
anything!
In summary, here are the characteristics of most Perl/Tk programs:
- A use Tk statement which imports the base Tk definitions.
- A MainWindow that contains other application widgets.
- Widget creation commands to populate the main window.
- Binding and callback creation commands.
- Geometry management commands to arrange and display widgets.
- A MainLoop() command to begin program execution.
Now let's look at a better version of the previous "Hello World"
program; this new program generated Figure 3 . Compare Figures 2 and
3 and note the visual difference. There area several other subtle
differences, as we'll see shortly.
1 #!/usr/local/bin/perl -w
2 #
3 # hiworld - "Hello World" with a Button and implicit class bindings.
4
5 use Tk;
6 use strict;
7
8 my $mw = MainWindow->new;
9
10 my $button = $mw->Button(-text => 'Hi World', -command => \&exit);
11 $button->pack;
12
13 MainLoop;
Line 3 reminds us that the program displays a Button widget rather
than a Label, and that the button has "implicit class bindings". In
this case, the button has a builtin binding for a event, so
we don't have to create it manually as we did for the label. This
binding applies to every instance of class Button - it's one feature
that differentiates labels from buttons.
A button also has a three dimensional appearance due of its
"raised" relief. If you'd like to make a label look like a button,
just change its -relief option using the ubiquitous configure() method:
$label->configure(-relief => 'raised');
But adding a relief and a button binding still doesn't turn a label into
a button because there are even more class bindings inherited by a
button instance. Move the cursor over a button and watch what happens
- it becomes highlighted. This is because the button has an
Enter binding, which invokes a builtin Tk callback that lightens the
button. Similarly, the button has a Leave binding to restore the
widget's appearance when the cursor leaves. And finally, there are
bindings to change the button's relief to "sunken" when selected, and
then back to "raised".
Line 10 creates the button, and now we know some of things that go on
behind to scene to make our programming life easier. The -command
argument automatically links the code reference \&exit to a Button-1
event.
It's time for something more complex - how about an application
skeleton? By that I mean a main window with a menubar across the
top, and File and Help menubuttons, which you can use as a basis for
your real applications. Along the way we'll visit the Dialog widget,
icons, and see some window manager commands. To make things really
easy I've included a tiny Perl program named "tkskel" that will
generate our mini application. It, and all other sample programs, are
available here
To use "tksel", type something like "tkskel frog", and a complete Perl/Tk
program named "frog" is created. Here's the resulting code:
1 #!/usr/local/bin/perl -w
2 #
3 # frog, Sun Apr 11 18:01:41 1999
4
5 use 5.005;
6 use Tk 8.0;
7 use Tk::widgets qw/Dialog/;
8 use subs qw/build_menubar fini init/;
9 use vars qw/$MW $VERSION/;
10 use strict;
11
12 $MW = MainWindow->new;
13 init;
14 MainLoop;
15
16 sub build_menubar {
17
18 # Create the menubar and File and Quit menubuttons. Note
19 # that the cascade's menu widget is automatically created.
20
21 my $menubar = $MW->Menu;
22 $MW->configure(-menu => $menubar);
23 my $file = $menubar->cascade(-label => '~File');
24 my $help = $menubar->cascade(-label => '~Help', -tearoff => 0);
25
26 # Create the menuitems for each menu. First, the File menu item.
27
28 $file->command(-label => "Quit", -command => \&fini);
29
30 # Finally, the Help menuitems.
31
32 $help->command(-label => 'Version');
33 $help->separator;
34 $help->command(-label => 'About');
35
36 my $ver_dialog = $MW->Dialog(-title => 'frog Version',
37 -text => "frog\n\nVersion $VERSION",
38 -buttons => ['OK'],
39 -bitmap => 'info');
40 my $about_dialog = $MW->Dialog(-title => 'About frog',
41 -text => 'About frog',
42 -buttons => ['OK']);
43 my $menu = $help->cget('-menu');
44 $menu->entryconfigure('Version', -command => [$ver_dialog => 'Show']);
45 $menu->entryconfigure('About', -command => [$about_dialog => 'Show']);
46
47 $menubar; # return the menubar
48
49 } # end build_menubar
50
51 sub fini {
52
53 exit;
54
55 } # end fini
56
57 sub init {
58
59 $VERSION = '1.0';
60
61 $MW->title("frog $VERSION");
62 $MW->iconname('frog');
63 my $xpm = $MW->Photo(-file => "skull.xpm", -format => 'xpm');
64 $MW->Icon(-image => $xpm); # full color icon picture
65
66 my $menubar = build_menubar;
67 my $frame = $MW->Frame(qw/-width 300 -height 50 -background cyan/)->pack;
68
69 } # end init
With the exception of line 7, I suspect lines 1 through 14 are
self-explanatory. Line 7 is how we import Tk widgets that are not
"core" widgets. Here are the 15 standard widgets automatically
imported when we say "use Tk": Button, Canvas, Checkbutton, Entry,
Frame, Label, Listbox, Menu, Menubutton, Message, Radiobutton, Scale,
Scrollbar, Text, and Toplevel.
Lines 16 through 49 build the menubar, but I'll defer most of that
discussion for another time. For now, it remains a task for you,
gentle reader, to figure out!
Line 61 is a window manager command - it defines the text shown at
the top of the window's title bar.
Some window managers support iconization, and line 62 simply gives our
icon a name. Likewise, line 64 changes the default appearance of the
icon to a colored image of our own design. To do that we first need
to create a Tk image object, which is what line 63 does. The image is
a small XPM file of, fittingly, a skull, grabbed from
http://www.skellramics.com
and used with their permission. Here's what "frog" looks like iconified:
Lines 66 and 67 complete the creation of the GUI elements. Line 66
creates the menubar and related components and saves its widget
reference in $menubar, just in case we need to (re)configure it later.
Line 67 creates a colored frame and packs it after the menubar. I
don't cover geometry management here, suffice it to say that by
default the packer arranges widgets top down in the main window, like
this:
Notice that the Help menubutton has been pressed, a menu posted,
and the Version menu item highlighted. Pressing Version pops up
this modal dialog:
Notice that the dialog is independent of our main window - it's built
from a special widget called a toplevel, which is similar to a main
window. And like a main window, you can pack other widgets inside a
toplevel. When we press OK the dialog is withdrawn from the
display - but it's not destroyed - dialogs are persistent objects and
must be explicitly destroyed.
Obviously, there's a lot more we can talk about, but enough's enough. To
help straighten out the tangle I've no doubt made of your mind, take a look
at these important Perl/Tk resources (in no particular order):