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.
use Tk
.
A program should also use use strict
and the -w switch to ensure
the program is working without common errors.
Any Tk application starts by creating the Tk main window. You then create
items inside the main window, or create new windows, before starting the
mainloop.
(You can also create more items and windows while you're running.)
The items will be shown on the display after you pack
them;
more info on this later.
Then you do a Tk mainloop; this will start the GUI and handle all events.
That's your application.
A trivial one-window example is show below:
#! /usr/bin/perl5 -w
use strict; use Tk;
my $main = new MainWindow; $main->Label(-text => 'Hello, world!')->pack; $main->Button(-text => 'Quit', -command => sub{exit} )->pack; MainLoop;
-option
=> value
pairs.
new MainWindow
.
This returns a window handle, assigned to $main
in the example above.
Keep track of the main handle.
You can use any Tk handle to create sub-items 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 $main
creates a
label widget inside the main window. 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, you must render it by calling pack
. (This
is not entirely true; more info 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 not 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,
with the exception of Toplevel windows.
So, where a traditional text-based system would look like this: (yes, this is obviously dumb code)
#! /usr/bin/perl5 -w
use strict;
print "Please type a font name\n"; my $font = <>; chop $font; # Validate font
print "Please type a file name\n"; my $filename = <>; chop $filename; # Validate filename
print "Type <1> to fax, <2> to print\n"; my $option = <>; chop $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"; }
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/perl5 -w
use strict; use Tk;
my $main = new MainWindow; $main->Label(-text => 'Print file')->pack; my $font = $main->Entry(-width => 10); $font->pack; my $filename = $main->Entry(-width => 10); $filename->pack; $main->Button(-text => 'Fax', -command => sub{do_fax($filename, $font)} )->pack; $main->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_val\n"; }
sub do_print { my ($file, $font) = @_; $file = get $file; $font = get $font; print "Sending file $file to printer in $font\n"; }
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 widget placement manager;
the packer is the most useful placement manager.
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 space, specify -fill => 'x', y, or both; for widgets that should grow, specify -expand => 'yes'. These parameters are not shown in the example below; see the widget demo.
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 (and to play games with borders and colors).
The example below shows the use of pack and frames:
#! /usr/bin/perl5 -w
use strict; use Tk;
# Take top, the bottom -> now implicit top is in the middle my $main = new MainWindow; $main->Label(-text => 'At the top (default)')->pack; $main->Label(-text => 'At the bottom')->pack(-side => 'bottom'); $main->Label(-text => 'The middle remains')->pack;
# Since left and right are taken, bottom will not work... my $top1 = $main->Toplevel; $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 = $main->Toplevel; 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;
#! /usr/bin/perl5 -w
use strict; use Tk;
my $main = new MainWindow; fill_window($main, 'Main'); my $top1 = $main->Toplevel; fill_window($top1, 'First top-level'); my $top2 = $main->Toplevel; fill_window($top2, 'Second top-level'); MainLoop;
sub fill_window { my ($window, $header) = @_; $window->Label(-text => $header)->pack; $window->Button(-text => 'close', -command => sub{destroy $window} )->pack(-side => 'left'); $window->Button(-text => 'exit', -command => sub{exit} )->pack(-side => 'right'); }
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/perl5 -w
use strict; use Tk;
my $main = new MainWindow; my $box = $main->Listbox(-relief => 'sunken', -width => -1, # Shrink to fit -height => 5, -setgrid => 'yes'); my @items = qw(One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve); foreach (@items) { $box->insert('end', $_); } my $scroll = $main->Scrollbar(-command => ['yview', $box]); $box->configure(-yscrollcommand => ['set', $scroll]); $box->pack(-side => 'left', -fill => 'both', -expand => 'yes'); $scroll->pack(-side => 'right', -fill => 'y');
MainLoop;
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/perl5 -w
use strict; use Tk;
# Create main window and canvas my $main = new MainWindow; my $canvas = $main->Canvas; $canvas->pack(-expand => 'yes', -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"; }