http://www.perlmonks.org?node_id=215789

Wx Control Cookbook :

the Wx::ProgressDialog


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.

Here is an example of a sample progress dialog. We can see the WxWindows developers have put together a collection of useful GUI primitives into the dialog, We have a configurable line of text (set to "processing item" in the screenshot), a progress meter, and 3 lines of text showing elapsed time, the estimated total time for the progress meter to reach 100 percent full, and the remaining time. Additionally, there's a button marked "Cancel". It may seem that this is a lot of information for, say, displaying the progress of a file copy, but this dialog has several configuration options which can help a developer make its appearance exactly what is required.

Some Source Code

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 :
  1. 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"
  2. "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.
  3. The maximum value that the progress meter is working towards. This is used with the Update method, described below
  4. A reference to a Wx::Frame (or potentially a Wx::Window) object. Should be set to $this (See above)
  5. 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
  1. 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.
  2. 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();