New Module: Tk::Wizard 
Author Message
 New Module: Tk::Wizard

This module provides a mechanism for walking a user through
a series of dialogs in order to fetch information from him.

It works fairly well, and presents the user with an interface
that they're used to (if they've ever used a MS product...).
We've used it here, internally, for some utilities.

Comments appreciated before I ship it over to CPAN.  This
is my first attempt at submitting a module, and an
OO-interface one at that.  Be kind.

-----cut here------
#!/usr/bin/perl

=head1 NAME

Wizard - Multipanel walk-through GUI

=head1 SYNOPSIS

        use Wizard;

        $wizard=new Wizard;

=head1 DESCRIPTION

This module provides a mechanism for walking a user through
a series of dialogs in order to fetch information from him.

Each function in the list is called in succession.  First one should be
an intro.  Last one should be a "Succedded" message.  Next-to-last should
be a confirmation of action.  All other functions in the list should collect
informaion on a walkthrough of some process.

Each function will be called with a window reference as the first argument.
Draw whatever you'd like in I<that> Window.  

=head2 Terminology

=over 4

=item Panel

Each time the user is prompted is called a "panel".  The panels look
something like this:

        |------------------|
        |     |            |
        |     |            |
        |     |            |  A panel, made up of 3 parts
        |     |            |
        |     |            |
        |     |            |
        --------------------
        |         |==| |==||
        --------------------

The leftmost frame is a frame which contains instructions, pretty
pictures, or whatever.  It remains constant through each panel.
This panel is set with the -lpanel option when creating the Wizard.

The bottommost frame contains the buttons.  The buttons are labeled
Next, Back, Cancel, Finish and Done as appropriate.

The rightmost frame is where you should place your widgets, text,
etc... to prompt the user for action.

=back

=head2 Options

=over 4

=item -title

Title for the Wizard

=item -height

=item -width

Height and width for the wizard.  These should be set when the
wizard is created, and remain fixed for the duration of the object.
(Having the Wizard change size is _very_ distracting.)

=item -lpanel

A reference to a function which will draw the Wizard description
in the leftmost panel.  If the option is not given, some lame
text is produced.  This function is only called once, when the
Wizard is drawn.

=back

=head2 Methods

=over 4

=item $wizard->spoil

This method sets a flag which causes the wizard to repeat the
previous panel.   The data given in the prior panel should be
checked for validity in the panel _following_.

=item $wizard->spoiled

This method checks the spoiled flag to see if this panel was
re-called because the data was incomplete.
This flag is returned to a panel function to indicated it
was re-called because of incorrect data.

In a chain of panels:

                           Err! Go
                            Back
                            to F3
        F1  ->  F2 -> F3 --> F4  -> F5 -> F6
                      ^      |
                      -------|  

To repeat an element (say, F3), the successive function (F4) must use
C<spoil> and then return from the callback.  Function F3 will then be
recalled, with C<spoiled> set.  

The function which raised the exception (F4 in this case) has the option of
popping up an error dialog, or simply forcing the user back a step.

In other words, to verify that the fields in F3 have been configured
properly, they must be I<checked> in F4.

=item $wizard->returncode(I<retval>)

This sets the return code for the entire wizard.  (This should probably be
done in the last panel.)  If unset, the Wizard returns I<undef>.

=item $wizard->wizfunc(I<panelno>)

This is the counter used by the Wizard to determine where in the
function list it is at.  Advancing it, makes it skip frames.  Retarding
it makes it repeat frames.  This is normally used I<internally> by the
Wizard.  Use with caution.  It's not the preferred way to repeat a
frame.

=item $wizard->predestroy(I<coderef>)

This I<coderef> is called before the panel is exited with the Next button.
So if "Next" or "Finish" is pressed, call this function if set.  (It is
unset if the panel is redrawn.)  This function is NOT called if the Back
button, Done or Cancel is pressed.

This is NOT the preferred way of establishing field-checking on panels.
Don't mess with C<spoil> or any of that stuff.  Its intended purpose is
to empty text and listboxes before they are destroyed.

=back 4

