Modeless Multi-Threaded Progress Dialog

Environment: VC6.0 SP2

The following is a progress dialog that can be used to show the progress of a lengthy operation, while also giving the user the ability to abort the process. The dialog itself is trivial, with three lines of static text, a progress control, and a "Cancel" button. The code has three classes, REBProgressManager (no base class), REBProgressThread (derived from CWinThread), and REBProgressDialog (derived from CDialog). It also uses a struct, REBPROGRESSDATA. I have this code in an ATL COM DLL with MFC support. My interface implementation class creates a REBProgressManager object and calls its functions. It also does some slight checking to make sure valid arguments are passed along (strings not too long, etc.).

Note that I renamed "resource.h" to "REBProgressRes.h," and renamed the "stdafx" files "REBProgressAfx".

The code uses a UI thread so that the dialog is very responsive. As soon as the uses presses "Cancel," the dialog displays the abort text, which is a variable the caller may set. It will no longer allow the caller to change the lines of static text. The next time the caller calls REBProgressManager::GetUserAbortFlag, a value of TRUE will be returned. The caller should check for a user abort each time through any long loop. If the user has pressed the abort button, stop the current process. The caller may set the caption, the three lines of static text, the progress, and the visible state of the progress control, the "Cancel" button, and the entire dialog. All of these may be set before showing the dialog, or while it is active. For example, it is useful to hide the "Cancel" button when you are doing something the user cannot abort, and it is useful to hide the progress control while performing a task that you do not know how long it will take.

After constructing a REBProgressManager object, call REBProgressManager::Init with the HWND of the progress dialog's parent, preferably the mainframe window. Without a parent window, the dialog does not know its place in the z-order, and the worker thread may pop up message boxes below it. Call REBProgressManager::BeginProgressDialog to show the dialog and REBProgressManager::EndProgressDialog to kill it. You should call REBProgressManager::Exit when done, although the destructor will call it if you do not.

The following is the header file:



#if !defined(AFX_REBPROGRESSDIALOG_H__6A18D7F2_AE8A_11D3_86DC_0008C773CB7F__INCLUDED_)
#define AFX_REBPROGRESSDIALOG_H__6A18D7F2_AE8A_11D3_86DC_0008C773CB7F__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// REBProgressDialog.h : header file
//

#include <afxcmn.h> // for CProgressCtrl

#define REBPROGRESSMAXSTRING 100
#define REBPROGRESSMAXSTATIC 3

typedef struct tagREBPROGRESSDATA
{
	TCHAR cCaption[REBPROGRESSMAXSTRING + 1];
	TCHAR cAbortText[REBPROGRESSMAXSTRING + 1];
	TCHAR cStaticText[REBPROGRESSMAXSTATIC][REBPROGRESSMAXSTRING + 1];
	BOOL  bCancelEnabled;
	BOOL  bProgressEnabled;
	BOOL  bVisible;
	int   nProgress;
} REBPROGRESSDATA;

void REBInitializeProgressData(REBPROGRESSDATA* pData);
void REBCopyProgressData(REBPROGRESSDATA* pDataDest, REBPROGRESSDATA* pDataSource);

class REBProgressDialog; // forward declaration
class REBProgressThread; // forward declaration

/////////////////////////////////////////////////////////////////////////////
// REBProgressManager

class REBProgressManager  
{
public:
	REBProgressManager();
	virtual ~REBProgressManager();

	void EndProgressDialog();
	void BeginProgressDialog();
	BOOL IsInited() { return m_bInited; }
	BOOL IsProgressDialogActive() { return (m_pThread != NULL); }
	void Exit();
	void Init(HWND hwndParent);

	BOOL GetVisible();
	void SetVisible(BOOL bNewVal);
	LPCTSTR GetAbortText();
	void SetAbortText(LPCTSTR pszText);
	BOOL IsProgressEnabled();
	BOOL IsCancelEnabled();
	void SetUserAbortFlag(BOOL bNewVal);
	BOOL GetUserAbortFlag();
	void EnableProgress(BOOL bEnable);
	void EnableCancel(BOOL bEnable);
	int GetProgress();
	void SetProgress(int nVal);
	LPCTSTR GetCaption();
	void SetCaption(LPCTSTR pszCaption);
	LPCTSTR GetStaticText(int nIndex);
	void SetStaticText(int nIndex, LPCTSTR pszText);

protected:
	HWND  m_hwndParent;
	BOOL m_bInited;
	REBProgressThread* m_pThread;
	BOOL m_bVisible;

protected:
	void PreAccess(int nCritSec);
	void PostAccess(int nCritSec);
	void NotifyChange();
	void WaitForProgressDialog();
	void CheckAndPump();
};

/////////////////////////////////////////////////////////////////////////////
// REBProgressThread

