home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


Book HomeMastering Perl/TkSearch this book

Chapter 15. Anatomy of the MainLoop

As programmers, we all know what a "main loop" is. It's the heart of our programs, the repeating chunk of code that carries out the task at hand. But Perl/Tk programs are event driven, so even if we write what we believe is our program's main loop, it must coexist with a higher order main loop that's a fundamental part of Tk. The Tk main loop is typically referred to as the event loop, and its job is to invoke callbacks in response to events such as button presses or timer expirations.

Callbacks are Perl subroutines associated with Tk events. In Perl/Tk, we can define callbacks that, from our point of view, are automatically invoked when the appropriate event occurs. The Tk core defines hundreds of other callbacks on our behalf that we're not even aware of. It's the combination of our own callbacks and Tk-defined callbacks that gives behavior to our Perl/Tk applications.

The event loop is activated once the Perl/Tk program's MainLoop statement is reached. From that point on, MainLoop controls our program. As events happen, MainLoop dispatches them to a handler (a callback) for processing and puts the application to sleep for a short amount of time when the event queue is empty. This repeats until there are no more MainWindows, at which time MainLoop returns. Any code after the MainLoop statement is then executed.

Here is the salient portion of the actual MainLoop subroutine from the Perl/Tk source distribution:

use Tk ':eventtypes';

while (Tk::MainWindow->Count) {
    DoOneEvent(ALL_EVENTS);
}

As we see, the Tk main loop processes all events, one by one, until the count of MainWindows becomes zero. The use tag :eventtypes imports various symbols used by DoOneEvent, the subroutine that actually dispatches individual events. We'll learn more about DoOneEvent later. For now it's sufficient to know that the subroutine expects one argument, a bit pattern, specifying what types of events to process and whether to return immediately or to wait if there are no such events.

The symbol ALL_EVENTS is the inclusive OR of all the various event types, which we'll examine in detail later. The individual event types that DoOneEvent recognizes are as follows:

WINDOW_EVENTS
These include things such as keyboard entry, button clicks, and window size and visibility changes.

FILE_EVENTS
These deal with reading and writing files and network sockets.

TIMER_EVENTS
These are created by the after and repeat commands.

IDLE_EVENTS
These are low-priority callbacks executed only after all events of the previous types have been processed. The most common idle events are those that redraw widgets and refresh the display. You can queue idle callbacks using DoWhenIdle.

The :eventtypes tag defines one other symbol, DONT_WAIT, that can be inclusively OR ed with a DoOneEvent bit pattern to make the subroutine call nonblocking. Notice that MainLoop does not include DONT_WAIT in its DoOneEvent bit pattern, meaning that DoOneEvent sleeps when there is nothing to do, instead of returning to MainLoop. This is actually a good thing, as it allows other programs running on our computer a slice of the CPU pie. Later we'll see when including DONT_WAIT works to our advantage.

MainLoop's job is to dispatch events to callbacks in a timely fashion. As you write callbacks, keep in mind you are in a mutually cooperative environment; all callbacks should be brief and nonblocking so the application remains responsive. A common novice mistake is to execute a long-running system command, then wonder why Buttons don't work and the display won't refresh. The novice fails to realize that MainLoop has been locked out, and the events responsible for Button actions and screen refreshes are being queued by the underlying operating system. We'll examine idioms to avoid blocking situations. The principle of mutual cooperation applies also when sharing events with other GUI packages, such as OpenGL.

And that, in a nutshell, describes the contents of this chapter. In summary, we'll learn:

  • How to create callbacks

  • About the different events, including virtual events

  • How to associate events with callbacks

  • About nonblocking programming techniques and how to cooperate with MainLoop

  • How to share the event loop with OpenGL

Let us move on and examine the details.

15.1. Creating a Callback

Perl/Tk has an expressive and well-defined callback syntax. Anywhere an option expects a callback, you can use this syntax. The most common option name is -command, but you'll also see -validatecommand, -browsecmd, or something similar. For instance, when you create a Button widget, you use -command to specify the callback invoked when the button is pressed. Similarly, when you create an event binding, you specify the event of interest and a callback to invoke when the event occurs.

At its simplest, a callback is a subroutine reference:

-command => \&callback

or:

-command => sub { ... }

The first example is a code reference to a named subroutine. The second is a code reference to an anonymous subroutine. Notice that you cannot pass explicit arguments to the subroutines using this callback format. A common mistake is to assume a statement of this form will work:

-command => \&callback(arguments)

Well, it "works" in the sense that it compiles and produces a result, but the result is probably not what you expect. You aren't creating a code reference to a subroutine that will execute sometime in the future. Instead, the subroutine is executed immediately, and you get a reference to the subroutine's return value. A fast session in the Perl debugger shows us the scary details:

[bug@Pandy Anatomy]$ perl -de 0
Default die handler restored.

