Modeless Multi-Threaded Progress Dialog
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
Jordan shoes mentioned Gene to go for the variety, a division of Nike
Posted by TaddyGaffic on 04/22/2013 03:22pmBut 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
ReplyInteresting
Posted by snareenactina on 12/22/2012 12:06amredeployment 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.
ReplyExcellent example
Posted by prashblr on 10/19/2011 03:47pmThis code example saved me a lot of time. Thanks a bunch for posting!
ReplyCool Bru
Posted by Legacy on 05/20/2003 12:00amOriginally 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:00amOriginally 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
ReplyHangs up
Posted by Legacy on 08/11/2002 12:00amOriginally posted by: Tomek
The same symptoms as Kannan's. Dialog just hangs up after BeginProgressDialog()...
ReplyVery GOOD ....
Posted by Legacy on 07/16/2002 12:00amOriginally posted by: changuel khemaies
it's very good, but with an example it will be more better
Replyplease example
Posted by Legacy on 02/26/2002 12:00amOriginally posted by: justine
A sample will be nice as we can 'see' how does the concept works
Replyan example
Posted by Legacy on 06/14/2001 12:00amOriginally posted by: DET
I tried to integrate your code to a sdi project but i couldnt. I get some errors. If you put an example project it would be easyer for us who are new at using second threads.
ReplyFix for threading bug
Posted by Legacy on 04/09/2001 12:00amOriginally posted by: Wes Cherry
ReplyLoading, Please Wait ...