· How do we define and handle a user-defined message?
We put the line
#define WM_THREADFINISHED (WM_USER+5)
in some header file that is included in all the files that mention the new message. For example, in this program, I put it in ThreadTest.h. The identifier WM_USER marks the place where the Win32 API says you can start defining your own messages. But MFC uses a few, so you are advised to start with WM_USER+5 when writing MFC programs.
Don't put this line in resource.h, since Visual Studio is always regenerating that file, and will wipe out your new line.
Now to get the message mapped to the right message handler. You cannot do this with Class Wizard, which cannot handle user-defined messages. Here are the steps:
In ComputeDlg.cpp, after BEGIN_MESSAGE_MAP but outside the AFX_MSG_MAP brackets:
ON_MESSAGE(WM_THREADFINISHED,
CComputeDlg::OnThreadFinished)
It goes all on one line, though it won't fit on one line here.
In ComputeDlg.h, add the prototype of the handler:
afx_msg LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);
Then, in ComputeDlg.cpp, add the message handler itself.
LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)
{ GetDlgItem(ID_START)->EnableWindow(TRUE);
KillTimer(m_nTimer); // otherwise there’s a memory leak
return 0;
}
This leaves the dialog up; we removed the OK button. Of course, we could call CDialog::OnOK() to dismiss it, but instead we just leave it up, enabling the Start button again.
Here it is in context: Italics show the added code.
In ComputeDlg.cpp:
BEGIN_MESSAGE_MAP(CComputeDlg, CDialog)
ON_MESSAGE(WM_THREADFINISHED,
CComputeDlg::OnThreadFinished)
//{{AFX_MSG_MAP(CComputeDlg)
ON_BN_CLICKED(ID_START, OnStart)
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
In ComputeDlg.h:
class CComputeDlg : public CDialog
{
// Construction
public:
int m_nCount;
CComputeDlg(CWnd* pParent = NULL);
afx_msg LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);
// Dialog Data
//{{AFX_DATA(CComputeDlg)
That finishes the explanation of user-defined messages.
· Why use the view window's handle instead of a pointer to the view window as a CWnd object?
Because the classes derived from CObject are not "thread-safe".
The details of exactly what this means are complicated, and as a normal Windows programmer, you do not need to know them. You just need to know that you should not pass pointers to objects derived from CWnd from one thread to another. It is all right to pass a pointer to CRect or CString, for example, but not to a CWnd, or for example to a CButton, since CButton is derived from CWnd.
7. Now we've programmed the worker thread. Here's how to stop it:
void CComputeDlg::OnCancel()
{ if(g_nCount == 0) // prior to Start button
CDialog::OnCancel();
else // computation in progress
g_nCount = g_nMaxCount; // force exit
}
8. It only remains to start the thread. Here's the way Kruglinksi does it. Below I discuss another way, which is often better.
void CComputeDlg::OnStart()
{
m_nTimer = SetTimer(1,100,NULL);
GetDlgItem(ID_START)->EnableWindow(FALSE);
AfxBeginThread(ComputeThreadProc, GetSafeHwnd(), THREAD_PRIORITY_NORMAL);
}
Well, the program works. You can press Start, and then interrupt the thread by pressing Cancel. You can tell it worked, because the progress bar stops and the Start button is re-enabled. Or, if you don't interrupt, the thread stops when it is done, and the Start button is re-enabled.
Creating Worker Threads
Method 1 (as in the Kruglinksi example)
Supply a pointer to a global “thread procedure” that will be used in place of Run. This pointer is used as a parameter to the MFC function Afx_BeginThread. Although this works OK in the example given in this lecture, it is harder to work with in more realistic examples.
Method 2
The default Run in CWinThread has a message loop, so to create a worker thread, you first create a class derived from CWinThread, and then override Run.
Уважаемый посетитель!
Чтобы распечатать файл, скачайте его (в формате Word).
Ссылка на скачивание - внизу страницы.