=head1 EXAMPLE

        use Tk;
        use Wizard;

        $top=new MainWindow;
        $wiz=$top->Wizard(-title => 'Test Wizard', -lpanel => \&message);
        $wiz->add(\&Panel1, \&Panel2, \&Panel3);
        $status=$wiz->Show;

        sub message {

                $$window->Label(-text => "\n\n\nTesting the Wizard")->pack;
        }

        sub Panel1 {

                $$window->Label(-text => "Please enter something")->pack;
                if ($wiz->spoiled) {
                        $$window->Label(-text => "again.")->pack;
                }
                $$window->Entry(-textvariable => \$text)->pack;
        }

        sub Panel2 {

                if (! $text) {
                        $msg=$wiz->Dialog(-title => "Error",
                                -text => "Enter something",
                                -buttons => [ "ok" ]);
                        $msg->Show;
                        $wiz->spoil;
                        return;
                }
                $$window->Button(-text => "Press for food pellet")->pack;
        }

        sub Panel3 {

                $$window->Label(-text => "\n\nDone!\n\n")->pack;
        }

=head1 BUGS

My first OO attempt.  I'm sure to have blown something.

=cut

package Tk::Wizard;
require 5.002;

Tk::Widget->Construct('Wizard');

sub Populate {

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

        return $cw;

Quote:
}

sub DESTROY {

Quote:
}

#
#  Public Methods
#
sub add {


Quote:
}

sub Show {

        if (! exists $self->{WizardM}) {
                $self->{Wizfunc}=0;

                $self->{WizardM}=$self->
                        Toplevel(-title => $self->{Configure}{-title});

                $self->{WizardFrame}=$self->{WizardM}->
                        Frame(-height =>
                                $self->{Configure}{height}?
                                        $self->{Configure}{height}:400,
                              -width=>
                                $self->{Configure}{width}?
                                        $self->{Configure}{width}:500,)->pack;

                $self->{WizardFrame}->packPropagate(0);
                $self->{WizardFrame}->
                        bind('<Destroy>', sub { &Wizdestroyed($self);}  );

                $self->{Wizsuccess}=undef; # undef;

                do {
                        &Wizpaint(\$self);
                        $self->{WizardM}->grab;
                        $self->waitVariable(\$self->{Wizfunc});
                } while (defined $self->{Wizfunc});

                $self->{WizardM}->destroy;
        }
        $retval=$self->{Wizsuccess};
        $self->{Wizfunc}=undef;
        if ($self) { $self->destroy; }
        return($retval);        

Quote:
}

sub returncode {

        $self->{Wizsuccess}=$status;

Quote:
}

sub spoil {

        $self->{Wizardspoil}=1;

Quote:
}

sub spoiled {

        return($self->{Wizardspoiled});

Quote:
}

sub wizfunc {

        if ($value) {
                $self->{Wizfunc}=$value;
                &Wizpaint(\$self);
        } else {
                return($self->{Wizfunc});
        }

Quote:
}

sub predestroy {

        print STDERR "Bleh\n";
        $self->{WizPreDestroy}=$value;      

Quote:
}

#
# Private Methods
#
sub Wizpaint {

TOP:
        if ($$self->{UFrame}) {
                foreach($$self->{UFrame}->children) {
                        destroy $_;
                }
        }

        if (! $$self->{TopFrame}) {
                $$self->{TopFrame}=$$self->{WizardFrame}
                        ->Frame()->pack(-side => 'top',
                        -fill => 'both', -expand => 1);
                $$self->{TopFrame}->packPropagate(1);
                #
                # Informational Frame, to the left
                #
                $$self->{Funframe}=$$self->{TopFrame}->Frame(-borderwidth=>1,
                        relief => 'groove')
                        ->pack(-side => 'left', -padx => '5',
                        -fill => 'y');

                if (! defined $$self->{Configure}{-lpanel}) {
                        my $foo=$$self->{Funframe}->
                                Label(-text => "\n\n\n\nSetup Wizard")->pack;
                } else {
                        &{$$self->{Configure}{-lpanel}}(\$$self->{Funframe});
                }

                $$self->{UFrame}=$$self->{TopFrame}->Frame->pack(-fill => 'both');
        }


        $$self->{WizPreDestroy}=undef;           # Clear the callback.
        $thing=$$self->{UFrame};
        &$sub( \$thing );                # Paint custom widgets for this frame
        $$self->{Wizardspoiled}=0;
        if ($$self->{Wizardspoil}) {
                $$self->{Wizfunc}--;
                $$self->{Wizardspoil}=0;
                $$self->{Wizardspoiled}=1;
                goto TOP;   # Sorry, it begged for one.
        }
        $$self->Button(-text => 'flalalalalaoo')->pack;
        &Wizbuttons($self);

Quote:
}

sub Wizdestroyed {

        $self->DESTROY;

Quote:
}

sub Wizbuttons {


        $$self->update;

        if (! $$self->{WizButFrame}) {
                $$self->{WizButFrame}=$$self->{WizardFrame}->Frame(-borderwidth => 5, -relief => 'groove')
                        ->pack(-fill => 'x', -side => 'bottom');
        } else {
                foreach($$self->{WizButFrame}->children) {
                        destroy $_;
                }
        }

        if (! $$self->{WizButLine}) {
                $$self->{WizButLine}=$$self->{WizardFrame}->Frame(-relief => 'groove', -height=> 5,
                        -borderwidth=>2,
                        -width => 50)->pack(-fill => 'x', -side => 'bottom');
                $$self->{WizButLine}->packPropagate(0);
        }


        #
        # On the Last pane, present only "done".
        #
        if ($$self->{Wizfunc} == ($num-1)) {
                $$self->{WizCancel}=$$self->{WizButFrame}
                        ->Button(-text => 'Done',
                        -command => sub { $$self->{Wizfunc}=undef; })->
                        pack(-side => 'right', -padx => 10, -pady => 10);
                return;
...

read more »



Tue, 13 Mar 2001 03:00:00 GMT  
 New Module: Tk::Wizard
Whoops.  For some reason, the version of perl/tk that I was using
let my erase the ConfigSpecs, and it kept on working.  Sorry 'bout
that.  Here's attempt #2:

---------cut-----------
#!/usr/bin/perl

=head1 NAME

Wizard - Multipanel walk-through GUI

=head1 SYNOPSIS

        use Wizard;

        $wizard=new Wizard;

=head1 DESCRIPTION

This module provides a mechanism for walking a user through
a series of dialogs in order to fetch information from him.

Each function in the list is called in succession.  First one should be
an intro.  Last one should be a "Succedded" message.  Next-to-last should
be a confirmation of action.  All other functions in the list should collect
informaion on a walkthrough of some process.

Each function will be called with a window reference as the first argument.
Draw whatever you'd like in I<that> Window.  

=head2 Terminology

=over 4

=item Panel

Each time the user is prompted is called a "panel".  The panels look
something like this:

        |------------------|
        |     |            |
        |     |            |
        |     |            |  A panel, made up of 3 parts
        |     |            |
        |     |            |
        |     |            |
        --------------------
        |         |==| |==||
        --------------------

The leftmost frame is a frame which contains instructions, pretty
pictures, or whatever.  It remains constant through each panel.
This panel is set with the -lpanel option when creating the Wizard.

The bottommost frame contains the buttons.  The buttons are labeled
Next, Back, Cancel, Finish and Done as appropriate.

The rightmost frame is where you should place your widgets, text,
etc... to prompt the user for action.

=back

=head2 Options

=over 4

=item -title

Title for the Wizard

=item -height

=item -width

Height and width for the wizard.  These should be set when the
wizard is created, and remain fixed for the duration of the object.
(Having the Wizard change size is _very_ distracting.)A

=item -lpanel

A reference to a function which will draw the Wizard description
in the leftmost panel.  If the option is not given, some lame
text is produced.  This function is only called once, when the
Wizard is drawn.

=back

=head2 Methods

=over 4

=item $wizard->spoil

This method sets a flag which causes the wizard to repeat the
previous panel.   The data given in the prior panel should be
checked for validity in the panel _following_.

=item $wizard->spoiled

This method checks the spoiled flag to see if this panel was
re-called because the data was incomplete.
This flag is returned to a panel function to indicated it
was re-called because of incorrect data.

In a chain of panels:

                           Err! Go
                            Back
                            to F3
        F1  ->  F2 -> F3 --> F4  -> F5 -> F6
                      ^      |
                      -------|  

To repeat an element (say, F3), the successive function (F4) must use
C<spoil> and then return from the callback.  Function F3 will then be
recalled, with C<spoiled> set.  

The function which raised the exception (F4 in this case) has the option of
popping up an error dialog, or simply forcing the user back a step.

In other words, to verify that the fields in F3 have been configured
properly, they must be I<checked> in F4.

=item $wizard->returncode(I<retval>)

This sets the return code for the entire wizard.  (This should probably be
done in the last panel.)  If unset, the Wizard returns I<undef>.

=item $wizard->wizfunc(I<panelno>)

This is the counter used by the Wizard to determine where in the
function list it is at.  Advancing it, makes it skip frames.  Retarding
it makes it repeat frames.  This is normally used I<internally> by the
Wizard.  Use with caution.  It's not the preferred way to repeat a
frame.

=item $wizard->predestroy(I<coderef>)

This I<coderef> is called before the panel is exited with the Next button.
So if "Next" or "Finish" is pressed, call this function if set.  (It is
unset if the panel is redrawn.)  This function is NOT called if the Back
button, Done or Cancel is pressed.

This is NOT the preferred way of establishing field-checking on panels.
Don't mess with C<spoil> or any of that stuff.  Its intended purpose is
to empty text and listboxes before they are destroyed.

=back 4

=head1 EXAMPLE

        use Tk;
        use Wizard;

        $top=new MainWindow;
        $wiz=$top->Wizard(-title => 'Test Wizard', -lpanel => \&message);
        $wiz->add(\&Panel1, \&Panel2, \&Panel3);
        $status=$wiz->Show;

        sub message {

                $$window->Label(-text => "\n\n\nTesting the Wizard")->pack;
        }

        sub Panel1 {

                $$window->Label(-text => "Please enter something")->pack;
                if ($wiz->spoiled) {
                        $$window->Label(-text => "again.")->pack;
                }
                $$window->Entry(-textvariable => \$text)->pack;
        }

        sub Panel2 {

                if (! $text) {
                        $msg=$wiz->Dialog(-title => "Error",
                                -text => "Enter something",
                                -buttons => [ "ok" ]);
                        $msg->Show;
                        $wiz->spoil;
                        return;
                }
                $$window->Button(-text => "Press for food pellet")->pack;
        }

        sub Panel3 {

                $$window->Label(-text => "\n\nDone!\n\n")->pack;
        }

=head1 BUGS

My first OO attempt.  I'm sure to have blown something.

=cut

package Tk::Wizard;
use Data::Dumper;
require 5.002;
use English;

Tk::Widget->Construct('Wizard');

sub Populate {

        $cw->SUPER::Populate($args);
        $cw->ConfigSpecs(
            -title =>  ['PASSIVE', 'title', 'Title', 'Wizard'],
            -lpanel => ['PASSIVE', 'lpanel', 'Lpanel', ''],
        );
        return $cw;

Quote:
}

sub DESTROY {

Quote:
}

#
#  Public Methods
#
sub add {


Quote:
}

sub Show {

        if (! exists $self->{WizardM}) {
                $self->{Wizfunc}=0;

                $self->{WizardM}=$self->
                        Toplevel(-title => $self->{Configure}{-title});

                $self->{WizardFrame}=$self->{WizardM}->
                        Frame(-height =>
                                $self->{Configure}{height}?
                                        $self->{Configure}{height}:400,
                              -width=>
                                $self->{Configure}{width}?
                                        $self->{Configure}{width}:500,)->pack;

                $self->{WizardFrame}->packPropagate(0);
                $self->{WizardFrame}->
                        bind('<Destroy>', sub { &Wizdestroyed($self);}  );

                $self->{Wizsuccess}=undef; # undef;

                do {
                        &Wizpaint(\$self);
                        $self->{WizardM}->grab;
                        $self->waitVariable(\$self->{Wizfunc});
                } while (defined $self->{Wizfunc});

                $self->{WizardM}->destroy;
        }
        $retval=$self->{Wizsuccess};
        $self->{Wizfunc}=undef;
        if ($self) { $self->destroy; }
        return($retval);        

Quote:
}

sub returncode {

        $self->{Wizsuccess}=$status;

Quote:
}

sub spoil {

        $self->{Wizardspoil}=1;

Quote:
}

sub spoiled {

        return($self->{Wizardspoiled});

Quote:
}

sub wizfunc {

        if ($value) {
                $self->{Wizfunc}=$value;
                &Wizpaint(\$self);
        } else {
                return($self->{Wizfunc});
        }

Quote:
}

sub predestroy {

        print STDERR "Bleh\n";
        $self->{WizPreDestroy}=$value;      

Quote:
}

sub Wizdestroyed {

        $self->DESTROY;

Quote:
}

#
# Private Methods
#

sub Wizpaint {

TOP:
        if ($$self->{UFrame}) {
                foreach($$self->{UFrame}->children) {
                        destroy $_;
                }
        }

        if (! $$self->{TopFrame}) {
                $$self->{TopFrame}=$$self->{WizardFrame}
                        ->Frame()->pack(-side => 'top',
                        -fill => 'both', -expand => 1);
                $$self->{TopFrame}->packPropagate(1);
                #
                # Informational Frame, to the left
                #
                $$self->{Funframe}=$$self->{TopFrame}->Frame(-borderwidth=>1,
                        relief => 'groove')
                        ->pack(-side => 'left', -padx => '5',
                        -fill => 'y');

                if (! defined $$self->{Configure}{-lpanel}) {
                        my $foo=$$self->{Funframe}->
                                Label(-text => "\n\n\n\nSetup Wizard")->pack;
                } else {
                        &{$$self->{Configure}{-lpanel}}(\$$self->{Funframe});
                }

                $$self->{UFrame}=$$self->{TopFrame}->Frame->pack(-fill => 'both');
        }


        $$self->{WizPreDestroy}=undef;           # Clear the callback.
        $thing=$$self->{UFrame};
        &$sub( \$thing );                # Paint custom widgets for this frame
        $$self->{Wizardspoiled}=0;
        if ($$self->{Wizardspoil}) {
                $$self->{Wizfunc}--;
                $$self->{Wizardspoil}=0;
                $$self->{Wizardspoiled}=1;
                goto TOP;   # Sorry, it begged for one.
        }
        $$self->Button(-text => 'flalalalalaoo')->pack;
        &Wizbuttons($self);

Quote:
}

sub Wizbuttons {


        $$self->update;

        if (! $$self->{WizButFrame}) {
                $$self->{WizButFrame}=$$self->{WizardFrame}->Frame(-borderwidth => 5, -relief => 'groove')
                        ->pack(-fill => 'x', -side => 'bottom');
        } else {
                foreach($$self->{WizButFrame}->children) {
                        destroy $_;
                }
        }

        if (! $$self->{WizButLine}) {
                $$self->{WizButLine}=$$self->{WizardFrame}->Frame(-relief => 'groove', -height=> 5,
                        -borderwidth=>2,
                        -width => 50)->pack(-fill => 'x', -side => 'bottom');
                $$self->{WizButLine}->packPropagate(0);
        }


        #
        # On the Last pane, present only "done".
        #
        if ($$self->{Wizfunc} == ($num-1)) {
                $$self->{WizCancel}=$$self->{WizButFrame}
                        ->Button(-text => 'Done',
                        -command => sub { $$self->{Wizfunc}=undef; })->
                        pack(-side => 'right', -padx => 10, -pady => 10);
                return;                 # No more buttons
        }
        #
        # Always present
...

read more »



Tue, 13 Mar 2001 03:00:00 GMT  
 New Module: Tk::Wizard

Quote:

> Damn.  Let's try that again.
> OK, for some odd reason it worked FINE on my 5.004 build, but
> apparently not elsewhere.  OK, here's the fix:
> 208,209d207
> <       $cw->ConfigSpecs( -lpanel => [ PASSIVE, undef, undef, undef ],
> <                       -title => [ PASSIVE, undef, undef, undef]  );

or, so options can be set in the options database:

        $cw->ConfigSpecs(
            -title =>  ['PASSIVE', 'title', 'Title', 'Default Title'],
            -lpanel => ['PASSIVE', 'lpanel', 'Lpanel', undef],
        );



Tue, 13 Mar 2001 03:00:00 GMT  
 
 [ 3 post ] 

 Relevant Pages 

1. RFQ: Tk::Wizard - a wizard style widget for Perl/Tk

2. RFQ: Tk::Wizard - a wizard style widget for Perl/Tk

3. Tk::* NAMESPACE - Tk::Wizard

4. New Module: Tk::DDTList - sophisticated Tk::BrowseEntry

5. RFC: new modules Tk::DataEditor and Tk::DataEditorDialog

6. ANNOUNCE: Call for testing new module: Tk::Tie::MenuHash

7. New module Tk::TreeGraph

8. New Tk Module Conventions

9. New Module: Tk::DDTList

10. New version of the Perl/Tk module list

11. New version of Tk::ObjScanner and Tk::ObjEditor

12. comp.lang.perl.modules (was Re: New module...)

 

 
Powered by phpBB® Forum Software