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

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



Comments

  • With Dialog box instead of MainFrame

    Posted by Legacy on 03/19/2003 12:00am

    Originally posted by: Vishal Kudchadkar

    Will the same logic/method work with Dialog box instead of a MainFrame??

    Thanks

    Vishal

    Reply
  • How can I do this in VB7.0

    Posted by Legacy on 02/12/2003 12:00am

    Originally posted by: michael

    I would appreciate it so much if you can help me to tell me how I can do this project in VB.Net.
    

    Reply
  • Status bar / child frame

    Posted by Legacy on 01/23/2003 12:00am

    Originally posted by: Jose Mario Fontes

    It is a nice work!
    
    How could I do the same work in a child frame status bar, considering, for example, the insertion of the file path of a just opened file in a MFC/MDI application ?

    Reply
  • nice..!! It couldn't be better...

    Posted by Legacy on 09/26/2002 12:00am

    Originally posted by: Andy Yoo

    ..

    Reply
  • CStatusBar's bug

    Posted by Legacy on 09/20/2000 12:00am

    Originally posted by: Eugene Shmelyov

    First of all,excuse my English.
    Thanks, it's very usefull in some case. I tested it in VC++4.2 (DEBUG target), and found out a bug (memory leaks).
    But it's not your bug. I guess it's a bug of a CStatusBar class. It appears if you put CStatusBar's member function SetPaneText(index, "") with empty string. And it works OK if I put nonzero length string, for a example SetPaneText(index, " ").
    Perhaps this bug is fixed in VC++ 6.0.

    Evgeniy Shmelyov.

    Reply
  • http://users5.50megs.com/eugenesh/

    Posted by Legacy on 09/20/2000 12:00am

    Originally posted by: Evgeniy Shmelyov

    Thanks, it's very usefull in some case.
    Excuse my English.
    I tested it in VC++4.2 (DEBUG target), and found out a bug (memory leaks). But it's not your bug. I guess it's a bug of a CStatusBar class. It appears if you put CStatusBar's member function SetPaneText(index, "") with empty string. And it works OK if I put nonzero length string, for a example SetPaneText(index, " ").
    Perhaps this bug is fixed in VC++ 6.0.
    Thanks.

    Reply
  • Fix for Cut & Past

    Posted by Legacy on 05/25/1999 12:00am

    Originally posted by: Jian Dai

    The following code will take care of the problem. here only shows how to fix the Ctrl-C (Copy) the others are
    similar.
    
    BOOL CStatusEdit::PreTranslateMessage(MSG* pMsg)
    {
    if(pMsg->message ==WM_KEYDOWN)
    {
    if (pMsg->wParam == 0x11)
    {
    //-------------------
    // [Ctrl] key is down
    //-------------------
    m_CopyKeyStatus = 1;
    }
    else if (pMsg->wParam == 0x43 )
    {
    if( m_CopyKeyStatus == 1)
    {
    //-------------------
    // User pushed Ctrl-C
    //-------------------
    Copy();
    }

    }
    }

    if(pMsg->message ==WM_KEYUP)
    {
    m_CopyKeyStatus = 0;
    }
    return CEdit::PreTranslateMessage(pMsg);
    }

    Reply
  • Cut&Paste function of edit control does not work!!

    Posted by Legacy on 05/11/1999 12:00am

    Originally posted by: Eugene Ng

    The cut&paste function of the edit control that been added onto the statusbar doesn't work anymore. How to solve this ?

    Reply
  • Excellent work

    Posted by Legacy on 02/23/1999 12:00am

    Originally posted by: Zhaohui Xing

    Greate

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • With 81% of employees using their phones at work, companies have stopped asking: "Is corporate data leaking from personal devices?" and started asking: "How do we effectively prevent corporate data from leaking from personal devices?" The answer has not been simple. ZixOne raises the bar on BYOD security by not allowing email data to reside on the device. In addition, Zix allows employees to maintain complete control of their personal device, therefore satisfying privacy demands of valued employees and the …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds