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 CProgressBarCtrl
s, 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 HWND
s 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
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.
BOOL ProgCreate(int iPane,
int nMin = 0, int nMax = 100,
DWORD dwStyle = WS_CHILD | WS_VISIBLE | PBS_SMOOTH,
DWORD dwExStyle = 0
);
void ProgDestroyWindow(void);
int ProgSetPos(int nPos) { return m_Progress.SetPos(nPos); }
int ProgStepIt() { return m_Progress.StepIt(); }
int ProgGetPane() const { return m_iProgressPane; }
protected:
CProgressBarCtrl m_Progress;
int m_iProgressPane;
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)
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.
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:
BOOL AddAny(int iPane,
HWND hw,
bool bManage = false)
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
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.
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.