/*
Module : PSTAT.CPP
Purpose: Provides a generalised framework for executing a lengthy operation
in a thread. Feedback is provided by a progress dialog which can
optionally be cancelled.
Author : PJN / 27-03-1997 Initially created
History: PJN / 18-02-1998 v1.1
Fixed a bug where the worker thread was
trying to close a dialog before it was
created. A CEvent member of CProgressThreadDlg
now protects this. Also removed the usual
appwizard comments from the demo program
PJN / 8-11-1998 v1.2
1. Added an option to confirm cancel by means
of a message box.
2. General tidy up of the code
3. Inclusion of VC 5 workspace files now as standard
4. All code now compiles cleanly at warning level 4
5. Code now supports UNICODE and build configurations
are now provided.
Copyright (c) 1997 - 1998 PJ Naughter.
All rights reserved.
*/
///////////////////////// Includes ///////////////////////////////////////////
#include "stdafx.h"
#include "resource.h"
#include "PStat.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
////////////////////// Local Prototypes //////////////////////////////////////
//Use to pass a structure to the worker threads
class CProgressThreadInfo : public CObject
{
public:
DECLARE_DYNAMIC(CProgressThreadInfo)
//Constructors / Destructors
CProgressThreadInfo();
CProgressThreadDlg* m_pStatusDlg; //Used to allow the UI to be updated during operation
FUNCTION_WITH_PROGRESS* m_pfnFunction; //Function to call to do the work
void* m_pData; //data to pass to the caller function
};
//The actual worker thread which is going to do the work
UINT ProgressDialogWorkerThread(LPVOID pParam);
/////////////////////// Implementation ////////////////////////////////////////
IMPLEMENT_DYNAMIC(CProgressThreadInfo, CObject)
CProgressThreadInfo::CProgressThreadInfo()
{
m_pStatusDlg = NULL;
m_pfnFunction = NULL;
m_pData = NULL;
}
IMPLEMENT_DYNAMIC(CProgressThreadDlg, CDialog)
CProgressThreadDlg::CProgressThreadDlg(BOOL bShowCancelButton, BOOL bConfirmCancel, CWnd* pParent)
: CDialog(bShowCancelButton ? IDD_PROGRESS_WITH_CANCEL : IDD_PROGRESS, pParent)
{
//{{AFX_DATA_INIT(CProgressThreadDlg)
//}}AFX_DATA_INIT
m_nCurrentPercentage = 0;
m_bReady = FALSE;
m_bCancelled = FALSE;
m_bShowCancelButton = bShowCancelButton;
if (bConfirmCancel)
ASSERT(m_bShowCancelButton);
m_bConfirmCancel = bConfirmCancel;
m_bOkToClose = FALSE;
m_pThread = NULL;
}
void CProgressThreadDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CProgressThreadDlg)
DDX_Control(pDX, IDC_PROGRESS, m_ctrlProgress);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CProgressThreadDlg, CDialog)
//{{AFX_MSG_MAP(CProgressThreadDlg)
ON_BN_CLICKED(IDC_CANCEL, OnCancelled)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BOOL CProgressThreadDlg::OnInitDialog()
{
//Let the parent do its thing
CDialog::OnInitDialog();
//initialise the progress control and remember the start time
m_ctrlProgress.SetRange(0, 100);
m_ctrlProgress.SetPos(0);
m_TimeCreation = CTime::GetCurrentTime();
m_bReady = TRUE;
if (!m_sCaption.IsEmpty())
SetWindowText(m_sCaption);
//the progress dialog is now initialised,
//signal the event to allow the worker thread
//to continue
m_EventReady.SetEvent();
return TRUE;
}
void CProgressThreadDlg::OnCancel()
{
if (m_bOkToClose)
CDialog::OnCancel();
else if (m_bShowCancelButton)
{
if (m_bConfirmCancel)
{
//Worker thread needs to be suspended while we are displaying the confirm cancel message box
ASSERT(m_pThread);
m_pThread->SuspendThread();
if (AfxMessageBox(m_sConfirmPrompt, MB_YESNO) == IDYES)
m_bCancelled = TRUE;
m_pThread->ResumeThread();
}
else
m_bCancelled = TRUE;
}
}
void CProgressThreadDlg::OnOK()
{
//deliberately do nothing
}
void CProgressThreadDlg::Close()
{
m_bOkToClose = TRUE;
PostMessage(WM_CLOSE);
}
void CProgressThreadDlg::OnCancelled()
{
OnCancel();
}
void CProgressThreadDlg::SetPercentageDone(int Percentage)
{
if (!m_bReady || m_nCurrentPercentage == Percentage)
return;
m_ctrlProgress.SetPos(Percentage);
m_nCurrentPercentage = Percentage;
if (Percentage) //avoid division by 0
{
long lSecondsLeft = ((CTime::GetCurrentTime() - m_TimeCreation).GetTotalSeconds() * (100 - Percentage)) / Percentage;
CTimeSpan TimeLeft = CTimeSpan(lSecondsLeft);
int Minutes = TimeLeft.GetMinutes();
int Seconds = TimeLeft.GetSeconds();
CString sVal;
CString sText;
if (Minutes)
{
sVal.Format(_T("%d"), Minutes);
AfxFormatString1(sText, IDS_PSTAT_TIMELEFT_IN_MINUTES, sVal);
}
else
{
if (Seconds)
{
sVal.Format(_T("%d"), Seconds);
AfxFormatString1(sText, IDS_PSTAT_TIMELEFT_IN_SECONDS, sVal);
}
else
{
//if 0 seconds are left then display no text
sText = CString(_T(""));
}
}
ASSERT(GetDlgItem(IDC_TIMELEFT));
GetDlgItem(IDC_TIMELEFT)->SetWindowText(sText);
}
}
BOOL ExecuteFunctionWithProgressDialog(FUNCTION_WITH_PROGRESS* pfnFunction, const CString& sProgressTitle,
void* pData, DWORD dwFlags, const CString& sConfirmPrompt, int nPriority, CWnd* pParent)
{
BOOL bShowCancel = (dwFlags & PSTAT_CANCEL);
BOOL bConfirmCancel = (dwFlags & PSTAT_CONFIRMCANCEL);
//Create the progress dialog and set its various flags
CProgressThreadDlg dlg(bShowCancel, bConfirmCancel, pParent);
dlg.m_sCaption = sProgressTitle;
dlg.m_sConfirmPrompt = sConfirmPrompt;
CProgressThreadInfo Info;
Info.m_pStatusDlg = &dlg;
Info.m_pfnFunction = pfnFunction;
Info.m_pData = pData;
//Create the worker thread (initally suspended)
CWinThread* pThread = AfxBeginThread(ProgressDialogWorkerThread, &Info, nPriority, 0, CREATE_SUSPENDED);
//Fail if the thread failed to create itself
if (!pThread)
{
ASSERT(FALSE);
return FALSE;
}
//Store away the worker thread pointer in the dialog
dlg.m_pThread = pThread;
//Resume the wortker thread
pThread->ResumeThread();
//bring up the dialog modal (thread will close it for us)
dlg.DoModal();
return !dlg.HasBeenCancelled();
}
UINT ProgressDialogWorkerThread(LPVOID pParam)
{
CProgressThreadInfo* pInfo = (CProgressThreadInfo*) pParam;
ASSERT(pInfo);
ASSERT(pInfo->IsKindOf(RUNTIME_CLASS(CProgressThreadInfo)));
//Call the user defined function
pInfo->m_pfnFunction(pInfo->m_pData, pInfo->m_pStatusDlg);
//wait until the progress dialog is initialised before
//we try to close it
CSingleLock lock(&pInfo->m_pStatusDlg->GetEventReady(), TRUE);
//close down the progress dialog
pInfo->m_pStatusDlg->Close();
return 0;
}