Showing Progress Bar/Icon/Arbitrary drawing in a status bar pane

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

It is based on the
technology contributed by Brad Mann (“Showing progress bar in a status bar
pane”) and the amendation by Thoams Stadler. This implementation is done as a
set of related MFC classes, with a reasonably clean interface. It also corrects
an error in the original code in which an attempt to create a control for a
pane that was clipped by the containing frame would create a control at the left of the status
bar because the GetItemRect method returns an empty rectangle for this case.

In the above bitmap, the rightmost four buttons create or destroy a
control in the status bar. The smiley face cycles through three
faces and then destroys the control; the progress bar cycles through five steps of
progress bar and then destroys the control. The combo box and edit controls are
alternatively created or destroyed. To illustrate what is happening, I log events
in the window, a CListView. I also log events such as edit-change and combo-selection-change,
a technique I discuss below.

I needed to display a sequence of icons in the status bar to indicate a
background thread was running, paused, terminated, etc. Once I saw how this
was done, I decided to change several text status panes to also use icons,
which meant that it would be easier if I had a C++ class. Having done this for
one class, I decided to do it for several. The zipfile includes a general
CWnd-derived class, and friend classes for edit, static, progress, and combo
controls. You can use these examples to add your own control classes. The
basic CWnd-derived class is shown below.

Adding a new Control

To add a new control, you must first add a new indicator to your status bar.

Adding a new Indicator

1. Go into the resource editor and add a string, with an ID such as
ID_INDICATOR_PROGRESS. Put some spaces or other characters into the string.
The length of the string determines the width of the status pane. (My library
sets the pane contents to “” so you can use a string like WWWW if you want).

2. In MainFrm.cpp, find the indicators array, whose name is “indicators”, and
add your new ID to the array in the position you want it to appear in the
status bar. It should follow the ID_SEPARATOR line.

3. Add the include files to MainFrm.cpp, ahead of the include of MainFrm.h. If
you are using one of the specific classes you will have to include
“StatusControl.h” before the desired class, for example:


#include “StatusControl.h”
#include “StatusProgress.h”

4. Declare a variable, public or protected, in MainFrm.h, of the appropriate
type, based on one of the types in the library (CStatusProgress for a progress
bar). For the rest of this example, assume it is called “progressbar”.

5. To create the window, you can either create it “on demand” or create it
during the “InitInstance” handler, depending on how you need it. When you
create it on demand you will typically destroy it when it is not needed.


progressbar.Create(&m_wndStatusBar, ID_INDICATOR_PROGRESS,
WS_VISIBLE | PBS_SMOOTH);

Note that my library automatically adds the WS_CHILD flag to the styles.

If you have done an on-demand create, you can destroy it by doing


progressbar.DestroyWindow();

6. To perform operations on the window, just call the normal methods of the
superclass, for example:


progressbar.SetRange(0, 500);
progressbar.StepIt();
progressbar.SetPos(value);

7. You must add an OnSize handler to CMainFrame. A typical MDI instance is
shown below. In this handler, call the Reposition method for each status bar
control you have created:

void CMainFrame::OnSize(UINT nType, int cx, int cy)
{
CMDIFrameWnd::OnSize(nType, cx, cy);
progressbar.Reposition();
}

Updating a control from a thread

If you are showing the progress of a background thread, I’ve found it
best to have the background thread use a user-defined message to notify the
main window that it should update the progress bar. There are some interesting
conditions that will cause the application to lock up if you try to access
the controls directly from another thread…for example, if the GUI thread blocks,
any thread that does a SendMessage to it will have to wait for the GUI thread
to run again, and this can actually create a deadlock situation.

Use a user-defined message.

While the traditional method for defining a user-defined message is to use a symbol
based on WM_USER, I’ve been done in so often by this technique that I only do
Registered Window Messages. I’ll show both methods here. The problem with
WM_USER-based messages is that you do not have a guarantee that your message
is unique. If you later write a DLL and don’t track the
WM_USER numbers you are using, you get conflicts; you can’t use DLLs other
people have written that use user-defined messages to post to a window; and
you can be done in by Microsoft, who has preempted any number of WM_USER
messages for their own controls. Note that now programmers are encouraged to
use the symbol WM_APP instead of WM_USER, but this only solves the problem of
conflicts with Microsoft code, not with DLLs you or other programmers may write.
Since a Registered Window Message is no harder
to use than a WM_USER/WM_APP-based message, I now use these exclusively.

Common to both WM_USER/WM_APP and Registered Window messages:

1. Choose a name for your message. I tend to do names like “UWM_” to indicate
a “User WM_” message. For example


// UWM_STEP_PROGRESS (WPARAM, LPARAM ignored)
// UWM_SET_PROGRESS (WPARAM is value, LPARAM ignored)

1. In MainFrm.h, add a declaration for a handler function in the afx_msg
section:

afx_msg LRESULT OnStepProgress(WPARAM, LPARAM);
afx_msg LRESULT OnSetProgress(WPARAM, LPARAM);

2. In MainFrm.cpp, add the implementations of the functions:

LRESULT CMainFrame::OnStepProgress(WPARAM, LPARAM)
{
progressbar.StepIt();
return 0;
}
LRESULT CMainFrame::OnSetProgress(WPARAM wParam, LPARAM)
{
progressbar.SetPos((int)wParam);
return 0;
}

4. To invoke the operation from the thread, do a PostMessage (not SendMessage)
to the main frame window.

AfxGetMainWnd()->PostMessage(UWM_STEP_PROGRESS);

Using a WM_USER message

1. Pick a value, such as (WM_APP+103), and define a symbol in an include file.


#define UWM_STEP_PROGRESS (WM_APP+103)
#define UWM_SET_PROGRESS (WM_APP+104)

2. In the module that implements the thread, and in MainFrm.cpp, include this
definition file.

3. In the message map for CMainFrame, add a line for each message:


ON_MESSAGE(UWM_STEP_PROGRESS, OnStepProgress)
ON_MESSAGE(UWM_SET_PROGRESS, OnSetProgress)

Using a Registered Window Message:

1. Choose a name for the message. I prefer to pick a useful name and then
suffix a truly unique ID using GUIDGEN, but that is not critical. Define
a string with the name, and put this in a header file, for example:


#define UWM_STEP_PROGRESS_MESSAGE _T(“UWM_STEP_PROGRESS”)
#define UWM_SET_PROGRESS_MESSAGE _T(“UWM_SET_PROGRESS”)

My names always have a GUIGEN suffix and tend to look like the one below,
which guarantees that they will never, ever, under any possible conditions,
conflict with any other Registered Window Message from anyone in the Known Universe.
(Well, OK, there is a 1 in 2-to-the-63-power chance, or something like that,
but this means the chance of this happening within the Heat Death of the Universe are
pretty slim).

_T(“UWM_STEP_PROGRESS-{152C2190-A98C-11d2-838D-886273000000}”)

2. In each module that implements the thread, and in MainFrm.cpp, include this
definition file.

3. In each module (including MainFrm.cpp) that uses the symbol, register
the message:


const WORD UWM_STEP_PROGRESS =
::RegisterWindowMessage(UWM_STEP_PROGRESS_MESSAGE);
const WORD UWM_SET_PROGRESS =
::RegisterWindowMessage(UWM_SET_PROGRESS_MESSAGE);

4. In the message map for CMainFrame, add a line for each message:

ON_REGISTERED_MESSAGE(UWM_STEP_PROGRESS, OnStepProgress)
ON_REGISTERED_MESSAGE(UWM_SET_PROGRESS, OnSetProgress)

The static and edit controls (CStatusStatic and CStatusEdit) when created will
be assigned the same font as their parent, the status bar control.

Receiving notifications from active controls


For active controls such as the Edit and ComboBox controls, if you want to
receive notifications such as EN_CHANGE, CBN_SELCHANGE, etc. you will have to
subclass the CStatusBar control with one of your own. You can then create the
appropriate handlers for these notifications; typically you will probably
reflect them up to the mainframe itself. An example of this is included in the
sample code. I chose to handle this by intercepting the WM_COMMAND message and
if it was for one of my controls, sending it upwards to the mainframe.

Unfortunately, this is a bit painful because ClassWizard doesn’t give you any
help. You can use ClassWizard to create a class which is a subclass of
CStatusBar, for example, I call mine CActiveStatusBar (for a status bar that
has an active control). I then went into the header file and manually added the line
shown below in the AFX_VIRTUAL section:


// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CActiveStatusBar)
virtual BOOL OnCommand(WPARAM, LPARAM);
//}}AFX_VIRTUAL

Then I went into ActiveStatusBar and added the function which reflects my
commands upwards:

BOOL CActiveStatusBar::OnCommand(WPARAM wParam, LPARAM lParam)
{
switch(LOWORD(wParam))
{ /* one of ours? */
case ID_INDICATOR_COMBO:
case ID_INDICATOR_EDIT:
return GetParent()->SendMessage(WM_COMMAND, wParam, lParam);
} /* one of ours? */

// Not one of ours
return CControlBar::OnCommand(wParam, lParam);
} // CActiveStatusBar::OnCommand


At that point, I went into the MainFrm.cpp file and added the handlers,
Unfortunately this really does have to be done “by hand”. For example,
add to the MainFrm.h file:

afx_msg void OnSelChangeStatusCombo();

and to the MainFrm.cpp file, add to the message map:

ON_CBN_SELCHANGE(ID_STATUS_COMBO, OnSelChangeStatusCombo)

and finally add the handler:

void CMainFrame::OnSelchangeStatusCombo()
{
int n = c_StatusCombo.GetCurSel();
if(n == CB_ERR)
return;
CString s;
c_StatusCombo.GetLBText(n, s);
//…do something with the text here
}

Details of the code

Each window that is created is created as a child window of the status bar, and
is assigned a control ID which is the same as its pane ID. Note that this means
that you cannot create two such windows for the same pane simultaneously, an
unlikely thing to actually do.

StatusControl.cpp

The heart of the code is sketched below. The general class is called CStatusControl, and
has two critical operations which are declared static so they can be shared with the
friend classes. In the code below the boilerplate MFC comments have been dropped.


class CStatusControl : public CWnd
{
public:
friend class CStatusEdit;
friend class CStatusProgress;
friend class CStatusStatic;
friend class CStatusCombo;
CStatusControl();
BOOL Create(LPCTSTR classname, CStatusBar * parent, UINT id, DWORD style);
void Reposition();
virtual ~CStatusControl();
protected:
static void reposition(CWnd * wnd);
static BOOL setup(CStatusBar * parent, UINT id, CRect & r);
DECLARE_MESSAGE_MAP()
}

The source code file is shown below, less the MFC boilerplate comments:

#include “stdafx.h”
#include “StatusControl.h”

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CStatusControl::CStatusControl(){}

CStatusControl::~CStatusControl(){}

BEGIN_MESSAGE_MAP(CStatusControl, CWnd)
//{{AFX_MSG_MAP(CStatusControl)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()


This function is a static function used by the friend classes to compute
the target rectangle. It handles the special case where the pane in question
has been clipped by the parent frame; in this case, GetItemRect returns
a (0,0,0,0) rectangle. The code below creates a very narrow window off the right
end of the status bar. If the proper protocol for the OnSize handler is obeyed,
resizing the window to make the pane visible will have the desired effect.

This function returns FALSE if it had to create an off-view window.


BOOL CStatusControl::setup(CStatusBar * parent, UINT id, CRect & r)
{
int i = parent->CommandToIndex(id);

parent->GetItemRect(i, &r);
parent->SetPaneText(i, “”);

if(r.IsRectEmpty())
{ /* offscreen */
CRect r1;
parent->GetWindowRect(&r1); // get parent width
r.left = r1.right + 1;
r.top = r1.top;
r.right = r1.right + 2;
r.bottom = r1.bottom;
return FALSE;
} /* offscreen */

return TRUE;
}


This function is a static function which is called by the Reposition method of
the friend classes to actually reposition the window. Because the window
encodes the pane indicator ID as its control ID, we can easily locate the
pane and its rectangle. Interestingly enough, this does not require the
same special-case for the empty rectangle, because the window already exists.

void CStatusControl::reposition(CWnd * wnd)
{
if(wnd == NULL || wnd->m_hWnd == NULL)
return;
UINT id = ::GetWindowLong(wnd->m_hWnd, GWL_ID);
CRect r;

CStatusBar * parent = (CStatusBar *)wnd->GetParent();
int i = parent->CommandToIndex(id);
parent->GetItemRect(i, &r);
wnd->SetWindowPos(&wndTop, r.left, r.top, r.Width(), r.Height(), 0);
}


This function allows you to create a window of an arbitrary window class by
specifying its class name and style flags.

BOOL CStatusControl::Create(LPCTSTR classname, CStatusBar * parent, UINT id, DWORD style)
{
CRect r;
setup(parent, id, r);
return CWnd::Create(classname, NULL, style | WS_CHILD, r, parent, id);
}

This function is the exported method for repositioning. Note that it calls the protected
reposition member to actually do the work.

void CStatusControl::Reposition()
{
reposition(this);
}

A sample friend class: CStatusProgress

Creating the progress control is quite simple; the Create method for the progress
control class is shown below. The id passed in is the ID of the pane to be
used for the control. The style, for a progress control, can be a combination
of the usual WS_ styles (you must explicitly provide WS_VISIBLE, but WS_CHILD
will be supplied for you) PBS_SMOOTH, and PBS_VERTICAL.

StatusProgress.h

The header file for the derived CProgressCtrl is quite simple; dropping
the MFC boilerplate comments we have the code below.


class CStatusProgress : public CProgressCtrl
{
public:
CStatusProgress();
BOOL Create(CStatusBar * parent, UINT id, DWORD style);
__inline void Reposition() { CStatusControl::reposition(this); }
virtual ~CStatusProgress();
DECLARE_MESSAGE_MAP()
};

StatusProgress.cpp

The code below drops the boilerplate MFC comments.


#include “stdafx.h”
#include “StatusControl.h”
#include “StatusProgress.h”

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CStatusProgress::CStatusProgress() {}

CStatusProgress::~CStatusProgress() {}

BEGIN_MESSAGE_MAP(CStatusProgress, CProgressCtrl)
//{{AFX_MSG_MAP(CStatusProgress)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL CStatusProgress::Create(CStatusBar * parent, UINT id, DWORD style)
{
CRect r;
CStatusControl::setup(parent, id, r);
return CProgressCtrl::Create(style | WS_CHILD, r, parent, id);
}

Doing arbitrary drawing

In addition to the classes contained herein, the general Create operation in
the CStatusControl class allows you to create a window of an arbitrary class, so
you can do custom drawings by using your own window class. You may
also choose to do arbitrary drawing by writing your own OnPaint handler for
a class you derive from the CStatusStatic class I provide.

Download demo project – 10KB

The source code has been compiled under VC++ 6.0 SP1. No guarantees for
any other version of the compiler. It was tested under NT 4.0 SP3.

Download source – 27KB

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read