class REBProgressThread : public CWinThread
{
public:
	DECLARE_DYNCREATE(REBProgressThread)
	REBProgressThread(HWND hwndParent);
	REBProgressThread(); // constructor required by DECLARE_DYNCREATE macro (not used)
	~REBProgressThread();
	virtual BOOL InitInstance();
	virtual int ExitInstance();
protected:
	HWND  m_hwndParent;
};

/////////////////////////////////////////////////////////////////////////////
// REBProgressDialog dialog

class REBProgressDialog : public CDialog
{
public:
// Construction
public:
	REBProgressDialog(CWnd* pParent = NULL);   // standard constructor

// Dialog Data
	//{{AFX_DATA(REBProgressDialog)
	enum { IDD = IDD_DIALOG_PROGRESS };
	CButton	m_buttonCancel;
	CProgressCtrl	m_ctrlProgress;
	CString	m_csText1;
	CString	m_csText2;
	CString	m_csText3;
	//}}AFX_DATA


// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(REBProgressDialog)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	void HandleUserAbort();
	void RefreshData(BOOL bInit = FALSE);
	CString m_csAbortText;
	BOOL m_bUserAbortFlag;
	BOOL m_bProgressEnabled;
	BOOL m_bCancelEnabled;
	BOOL m_bVisible;
	int m_nProgress;
	CString m_csCaption;
	REBPROGRESSDATA m_TempData;

	// Generated message map functions
	//{{AFX_MSG(REBProgressDialog)
	virtual void OnCancel();
	virtual BOOL OnInitDialog();
	afx_msg void OnDestroy();
	//}}AFX_MSG
	afx_msg LRESULT OnRefresh(WPARAM wParam, LPARAM lParam);
	DECLARE_MESSAGE_MAP()
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_REBPROGRESSDIALOG_H__6A18D7F2_AE8A_11D3_86DC_0008C773CB7F__INCLUDED_)

And here is the implementation code:



// REBProgressDialog.cpp : implementation file
//

#include "REBProgressAfx.h"
#include "REBProgressRes.h"
#include "REBProgressDialog.h"

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

void REBInitializeProgressData(REBPROGRESSDATA* pData)
{
	// Set defaults
	ASSERT(pData);
	::_tcscpy(pData->cCaption,       "Progress Dialog");
	::_tcscpy(pData->cAbortText,     "Aborting process, please wait...");
	::_tcscpy(pData->cStaticText[0], "In Process");
	::_tcscpy(pData->cStaticText[1], "");
	::_tcscpy(pData->cStaticText[2], "");
	pData->bCancelEnabled =	         FALSE;
	pData->bProgressEnabled =        FALSE;
	pData->bVisible =                TRUE;
	pData->nProgress =               0;
}

void REBCopyProgressData(REBPROGRESSDATA* pDataDest, REBPROGRESSDATA* pDataSource)
{
	// Copy data in pDataSource to pDataDest
	ASSERT(pDataDest && pDataSource);
	::_tcscpy(pDataDest->cCaption,   pDataSource->cCaption);
	::_tcscpy(pDataDest->cAbortText, pDataSource->cAbortText);
	for(int i=0; i < REBPROGRESSMAXSTATIC; i++)
	{
		::_tcscpy(pDataDest->cStaticText[i], pDataSource->cStaticText[i]);
	}
	pDataDest->bCancelEnabled =   pDataSource->bCancelEnabled;
	pDataDest->bProgressEnabled = pDataSource->bProgressEnabled;
	pDataDest->bVisible =         pDataSource->bVisible;
	pDataDest->nProgress =        pDataSource->nProgress;
}

REBPROGRESSDATA    g_ProgressData; // global structure holding progress data
BOOL               g_bUserAbortFlag = FALSE; // did user abort?
CRITICAL_SECTION   g_Crit1; // protects g_ProgressData structure
CRITICAL_SECTION   g_Crit2; // protects g_bUserAbortFlag
HWND               g_hwndProgress = NULL;
UINT               REBREFRESHPROGRESS = ::RegisterWindowMessage("REBRefreshProgress");
DWORD              g_dwTime = 0;

#define REBPRGRS_PUMPINTERVAL 2000

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// REBProgressManager - runs the progress dialog

REBProgressManager::REBProgressManager()
{
	m_bInited = FALSE;
	m_pThread = NULL;
	g_dwTime = ::GetTickCount();
}

REBProgressManager::~REBProgressManager()
{
	if(m_bInited)
	{
		Exit();
	}
	ASSERT(m_pThread == NULL);
}

void REBProgressManager::CheckAndPump()
{
	// This is a modified YieldProc type function - it gets called often, but only 
	// pumps the message que when REBPRGRS_PUMPINTERVAL has elapsed.
	DWORD dwNow = ::GetTickCount();
	if((dwNow - g_dwTime) >= REBPRGRS_PUMPINTERVAL)
	{
		g_dwTime = dwNow;
		// pump message que
		MSG msg;
		while(::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
		{
			if(!AfxGetApp()->PumpMessage())
			{
				::PostQuitMessage(0);
			}
		}
		LONG lIdle = 0;
		while(AfxGetApp()->OnIdle(lIdle++));
	}
}

void REBProgressManager::Init(HWND hwndParent)
{
	m_hwndParent = hwndParent;
	ASSERT(::IsWindow(m_hwndParent));
	ASSERT(REBREFRESHPROGRESS != 0);
	REBInitializeProgressData(&g_ProgressData);
	m_bVisible = g_ProgressData.bVisible;
	m_bInited = TRUE;
}

void REBProgressManager::Exit()
{
	m_bInited = FALSE;
	if(m_pThread != NULL)
	{
		EndProgressDialog();
	}
}

void REBProgressManager::BeginProgressDialog()
{
	ASSERT(m_pThread == NULL);
	ASSERT(m_hwndParent && ::IsWindow(m_hwndParent));

	g_bUserAbortFlag = FALSE;

	::InitializeCriticalSection(&g_Crit1);
	::InitializeCriticalSection(&g_Crit2);
	m_pThread = new REBProgressThread(m_hwndParent);
	m_pThread->m_bAutoDelete = FALSE;
	VERIFY( m_pThread->CreateThread() );

	// REB - apparently, you have to show the window for the first time from outside the thread it was 
	// created it in, or things want to hang.  The same thing with destroying the window (see EndProgressDialog).
	WaitForProgressDialog(); // wait while UI thread creates dialog
	::ShowWindow(g_hwndProgress, (m_bVisible ? SW_SHOW : SW_HIDE)); // show window for the first time
}

void REBProgressManager::EndProgressDialog()
{
	ASSERT(m_pThread);

	// Destroy the progress dialog
	ASSERT(g_hwndProgress);
	::SendMessage(g_hwndProgress, WM_DESTROY, 0, 0);
	while(::IsWindow(g_hwndProgress))
	{
		::Sleep(0); // wait until it is destroyed
	}
	g_hwndProgress = NULL;

	::PostThreadMessage(m_pThread->m_nThreadID, WM_QUIT, 0, 0);
	::WaitForSingleObject(m_pThread->m_hThread, INFINITE);
	delete m_pThread;
	m_pThread = NULL;
	::DeleteCriticalSection(&g_Crit2);
	::DeleteCriticalSection(&g_Crit1);

	REBInitializeProgressData(&g_ProgressData); // reinitialize data in case progress dialog is used again
	m_bVisible = g_ProgressData.bVisible;
}

void REBProgressManager::PreAccess(int nCritSec)
{
	ASSERT(nCritSec == 1 || nCritSec == 2);
	if(m_pThread != NULL)
	{
		::EnterCriticalSection(((nCritSec == 1) ? &g_Crit1 : &g_Crit2));
	}
}

void REBProgressManager::PostAccess(int nCritSec)
{
	ASSERT(nCritSec == 1 || nCritSec == 2);
	if(m_pThread != NULL)
	{
		::LeaveCriticalSection(((nCritSec == 1) ? &g_Crit1 : &g_Crit2));
	}
	CheckAndPump();
}

void REBProgressManager::NotifyChange()
{
	if(m_pThread != NULL)
	{
		WaitForProgressDialog();
		::SendMessage(g_hwndProgress, REBREFRESHPROGRESS, 0, 0);
	}
}

void REBProgressManager::WaitForProgressDialog()
{
	if(m_pThread != NULL)
	{
		// if our thread has just started, it may not have had time yet to initialize the dialog window
		if(g_hwndProgress == NULL)
		{
			while(g_hwndProgress == NULL)
			{
				::Sleep(0); // give up THIS thread's time slice to allow the other thread to finish its InitInstance
			}
		}
		if(!::IsWindow(g_hwndProgress))
		{
			while(!::IsWindow(g_hwndProgress))
			{
				::Sleep(0); // give up THIS thread's time slice to allow the other thread to finish its InitInstance
			}
		}
	}
}

void REBProgressManager::SetUserAbortFlag(BOOL bNewVal)
{
	ASSERT(FALSE); // cannot be programmatically set; to end progress dialog programmatically, call EndProgressDialog
}

BOOL REBProgressManager::GetUserAbortFlag()
{
	ASSERT(m_bInited);

	BOOL bRet = FALSE;
	PreAccess(2);
	bRet = g_bUserAbortFlag;
	PostAccess(2);
	return bRet;
}

LPCTSTR REBProgressManager::GetAbortText()
{
	ASSERT(m_bInited);
	LPCTSTR pszRet = NULL;
	PreAccess(1);
	pszRet = g_ProgressData.cAbortText;
	PostAccess(1);
	return pszRet;
}

void REBProgressManager::SetAbortText(LPCTSTR pszText)
{
	ASSERT(m_bInited);
	ASSERT(::_tcslen(pszText) <= REBPROGRESSMAXSTRING);
	PreAccess(1);
	::_tcscpy(g_ProgressData.cAbortText, pszText);
	PostAccess(1);
	NotifyChange();
}

BOOL REBProgressManager::IsProgressEnabled()
{
	ASSERT(m_bInited);
	BOOL bRet = FALSE;
	PreAccess(1);
	bRet = g_ProgressData.bProgressEnabled;
	PostAccess(1);
	return bRet;
}

BOOL REBProgressManager::IsCancelEnabled()
{
	ASSERT(m_bInited);
	BOOL bRet = FALSE;
	PreAccess(1);
	bRet = g_ProgressData.bCancelEnabled;
	PostAccess(1);
	return bRet;
}

void REBProgressManager::EnableProgress(BOOL bEnable)
{
	ASSERT(m_bInited);
	PreAccess(1);
	g_ProgressData.bProgressEnabled = bEnable;
	PostAccess(1);
	NotifyChange();
}

void REBProgressManager::EnableCancel(BOOL bEnable)
{
	ASSERT(m_bInited);
	PreAccess(1);
	g_ProgressData.bCancelEnabled = bEnable;
	PostAccess(1);
	NotifyChange();
}

int REBProgressManager::GetProgress()
{
	ASSERT(m_bInited);
	int nProg = 0;
	PreAccess(1);
	nProg = g_ProgressData.nProgress;
	PostAccess(1);
	return nProg;
}

void REBProgressManager::SetProgress(int nVal)
{
	ASSERT(m_bInited);
	PreAccess(1);
	g_ProgressData.nProgress = nVal;
	PostAccess(1);
	NotifyChange();
}

void REBProgressManager::SetVisible(BOOL bNewVal)
{
	ASSERT(m_bInited);
	PreAccess(1);
	g_ProgressData.bVisible = bNewVal;
	PostAccess(1);
	m_bVisible = bNewVal;
	NotifyChange();
}

BOOL REBProgressManager::GetVisible()
{
	ASSERT(m_bInited);
	BOOL bRet = FALSE;
	PreAccess(1);
	bRet = g_ProgressData.bVisible;
	PostAccess(1);
	return bRet;
}

LPCTSTR REBProgressManager::GetCaption()
{
	ASSERT(m_bInited);
	LPCTSTR pszRet = NULL;
	PreAccess(1);
	pszRet = g_ProgressData.cCaption;
	PostAccess(1);
	return pszRet;
}

void REBProgressManager::SetCaption(LPCTSTR pszCaption)
{
	ASSERT(m_bInited);
	ASSERT(::_tcslen(pszCaption) <= REBPROGRESSMAXSTRING);
	PreAccess(1);
	::_tcscpy(g_ProgressData.cCaption, pszCaption);
	PostAccess(1);
	NotifyChange();
}

LPCTSTR REBProgressManager::GetStaticText(int nIndex)
{
	ASSERT(m_bInited);
	BOOL bValidIndex = (nIndex > -1 && nIndex < REBPROGRESSMAXSTATIC);
	ASSERT(bValidIndex);
	if(!bValidIndex)
	{
		return NULL;
	}

	LPCTSTR pszReturn = NULL;
	PreAccess(1);
	pszReturn = g_ProgressData.cStaticText[nIndex];
	PostAccess(1);
	return pszReturn;
}

void REBProgressManager::SetStaticText(int nIndex, LPCTSTR pszText)
{
	ASSERT(m_bInited);
	ASSERT(::_tcslen(pszText) <= REBPROGRESSMAXSTRING);
	BOOL bValidIndex = (nIndex > -1 && nIndex < REBPROGRESSMAXSTATIC);
	ASSERT(bValidIndex);
	if(!bValidIndex)
	{
		return;
	}

	PreAccess(1);
	::_tcscpy(g_ProgressData.cStaticText[nIndex], pszText);
	PostAccess(1);
	NotifyChange();
}

///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// REBProgressThread - the UI thread that runs the progress dialog

IMPLEMENT_DYNCREATE(REBProgressThread, CWinThread);
					
REBProgressThread::REBProgressThread(HWND hwndParent) : 
	m_hwndParent(hwndParent)
{
}

REBProgressThread::REBProgressThread() : // constructor required by DECLARE_DYNCREATE macro (not used)
	m_hwndParent(NULL)
{
}

REBProgressThread::~REBProgressThread()
{
}

BOOL REBProgressThread::InitInstance()
{
	// NOTE: the memory allocated below is freed by REBProgressDialog::OnDestroy (it deletes itself)
	REBProgressDialog* pDlg = new REBProgressDialog(CWnd::FromHandle(m_hwndParent));
	VERIFY( pDlg->Create(IDD_DIALOG_PROGRESS, CWnd::FromHandle(m_hwndParent)) );
	g_hwndProgress = pDlg->GetSafeHwnd();
	return TRUE;
}

int REBProgressThread::ExitInstance()
{
	return CWinThread::ExitInstance();
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// REBProgressDialog dialog

REBProgressDialog::REBProgressDialog(CWnd* pParent /*=NULL*/)
	: CDialog(REBProgressDialog::IDD, pParent)
{
	//{{AFX_DATA_INIT(REBProgressDialog)
	m_csText1 = _T("");
	m_csText2 = _T("");
	m_csText3 = _T("");
	//}}AFX_DATA_INIT

	m_bUserAbortFlag = FALSE;
	m_bCancelEnabled = TRUE;
	m_bProgressEnabled = TRUE;
	m_nProgress = 0;
}


void REBProgressDialog::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(REBProgressDialog)
	DDX_Control(pDX, IDCANCEL, m_buttonCancel);
	DDX_Control(pDX, IDC_PROGRESS_CTRL, m_ctrlProgress);
	DDX_Text(pDX, IDC_STATIC_TEXT1, m_csText1);
	DDX_Text(pDX, IDC_STATIC_TEXT2, m_csText2);
	DDX_Text(pDX, IDC_STATIC_TEXT3, m_csText3);
	//}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(REBProgressDialog, CDialog)
	//{{AFX_MSG_MAP(REBProgressDialog)
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
	ON_REGISTERED_MESSAGE(REBREFRESHPROGRESS,OnRefresh)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// REBProgressDialog message handlers

BOOL REBProgressDialog::OnInitDialog() 
{
	CDialog::OnInitDialog();

	// REB - do not use CenterWindow (this locks things up)
	HWND hwndParent = ::GetParent(GetSafeHwnd());
	ASSERT(hwndParent);
	CRect rect, rectParent;
	::GetWindowRect(GetSafeHwnd(), &rect);
	::GetWindowRect(hwndParent, &rectParent);
	BOOL bOK = ((rectParent.Height() > rect.Height()) && (rectParent.Width() > rect.Width()));
	if(!bOK)
	{
		// if parent window is too small, center on desktop
		::GetWindowRect(::GetDesktopWindow(), &rectParent);
	}
	int nLeft = (rectParent.left + (rectParent.Width() / 2)) - (rect.Width() / 2);
	int nTop = (rectParent.top + (rectParent.Height() / 2)) - (rect.Height() / 2);
	::MoveWindow(GetSafeHwnd(), nLeft, nTop, rect.Width(), rect.Height(), FALSE);

	m_ctrlProgress.SetRange32(0, 100);
	RefreshData(TRUE);
	return TRUE;  // return TRUE unless you set the focus to a control
				  // EXCEPTION: OCX Property Pages should return FALSE
}

void REBProgressDialog::OnCancel() 
{
	m_bUserAbortFlag = TRUE;
	::EnterCriticalSection(&g_Crit2);
	g_bUserAbortFlag = TRUE;
	::LeaveCriticalSection(&g_Crit2);
	HandleUserAbort();
}

void REBProgressDialog::OnDestroy() 
{
	CDialog::OnDestroy();
	delete this; // destroy our C++ object
}

void REBProgressDialog::HandleUserAbort()
{
	ASSERT(m_bUserAbortFlag == TRUE);
	UpdateData(TRUE);
	m_buttonCancel.EnableWindow(FALSE);
	m_buttonCancel.ShowWindow(SW_HIDE);
	m_ctrlProgress.EnableWindow(FALSE);
	m_ctrlProgress.ShowWindow(SW_HIDE);
	m_csText1 = m_csAbortText;
	m_csText2.Empty();
	m_csText3.Empty();
	m_nProgress = 0;
	m_bCancelEnabled = FALSE;
	m_bProgressEnabled = FALSE;
	UpdateData(FALSE);
	UpdateWindow();
}

LRESULT REBProgressDialog::OnRefresh(WPARAM wParam, LPARAM lParam)
{
	RefreshData();
	return 0;
}

void REBProgressDialog::RefreshData(BOOL bInit /*= FALSE*/)
{
	if(!m_bUserAbortFlag)
	{
		::EnterCriticalSection(&g_Crit1);
		::REBCopyProgressData(&m_TempData, &g_ProgressData);
		::LeaveCriticalSection(&g_Crit1);

		BOOL bUpdateWindow = FALSE;
		m_csAbortText = m_TempData.cAbortText;
		m_csText1 = m_TempData.cStaticText[0];
		m_csText2 = m_TempData.cStaticText[1];
		m_csText3 = m_TempData.cStaticText[2];
		m_nProgress = m_TempData.nProgress;

		if(bInit) // TRUE when called from OnInitDialog
		{
			bUpdateWindow = TRUE;
			m_bVisible = m_TempData.bVisible;
			m_bCancelEnabled = m_TempData.bCancelEnabled;
			m_bProgressEnabled = m_TempData.bProgressEnabled;
			m_csCaption = m_TempData.cCaption;
			m_buttonCancel.EnableWindow(m_bCancelEnabled);
			m_buttonCancel.ShowWindow(m_bCancelEnabled ? SW_SHOW : SW_HIDE);
			m_ctrlProgress.EnableWindow(m_bProgressEnabled);
			m_ctrlProgress.ShowWindow(m_bProgressEnabled ? SW_SHOW : SW_HIDE);
			if(m_bProgressEnabled)
			{
				m_ctrlProgress.SetPos(m_nProgress);
			}
			SetWindowText(m_csCaption);
//			ShowWindow(m_bVisible ? SW_SHOW : SW_HIDE); // REBProgressManager will call the initial ShowWindow
		}
		else
		{
			if(m_bCancelEnabled != m_TempData.bCancelEnabled)
			{
				m_bCancelEnabled = m_TempData.bCancelEnabled;
				m_buttonCancel.EnableWindow(m_bCancelEnabled);
				m_buttonCancel.ShowWindow(m_bCancelEnabled ? SW_SHOW : SW_HIDE);
				bUpdateWindow = TRUE;
			}
			if(m_bProgressEnabled != m_TempData.bProgressEnabled)
			{
				m_bProgressEnabled = m_TempData.bProgressEnabled;
				m_ctrlProgress.EnableWindow(m_bProgressEnabled);
				m_ctrlProgress.ShowWindow(m_bProgressEnabled ? SW_SHOW : SW_HIDE);
				bUpdateWindow = TRUE;
			}
			if(m_bProgressEnabled)
			{
				m_ctrlProgress.SetPos(m_nProgress);
			}
			if(m_csCaption.Compare(m_TempData.cCaption) != 0)
			{
				m_csCaption = m_TempData.cCaption;
				SetWindowText(m_csCaption);
				bUpdateWindow = TRUE;
			}
			if(m_bVisible != m_TempData.bVisible)
			{
				m_bVisible = m_TempData.bVisible;
				ShowWindow(m_bVisible ? SW_SHOW : SW_HIDE);
				bUpdateWindow = TRUE;
			}
		}

		UpdateData(FALSE);
		if(bUpdateWindow)
		{
			UpdateWindow();
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

Note that the REBProgressManager::BeginProgressDialog functions kicks off a REBProgressThread, and the InitInstance of REBProgressThread creates the modeless dialog. The dialog deletes itself in its OnDestroy handler. REBProgressManager also initializes two critical section variables to synchronize data transfer between the two threads.

The REBProgressManager object writes to the REBPROGRESSDATA global struct, and the REBProgressDialog reads from it. The REBProgressDialog writes to the BOOL g_bUserAbortFlag variable, and REBProgressManager reads from it. When REBProgressManager gets new data, it sends a windows message to REBProgressDialog to inform it to read the data and update itself.

Note the REBProgressManager::CheckAndPump function, which is called every time the progress data is read or written. This function pumps the message que of the main thread every two seconds. You need to pump the message que during a long process to allow the application to process WM_PAINT and other messages. If you do not pump the message que, the application cannot redraw itself until your process completes. We use a global function called "YieldProc" to pump the message que during long processes, but if you call it too often, performance can degrade; profiler programs will show an inordinate amount of time spent in this function. By pumping only every two seconds REBProgressManager::CheckAndPump is quicker. The result is, if the use brings another applications's window above yours during a lengthy operation for which you are using the progress dialog, when they bring your application back to the forefront, it will take at most two seconds to redraw. I think that is fairly acceptable while doing long processing, and the user should not try to kill your application thinking it is locked up or in an endless loop. Often, while you are in the middle of a long process, Task Manager will show your program as "Not Responding," because you cannot respond to the windows messages it is sending you app.

This was my first attempt at multi-threaded coding, but it seems to work well. I learned a lot about how MFC classes are NOT thread safe. People I have described this to tell me that a semaphore might be the better choice for synchronizing the threads, but critical sections seem to work fine, and I have not yet read up to semaphores in the multi-threading chapter of Richter's "Advanced Windows"! Hope this comes in handy.



Comments

  • Chaque ghd pink aura un code de suivi unique qui peut être vérifié en suivant les instructions de la boîte

    Posted by tlxhuw917 on 07/16/2013 05:59am

    Ces produits de beauté GHD sont adaptés pour obtenir à peu près n'importe quel type de cheveux parfaite stil.Du peut profiter de défriser les cheveux GHD attirer l'attention sur les traits de votre visage que vous préféreriez être remarqué plus. Par exemple, si vous avez des pommettes bien défini puis se redressant, votre frange ou la couche supérieure de cheveux pour mettre l'accent non seulement vos pommettes, mais même dans les yeux. Les femmes qui ont les cheveux volumineux mais ils ont peur que ces défrisants peuvent leur faire perdre leur rebond peuvent bomber dehors seule la couche supérieure pour être les cheveux très lisse sur le dessus et leurs mèches ondulées et plein d'entrain habituel ci-dessous. [url=http://ghdpascherferfr.blinkweb.com/]lisseur ghd[/url] Défrisants GHD ne sont pas seulement lisser vos cheveux et lui donner l'air élégant et brillant. En utilisant correctement, curl, flip ou agiter vos cheveux. Le même produit que vous utilisez pour le lissage peut également être utilisé pour d'autres styles innovants, c'est à cause de ses plaques et poignées arrondies, conçus spécifiquement pour soutenir votre publics wrist.The bientôt accepté la marque, et dans un court laps de temps, a atteint le succès . Maintenant de nombreuses célébrités et des stylistes professionnels admirer les produits de GHD. [url=http://ghdpascherferfr.webgarden.com/]lisseur ghd[/url] Troisièmement, ils ont également arrondis barils qui peuvent faire un fer GHD plus polyvalent que beaucoup d'autres types d'appareils de coiffure sur le marché. Si vous souhaitez modifier votre chevelure est souvent avantageux d'utiliser défrisants ghd peut aussi créer des vagues, sauts et loops.Endelig, vous devez sélectionner un fer GHD est conçu pour une utilisation dans une explosion ou frange. GHD IV styler Mini est parfait pour beaucoup moins de cheveux. Combinez ces excellentes caractéristiques redresseurs les plus complets, mais il est plus facile à utiliser sur des petits morceaux de hair.Maybe vous savez ce ghd lisseur

    Reply
  • Als men verliest de eigen gevoelens, hij of zij lijken aanvaardbaar Beats by Dr Dre oordopjes

    Posted by mrswanzi on 06/05/2013 11:32pm

    [url=http://koptelefoon-monsterbeats.tumblr.com/]beats by dre[/url] Met de urBeats van Monster Beats by Dr Dre gaat er een wereld van geluid voor je open. Klein van stuk, maar groots van geluid! Heeft u genoeg van het steeds vervangen van die in-ears Maak daar dan voor eens en voor altijd korte mette mee en haal de Beats in huis, en u hoort wat u al die tijd hebt gemist. Ze zijn zo gemaakt dat u al het buitengewone HD-audio materiaal van uw iPod, iPad of iPhone weer kunt horen. Het is ongelooflijk wat voor geweldig geluid er uit zo'n klein oortelefoontje kan komen. Als u eenmaal over de Beats beschikt wilt u nooit meer anders! [url=http://koptelefoon-monsterbeats.tumblr.com/]beats by dre[/url] Op drebeatskopen.com tref je een uitgebreid assortiment hoofdtelefoons aan van Beats By Dre. Een goed geluid gaat nu eenmaal hand-in-hand met een degelijk kwaliteitsmerk. Over de hele wereld genieten muziekliefhebbers, dj¡¯s en geluidspuristen van de hoge kwaliteit van dit geweldige stijlvolle merk en dat aantal stijgt steeds meer. Muzikaal genot wordt pas echt tot leven gebracht met een mooie, elegante en eigenwijze ontworpen koptelefoon. Beats By Dre heeft het voor elkaar gekregen om de koptelefoon te verheffen tot stijlicoon. Maar dan met behoud van de beste geluidskwaliteit, vergelijkbaar met niets minder dan studiokwaliteit. Je beleeft de muziek zoals de artiest het echt heeft bedoeld! [url=http://koptelefoon-monsterbeats.manifo.com/]beats by dre[/url] Op drebeatskopen.com tref je een uitgebreid assortiment hoofdtelefoons aan van Beats By Dre. Een goed geluid gaat nu eenmaal hand-in-hand met een degelijk kwaliteitsmerk. Over de hele wereld genieten muziekliefhebbers, dj¡¯s en geluidspuristen van de hoge kwaliteit van dit geweldige stijlvolle merk en dat aantal stijgt steeds meer. Muzikaal genot wordt pas echt tot leven gebracht met een mooie, elegante en eigenwijze ontworpen koptelefoon. Beats By Dre heeft het voor elkaar gekregen om de koptelefoon te verheffen tot stijlicoon. Maar dan met behoud van de beste geluidskwaliteit, vergelijkbaar met niets minder dan studiokwaliteit. Je beleeft de muziek zoals de artiest het echt heeft bedoeld!

    Reply
  • Jordan shoes mentioned Gene to go for the variety, a division of Nike

    Posted by TaddyGaffic on 04/22/2013 03:22pm

    But in an e mail concerning the LSU activity from South Carolina on October 13, the faculty included the image but digitally erased the cross. The picture was in any other case untouched. A minimum of 15 folks have already been injured as Polish and Russian football enthusiasts clashed in Warsaw forward of your teams' 1-1 attract.. Once I have said, the soccer cleats by [url=http://northernroofing.co.uk/roofins.cfm]nike free uk[/url] Adidas have the similar characteristics, therefore in order to know all of them, then expect to have a lengthy list. Obviously, those are the same in ways that Adidas is the one brand that they carry. The lightness of the weight of the materials used in making the Adidas soccer cleats is definitely the main reason why the soccer cleats the manufacture and they are sold in the market are lightweight too. Pokhara is our base for this aerial playground. The [url=http://fossilsdirect.co.uk/glossarey.cfm]nike huarache free[/url] choppy green hills all around offer a lifetime's worth of glorious flying possibilities, many of them still virgin. I Alpine (forward) launch, leaning forward, pulling my lines taut, with my glider laid out in an arc behind me. That is, you want something whose design is similar to that of a ski boot. That means that the inside removable liner, is a moldable type. ( it forms to the contours of your foot, providing maximum comfort and support.) You want the boot itself, made of a stiff plastic, or waterproof outer shell, again for support and insulation. Self defense purposes purposes dealing with #3 * Having the ability to contemplate [url=http://northernroofing.co.uk/roofins.cfm]nike free run 3[/url] gets and in addition leg techinques will not likely typically injure. I propose you will get out there applying reside training goods and rehearse fighting and also staying get to that has a close friend and even martial arts university undergraduate. Physical exercise diffusing strikes, using images, supplying photos, and in addition keeping crystal clear focused although becoming bombarded with plenty hits in addition to tennis shoes

    Reply
  • Interesting

    Posted by snareenactina on 12/22/2012 12:06am

    redeployment Regulating and testing fuel economy plays an important role in deterring air pollution throughout the Unites States’ streets and communities. EPA’s Fuel Economy pages provide information on current standards and how federal agencies work to enforce those laws, testing for national Corporate Average Fuel Economy or CAFE standards, and what you can do to reduce your own vehicle emissions. After viewing product detail pages or search results, look here to find an easy way to navigate back to pages you are interested in.#BREAK#Welcome, $UserDisplayName I have put together a selection of updated trade and balance of payments data charts focusing on the UK economy but also including details of our trade performance with other countries and some international comparisons of current account deficits and surpluses. Many teachers are covering the balance of payments at this time of the year so I hope this resource might be useful. quarrels European stock markets rose on Tuesday after growth data suggested that prospects for core eurozone countries were slightly better than expected, analysts said. americana Americans increased their spending at retailers in July by 0.8 percent, the most in five months. The increase suggests the economy may be emerging from its spring slump. constantine One would be a fool to think that China is not eyeballing Siberia. Lots of useful resources and much closer than North America. orricocci According to him, the situation is capable of crippling the real sector of the economy, which is supposed to be more active than it is at present. subramanian These graduates are some of the brightest people on earth. They are entrepreneurial and willing to take risks. They set up new companies which grow rapidly which ensure that our economic growth will never be under 9% for at least the next 50 years. berk We found a 1918 menu from Delmonico's, a New York steakhouse. Here's what it taught us. maller Chemical manufacturing yields such products as industrial chemicals, drugs, plastics, and soaps and cleaners. Production takes place in many parts of the nation, but is most heavily concentrated in the Northeast, especially in New York, New Jersey, and Pennsylvania, and in Illinois, Ohio, and Michigan. Large chemical plants are also located along the Texas and Louisiana coast and in California.

    Reply
  • Excellent example

    Posted by prashblr on 10/19/2011 03:47pm

    This code example saved me a lot of time. Thanks a bunch for posting!

    Reply
  • Cool Bru

    Posted by Legacy on 05/20/2003 12:00am

    Originally posted by: hannes

    dude...this is da best code sample eva..!!

    Reply
  • Unfortunately, locks up on XP

    Posted by Legacy on 12/19/2002 12:00am

    Originally posted by: Gary

    Got the code to build correctly on WinXP, but it locks up before the modeless dialog is ever created. Wondering what I am doing wrong...

    Gary

    Reply
  • Hangs up

    Posted by Legacy on 08/11/2002 12:00am

    Originally posted by: Tomek

    The same symptoms as Kannan's. Dialog just hangs up after BeginProgressDialog()...

    Reply
  • Very GOOD ....

    Posted by Legacy on 07/16/2002 12:00am

    Originally posted by: changuel khemaies

    it's very good, but with an example it will be more better

    Reply
  • please example

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

    Originally posted by: justine

    A sample will be nice as we can 'see' how does the concept works

    Reply
  • Loading, Please Wait ...

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

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • Live Event Date: October 23, 2014 @ 12:00 p.m. ET / 9:00 a.m. PT Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this eSeminar to learn why physical appliances are preferable to …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds