Book HomeMastering Perl/TkSearch this book

15.7. Splash Screens

Splash screens are those windows that pop up for the amusement of the user while a long-loading program gets underway. Some folks display their splash screens during program initialization sequentially, so that if a splash screen stays on the display for three seconds, the program takes three seconds longer to load. We, however, prefer that our splash screens run in parallel with program initialization. One approach might be:

  1. Create a Toplevel splash screen.

  2. Queue a timer event to set a variable after X seconds.

  3. Initialize program.

  4. Wait for splash timer to expire with waitVariable.

  5. Destroy splash screen and enter MainLoop.

There's a problem with this scheme: if initialization takes too long and the splash timer expires, the waitVariable will hang. This can also happen if the splash delay is set too small. We could use waitVariableX with a timeout, resulting in code that might look like this:

    my $mw = MainWindow->new;
    $mw->withdraw;

    my ($splash_scr, $splash_tid, $splash_var) = splash 3000;

    # - program initialization.

    my $why = $mw->&waitVariableX(3000, $splash_var);
    $splash_scr->afterCancel($splash_tid);
    $splash_scr->destroy;

    $mw->deiconify;

But this just doesn't feel right. First, having the splash screen remain on the screen for X seconds one time, and X+3 seconds at others, is an unsatisfactory hack. Second, too much of the work is left to the application. We need to encapsulate things in a mega-widget. Besides, there are some subtle details, as we are about to see.

15.7.1. Tk::Splashscreen

We've just written tkhp16c, our version of the venerable RPN programming calculator, shown in Figure 15-6. As Tk programs go, this application loads slowly, because it's composed of so many widgets. So we'll incorporate a splash screen.

Figure 15-6

Figure 15-6. An HP-16C RPN calculator

Tk::Splashscreen is a Toplevel mega-widget providing all the display, destroy, and timing events. All we do is create the Splashscreen widget, populate it, then invoke Splash to display it and Destroy to tear it down. The plan for our splash screen is that it contain a progress bar; we'll be sure to sprinkle update calls throughout our initialization code so that any Splashscreen events are handled.

Here's the mega-widget preamble. If it's unfamiliar, please read Chapter 14, "Creating Custom Widgets in Pure Perl/Tk" for complete details. Note that for this mega-widget, we import the DoOneEvent bit patterns.

$Tk::Splashscreen::VERSION = '1.0';

package Tk::Splashscreen;

use Tk qw/Ev/;
use Tk qw/:eventtypes/;
use Tk::waitVariableX;
use Tk::widgets qw/Toplevel/;
use base qw/Tk::Toplevel/;

Construct Tk::Widget 'Splashscreen';

Subroutine Populate immediately removes the empty Toplevel from the display so tkhp16c can fill it at its leisure. Then overrideredirect removes the window manager decorations. Of course, with the decorations gone, the Toplevel can't be moved around by normal means, so we'll have to create our own movement bindings. The widget uses mouse button 3 for this purpose and keeps state information in the instance variables $self->{ofx} and $self->{ofy}, the x and y pixel offsets from the Splashscreen's top-left corner to the cursor at the time the button is pressed.

The two button bindings use the special format where we explicitly state the object to use, $self rather than letting Tk supply us one indirectly. This forces Tk to look up the methods b3prs and b3rls in the package Tk::Splashscreen, which is where they are located. Otherwise, if for instance the Splashscreen contained a Label and we clicked on it, Tk would try to invoke Tk::Label::b3prs, and that would fail. We also use the Ev subroutine to pass event data to the callback.

Lastly, instance variable $self->{tm0} stores the time the Splashscreen is first shown.

sub Populate {
    my ($self, $args) = @_;

    $self->withdraw;
    $self->overrideredirect(1);

    $self->SUPER::Populate($args);

    $self->{ofx} = 0;           # X offset from top-left corner to cursor
    $self->{ofy} = 0;           # Y offset from top-left corner to cursor
    $self->{tm0} = 0;           # microseconds time widget was Shown

    $self->ConfigSpecs(
        -milliseconds => [qw/PASSIVE milliseconds Milliseconds 0/],
    );

    $self->bind('<ButtonPress-3>'   => [$self => 'b3prs', Ev('x'), Ev('y')]);
    $self->bind('<ButtonRelease-3>' => [$self => 'b3rls', Ev('X'), Ev('Y')]);

} # end Populate

At this point, we have an empty Splashscreen widget. Before we show it, let's put something inside. We'll keep it simple, with a MacProgressBar and a picture of an actual HP-16C calculator, as shown in Figure 15-7.

A MacProgressBar widget has a 3D look, exactly like the classic Macintosh progress bar. We won't examine the code here, but it's listed in Appendix C, "Complete Program Listings". It's a versatile widget. Here's a pseudo-volume meter:

$pb = $mw->MacProgressBar(-width => 150, -bg => 'cyan')->pack;

while (1) {
    my $w = rand(100);
    $pb->set($w);
    $mw->update;
    $mw->after(250);
} 
Figure 15-7

Figure 15-7. tkhp16c initialization is 90% complete

Anyway, we keep the MacProgressBar widget reference in the global variable $MAC_PB, so we can access it throughout the various initialization subroutines. For our Splashscreen, we've use the -milliseconds option to specify that the Splashscreen remain posted for a minimum of three seconds.

$splash = $mw->Splashscreen(-milliseconds => 3000);
$splash->Label(-text => 'Building your HP 16C ...', -bg => $BLUE)->
    pack(qw/-fill both -expand 1/);
$MAC_PB = $splash->MacProgressBar(-width => 300);
$MAC_PB->pack(qw/-fill both -expand 1/);
$splash->Label(-image => $mw->Photo(-file => 'hp16c-splash.gif'))->pack;

Here's how we use the Splashscreen. First, withdraw the MainWindow and show the Splashscreen. Now perform program initialization. Note how we use the set method to update the MacProgressBar to 100% before destroying the Splashscreen. With the Splashscreen gone, redisplay the MainWindow containing the completed calculator.

my $mw = MainWindow->new;
$mw->withdraw;
$splash->Splash;                     # show Splashscreen

build_help_window;
build_calculator;

$MAC_PB->set($MAC_PB_P = 100);
$splash->Destroy;                    # tear down Splashscreen

$mw->deiconify;                      # show calculator

The Splash method serves to record the second of the epoch that the Splashscreen is first displayed. This datum is used to ensure that the Splashscreen remains visible for the specified minimum amount of time. Then Splash maps the widget in the center of the screen.

sub Splash {

    my ($self, $millis) = @_;

    $millis = $self->cget(-milliseconds) unless defined $millis;
    $self->{tm0} = Tk::timeofday;
    $self->configure(-milliseconds => $millis);
    $self->Popup;

} # end_splash

Destroy's first duty is to ensure that the Splashcreen remains visible for its allotted minimum time. It does this with a simple computation, which, if positive, gives the time to delay. If the result is negative, we set it to zero so there is no wait.

We then create a generic completion callback that does one final update call (to ensure all pending events are completed) and destroys the Splashscreen.

Now, if the program initialization has taken longer than the minimum Splashscreen time, we call the completion callback and return. Otherwise, we process all timer events, wait the requisite amount of time, and destroy the Splashscreen.

sub Destroy {

    my ($self, $millis) = @_;

    $millis = $self->cget(-milliseconds) unless defined $millis;
    my $t = Tk::timeofday;
    $millis = $millis - ( ($t - $self->{tm0}) * 1000 );
    $millis = 0 if $millis < 0;

    my $destroy_splashscreen = sub {
			$self->update;
			$self->after(100);			# ensure 100% of PB seen
			$self->destroy;
    };

    do { &$destroy_splashscreen; return } if $millis == 0;

    while ( $self->DoOneEvent (DONT_WAIT | TIMER_EVENTS)) {}

    $self->waitVariableX( [$millis, $destroy_splashscreen] );

} # end Destroy

These are the private methods responsible for moving a Splashscreen widget. On a button press, we record the cursor's x and y coordinates relative to the Splashscreen's top-left corner. When the button is released, we compute new x and y coordinates relative to the display's top-left corner and use geometry to move the Toplevel.

sub b3prs {
    my ($self, $x, $y) = @_;
    $self->{ofx} = $x;
    $self->{ofy} = $y;
} # end b3prs

sub b3rls {
    my($self, $X, $Y) = @_;
    $X -= $self->{ofx};
    $Y -= $self->{ofy};
    $self->geometry("+${X}+${Y}");
} # end b3rls

To complete our discussion on Tk::Splashscreen, here is a bindDump output:

## Binding information for '.splashscreen', Tk::Splashscreen=HASH(0x83a6874) ##

1. Binding tag 'Tk::Splashscreen' has no bindings.

2. Binding tag '.splashscreen' has these bindings:
             <ButtonRelease-3> : Tk::Callback=ARRAY(0x83aaaf8)           
                                   Tk::Splashscreen=HASH(0x83a6874)
                                     'b3rls'
                                     Tk::Ev=SCALAR(0x83aab1c)               : 'X'
                                     Tk::Ev=SCALAR(0x83aab58)               : 'Y'
                    <Button-3> : Tk::Callback=ARRAY(0x83aaae0)           
                                   Tk::Splashscreen=HASH(0x83a6874)
                                     'b3prs'
                                     Tk::Ev=SCALAR(0x839a348)               : 'x'
                                     Tk::Ev=SCALAR(0x83aab04)               : 'y'

3. Binding tag 'all' has these bindings:
                     <Key-F10> : Tk::Callback=SCALAR(0x839a3fc)          
                                   'FirstMenu'
                     <Alt-Key> : Tk::Callback=ARRAY(0x839a390)           
                                   'TraverseToMenu'
                                     Tk::Ev=SCALAR(0x816e198)               : 'K'
                   <<LeftTab>> : Tk::Callback=SCALAR(0x839a360)          
                                   'focusPrev'
                     <Key-Tab> : Tk::Callback=SCALAR(0x839a264)          
                                   'focusNext'


Library Navigation Links

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