The Wx toolkit contains several objects that are designed to make a
programmer's life less difficult when it comes to communicating with a
user. These objects are primarily dialogs, such as the Wx::ColourDialog,
Wx::DirDialog, Wx::FileDialog, and Wx::TextEntryDialog, among
others.
1 : #!/usr/bin/perl
2 :
3 : use Wx;
4 : package MyApp;
5 : use strict;
6 : use vars qw(@ISA);
7 : @ISA=qw(Wx::App);
8 : sub OnInit {
9 : my($this) = @_;
10 : my($frame) = MyFrame->new("Progress bar demo", Wx::Point->new(50, 50), Wx::Size->new(450, 350));
11 : $this->SetTopWindow($frame);
12 : $frame->Show(1);
13 : 1;
14 : }
15 :
16 : package MyFrame;
17 :
18 : use strict;
19 : use vars qw(@ISA);
20 :
21 : @ISA=qw(Wx::Frame);
22 :
23 : use Wx::Event qw(EVT_MENU);
24 : use Wx qw(wxBITMAP_TYPE_ICO wxMENU_TEAROFF);
25 :
26 : sub new {
27 : my($class) = shift;
28 : my($this) = $class->SUPER::new(undef, -1, $_[0], $_[1], $_[2]);
29 : my($mfile) = Wx::Menu->new(undef, wxMENU_TEAROFF);
30 :
31 : my($ID_TEST, $ID_EXIT) = (1, 2);
32 : $mfile->Append($ID_TEST, "&Test Progress Dialog\tCtrl-T", "Display a test dialog");
33 : $mfile->Append($ID_EXIT, "E&xit\tAlt-X", "Quit this program");
34 :
35 : my($mbar) = Wx::MenuBar->new();
36 : $mbar->Append($mfile, "&Test");
37 : $this->SetMenuBar($mbar);
38 : EVT_MENU($this, $ID_TEST, \&OnTest);
39 : EVT_MENU($this, $ID_EXIT, \&OnQuit);
40 : $this;
41 : }
42 :
43 : sub OnQuit {
44 : my($this, $event) = @_;
45 : $this->Close(1);
46 : }
47 :
48 : use Wx qw(wxOK wxICON_INFORMATION wxVERSION_STRING);
49 : use Wx qw(:progressdialog);
50 :
51 : sub OnTest {
52 : my($this, $event) = @_;
53 : my($max) = 10;
54 : my $dialog = Wx::ProgressDialog->new('Progress dialog example',
55 : 'An example',
56 : $max, $this,
57 : wxPD_CAN_ABORT|
58 : wxPD_APP_MODAL|wxPD_ELAPSED_TIME|
59 : wxPD_ESTIMATED_TIME|
60 : wxPD_REMAINING_TIME);
61 :
62 : my($usercontinue) = 1;
63 : foreach (1 .. $max) {
64 : $usercontinue = $dialog->Update($_,"Processing item $_");
65 : #check for user interruption, then move on to data processing
66 : last if $usercontinue==0;
67 : sleep (1); #your code here!
68 : }
69 : $dialog->Destroy;
70 : }
71 :
72 : package main;
73 :
74 : my($app) = MyApp->new();
75 : $app->MainLoop();
Most of this is boilerplate taken from Mattai Barbon's minimal.pl, a
small "hello world"-esque example script. Pressing Ctrl-T or clicking on
the Test menu, then Test Progress Dialog menu item will bring up the
sample dialog.
The real work takes place in the sub OnTest (lines 51-70), where our example progress
dialog is initialized and displayed. Line 52 initializes $this, and
$event, variables passed by Wx to our sub because the sub is an event
handler. $this is a reference to the window or frame that the event is
acting upon (it may be easier if you think of $this as $self) and $event
contains a Wx::Event object. Line 53 sets
up the number of dummy items we'll be using to drive the process dialog.
Lines 54 to 60 create a new Wx::ProgressDialog. This particular example
has all of the options this particular sort of dialog can possess
enabled. Let's examine the parameters we're passing to the constructor :
-
We've got a string value that will be displayed in the
titlebar of the dialog. Our example value is set to "Progress Dialog
Example"
-
"An Example" is what the dialog will show as the initial line of
text. This is a good place for, e.g., file names, tasks, whatever
list of things are being processed.
- The maximum value that the progress meter is working towards. This
is used with the Update method, described below
- A reference to a Wx::Frame (or potentially a Wx::Window) object.
Should be set to $this (See above)
- Option flags. These flags can consist of any of the following
- wxPD_APP_MODALThe dialog will be modal to all windows
that have $this as a parent, and to the dialog's parent window (the
$this window). If this option is not set, the dialog will still
be modal to its parent, but not to sibling windows.
- wxPD_AUTO_HIDE When the progress meter reaches its
maximum value, the dialog will close itself.
- wxPD_CAN_ABORTAdds a "Cancel" button to the progress
dialog. When this button is pressed by the user, the dialog will
close. This action can be checked by examining the return value of
the Update method (explained below). This does not cancel any of the
script's processing, but simply flags the user's desire to end
processing.
- wxPD_ELAPSED_TIME, wxPD_ESTIMATED_TIME, and
wxPD_REMAINING_TIME These options will include the time
displays in the dialog.
If the constructor succeeds, the dialog displays automatically.
Lines 62-68 are where the script processes data, and updates the dialog.
Line 62 creates $usercontinue, a variable which will signal the user's
interruption of the dialog (see wxPD_CAN_ABORT, above).
Line 64 gives us the only method of communicating to the dialog, the
Update method. Update() takes 2 parameters
- A numeric value. Should be a positive value, and equal or less
than the the maximum set in the constructor. Setting this value above
the maximum will have unintended consequences.
- A string value used to update the text message in the dialog. This
parameter is optional. If it's missing, the text message will remain
the same as when the dialog was created, or what it was set to during
the last update.
Line 65 checks for $usercontinue. If it's set to zero, we exit the loop,
ending our processing early. In a weightier script, you may have to
perform clean up duties if this occurs. You may also choose to remove
$usercontinue from scripting, and check the return value of Update()
directly in an if statement.
Line 66 shows where any real processing would occur. Finally, line 68
calls Destroy, and cleans up the dialog.
There is another method to Wx::ProgressDialog not discussed here, called
Resume, which should only be called after the user's chosen to abort the
dialog.
Resume will redisplay the dialog from the point when
the user clicked "Cancel". It is still the programmer's duty to make
sure that the data being processed remains in sync with the dialog. Do
not call Resume if you've already called Destroy on a dialog.
An example
Let's say we've created the world's greatest GUI file copying utility,
ever. We'd like to have the app display which file it's currently
copying, and give the user the chance to stop copying if he chooses. We
also want to show the estimated time remaining to complete the copying,
but aren't entirely concerned about the elapsed time, nor the total time
the copying should take. We also have an array,
@items, which
contains the names of the files we're copying. Assume that we're looking
in a sub called OnCopy, and $this is a reference to our parent window.
Here's how the dialog would be created :
my $progress = Wx::ProgressDialog->new( 'Copying files',
$items[0],scalar @items, $this,
wxPD_CAN_ABORT | wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_REMAINING_TIME);
and here's where we copy the files, and update the dialog :
foreach (0..$#items) {
my $fn = $item[$_]];
my $destination = "$dir/$fn";
last if $progress->Update ( $_, "copying $fn to $destination") == 0;
copy ($fn, $destination); # include some GUI error handling later.
Wx::Yield(); # redraw, etc.
}
While it might seem unwieldy to use
(0..$#items) as the control
for the loop, the alternative would be something like
my $i=0; # loop incr
foreach (@items) {
my $fn = $item[$_];
my $destination = "$dir/$fn";
last if $progress->Update ( $i, "copying $fn to $destination") == 0;
copy ($fn, $destination); # include some GUI error handling later.
Wx::Yield(); # redraw, etc.
$i++;
}
This has the disadvantage of using a temporary variable ($i) where none
is needed. However, if you're dealing with a nested data structure, it
may save you keystrokes to do the latter.
In summary
This control is an excellent way to provide feedback to a user during
list processing, not only because of the progress bar, but because a
programmer can contextualize the progress bar with textual information
and time data. There is a small amount of overhead that can occur when
trying to implement schemes to use
Resume, but overall,
Wx::ProgressBar is a fast and worth widget in a GUI programmers toolkit.
The Code for Your Downloading Pleasure
I chose to wrap the above code snippets in tt tags rather than code tags
so I could use line numbering and provide several examples without
having to worry about problems downloading code. Presented here is code
tag wrapped source :
#!/usr/bin/perl
use Wx;
package MyApp;
use strict;
use vars qw(@ISA);
@ISA=qw(Wx::App);
sub OnInit {
my($this) = @_;
my($frame) = MyFrame->new("Progress bar demo", Wx::Point->new(50, 50
+), Wx::Size->new(450, 350));
$this->SetTopWindow($frame);
$frame->Show(1);
1;
}
package MyFrame;
use strict;
use vars qw(@ISA);
@ISA=qw(Wx::Frame);
use Wx::Event qw(EVT_MENU);
use Wx qw(wxBITMAP_TYPE_ICO wxMENU_TEAROFF);
sub new {
my($class) = shift;
my($this) = $class->SUPER::new(undef, -1, $_[0], $_[1], $_[2]);
my($mfile) = Wx::Menu->new(undef, wxMENU_TEAROFF);
my($ID_TEST, $ID_EXIT) = (1, 2);
$mfile->Append($ID_TEST, "&Test Progress Dialog\tCtrl-T", "Display a
+ test dialog");
$mfile->Append($ID_EXIT, "E&xit\tAlt-X", "Quit this program");
my($mbar) = Wx::MenuBar->new();
$mbar->Append($mfile, "&Test");
$this->SetMenuBar($mbar);
EVT_MENU($this, $ID_TEST, \&OnTest);
EVT_MENU($this, $ID_EXIT, \&OnQuit);
$this;
}
sub OnQuit {
my($this, $event) = @_;
$this->Close(1);
}
use Wx qw(wxOK wxICON_INFORMATION wxVERSION_STRING);
use Wx qw(:progressdialog);
sub OnTest {
my($this, $event) = @_;
my($max) = 10;
my $dialog = Wx::ProgressDialog->new('Progress dialog example',
'An example',
$max, $this,
wxPD_CAN_ABORT|
wxPD_APP_MODAL|wxPD_ELAPSED_TI
+ME|
wxPD_ESTIMATED_TIME|
wxPD_REMAINING_TIME);
my ($usercontinue) = 1;
foreach (1 .. $max) {
$usercontinue = $dialog->Update($_);
print $usercontinue;
last if $usercontinue==0;
sleep (1);
}
$dialog->Destroy;
}
package main;
my($app) = MyApp->new();
$app->MainLoop();