Loading DB routines from perl5db.pl version 1.07
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   0
  DB<1> sub frog {print "frog args=@_!\n"; return 456}

  DB<2> &frog(1, 2, 3)
frog args=1 2 3!

  DB<3> $cref1 = \&frog

  DB<4> p $cref1
CODE(0x82c45f8)
  DB<5> $cref2 = \&frog(789)
frog args=789!

  DB<6> p $cref2
SCALAR(0x82c6818)
  DB<7> p $$cref2
456
  DB<8> q

Debug line 1 first creates the subroutine frog that prints its arguments and returns the integer 456. Line 2 then calls frog as a test. Line 3 takes a reference to frog, verified in line 4. Notice in line 5 that frog is called immediately and prints its argument 789. Line 6 shows us that we have failed to create a code reference but have a reference to a scalar instead. Line 7 dereferences $cref2 and prints the result, which is 456, frog's return value. You have been warned!

When you want to pass arguments to a callback, specify an array reference, with the callback code reference as first element and the callback arguments as subsequent array elements:

-command => [ \&callback, arg1, arg2 ...]

or:

-command => [ sub { ... }, arg1, arg2, ... ]

Finally, there's a third callback form in which you specify a method name as a string. This form is used more often in binding commands and when writing mega-widgets, because it's very easy for a subclass to override the subroutine by providing it's own method with the same name. We'll see examples later on in this chapter. Table 15-1 shows legal callback syntax.

Table 15-1. Legal callback syntax

Callback formats without arguments

Callback formats with arguments

\&callback

[ \&callback, arg1, arg2, ... ]

sub { ... }

[ sub { ... }, arg1, arg2, ... ]

'methodname'

[ 'methodname', arg1, arg2, ... ]

Regardless of the syntax you use, Perl/Tk ends up creating a Tk::Callback object.

One final note: for callbacks with arguments, Perl/Tk evaluates the contents of the (anonymous) array when the callback is parsed. To defer evaluation of an argument until the callback is executed, use the Ev method, described in Section 15.2.5, "Binding to a MouseWheel Event". The Ev method should only be used to construct parameters for event callbacks.

15.1.1. Callbacks and Closures

Creating a number of widgets using a Perl loop construct is a common programming task, which in itself is easy enough:

foreach $b (1 .. 5) {
    $mw->Button(
        -text    => $b, 
    )->pack;
}

This code produces five Buttons aligned vertically, labeled 1 through 5. But the Buttons don't do anything, and trouble usually begins when you try to specify a callback. Since we're creating Buttons in a loop, the assumption is that they do similar things but vary slightly depending upon which one is pressed. So the problem reduces to how to tell the callback which button invoked it.

Here's a first attempt at creating a series of Buttons with unique identifiers (differences are shown in bold type). It's doomed to failure, because the scope of $b is local to the for loop only, and although the Button text is correct, by the time a Button callback is executed, $b has gone out of scope and no longer exists.

foreach $b (1 .. 5) {
    $mw->Button(
        -text    => $b, 
        -command => sub {print "Button $b\n"},
    )->pack;
}

In the previous example, every time you click on any of the Buttons, you see this:

Use of uninitialized value in concatenation (.) at ./close1 line 12.
Button 

Our second attempt at creating a series of Buttons with unique identifiers also fails, because the callback uses the value that $n had at the end of the for statement. This is simply a variation of our first attempt.

$n = 1;
foreach $b (1 .. 5) {
    $mw->Button(
        -text    => $b, 
        -command => sub {print "Button $n\n"},
    )->pack;
    $n++;
}

When you click on any Button, you see this:

Button 6

For our third attempt, we declare $b a my, or lexical, variable, and voilà, it works! Every Button callback correctly prints its Button ID number.

foreach my $b (1 .. 5) {
    $mw->Button(
        -text    => $b, 
        -command => sub {print "Button $b\n"},
    )->pack;
}

What's so magical about lexicals? In simple terms, when an anonymous subroutine is defined, the values of lexical variables it references outside its scope become "closed," or finalized, as the subroutine is defined. Closures are ideal for creating callbacks, because they can enclose current information in their definitions, which are available later in a different scope. For an authoritative essay on closures, please read the perlref manpage.

Here's another version, which also works as expected because Perl/Tk creates the closures for us. It's somewhat verbose, but it does the job.

foreach $b (1 .. 5) {
    $mw->Button(
        -text    => $b, 
        -command => [\&do_button, $b],
    )->pack;
}

MainLoop;

sub do_button {
    $n = shift;
    print "Button $n\n";
}

Here's our final attempt at creating a series of Buttons with unique identifiers. This is a variation of our previous attempt that avoids the use of an explicit subroutine.

foreach $b (1 .. 5) {
    $mw->Button(
        -text    => $b, 
        -command => [sub {print "Button $_[0]\n"}, $b],
    )->pack;
}

Generally, the preferred solution to this problem is either this most recent attempt or to use the lexical for loop variable (our third attempt).



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.