It is based on the
technology contributed ("Showing
progress bar in a status bar pane") and the
amendation. 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
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:
virtual BOOL OnCommand(WPARAM, LPARAM);
Then I went into
ActiveStatusBar and added the function which reflects my
commands upwards:
BOOL CActiveStatusBar::OnCommand(WPARAM wParam, LPARAM lParam)
{
switch(LOWORD(wParam))
{
case ID_INDICATOR_COMBO:
case ID_INDICATOR_EDIT:
return GetParent()->SendMessage(WM_COMMAND, wParam, lParam);
}
return CControlBar::OnCommand(wParam, lParam);
}
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);
}
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)
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())
{
CRect r1;
parent->GetWindowRect(&r1);
r.left = r1.right + 1;
r.top = r1.top;
r.right = r1.right + 2;
r.bottom = r1.bottom;
return FALSE;
}
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)
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.