CancelDlg.h
////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
////////////////
// Generic Cancel Dialog. To use it, you must design a dialog with
// an IDCANCEL button, then instantiate CCancelDlg using your ID as the
// resource, and call CDialog::Create to create a modeless dialog. Then
// start your "long process". You must call CCancelDlg::Abort periodically
// to see if the user has aborted, and if so, quit.
//
class CCancelDlg : public CDialog {
public:
CCancelDlg() : m_bAbort(FALSE) { }
void Reset() { m_bAbort = FALSE; }
virtual BOOL Abort();
protected:
BOOL m_bAbort;
virtual void OnCancel();
};
CancelDlg.cpp
////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//
// Implementation for CCancelDlg, a generic cancel dialog.
//
#include "stdafx.h"
#include "CancelDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//////////////////
// User pressed Cancel: set flag
//
void CCancelDlg::OnCancel()
{
m_bAbort = TRUE;
}
//////////////////
// Test for abort. This is my chance to run peek/pump message loop;
// ie, to process any messages that may be waiting for me
// or--in Windows 3.1--for other apps as well.
//
BOOL CCancelDlg::Abort()
{
MSG msg;
while (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) {
AfxGetThread()->PumpMessage();
}
return m_bAbort;
}
Figure 4 CANCEL1
////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//
// Shows how to use CCancelDlg.
//
#include "stdafx.h"
#include "resource.h" // main symbols
#include "CancelDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
// This function simulates the long operation of dumping a record.
//
const NUMRECS = 100;
void DumpRecord(int n)
{
Sleep(500); // sleep half a second
}
//////////////////
// My cancel dialog: derived from generic cancel dialog to report status too.
//
class CMyCancelDlg : public CCancelDlg {
public:
CString m_sProgress;
virtual void DoDataExchange(CDataExchange* pDX);
};
////////////////
// Data exchange: use static text control to display # records dumped
// Standard MFC DDX stuff.
//
void CMyCancelDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_PROGRESS, m_sProgress);
}
//////////////////
// Application main window is a dialog with "Begin Dumping"
// and "Exit" buttons.
//
class CMainDlg : public CDialog {
public:
CMainDlg() : CDialog(IDD_MAINFRAME) { };
protected:
afx_msg void OnBeginDump();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMainDlg, CDialog)
ON_COMMAND(IDC_BEGIN_DUMP, OnBeginDump)
END_MESSAGE_MAP()
//////////////////
// User clicked "Begin Dumping" button: start dumping.
//
void CMainDlg::OnBeginDump()
{
EnableWindow(FALSE); // disable myself
CMyCancelDlg dlg; // create cancel dialog object..
dlg.Create(IDD_CANCELDLG, this); // ..and window (modeless dialog)
// Now dump the records. Quit if CCancelDlg::Abort returns TRUE.
for (int i=0; i<NUMRECS && !dlg.Abort(); i++) {
::DumpRecord(i);
dlg.m_sProgress.Format(_T("Dumping record %d of %d"),
i+1, NUMRECS);
dlg.UpdateData(FALSE);
}
// done, or user canceled
dlg.DestroyWindow(); // destroy cancel dialog
EnableWindow(TRUE); // enable myself..
SetForegroundWindow(); // ..and make foreground window
}
//////////////////
// Application class
//
class CMyApp : public CWinApp {
public:
virtual BOOL InitInstance();
};
CMyApp theApp;
//////////////////
// MFC InitInstance: run main dialog and then quit.
//
BOOL CMyApp::InitInstance()
{
CMainDlg dlg; // create main dialog
m_pMainWnd = &dlg; // set main window for good luck
dlg.DoModal(); // run the dialog..
return FALSE; // ..and then quit
}
Figure 5 CthreadJob
ThreadJob.h
////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
////////////////
// Generic worker thread object. Use it to run a task in a separate thread.
// To use:
// * Derive your own class from CThreadJob and implement DoWork:
// - periodically check m_bAbort
// - call OnProgress if you want to report progress
// * Create an instance of your class (not on stack!)
// * Call Begin to start, with CWnd and msg ID for OnProgress notifications.
// * Call Kill to abort
//
class CThreadJob : public CObject {
private:
static UINT ThreadProc(LPVOID pObj);
CWinThread* m_pThread; // running thread, if any
protected:
HWND m_hWndOwner; // HWND, *not* CWnd* of owner window
UINT m_ucbMsg; // callback message for OnProgress
UINT m_uErr; // thread error code
BOOL m_bAbort; // whether to abort: DoWork must check this
// Call this from DoWork to report progress.
// Meaning of WPARAM/LPARAM is up to you.
void OnProgress(WPARAM wp=0, LPARAM lp=0);
// You must implement to do the work
virtual UINT DoWork() = 0;
public:
virtual BOOL Begin(CWnd* pWndOwner=NULL, UINT ucbMsg=0);
virtual void Kill();
DECLARE_DYNAMIC(CThreadJob)
};
ThreadJob.cpp
////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//
// Implementation for CThreadJob, a generic worker thread.
//
#include "stdafx.h"
#include "ThreadJob.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNAMIC(CThreadJob, CObject)
//////////////////
// Thread proc calls virtual DoWork function. This converts the
// Windows/C-style thread procedure into an MFC/C++-style virtual function.
// To do the "work" of the thread, implement DoWork and don't worry about
// the thread proc.
//
UINT CThreadJob::ThreadProc(LPVOID pObj)
{
CThreadJob* pJob = (CThreadJob*)pObj;
ASSERT_KINDOF(CThreadJob, pJob);
pJob->m_uErr = pJob->DoWork(); // call virt fn to do the work
pJob->m_pThread = NULL; // done: clear
return pJob->m_uErr; // ..and return error code to Windows
}
//////////////////
// Begin running the worker thread. Args are owner window and callback
// message ID to use for OnProgress notifications, if any. You could enhance
// this to expose priority and other AfxBeginThread args.
//
BOOL CThreadJob::Begin(CWnd* pWndOwner, UINT ucbMsg)
{
m_hWndOwner = pWndOwner->GetSafeHwnd();
m_ucbMsg = ucbMsg;
m_bAbort = FALSE;
m_uErr = 0;
m_pThread = AfxBeginThread(ThreadProc, this);
return m_pThread != NULL;
}
//////////////////
// Abort the thread. All this does is set m_bAbort = TRUE.
// It's up to you to check this flag periodically in your DoWork function.
//
void CThreadJob::Kill()
{
m_bAbort = TRUE;
}
//////////////////
// Report progress generically in the form of WPARAM/LPARAM.
// Your DoWork function can call this whenever it likes to post a message
// to the owning window. It's up to you what wp/lp mean.
// OnProgress uses PostMessage (instead of SendMessage) so the code that
// handles the thread will run in the owner window's thread.
//
void CThreadJob::OnProgress(WPARAM wp, LPARAM lp)
{
if (m_hWndOwner && m_ucbMsg)
::PostMessage(m_hWndOwner, m_ucbMsg, wp, lp);
}
////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
// Shows how to use CThreadJob
#include "stdafx.h"
#include "resource.h" // main symbols
#include "ThreadJob.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//////////////////
// Message used for progress notification
//
const UINT WM_MYPROGRESS = WM_USER;
// This function simulates the long operation of dumping a record.
//
const NUMRECS = 100;
void DumpRecord(int n)
{
Sleep(500); // sleep half a second
}
//////////////////
// Dump record job is a special case of CThreadJob, a worker thread
//
class CDumpRecordsJob : public CThreadJob {
public:
virtual UINT DoWork();
};
//////////////////
// This is the function that does the work of the job
//
UINT CDumpRecordsJob::DoWork()
{
// Dump the records. Quit if m_bAbort is TRUE.
for (int i=0; i<NUMRECS && !m_bAbort; i++) {
::DumpRecord(i); // dump next record
OnProgress(i+1); // report progress
}
OnProgress(m_bAbort ? -1 : 0); // report done or cancelled
return 0;
}
//////////////////
// Application main window is a dialog with "Begin Dumping", "Stop",
// and "Exit" buttons, and a static text control to show progress.
//
class CMainDlg : public CDialog {
public:
CMainDlg() : CDialog(IDD_MAINFRAME) { };
protected:
CStatic m_wndProgress; // static text for progress
CButton m_wndBeginDump; // "Begin Dumping" button
CDumpRecordsJob m_job; // worker job (thread)
virtual BOOL OnInitDialog();
afx_msg void OnBeginDump();
afx_msg void OnStop();
afx_msg void OnProgress(WPARAM wp, LPARAM lp);
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMainDlg, CDialog)
ON_COMMAND(IDC_BEGIN_DUMP, OnBeginDump)
ON_COMMAND(IDC_STOP_DUMP, OnStop)
ON_MESSAGE(WM_MYPROGRESS, OnProgress)
END_MESSAGE_MAP()
//////////////////
// Initialize dialog: subclass controls
//
BOOL CMainDlg::OnInitDialog()
{
m_wndProgress.SubclassDlgItem(IDC_PROGRESS, this);
m_wndBeginDump.SubclassDlgItem(IDC_BEGIN_DUMP, this);
return TRUE;
}
//////////////////
// User clicked "Begin Dumping" button: start job (thread)
//
void CMainDlg::OnBeginDump()
{
m_wndBeginDump.EnableWindow(FALSE); // disable start button
m_job.Begin(this, WM_MYPROGRESS); // and start the job
}
//////////////////
// User clicked "Stop" button: kill the job (thread)
//
void CMainDlg::OnStop()
{
m_job.Kill();
}
//////////////////
// Handle progress notification from thread.
// wp = 0 ==> done
// wp = -1 ==> aborted
// else wp = number of records dumped.
//
void CMainDlg::OnProgress(WPARAM wp, LPARAM lp)
{
CString s;
if (wp==0)
s = _T("Done");
else if (wp==-1)
s = "Aborted";
else
s.Format(_T("Dumping record %d of %d"), wp, NUMRECS);
if (wp<=0)
// abort or done: re-enable Start button
m_wndBeginDump.EnableWindow(TRUE);
m_wndProgress.SetWindowText(s);
}
//////////////////
// Application class
//
class CMyApp : public CWinApp {
public:
virtual BOOL InitInstance();
};
CMyApp theApp;
//////////////////
// MFC InitInstance: run main dialog and then quit.
//
BOOL CMyApp::InitInstance()
{
CMainDlg dlg; // create main dialog
m_pMainWnd = &dlg; // set main window for good luck
dlg.DoModal(); // run the dialog..
return FALSE; // ..and then quit
}