MSDN Magazine > Issues and Downloads > 2003 > January >  C++ Q&A: Progress Indicator in the Status Bar, ...
C++ Q&A
Progress Indicator in the Status Bar, International UI Terms
Paul DiLascia

Code download available at: CQA0301.exe (67 KB)
Browse the Code Online

Q I'm developing a C++/MFC app that sometimes has to load a large file and I'd like to display a progress indicator like the one in the standard progress control. I saw another app that did this in the status bar, which I thought was cool. How can I add a progress bar to my status bar? I tried various ways, but none of them worked.
Q I'm developing a C++/MFC app that sometimes has to load a large file and I'd like to display a progress indicator like the one in the standard progress control. I saw another app that did this in the status bar, which I thought was cool. How can I add a progress bar to my status bar? I tried various ways, but none of them worked.
Martin Issacson

A I'm not sure exactly what you tried, but I was able to build a little demo app with a progress control in the status bar using a fairly straightforward approach. The idea is to create the progress control as a child window of your status bar, then either hide or show it as needed to display progress.
A I'm not sure exactly what you tried, but I was able to build a little demo app with a progress control in the status bar using a fairly straightforward approach. The idea is to create the progress control as a child window of your status bar, then either hide or show it as needed to display progress.
PBAR is a normal MFC doc/view app that uses an edit view to display text files. When you open a file, PBAR simulates a lengthy load operation and displays its progress in the status bar (see Figure 1). I encapsulated the status-bar-with-progress-control in a new class, CProgStatusBar (see Figure 2), which I'll describe so you can use it in your own apps.

ProgBar.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#pragma once

//////////////////
// Status bar with progress control.
//
class CProgStatusBar : public CStatusBar {
public:
   CProgStatusBar();
   virtual ~CProgStatusBar();
   CProgressCtrl& GetProgressCtrl() {
      return m_wndProgBar;
   }
   void OnProgress(UINT pct);

protected:
   CProgressCtrl m_wndProgBar;          // the progress bar
   afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
   afx_msg void OnSize(UINT nType, int cx, int cy);
   DECLARE_MESSAGE_MAP()
   DECLARE_DYNAMIC(CProgStatusBar)
};
ProgBar.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "StdAfx.h"
#include "ProgBar.h"

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

IMPLEMENT_DYNAMIC(CProgStatusBar, CStatusBar)
BEGIN_MESSAGE_MAP(CProgStatusBar, CStatusBar)
   ON_WM_CREATE()
   ON_WM_SIZE()
END_MESSAGE_MAP()

CProgStatusBar::CProgStatusBar()
{
}

CProgStatusBar::~CProgStatusBar()
{
}

/////////////////
// Status bar created: create progress bar too.
//
int CProgStatusBar::OnCreate(LPCREATESTRUCT lpcs)
{
   lpcs->style |= WS_CLIPCHILDREN;
   VERIFY(CStatusBar::OnCreate(lpcs)==0);
   VERIFY(m_wndProgBar.Create(WS_CHILD, CRect(), this, 1));
   m_wndProgBar.SetRange(0,100);
   return 0;
}

//////////////////
// Status bar was sized: adjust size of progress bar to same as first
// pane (ready message). Note that the progress bar may or may not be
// visible.
//
void CProgStatusBar::OnSize(UINT nType, int cx, int cy)
{
   CStatusBar::OnSize(nType, cx, cy); // call base class
   CRect rc;                          // rectangle 
   GetItemRect(0, &rc);               // item 0 = first pane, "ready" 
                                      // message
   m_wndProgBar.MoveWindow(&rc,FALSE);// move progress bar
}

//////////////////
// Set progress bar position. pct is an integer from 0 to 100:
//
//  0 = hide progress bar and display ready message (done);
// >0 = (assumed 0-100) set progress bar position
//
// You should call this from your main frame to update progress.
// (See Mainfrm.cpp)
//
void CProgStatusBar::OnProgress(UINT pct)
{
   CProgressCtrl& pc = m_wndProgBar;
   DWORD dwOldStyle = pc.GetStyle();
   DWORD dwNewStyle = dwOldStyle;
   if (pct>0)
      // positive progress: show prog bar
      dwNewStyle |= WS_VISIBLE;
   else
      // prog <= 0: hide prog bar
      dwNewStyle &= ~WS_VISIBLE;

   if (dwNewStyle != dwOldStyle) {
      // change state of hide/show
      SetWindowText(NULL);                               // clear old text
      SetWindowLong(pc.m_hWnd, GWL_STYLE, dwNewStyle);   // change style
   }

   // set progress bar position
   pc.SetPos(pct);
   if (pct==0)
      // display MFC idle (ready) message.
      GetParent()->PostMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE);
}
Figure 1 Showing Progress in the Status Bar 
CProgStatusBar is derived from CStatusBar, the standard MFC status bar control. CProgStatusBar adds a progress control (CProgressCtrl) as a data member m_wndProgBar, and three methods of significance: OnCreate, OnSize, and OnProgress.
OnCreate, which receives control when the status bar is first created, creates and initializes the progress bar as a child window:
int CProgStatusBar::OnCreate(LPCREATESTRUCT lpcs)
{
   lpcs->style |= WS_CLIPCHILDREN;
   VERIFY(CStatusBar::OnCreate(lpcs)==0);
   VERIFY(m_wndProgBar.Create(WS_CHILD, CRect(), this, 1));
   m_wndProgBar.SetRange(0,100);
   return 0;
} 
OnCreate adds WS_CLIPCHILDREN to the status bar style; this reduces screen flicker by telling Windows® not to paint areas of the status bar that lie under child windows. OnCreate then creates the progress control and sets its range to the interval [0, 100]. Note that I did not use WS_VISIBLE to create the progress bar because I want it to be hidden initially.
Whenever you add a child window to any window, you're responsible for resizing the child when the parent is resized. The place to do it, as usual, is in the parent window's WM_SIZE/OnSize handler:
// status bar was resized
void CProgStatusBar::OnSize(...)
{
   CStatusBar::OnSize(...);
   CRect rc;
   GetItemRect(0, &rc);
   m_wndProgBar.MoveWindow(&rc,FALSE);
} 
CProgStatusBar::OnSize moves the progress bar to the desired position: in this case, the same position as the first status bar pane, the one used for the ready message and command prompts. Note that MoveWindow moves the window whether it's hidden or visible—so this code works even when the progress control is hidden.
So much for sizing. How do you actually show progress? CProgStatusBar::OnProgress is the method to call. Figure 2 shows the source. OnProgress takes a single UINT in the range 0 to 100, the percent progress made, with 0 meaning no pro-gress at all and 100 meaning it's done. If the progress is greater than zero, OnProgress shows the progress control and sets the bar position; if the progress is zero, OnProgress hides the progress control.
While it's often expedient to place a child control on top of an area that the parent window can paint, you always do so at the risk of paint problems. In particular, if you hide and show the progress bar as described, you'll discover a couple of glitches. For example, if there's any text already in the status bar's message pane, it will show through the progress indicator blocks, as shown in Figure 3. That's because the progress control assumes its back-ground is clear and paints only the colored portion of the progress bar. The easiest way to fix this is to call CStatusBar::SetWindowText(NULL) to clear the text before showing the progress control. (For a status bar, SetWindowText sets the text in pane zero—the first pane.) Figure 2 shows the details.
Figure 3 Paint Problem 
A similar glitch appears going in the opposite direction: namely, when you call OnProgress(0) to clear the progress. In that case, CProgStatusBar::OnProgress hides the progress bar. But now what about the "ready" message? When your app isn't doing anything, MFC displays the resource string AFX_IDS_IDLEMESSAGE in your status bar. (By default, this string is "Ready", but you can change it to anything you like in your RC file.) Personally, I've always thought the ready message is a little dopey—but if there's any time you'd like to inform the user that your app is now "ready," it would be after an operation so potentially lengthy that it requires a progress indicator. So how do you make MFC display the ready message in the status bar? Easy:
// in CProgStatusBar::OnProgress
// WM_SETMESSAGESTRING defined in <afxpriv.h>
GetParent()->PostMessage(WM_SETMESSAGESTRING, 
  AFX_IDS_IDLEMESSAGE); 
If you like, you can create a different ID and message like ID_DONE_LOADING for "Done Loading", and use them instead.
CProgStatusBar implements the status bar with progress control. How do you use it in your app? All you have to do is use CProgStatusBar instead of CStatusBar, then call CProgStatusBar::OnProgress whenever you want to show the progress. Figure 4 shows the details (to save space, only the relevant sections are shown—full source is available, as always, at the link at the top of this article). Creating the CProgStatusBar is the easy part—it works the same as for a vanilla status bar—but when and where do you call OnProgress?

MainFrm.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "ProgBar.h"

/////////////////
// Main frame window has menu manager, flatbar, status bar
//
class CMainFrame : public CFrameWnd {
public:
   CMainFrame();
   ~CMainFrame();

protected:
   CProgStatusBar m_wndStatusBar; // progress control status bar
   CToolBar       m_wndToolBar;   // if using toolbar

   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
   afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
   afx_msg LRESULT OnProgress(WPARAM wp, LPARAM lp);
   DECLARE_MESSAGE_MAP()
   DECLARE_DYNCREATE(CMainFrame)
};
MainFrm.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "StdAfx.h"
#include "MainFrm.h"
#include "View.h"
#include "resource.h"

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

static UINT indicators[] = {
   ID_SEPARATOR,           // status line indicator
   ID_INDICATOR_CAPS,
   ID_INDICATOR_NUM,
   ID_INDICATOR_SCRL,
};

////////////////////////////////////////////////////////////////
// CMainFrame
//
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_WM_CREATE()
   ON_MESSAGE(MYWM_PROGRESS,OnProgress)
END_MESSAGE_MAP()

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

////////////////
// Override flicker-free drawing with no CS_VREDRAW and CS_HREDRAW. This 
// has nothing to do with coolbars, but it's a good thing to do.
//
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
   cs.lpszClass = AfxRegisterWndClass(
      0,                                // no redraw
      NULL,                             // no cursor (use default)
      NULL,                             // no background brush
      AfxGetApp()->LoadIcon(IDR_MAINFRAME)); // app icon
   ASSERT(cs.lpszClass);
   return CFrameWnd::PreCreateWindow(cs);
}

//////////////////
// Create handler creates control bars.
//
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   VERIFY(CFrameWnd::OnCreate(lpCreateStruct) == 0);

   VERIFY(m_wndToolBar.Create(this));
   VERIFY(m_wndToolBar.LoadToolBar(IDR_MAINFRAME));
   m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT);

   // Create status bar and set indicators
   VERIFY(m_wndStatusBar.Create(this));
   VERIFY(m_wndStatusBar.SetIndicators(indicators,
      sizeof(indicators)/sizeof(UINT)));

   return 0;
}

///////////////////
// Handle progress message—any object can call send one.
//
LRESULT CMainFrame::OnProgress(WPARAM wp, LPARAM lp)
{
   m_wndStatusBar.OnProgress(wp); // pass to prog/status bar
   return 0;
}
Doc.h
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
class CMyDoc : public CDocument {
public:
   virtual void Serialize(CArchive& ar);

•••// standard doc class stuff not shown

};
Doc.cpp
////////////////////////////////////////////////////////////////
// MSDN Magazine — January 2003
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 6.0 or Visual Studio .NET on Windows XP. 
// Tab size=3.
//
#include "StdAfx.h"
#include "Doc.h"
#include "resource.h"

••• // standard doc class stuff not shown

//////////////////
// Serialize function simulates slow-loading doc
//
void CMyDoc::Serialize(CArchive& ar)
{
   CWnd* pFrame = AfxGetMainWnd();
   if (!ar.IsStoring()) {
      // loading: simulate length operation by sleeping, but wake up
      // every 150 msec to notify frame of progress.
      //
      for (int pct=10; pct<=100; pct+=10) {
         Sleep(150);
         if (pFrame)
            pFrame->SendMessage(MYWM_PROGRESS, pct);
      }
   }
   if (pFrame)
      pFrame->SendMessage(MYWM_PROGRESS, 0);

   ((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);
}
The exact approach will depend on your application. However, I suggest you follow an approach similar to the one I implemented in PBAR. Rather than exposing CProgStatusBar::OnProgress directly, PBAR's main window implements a special message, called MYWM_PROGRESS, that passes its WPARAM to CProgStatusBar::OnProgress.
// MYWM_PROGRESS defined in resource.h
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_MESSAGE(MYWM_PROGRESS,OnProgress)
END_MESSAGE_MAP()

// handle MYWM_PROGRESS
LRESULT CMainFrame::OnProgress(WPARAM wp, LPARAM lp)
{
   m_wndStatusBar.OnProgress(wp);
   return 0;
} 
Now any object that wants to report progress can do so by sending a message to the main frame instead of calling the status bar directly. For example, in PBAR, the document's Serialize function simulates a lengthy load process by sleeping for 1.5 seconds but waking up every 150 milliseconds to report its progress (see Figure 4). Usually, you want low-level objects like documents to contain as little UI code as possible. Ideally, your document class should be able to operate with no UI (because you might someday want to use it in a service or command-line program)—though in practice few programmers observe this restriction religiously. In any case, it's always better to send a message to the frame than to expose the frame's internal members. To be safe, the document's Serialize function checks for the existence of the frame before sending it a message.
If you don't like sending window messages from your document, you could use the MFC view update mechanism instead. You could invent a "hint" code and a small structure to hold the progress/percent complete and call CDocument::UpdateAllViews with this information. Your view class's OnUpdate handler would then process this hint information by sending MYWM_PROGRESS to the frame. This is the cleanest way to propagate progress information from the document to view/frame.
CProgStatusBar assumes that you'll always report progress as an integer from 0 to 100 and also that you want the broken-look progress bar. If you want to change these settings, you can modify CProgStatusBar or call CProgStatusBar::GetProgressCtrl to access the progress control directly.

Q In your article ".NET GUI Bliss" in the November 2002 issue of MSDN Magazine, you described how to build a system that uses XML to code the UI. I really like this idea, but I want to go global. Is there some book that will tell me the foreign words for common terms and menu items like File, View, Edit, and so on?
Q In your article ".NET GUI Bliss" in the November 2002 issue of MSDN Magazine, you described how to build a system that uses XML to code the UI. I really like this idea, but I want to go global. Is there some book that will tell me the foreign words for common terms and menu items like File, View, Edit, and so on?
Mark Desoura

A At first I was going to say: get a dictionary. But then I did a little poking around and found a resource that may be just what you want. If you look on the MSDN Library CD, under the section "User Interface Design and Development," there's a book called The Windows User Experience (also published by Microsoft® Press in hard copy) which has a reference at the end called "International Word List." This wonderful resource lets you select a language from a dropdown combobox to get a dictionary of common user interface terms in that language (see Figure 5). Amazing! From the word list, you can learn that Cancel in Czech is Storno, Minimize in Hungarian is Kis méret, and when your friend from Finland says helppokäyttötoiminnot, she means accessibility. Naturally, there's no substitute for a human translator, and dictionaries alone can't help you write help—but if you have a relatively simple application and all you want to do is translate your menus and dialogs, the International Word List is a start. The International Word List is really only intended to provide a standard lexicon for Windows-centric terms like Favorites, dialog box, and active window, not a substitute for human translators. Nor is the list completely up-to-date with the ever-changing Windows world. For example, you can find the Polish for the old Windows 98 phrase Network Neighborhood, but not the newfangled My Network Places found in Windows XP. Hasta Luego!
A At first I was going to say: get a dictionary. But then I did a little poking around and found a resource that may be just what you want. If you look on the MSDN Library CD, under the section "User Interface Design and Development," there's a book called The Windows User Experience (also published by Microsoft® Press in hard copy) which has a reference at the end called "International Word List." This wonderful resource lets you select a language from a dropdown combobox to get a dictionary of common user interface terms in that language (see Figure 5). Amazing! From the word list, you can learn that Cancel in Czech is Storno, Minimize in Hungarian is Kis méret, and when your friend from Finland says helppokäyttötoiminnot, she means accessibility. Naturally, there's no substitute for a human translator, and dictionaries alone can't help you write help—but if you have a relatively simple application and all you want to do is translate your menus and dialogs, the International Word List is a start. The International Word List is really only intended to provide a standard lexicon for Windows-centric terms like Favorites, dialog box, and active window, not a substitute for human translators. Nor is the list completely up-to-date with the ever-changing Windows world. For example, you can find the Polish for the old Windows 98 phrase Network Neighborhood, but not the newfangled My Network Places found in Windows XP. Hasta Luego!


Send your questions and comments for Paul to cppqa@microsoft.com.


Paul DiLasciais a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at askpd@pobox.com or http://www.dilascia.com.

Page view tracker