NAME¶
Tk::UserGuide - Writing Tk applications in Perl 5
DESCRIPTION¶
This document is for beginners. It assumes you know some
Perl, and have
it and Tk running. If you are
not currently reading this document
courtesy of the
widget demonstration program, please be sure to run
widget, as it will show you the various widget types supported by Tk
and how to use them.
widget should be installed in your default path,
so type
widget at a command prompt.
Here are links to other novice tutorials:
<
http://www.lehigh.edu/~sol0/ptk/tpj1.html>
<
http://www.lehigh.edu/~sol0/ptk/perlmonth01/pm1.html>
Mastering Perl/Tk is the definitive book on Perl/Tk:
<
http://www.oreilly.com/catalog/mastperltk>
Some Background¶
Tk GUI programming is event-driven. (This may already be familiar to you.) In
event-driven programs, the main GUI loop is outside of the user program and
inside the GUI library. This loop - initiated by calling
MainLoop -
watches all events of interest and activates the correct handler procedures to
handle these events. Some of these handler procedures may be user-supplied;
others will be part of the library.
For a programmer, this means that you're not watching what is happening;
instead, you are requested by the toolkit to perform actions whenever
necessary. So, you're not watching for 'raise window / close window / redraw
window' requests, but you tell the toolkit which routine will handle such
cases, and the toolkit will call the procedures when required. These
procedures are known as
callbacks, and some of them you write yourself.
First Requirements¶
Perl programs that use Tk need to include "use Tk". A program
should also use "use strict" and the
-w switch to ensure the
program is working without common errors.
Any Perl/Tk application starts by creating the Tk
MainWindow. You then
create items inside the
MainWindow, and/or create new windows called
Toplevels that also contain child items, before starting the
MainLoop, which is the last logical statment in your program. You can
also create more items and windows while you're running, using callbacks.
Items are only shown on the display after they have been arranged by a
geometry manager like
pack; more information on this later.
MainLoop starts the GUI and handle all events. That's all there is to
it! A trivial one-window example is shown below:
#!/usr/bin/perl -w
use Tk;
use strict;
my $mw = MainWindow->new;
$mw->Label(-text => 'Hello, world!')->pack;
$mw->Button(
-text => 'Quit',
-command => sub { exit },
)->pack;
MainLoop;
Please run this example. It shows you two widget types, a
Label and a
Button, and how they are packed. When clicked, the
Button widget
invokes the callback specified by the "-command" option. Finally,
note the typical Tk style using "-option" => "value"
pairs.
Tk windows and widgets are hierarchical, i.e. one window includes one or
more other windows. You create the first Tk window using
"MainWindow->new". This returns a window handle, assigned to $mw
in the example above. Keep track of the main handle, commonly called a
widget reference.
You can use any Tk handle to create child widgets within the window (or widget).
This is done by calling the Tk constructor method on the variable. In the
example above, the "Label" method called from $mw creates a
Label widget inside the
MainWindow. In the constructor call, you
can specify various options; you can later add or change options for any
widget using the
configure method, which takes the same parameters as
the constructor. The one exception to the hierarchical structure is the
Toplevel constructor, which creates a new outermost window.
After you create any widget (other than the
MainWindow or
Toplevels, you must render it by calling
pack. (This is not
entirely true; more later)). If you do not need to refer to the widget after
construction and packing, call
pack off the constructor results, as
shown for the
Label and
Button in the example above. Note that
the result of the compound call is the result of
pack, which is a valid
Tk handle.
Windows and widgets are deleted by calling
destroy on them; this will
delete and un-draw the widget and all its children.
Here is an itemize of the standard Tk widget set.
- Button
- Canvas
- Checkbutton
- Entry
- Frame
- Label
- Labelframe
- Listbox
- Menu
- Menubutton
- Message
- Panedwindow
- Radiobutton
- Scale
- Scrollbar
- Spinbox
- Text
- Toplevel
Perl/Tk provides an equal number of new widgets, above and beyond this core set.
- Adjuster
- Balloon
- BrowseEntry
- ColorEditor
- Dialog
- DialogBox
- DirTree
- ErrorDialog
- FBox
- FileSelect
- HList
- LabEntry
- LabFrame
- NoteBook
- Optionmenu
- Pane
- ProgressBar
- ROText
- Table
- TextUndo
- Tiler
- TList
- Tree
Variables and callback routines¶
Most graphical interfaces are used to set up a set of values and conditions, and
then perform the appropriate action. The Tk toolkit is different from your
average text-based prompting or menu driven system in that you do not collect
settings yourself, and decide on an action based on an input code; instead,
you leave these values to your toolkit and only get them when the action is
performed.
So, where a traditional text-based system would look like this:
#!/usr/bin/perl -w
use strict;
print "Please type a font name\n";
my $font = <>; chomp $font;
# Validate font
print "Please type a file name\n";
my $filename = <>; chomp $filename;
# Validate filename
print "Type <1> to fax, <2> to print\n";
my $option = <>; chomp $option;
if ($option eq 1) {
print "Faxing $filename in font $font\n";
} elsif ($option eq 2) {
print "Now sending $filename to printer in font $font\n";
}
The slightly larger example below shows how to do this in Tk. Note the use of
callbacks. Note, also, that Tk handles the values, and the subroutine uses the
method
get to get at the values. If a user changes his mind and wants
to change the font again, the application never notices; it's all handled by
Tk.
#!/usr/bin/perl -w
use Tk;
use strict;
my $mw = MainWindow->new;
$mw->Label(-text => 'File Name')->pack;
my $filename = $mw->Entry(-width => 20);
$filename->pack;
$mw->Label(-text => 'Font Name')->pack;
my $font = $mw->Entry(-width => 10);
$font->pack;
$mw->Button(
-text => 'Fax',
-command => sub{do_fax($filename, $font)}
)->pack;
$mw->Button(
-text => 'Print',
-command => sub{do_print($filename, $font)}
)->pack;
MainLoop;
sub do_fax {
my ($file, $font) = @_;
my $file_val = $file->get;
my $font_val = $font->get;
print "Now faxing $file_val in font $font_val\n";
}
sub do_print {
my ($file, $font) = @_;
my $file_val = $file->get;
my $font_val = $font->get;
print "Sending file $file_val to printer in font $font_val\n";
}
In the examples above, you must have noticed the
pack calls. This is one
of the more complicated parts of Tk. The basic idea is that any window or
widget should be subject to a Tk geometry manager; the
packer is one of
the placement managers, and
grid is another.
The actions of the packer are rather simple: when applied to a widget, the
packer positions that widget on the indicated position within the remaining
space in its parent. By default, the position is on top; this means the next
items will be put below. You can also specify the left, right, or bottom
positions. Specify position using
-side => 'right'.
Additional packing parameters specify the behavior of the widget when there is
some space left in the
Frame or when the window size is increased. If
widgets should maintain a fixed size, specify nothing; this is the default.
For widgets that you want to fill up the current horizontal and/or vertical
space, specify
-fill => 'x',
'y', or
'both'; for
widgets that should grow, specify
-expand => 1. These parameters are
not shown in the example below; see the
widget demonstration.
If you want to group some items within a window that have a different packing
order than others, you can include them in a Frame. This is a do-nothing
window type that is meant for packing or filling (and to play games with
borders and colors).
The example below shows the use of pack and Frames:
#!/usr/bin/perl -w
use Tk;
use strict;
# Take top and the bottom - now implicit top is in the middle
my $mw = MainWindow->new;
$mw->title( 'The MainWindow' );
$mw->Label(-text => 'At the top (default)')->pack;
$mw->Label(-text => 'At the bottom')->pack(-side => 'bottom');
$mw->Label(-text => 'The middle remains')->pack;
# Since left and right are taken, bottom will not work...
my $top1 = $mw->Toplevel;
$top1->title( 'Toplevel 1' );
$top1->Label(-text => 'Left')->pack(-side => 'left');
$top1->Label(-text => 'Right')->pack(-side => 'right');
$top1->Label(-text => '?Bottom?')->pack(-side => 'bottom');
# But when you use Frames, things work quite alright
my $top2 = $mw->Toplevel;
$top2->title( 'Toplevel 2' );
my $frame = $top2->Frame;
$frame->pack;
$frame->Label(-text => 'Left2')->pack(-side => 'left');
$frame->Label(-text => 'Right2')->pack(-side => 'right');
$top2->Label(-text => 'Bottom2')->pack(-side => 'bottom');
MainLoop;
More than one window¶
Most real applications require more than one window. As you just saw, you can
create more outermost windows by using a
Toplevel widget. Each window
is independent; destroying a
Toplevel window does not affect the others
as long as they are not a child of the closed
Toplevel. However,
exiting the
MainWindow will destroy all remaining
Toplevel
widgets and end the application. The example below shows a trivial
three-window application:
#!/usr/bin/perl -w
use Tk;
use strict;
my $mw = MainWindow->new;
fill_window($mw, 'Main');
my $top1 = $mw->Toplevel;
fill_window($top1, 'First top-level');
my $top2 = $mw->Toplevel;
fill_window($top2, 'Second top-level');
MainLoop;
sub fill_window {
my ($window, $header) = @_;
$window->Label(-text => $header)->pack;
$window->Button(
-text => 'close',
-command => [$window => 'destroy']
)->pack(-side => 'left');
$window->Button(
-text => 'exit',
-command => [$mw => 'destroy']
)->pack(-side => 'right');
}
More callbacks¶
So far, all callback routines shown called a user procedure. You can also have a
callback routine call another Tk routine. This is the way that scroll bars are
implemented: scroll-bars can call a Tk item or a user procedure, whenever
their position has changed. The Tk item that has a scrollbar attached calls
the scrollbar when its size or offset has changed. In this way, the items are
linked. You can still ask a scrollbar's position, or set it by hand - but the
defaults will be taken care of.
The example below shows a
Listbox with a scroll bar. Moving the scrollbar
moves the
Listbox. Scanning a
Listbox (dragging an item with the
left mouse button) moves the scrollbar.
#!/usr/bin/perl -w
use Tk;
use strict;
my $mw = MainWindow->new;
my $box = $mw->Listbox(
-relief => 'sunken',
-height => 5,
-setgrid => 1,
);
my @items = qw(One Two Three Four Five Six Seven
Eight Nine Ten Eleven Twelve);
foreach (@items) {
$box->insert('end', $_);
}
my $scroll = $mw->Scrollbar(-command => ['yview', $box]);
$box->configure(-yscrollcommand => ['set', $scroll]);
$box->pack(-side => 'left', -fill => 'both', -expand => 1);
$scroll->pack(-side => 'right', -fill => 'y');
MainLoop;
Note that there's a convenience method
Scrolled which helps constructing
widgets with automatically managed scrollbars.
One of the most powerful widgets in Tk is the
Canvas window. In a
Canvas window, you can draw simple graphics and include other widgets.
The
Canvas area may be larger than the visible window, and may then be
scrolled. Any item you draw on the canvas has its own id, and may optionally
have one or more
tags. You may refer to any item by its id, and may
refer to any group of items by a common tag; you can move, delete, or change
groups of items using these tags, and you can
bind actions to tags. For
a properly designed (often structured)
Canvas, you can specify powerful
actions quite simply.
In the example below, actions are bound to circles (single click) and blue items
(double-click); obviously, this can be extended to any tag or group of tags.
#!/usr/bin/perl -w
use Tk;
use strict;
# Create B<MainWindow> and canvas
my $mw = MainWindow->new;
my $canvas = $mw->Canvas;
$canvas->pack(-expand => 1, -fill => 'both');
# Create various items
create_item($canvas, 1, 1, 'circle', 'blue', 'Jane');
create_item($canvas, 4, 4, 'circle', 'red', 'Peter');
create_item($canvas, 4, 1, 'square', 'blue', 'James');
create_item($canvas, 1, 4, 'square', 'red', 'Patricia');
# Single-clicking with left on a 'circle' item invokes a procedure
$canvas->bind('circle', '<1>' => sub {handle_circle($canvas)});
# Double-clicking with left on a 'blue' item invokes a procedure
$canvas->bind('blue', '<Double-1>' => sub {handle_blue($canvas)});
MainLoop;
# Create an item; use parameters as tags (this is not a default!)
sub create_item {
my ($can, $x, $y, $form, $color, $name) = @_;
my $x2 = $x + 1;
my $y2 = $y + 1;
my $kind;
$kind = 'oval' if ($form eq 'circle');
$kind = 'rectangle' if ($form eq 'square');
$can->create(
($kind, "$x" . 'c', "$y" . 'c',
"$x2" . 'c', "$y2" . 'c'),
-tags => [$form, $color, $name],
-fill => $color);
}
# This gets the real name (not current, blue/red, square/circle)
# Note: you'll want to return a list in realistic situations...
sub get_name {
my ($can) = @_;
my $item = $can->find('withtag', 'current');
my @taglist = $can->gettags($item);
my $name;
foreach (@taglist) {
next if ($_ eq 'current');
next if ($_ eq 'red' or $_ eq 'blue');
next if ($_ eq 'square' or $_ eq 'circle');
$name = $_;
last;
}
return $name;
}
sub handle_circle {
my ($can) = @_;
my $name = get_name($can);
print "Action on circle $name...\n";
}
sub handle_blue {
my ($can) = @_;
my $name = get_name($can);
print "Action on blue item $name...\n";
}
Perl/Tk and Unicode¶
Perl/Tk follows Perl's model of handling Unicode. That is, if a string is
correctly flagged as a "character" string in the sense like
described in "TERMINOLOGY" in Encode, then Perl/Tk will very
probably display and handle this string correctly.
Note that every variable which is passed somehow into a Perl/Tk method will be
implicitely changed into an internally utf8-flagged variable. Semantically
nothing changes, as the series of codepoints stays the same, but things will
change when variables with high-bit iso-8859-1 characters will be passed to
the "outer" world. In this case you have to explicitly mark the
encoding of your output stream if using IO, or encode the variables using
Encode for other style of communication.
This is the theory, now some examples.
If you use non-iso-8859-1 characters in the source code, then use either the
"use utf8;" or "use encoding '
encodingname'"
pragma:
use utf8;
use Tk;
my $x = "some characters using utf8 encoding";
tkinit->Label(-text => $x)->pack;
MainLoop;
For data that comes from a file you have to specify the encoding unless it's
encoded as ascii or iso-8559-1:
use Tk;
open my $FH, "<:encoding(utf-8)", "filename" or die $!;
# or for utf-16 data: open my $FH, "<:encoding(utf-16)", "filename" or die $!;
my $data = <$FH>;
tkinit->Label(-text => $data)->pack;
MainLoop;
Likewise, the encoding must be specified for all data which is read from Tk
widgets and that shall be output into a file. For the output, the encoding
should be always specified, even if it is iso-8859-1:
use Tk;
$mw = tkinit;
$mw->Entry(-textvariable => \$input)->pack;
$mw->Button(
-text => "Write to file",
-command => sub {
open my $FH, ">:encoding(iso-8859-1)", "filename" or die $!;
print $FH $input;
},
)->pack;
MainLoop;
Note that Tk is Unicode-capable. So you need to be prepared that the user has
the appropriate input methods activated to enter non-ascii characters. If an
output encoding is used which does not cover the whole of Unicode codepoints
then a warning will be issued when writing the file, like this:
"\x{20ac}" does not map to iso-8859-1 at /usr/local/lib/perl5/site_perl/5.8.8/mach/Tk.pm line 250.
Also, the same hexadecimal notation will be used as replacements for the
unhandled characters.
Handling encoding in I/O is pretty simple using the "encoding" PerlIO
layer, as described above. In other cases, such as when dealing with
databases, encoding the data usually has to be done manually, unless the
database driver has some means for automatically do this for you. So when
working with a MySQL database, one could use:
use Tk;
use DBI;
use Encode qw(encode);
$mw = tkinit;
$mw->Entry(-textvariable => \$input)->pack;
$mw->Button(
-text => "Write to database",
-command => sub {
my $dbh = DBI->connect("dbi:mysql:test", "root", "") or die;
my $encoded_input = encode("iso-8859-1", $input);
$dbh->do("INSERT INTO testtable VALUES (?)", undef, $encoded_input) or die;
},
)->pack;
MainLoop;
Unfortunately, there are still places in Perl ignorant of Unicode. One of these
places are filenames. Consequently, the file selectors in Perl/Tk do not
handle encoding of filenames properly. Currently they suppose that filenames
are in iso-8859-1 encoding, at least on Unix systems. As soon as Perl has a
concept of filename encodings, then Perl/Tk will also implement such
schemes.