הלינקייה: מגזין חודשי למפתחים

רוצה לשמוע על כל האירועים, המדריכים, הקורסים והמאמרים שנכתבו החודש ?
הלינקייה הינו מגזין חופשי בעברית שמשאיר אותך בעניינים.
בלי ספאם. בלי שטויות. פעם בחודש אצלך בתיבה.

Tk UI

In perl, there are many ways to create UI elements on the screen, each with its own library, coding style and UI look and feel. In this lesson, we're going to learn about the Tk library and its perl bindings as a way to create graphical UI elements in perl. For further reading about the Tk graphical framework, refer to its wikipedia page.

Programming Style

The Perl/Tk framework, as many other UI toolkits, is event driven, as opposed to the normal flow nature of a perl script. In an event driven programming, you need to think of the various ways in which a user may interact with your program (the events that happen), and define the code to handle each of these events. For each event, we provide the Tk framework with a subroutine reference and that subroutine is called by Tk when the event occurs to handle it.

A Perl script that uses Tk should start with creating a Tk main window, populate that window with GUI elements and assign subroutines to handle the events generated from those GUI elements.We add the GUI elements by calling the pack() method.

In the end, we need to tell Tk to start running the event loop. After the call to EventLoop, no other line is executed.

Here's a sample Hello Tk script:

use Tk;
use strict;
 
my $mw = MainWindow->new;
$mw->Label(-text => 'Hello, world!')->pack;
$mw->Button(
        -text    => 'Quit',
        -command => sub { exit },
)->pack;
MainLoop;
use Tk;
use File::Find;
use strict;
use warnings;
 
our $text_to_find;
our $results;
our $search_entry;
our @directories_to_search;
 
sub wanted { 
	open(CURR, "<", $_) or die "Error opening file $_ - $!\n";
 
	while(<CURR>) {
		if (/$text_to_find/) {
			$results->insert('end', $File::Find::name);
			print $File::Find::name,"\n";
			last;
		}		 
	}
 
	close(CURR);
}
 
@directories_to_search = qw(.);
 sub search {
	$results->delete(0, $results->size);
	$text_to_find = $search_entry->get;	
    find(\&wanted, @directories_to_search);
}
 
# Create the main window
my $mw = MainWindow->new;
 
# Create a text label, and place it on screen
$mw->Label(-text => 'Hello, world!')->pack;
 
# Create a Text Entry, and place it on screen
$search_entry = $mw->Entry(-text => 'Text To Search')->pack;
 
# Create a list box, and place it on screen
$results = $mw->Listbox->pack;
 
#Create Two buttons, and place them on screen one on each side
$mw->Button(
        -text    => 'Quit',
        -command => sub { exit },
    )->pack (-side => 'right');
 
$mw->Button(
	-text    => 'Search',
	-command => \&search
	)->pack( -side => 'left' );
 
# Start the Main Loop
MainLoop;
 
use strict;
use warnings;
use Tk;
 
our $clear_next_time = 1;
 
sub btn_click {
	my ($label, $text) = @_;
	return sub { 
		if ($clear_next_time) {
			$label->configure(-text, '');	
			$clear_next_time = 0;
		} 
		my $curr = $label->cget(-text);
		$label->configure(-text => $curr.$text);
	}
}
 
sub equal_btn_click {
	my $label = shift;
	return sub {
		$clear_next_time = 1;
		my $curr = $label->cget(-text);
		my $result = eval($curr);
		$label->configure(-text => $result);
	}
}
 
##################################################################
 
my $mw = MainWindow->new;
my $f1 = $mw->Frame->pack;
my $f2 = $mw->Frame->pack;
 
my $lbl = $f1->Label(-text => "<start typing>")->pack;
 
my $i;
for $i (0..9) {
	my ($c, $r) = ($i / 3, $i % 3);
	$f2->Button(
		-text => $i, 
		-command => btn_click($lbl, $i))
	->grid( -column => $c, -row => $r);
}
 
$f2->Button(
	-text => '+', 
	-command => btn_click($lbl, '+'))
	->grid(-column => 3, -row => 1);
 
$f2->Button(
	-text => '=', 
	-command => equal_btn_click($lbl))
	->grid(-column => 3, -row => 2);
 
MainLoop;

Closures

The second example is a little more interesting. It uses the concept of closure to solve a limitation of the framework - let's elaborate on that.

When Tk calls the button clicked event, it passes no arguments. This means that our event handling code has no indication which object generated the event. A problem arises when writing a calculator - we have 9 buttons that do "almost" the same thing. The only difference between the various event handlers is the specific digit that was clicked on. Still, in theory, we need to have a different event handler for each button - each has its own digit hard coded - or do we ?

Using closures, we can create 9 event handlers which act almost the same, and whose only difference is the digit they refer to. Then, we attach each button with its special event handler that was created especially for it. The subroutine btn_click does just that. It takes the digit and returns a new special event handler to handle the clicking on that specific button, as an anonymous subroutine. We then assign that returned subroutine as the event handler to the button in line 42.

Further Readings

The man page is an excellent place to start. It can be accessed from anywhere using perldoc Tk or online at this page.
Further explanation on closures can be found at the perlfaq

course: