Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Putting a Progress Bar, BitMap, Animation or Dialog in a Multi Pane Status Bar (The Easy Way).

0.00/5 (No votes)
20 Apr 2004 2  
Put a Progress Bar in a pane of your status bar with just <b>three lines of code</b>, or a bitmap, animation or <b>anything with a HWND</b> with just <b>one</b>.

Sample application:
The red arrow points to an AVI control, 
the blue one to a progress bar, 
and the green one to a bitmap. 
The first and last panes show icons.

The red arrow points to an AVI control, the blue one to a progress bar, and the green one to a bitmap. The first and last panes show icons.

Introduction

Three lines of code, that's all you need to show a progress bar in one of the panes of a status bar: one to create it, one to update its position, and one to close it.

A call to ProgCreate() will create a progress bar in the status bar's pane of your choice.

A call to ProgSetPos() (or one of its many equivalents) will set the progress bar's position wherever you like. A call to ProgDestroyWindow() will close the progress bar, leaving the status bar as was.

Or, if your desire is to show a bitmap in a pane, just one line of code will do: SetBitmap() is all you need. If you fancy animations, one line of code is all you need: a call to AnimCreate(), and you're done.

And, for flexibility, just throw anything with an HWND into a pane, with one line of code: a call to AddAny().

Of course, you'll have to include the relevant headers, and declare a member variable... but that doesn't count as code, does it?

Background

The problems:

Your program performs from time to time a lengthy operation, so you decide to use a progress bar during it (or, if the operation's duration is unknown and hard to guess, such as connecting to a server or database, showing an animation might be your choice). Everyone else does it, right? If your app has a status bar, that's where you'd like to put it. Since your Internet browser and your word processor use a multi-pane status bar to do this, and put a 'Loading...' or 'Saving...' message next to the progress bar, you decide to do just the same, and, by the way, to add your company's logo as a bitmap in another pane, because your boss, after seeing an application which shows its provider's logo in the status bar, cried "Me too!" (and that means you).

Everything looks fine... until you (or your QA person, or a user) resize your window while the progress bar is shown, and the progress bar slips out of its pane, while the bitmap escapes the bounds of the status bar altogether. Or a user browses around menus, and the string at the status bar which explains what the current menu choice does is obscured by the progress bar and/or bitmap.
And you plan to write more than one WTL application...

The solution:

Let the status bar own a progress bar, maybe a few bitmaps, and from time to time an animation, both as child windows and as C++ members, and move them around when handling WM_SIZE, and hide or show them on SB_SIMPLE.

The ATL/WTL way to add functionality to an existent class, is to write a mixin class which implements the said functionality, and throw it into the mix. So, four implementation classes are defined in the header:

  • CProgressBarInPaneImpl<T> contains a WTL::CProgressBarCtrl, in a pane of its sister WTL::CMultiPaneStatusBarImpl, thus adding progress bar functionality to the mix.
  • CBitmapInPaneImpl<T> handles an array of fifteen structures, each one managing a bitmap linked to a pane (using WTL::CStatic). Your status bar can turn into a miniature art gallery.
  • CAnimationInPaneImpl<T> contains a WTL::CAnimateCtrl, tied to a pane.
  • CAnythingInPaneImpl<T> will take any HWND you give it (up to 15, again), move it over a pane and keep it there. In the sample application, you can see a modeless dialog.

These classes, together with WTL::CMultiPaneStatusBarImpl, are inherited by non-template classes in several combinations. The sample application uses CMPSBarWithAll, which, as the alert reader might have cleverly assumed, uses the functionality of all the template classes described in this article.

Implementation classes contain the WTL classes which are in charge of status bars, bitmaps and animations, thus letting WTL handle most of the functionality, and keeping compatibility with future releases.

Containment (buzzword for 'having a member of the other class, and not making it public') allows to expose only part of the member's functionality: for instance, there's no reason to expose the option to create a CProgressBarCtrl without the Status Bar for a parent, or to allow opening a second CProgressBarCtrl when one is already open: most applications don't display more than one progress bar simultaneously, and the class complies with this de-facto standard. Otherwise, the need would arise to manage a container full of CProgressBarCtrls, check that they don't share panes, and, to make a long story short, a lot of work (pun carefully avoided) for very little reward.

The correspondent container class exposes most of CProgressBarCtrl's functions (with a "Prog" prefix: e.g., SetPos(int nPos) becomes ProgSetPos(int nPos)), but Create and Destroy are completely replaced.

Also, SB_SIMPLE is handled by the classes, to hide and show the progress bar/animation/bitmaps in sync with the panes over which they are displayed.

How things work

Those interested in the actual code can read MultiPaneStatusBarEx.h, there are quite a few comments in the code, I tried to be as explicit as possible.

For those who are satisfied by the highlights, let's say that the first three implementation classes are mixins which aggregate one or more members of the WTL class which provide similar functionality, have Create() and Destroy() functions which take a pane's ordinal as their first parameter (if needed), handle SB_SIMPLE by hiding or showing their goods, and setting bHandled to false to let WTL::CMultiPaneStatusBarImpl get a shot at the message, and implement each a version of UpdatePanesLayout() which moves whatever they contain around to keep in sync with their sister WTL::CMultiPaneStatusBarImpl.
CAnythingInPaneImpl is different here: it does not create the HWNDs it holds (although it can optionally destroy them), and it does not make the status bar their parent, so notifications, if any, are sent to the original parent. It is meant mainly to hold modeless dialog boxes, and controls: programmers will put combos in the most unusual places...

Using the code

Let's assume you are familiar with WTL's CMultiPaneStatusBarCtrl, if not, please check the articles quoted in the References section at the bottom, particularly How to use the WTL multipane status bar control by Ed Gadziemski.

To begin with, #include <MultiPaneStatusBarEx.h>. Please note that you must include the atlctrlx.h header file, which defines the CMultiPaneStatusBarCtrl class, and atlctrls.h for WTL::CProgressBarCtrl, WTL::CStatic and WTL::CAnimateCtrl.

Wherever you'd use a CMultiPaneStatusBarCtrl, use one of the non-template classes defined in MultiPaneStatusBarEx.h instead. Usually, it'll be a member of your frame class.

Adding Progress Bars

A status bar with an animation in its second pane, 
a progress bar in the third,
and a bitmap in the fourth

After you have attached the CMPSBarWithProgress, or CMPSBarWithProgressAndBMP, or CMPSBarWithAll to your status bar's HWND, and set the panes using an array of string resource IDs, you can start playing with progress bars: to create one, call ProgCreate(). The first parameter is the zero-based ordinal of the pane where you want to create your progress bar, the second and third are the limits of the progress bar's range (the default, 1 to 100, assumes percentages are widely used), and all other parameters have reasonable defaults (a polite way to say 'better don't touch').

You can set the bar's position by calling ProgSetPos(), ProgOffsetPos() or ProgStepIt(), which resolve to SetPos(), OffsetPos() and StepIt() respectively. If you decide to use ProgStepIt(), you'll most likely want to call ProgSetStep() before.

When you're done with progress, call ProgDestroyWindow(), which closes the progress bar.

    // Function declarations, for opening and closing 

    // a progress bar, from class CProgressBarInPaneImpl

    BOOL ProgCreate(int iPane, // Status pane where 

      // we'll create the progress bar, zero-based.

      int nMin = 0, int nMax = 100,  // Progress bar initial range

      DWORD dwStyle = WS_CHILD | WS_VISIBLE | PBS_SMOOTH, 
                                     // Progress bar styles

      DWORD dwExStyle = 0
    ); 
    void ProgDestroyWindow(void);

    // Contained accessors, all of them inline (there's a dozen):

    int ProgSetPos(int nPos)        { return m_Progress.SetPos(nPos); }
    int ProgStepIt()                { return m_Progress.StepIt(); }

    // Member accessors

    int   ProgGetPane() const       { return m_iProgressPane; }

    // Data members

protected:
    CProgressBarCtrl m_Progress;  // This is the contained control, 

              // which does the 'real work'.

    int m_iProgressPane;   // Pane ordinal where the progress 

              // bar resides, or -1 when off.

The pane where you displayed the progress bar, bitmap or animation, is hidden by it, so if there is some text there, it'll be shown when you destroy the progress bar.

Caveat:

As Erik Johnson has kindly pointed, if a progress bar is shown, and the user opens a menu, and starts browsing menus with the left-right keys, the progress bar seems to blink. This is a result of the way those keys are handled: the status bar goes to multi pane when coming out of a menu, and then to single pane when going into the next.
The behavior can be reproduced in Internet Explorer, while loading a page, specially if your Favorites folder is rather full.
There's no way to avoid this, without tampering with WTL::CFrameWindowImpl. To minimize this, consider calling ProgSetPos() not too often: for instance, there's no sense in updating a 50-pixel wide status bar 500 times.

Adding Bitmaps

If bitmaps are your choice, one line of code will do it:

m_stat.SetBitmap(int nPane, HBITMAP hb, bool bManage)

The parameters, as you'd expect, are the ordinal of the desired pane, a handle to your bitmap, and a bool which tells the class if you'd like it to destroy the bitmap when it's done using it. You can also call DestroyBitmap(int iPane) to destroy the static window which shows the bitmap.

Some readers might think: "Hey, wait a minute! If I have to write the code to load the bitmap myself, that's more than one line of code!" Well, you're right. So there is an overloaded version of the function, which takes as its second parameter the resource ID of your bitmap. This one takes no bool, since you don't have access to the HBITMAP anyway.

Caveat:

The bitmap is stretched or squeezed by CStatic until it is of suitable size for the pane. This means that if you load a bitmap too big for its pane, or with very different proportions, it might look like a frog trampled by an elephant. (No animals were hurt in the writing of this article.)

Adding an Animation

If your status bar member variable inherits from CAnimationInPaneImpl, you can show an AVI file or resource in it, by calling:

AnimCreate(int iPane, ATL::_U_STRINGorID FileName, 
  DWORD dwStyle,  DWORD dwExStyle)
//  The last two parameters have reasonable defaults

To hide the animation, use AnimDestroyWindow(void).

ATL::_U_STRINGorID is an ATL helper class which takes either a string (holding a file name) or an integer (holding a resource's ID) at its constructor, thus making a function which takes it for parameter more versatile.

Caveats:

The animation control does NOT size itself to the rectangle passed to it on creation, so you need an AVI file (or resource) of a suitable size. The one displayed by the sample application, provided by Microsoft with Visual Studio, shows how ugly things might get, but if you build your own AVI, there's much to gain. The animation control itself is, according to what I vaguely remember having read, quite picky about the animation formats it's willing to handle: just uncompressed AVI, in fact, so don't try to put a GIF there!

Adding Anything with an HWND.

A status bar with a modeless dialog in its third pane

Classes inheriting from CAnythingInPaneImpl (currently, only CMPSBarWithAll), let you put controls or modeless dialogs in a pane. As a user, I prefer seeing interactive controls in a rebar band, which is what most of us are used to, but the choice is yours.

One line of code, a call to AddAny(), is all that it takes:

    // Add a window to the status bar, in a pane chosen by ordinal.

    BOOL AddAny(int iPane, // Zero based ordinal 

            // (not resource ID) of the chosen pane.

            HWND hw, // Handle to a window, whatever control 

                //you like (or a dialog of yours)...

            bool bManage = false) // If true, DestroyWindow() 

                     // will be called on the HWND.

In the sample app, there's a modeless dialog, with a WTL::CHyperLink pointing either to this article or to the 'articles by this author' page, depending on the choice in a combo box.
If nothing else, it might help to keep the hit count high...

Adding a Combination of Any of the Above.

  • Class CMPSBarWithProgress adds progress bar support to CMultiPaneStatusBarCtrl, using WTL::CProgressBarCtrl.
  • CMPSBarWithBitmaps adds bitmap (up to fifteen) support to CMultiPaneStatusBarCtrl, using WTL::CStatic.
  • CMPSBarWithProgressAndBMP adds progress bar and bitmap support to CMultiPaneStatusBarCtrl.
  • CMPSBarWithAnimation adds AVI animation support to CMultiPaneStatusBarCtrl, using WTL::CAnimateCtrl.
  • CMPSBarWithPrg_BMP_Anim adds progress bar, bitmap and animation support to CMultiPaneStatusBarCtrl.
  • CMPSBarWithAll adds progress bar, bitmap, animation and 'anything with an HWND' support to CMultiPaneStatusBarCtrl.

If you've read this far, you can easily guess what kind of extensions would provide the other classes in MultiPaneStatusBarEx.h, had they been implemented. So much to do, so little time!

Dynamically Adding and Removing Panes.

If you have read CExtStatusControlBar - Managing status bar's panes is getting easier, you'll probably ask: Hey, how do we let the user add and remove panes? MFC guys can do it!

WTL::CMultiPaneStatusBarCtrlImpl<class T, class TBase = CStatusBarCtrl>::SetPanes(int* pPanes, int nPanes, bool bSetText = true) implements the needed functionality, so it's not duplicated here. You just have to keep your ID array handy. Just clean everything from the added/removed pane to the right, and restore things later. Other useful functions there might be GetPaneTextLength(), SetPaneWidth(), and GetPaneIndexFromID(), all supplied by WTL.

The Sample Application

On the Screen

A status bar with a distorted icon in its second pane
and amodeless dialog in the third

In the first pane, you see the application icon, widened a bit to make it larger. You can even discern the 'A, T, L' there. The second shows an icon stretched so far that the word 'icon', originally written on it gets completely blurred, showing why we need bitmaps.

The third pane holds a modeless dialog, with a combo box and a hyperlink. In the combo, you can choose where the link will point: this article, or the 'articles by the same author' page.
You can see a bitmap in the fourth pane, chosen randomly from four, which were added to the project as resources. If you download the sample project and take a closer look at the pictures, you'll notice one of them (shown here) is an image of a bus, which gets crushed beyond recognition when it's squeezed into a pane. Even the 'WTL rocks' bitmap shown in the image below does not look very good. The last pane holds another icon, distorted to look wider, but still readable.

A status bar with an animation in its second pane, 
a progress bar in the third,
and a bitmap in the fourth

To make things complete, you can see here an animation (this one comes with Visual Studio) in the second pane, and a progress bar in the third.

Using the application

In the dialog, you can put the lower and upper bounds of a WTL::CProgressBarCtrl, and the selected pane, and it will run there on a timer. To spice things up a bit, whenever the progress bar is shown, an AVI animation plays in the pane to the left of it. The AVI file is named THIS.AVI, and it's assumed to be located in the same directory as the application. This gave me an easy way to test several AVI files, without recompiling.

The modeless dialog which is shown on the third pane can be used to reach this article, or the list of articles by the author. You can choose your destination in the combo.
Putting an interactive control in a status bar is not something I enjoyed doing, since in my opinion, read-only controls look better there, but if MFC programmers can do it (did you read the 'Article of the Month' for January? It is really good!), WTL programmers should be able too.

Design

In order to share the status bar, which belongs to the frame, with the view, I just passed a pointer. A tad crude, but efficient, since the purpose of this program is not to show class decoupling.

The sample application is by no means a design masterpiece, just a showcase for a few classes I hope you'll find useful, as I did with so many articles here at CodeProject.

References

History

  • March 2004: Created.
  • April 2004: Added support for WTL 7.0 (_U_STRINGorID was then under namespace WTL)
    Fixed a bug in the message map of CMPSBarWithProgressAndBMP, which referred to the wrong class.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here