A UI class for long operation feedback

CLongOperation is a class designed to give visual feedback for long-lasting operations. It has support for:

  • Displaying a wait cursor
  • Showing a text in the status bar
  • Displaying a progress bar in a dynamically created status bar pane

In the simplest case, this class can be used as a replacement for MFC's CWaitCursor:

	CLongOperation wait;
	// some hard work going here...

To display some textual progress information in the status bar:

	CLongOperation wait;
	wait.SetText("Pass 1");
	// ...
	wait.SetText("Pass 2");
	// ...

To display a progress bar in the status bar:

	CLongOperation wait;
	for (int i = 0; i < count; i++)
	{
		wait.Step((100*i)/cnt);
		// ...
	}
	wait.Stop();

/////////////////////////////////////////////////////////////////
// LongOperation.h
// (c) 1997, Klaus G|tter
class CLongOperation : public CObject
{
public:
	// IDS_PLEASE_WAIT is a string resource ID for the default text,
	// e.g. "Please wait..."
	CLongOperation(UINT nIDText = IDS_PLEASE_WAIT, bool bStart = true);
	CLongOperation(LPCTSTR lpszText, bool bStart = true);
	~CLongOperation();

	void Start();
	void Stop();
	void Step(int nPercentage = -1);
	void SetText(LPCTSTR lpszText);

protected:
	CString m_strText;
	bool m_bStarted;
	HWND m_hwndProgress;
	void CreateProgressControl();
};


/////////////////////////////////////////////////////////////////
// LongOperation.cpp
// (c) 1997, Klaus G|tter

#include "stdafx.h"
#include <afxpriv.h> // defines WM_SETMESSAGESTRING
#include "LongOperation.h"

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

CLongOperation::CLongOperation(UINT nIDText, bool bStart)
:	m_bStarted(false)
,	m_hwndProgress(NULL)
{
	VERIFY(m_strText.LoadString(nIDText));
	if (bStart)
		Start();
}

CLongOperation::CLongOperation(LPCTSTR lpszText, bool bStart)
:	m_strText(lpszText)
,	m_bStarted(false)
,	m_hwndProgress(NULL)
{
	if (bStart)
		Start();
}

CLongOperation::~CLongOperation()
{
	if (m_bStarted)
		Stop();
}

void CLongOperation::Start()
{
	if (m_bStarted)
		Stop();

	// display text in the status bar
	CWnd* pMainWnd = ::AfxGetMainWnd();
	if (pMainWnd)
		pMainWnd->SendMessage(WM_SETMESSAGESTRING, 0, (LPARAM)(LPCTSTR)m_strText);

	// switch on wait cursor
	::AfxGetApp()->BeginWaitCursor();

	m_bStarted = true;
}

void CLongOperation::Stop()
{
	if (!m_bStarted)
		return;

	if (m_hwndProgress)
	{
		// clean up and destroy progress bar
		CStatusBar* pStatusBar = DYNAMIC_DOWNCAST(CStatusBar, CWnd::FromHandle(::GetParent(m_hwndProgress)));
		ASSERT_VALID(pStatusBar);

		::DestroyWindow(m_hwndProgress);
		m_hwndProgress = NULL;

		// remove progress bar pane
		int anPart[32];
		int nParts = pStatusBar->GetStatusBarCtrl().GetParts(31, anPart);
		nParts--;
		pStatusBar->GetStatusBarCtrl().SetParts(nParts, anPart+1);
	}

	// switch back to standard text in the status bar
	CWnd* pMainWnd = ::AfxGetMainWnd();
	if (pMainWnd)
		pMainWnd->SendMessage(WM_SETMESSAGESTRING, AFX_IDS_IDLEMESSAGE, 0);

	// switch off wait cursor
	::AfxGetApp()->EndWaitCursor();

	m_bStarted = false;
}

void CLongOperation::Step(int nPercentage)
{
	if (!m_bStarted)
		Start();

	::AfxGetApp()->RestoreWaitCursor();

	if (nPercentage >= 0)
	{
		ASSERT(nPercentage <= 100);
		// create or update a progress control in the status bar
		if (m_hwndProgress == NULL)
			CreateProgressControl();

		if (m_hwndProgress)
			::SendMessage(m_hwndProgress, PBM_SETPOS, (WPARAM)nPercentage, 0);
	}
}

void CLongOperation::SetText(LPCTSTR lpszText)
{
	m_strText = lpszText;
	CWnd* pMainWnd = ::AfxGetMainWnd();
	if (pMainWnd)
		pMainWnd->SendMessage(WM_SETMESSAGESTRING, 0, (LPARAM)(LPCTSTR)m_strText);
}

void CLongOperation::CreateProgressControl()
{
	ASSERT(m_hwndProgress == NULL);

	// find status bar
	CWnd* pMainWnd = ::AfxGetMainWnd();
	if (pMainWnd == NULL)
		return;
	CStatusBar* pStatusBar = DYNAMIC_DOWNCAST(CStatusBar, 
		pMainWnd->GetDescendantWindow(AFX_IDW_STATUS_BAR, TRUE));
	if (pStatusBar == NULL || pStatusBar->m_hWnd == NULL)
		return;

	CRect rc; // this will be the location for the progress bar pane
	pStatusBar->GetItemRect(0, rc);
	if (!m_strText.IsEmpty())
	{
		// adjust so that the text in the leftmost pane will not be covered
		CClientDC dc(pStatusBar);
		dc.SelectObject(pStatusBar->GetFont());
		CSize sz = dc.GetTextExtent(m_strText);
		TEXTMETRIC tm;
		dc.GetTextMetrics(&tm);
		rc.left += sz.cx + 2*tm.tmAveCharWidth;
	}
	int cx = rc.Width();
	if (cx < 20)
	{
		// no sense in displaying such a small progress bar
		TRACE0("ProgressDisplay would be too small\n");
		return;
	}
	else if (cx > 200)
	{
		// arbitrarily limiting progress bar width to 200 pixel
		cx = 200;
		rc.left = rc.right - cx;
	}

	// add a pane between the text and the currently leftmost pane
	int anPart[32];
	int nParts = pStatusBar->GetStatusBarCtrl().GetParts(31, anPart+1);
	anPart[0] = rc.left;
	nParts++;
	pStatusBar->GetStatusBarCtrl().SetParts(nParts, anPart);
	pStatusBar->GetStatusBarCtrl().GetRect(1, rc);

	// create progress bar control
	m_hwndProgress = ::CreateWindow(PROGRESS_CLASS, "",
		WS_CHILD | WS_VISIBLE, rc.left, rc.top, rc.Width(), rc.Height(),
		pStatusBar->m_hWnd, (HMENU)1, AfxGetInstanceHandle(), NULL);

		pStatusBar->UpdateWindow();
}



Comments

  • There are no comments yet. Be the first to comment!

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

Top White Papers and Webcasts

  • The operational costs of managing an x86 base are taxing IT budgets, making it difficult to fund and staff new initiatives. Today's IT organization must seek efficiencies in its operations and shift to a more agile infrastructure that's flexible enough to adapt to future changes in the business. Read this Q & A session with Jed Scaramella, research manager for IDC's Enterprise Platforms and Data Center Trends, to learn how the integrated nature of the blade platform delivers critically needed efficiencies …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds