Fanning Software Consulting

Implementing a Progress Bar

QUESTION: I have a time-intensive calculation in one of my programs. I would like to have a "show progress" indicator that shows the user the progress of the calculation, which is done in a loop. I would also like the user to be able to interrupt the calculation if needed. How can I build this in IDL?

ANSWER: The basic idea here is to build a little widget program that can be updated by the program that is running the loop. I've written an example program called ShowProgress that illustrates this technique. After you download the program, compile it in IDL:

   IDL> .Compile showprogress

You will see four modules compiled:

   IDL> .COMPILE "C:\RSI\IDL52\david\showprogress.pro"
   % Compiled module: SHOWPROGRESS.
   % Compiled module: CENTERTLB.
   % Compiled module: TEST_EVENT.
   % Compiled module: TEST.

The ShowProgress module is the show progress indicator widget. The CenterTLB module is a utility routine I use to center the widget on the display. The modules Test and Test_Event are the programs that use the show progress indicator.

To see an example of the show progress indicator, just run the TEST program:

   IDL> Test

Study the Test_Event program to see how to incorporate the show progress indicator into your own code. You must have some kind of a loop in order to use a show progress indicator.

Here is how the ShowProgress program is called from within the Test_Event program:

   Widget_Control, event.top, TLB_Get_Offset=offsets
   info = ShowProgress(Message='Performing Large Calculation...', $
	XOffset=offsets(0)+50, YOffset=offsets(1)+50, $
 	DrawSize=200, ButtonTitle='Cancel Local Operation')

The return variable (info in this case) is a structure containing four fields: Cancel is the widget identifier of the Cancel button and is used to determine if that button is selected; WID is the window index number of the show progress graphics window and is required to draw graphics into the show progress graphics window; DrawSize is the X size of the show progress graphics window in pixel units; and Top is the widget identifier of the top-level base of the show progress indicator and is used to destroy the show progress indicator when it is no longer needed.

Interrupting the Loop

The basic idea behind interrupting the progress indicator is to check to see if the Cancel button has been selected. The important thing about this is that checking the button must be done explicitly with Widget_Event rather than with the XManager command.

Here is the relevant code inside the Test_Event program that tests to see if the Cancel button has been activated. The variable info.cancel is the widget identifier of the Cancel button and is part of the information returned by the ShowProgress function.

   progressEvent = Widget_Event(info.cancel, /NoWait)
            
      ; Is this a button event?
               
   eventName = Tag_Names(progressEvent, /Structure_Name)
   IF eventName EQ 'WIDGET_BUTTON' THEN BEGIN
            
      ; If it IS a button event, destroy the widget program and
      ; issue an informational message to the user to alert him or her.
                   
   Widget_Control, info.top, /Destroy
   result = Widget_Message('Operation Canceled!!', /Information)
                
      ; Escape from the loop. Interrupt code would go here.
                   
   GoTo, outSideLoop
   ENDIF

Notice that Widget_Event is called with the NoWait keyword. This is important. If it is not done, Widget_Event will wait for a button event, which is not what you want. You want to continue program execution unless you get an event.

Writing the Progress Bar as an Object

For a long time I have been unhappy with the program described above. It was just too hard to learn how to use it. I wanted to make it easier to understand and implement. So, I finally got around to writing a progress bar as an object, which is how it should have been done in the first place. The new program is called cgProgressBar. If you compile the program at the IDL command line, you will find an example program at the end of the file that shows you how it works.

   IDL> .Compile cgprogressbar__define
        % Compiled module: CGPROGRESSBAR::INIT.
        % Compiled module: CGPROGRESSBAR::CLEANUP.
        % Compiled module: CGPROGRESSBAR::CHECKCANCEL.
        % Compiled module: CGPROGRESSBAR::CREATEIMAGE.
        % Compiled module: CGPROGRESSBAR::DESTROY.
        % Compiled module: CGPROGRESSBAR::UPDATE.
        % Compiled module: CGPROGRESSBAR::UPDATEMASK.
        % Compiled module: CGPROGRESSBAR::START.
        % Compiled module: PROGRESSBAR_EXAMPLE_EVENT.
        % Compiled module: PROGRESSBAR_EXAMPLE.
        % Compiled module: CGPROGRESSBAR__DEFINE.
   IDL> ProgressBar_Example

The progress bar can be display with and without a Cancel button.

     IDL> progressbar = Obj_New('cgProgressBar', /Start, Percent=75)
A progress bar without a Cancel button.
A progress bar without a Cancel button.
 
     IDL> progressbar = Obj_New('cgProgressBar', /Start, Percent=75, /CancelButton)
A progress bar with a Cancel button.
A progress bar with a Cancel button.
 

If a Cancel button is shown, it is the user's responsibility to check to see if it has been clicked. The code to do so will look like this.

   cgProgressBar = Obj_New("cgProgressBar", /Cancel)
   cgProgressBar -> Start
   FOR j=0,9 DO BEGIN
      IF cgProgressBar -> CheckCancel() THEN BEGIN
         k = Dialog_Message('The user cancelled operation.')
         RETURN
      ENDIF
      Wait, 0.5  ; Would probably be doing something ELSE here!
      cgProgressBar -> Update, (j+1)*10
   ENDFOR
   cgProgressBar -> Destroy

There are a number of keywords that can be used to change the look of the progress bar. See the on-line help to learn how.

Version of IDL used to prepare this article: IDL 7.1.2.

Written: 1 August 1999
Updated: 28 September 2012