Click here to Skip to main content
15,878,748 members
Articles / Programming Languages / C++
Article

Collapsing Multiple Progress Bars

Rate me:
Please Sign up or sign in to vote.
4.38/5 (13 votes)
15 Nov 2007CPOL3 min read 48.9K   1.4K   47   4
An article on how to collapse the progress feedback given by multiple routines in a single progress bar
Screenshot - Article.gif

Introduction

Everybody knows the frustration caused by a new progress bar popping up and starting from 0% just after the previous one reached 100%. This annoyance is caused by several consecutive subroutines providing independent progress feedback. Collapsing these isolated progress bars into one bar running from 0 to 100% can be a nasty problem. In this article, an object-oriented approach is presented that provides an elegant and transparent solution to this problem.

Background

In complex software packages, there are often multiple lengthy tasks that provide progress feedback. A single user action could lead to a number of such tasks being executed, typically resulting in several consecutive instances of progress running from 0 to 100%. It is desirable to collapse the isolated instances of progress feedback into one overall instance of feedback running from 0 to 100%. This willl give the user a good notion of how much of the overall task has been finished and how much is left.

It is possible to solve this problem by hard-coding the progress range into every function or by passing the current progress as parameters to each function. However, the first solution is not very flexible (consider calling the same subroutines in a different order or different context) and the latter poses constraints to your code. Therefore this article presents a transparent and flexible solution to the above-described problem.

We introduce a CProgress class that subscribes itself to a linked list containing all active progress feedback in the application. This subscription mechanism, however, is completely hidden from a programmer using the CProgress class, which makes it very easy to use.

Using the Code

Consider a simple traditional class for progress feedback, CTraditionalProgress, which could be used in the following way:

C++
void A()
{
   CTraditionalProgress myProgress;
   myProgress.Set(0);
   B(); //call function B
   myProgress.Set(60);
   C(); //call function C
   myProgress.Set(100);
}

It is very simple to imagine how the code of CTraditionalProgress could look. The Set() function simply sets the progress bar to the number that is passed.

Now suppose that functions B() and C() have their own progress bars and suppose that we also want to capture that progress in the progress bar of function A(). In other words, we want to collapse the progress feedback of functions B() and C() into the progress bar of function A(). Well, that is exactly what the CProgress class does for us. The beauty of it is that we hardly need to change any code. Most especially, we do not need to pass any additional parameters to the B() and C() subroutines.

One small adjustment to our code concerns replacing the Set() member function with the SetRange() function. Rather than indicating what percentage of the progress has already finished, this function indicates the fraction of the total time that the next block of code is expected to demand. The example code now looks like this:

C++
void A()
{
   CProgress myProgress;
   myProgress.SetRange(0.60);
   B(); //call function B
   myProgress.SetRange(0.40);
   C(); //call function C
}

void B()
{
   CProgress myProgress;
   myProgress.SetRange(0.20);
   // Do something, estimated duration 20% of time for function B()

   for (int n=0; n < 80; n++) {
      myProgress.SetRange(0.01);
      // Do something, estimated duration 1%
   }
}

The progress feedback of function B() is automatically mapped on the range of 0.60, which was specified in function A(). If function B() calls any routines that provide feedback regarding their progress, then that is again collapsed to the range that was specified in function B(). This process of collapsing is illustrated in the figure below.

Screenshot - Collapse.gif

Points of Interest

If you are interested in the inner workings of the collapsing mechanism, have a look at the sample code and at the PDF article that is attached in the ZIP file. The main trick is the fact that each CProgress instance registers itself to a chained list. Since this chained list does not have an explicit owner or parent, it was decided to call it an "orphaned chained list." It is not hard to understand the CProgress source code. The class consists of less than 50 lines of code. That degree of efficiency could be reached because the traversal of the parent progress nodes is done by simple recursive calls.

History

  • 7 November, 2007 -- Version 1.0
  • 15 November, 2007 -- Downloads updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Netherlands Netherlands
As a 3D Imaging Scientist, I build clinical prototype software, mainly in the domain of 3D cardio-vascular x-ray. The value of the prototypes is evaluated in hospitals by physicians, who use them in cardio-vascular interventions. Further it is my task to stay in touch with the scientific developments in 3D medical imaging.

I have been writing software for about 20 years now. The past 6 years I concerned myself mainly with 3D medical image processing, visualization and GPU programming.

Comments and Discussions

 
GeneralNice, simple approach to the problem Pin
wtwhite12-Nov-07 16:29
wtwhite12-Nov-07 16:29 
GeneralRe: Nice, simple approach to the problem Pin
Danny Ruijters12-Nov-07 21:28
Danny Ruijters12-Nov-07 21:28 
GeneralRe: Nice, simple approach to the problem Pin
Vattila13-Nov-07 1:08
Vattila13-Nov-07 1:08 
Agree. Accumulating and setting the range in advance is a neat solution. My current solution uses scoped sections and relies on the constructor to set the range and the destructor to progress. This makes for more verbose code though:

void CalculateAndStore (int n, obj* p)
  {
  Busy::Section total ("Exporting objects"); // 100%

  // Initialize.
    {
    Busy::Section part ("initializing", 10); // %
    Init (n, p);
    }
  // Process.
    {
    Busy::Section part ("processing", 70); // %
    Process (n, p);
    }
  // Store.
    {
    Busy::Section part ("writing", 20); // %
    Store (n, p);
    }
  }

void Process (int n, obj* p)
  {
  Busy::Section total;
  for (int i; i != n; ++i)
    {
    Busy::Section part (1, n); // 1 part out of n total
    ProcessObject (p [i]);
    }
  }

Some comments:

- You should forward-declare CProgressCtrl in your header.

- Use an abstract Observer pattern instead of hard-coding the CProgressCtrl observer (this is already suggested in the code).

- SetRange repeatedly traverses the whole linked list to calculate the progress values. This is necessary only once for each nesting level, and can be eliminated altogether by tracking the global range and progress.

- Don't update the observer unless the rounded progress value changes (eliminate flicker).

- The destructor should not throw exceptions. The current code is not exception safe unless SetRange is guaranteed not to throw.

Regards,
Vidar Hasfjord
GeneralRe: Nice, simple approach to the problem Pin
Danny Ruijters16-Nov-07 3:09
Danny Ruijters16-Nov-07 3:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.