Introduction
I was working on one of my pet projects yesterday night, there was a requirement of using UI thread. As I had the liberty of using MFC, I thought of using CWinThread
. When I dived more into its concepts , I though of writing an article on this highly magestic OLD technology, far away from the .NET world.
Using the Code
So now, here is our problem statement.
Problem Statement #1
"Create a UI Thread, which posts message to Main thread every second increasing the counter by 1."
Step By Step Guide to Solve Problem #1
- These days, VS wizards are so powerful, you don’t need to write MFC Classes derived classes by hand, you just need to right click on project in Class View and Add Class derived from
CWinThread
. So just right Click on Project Name ->Add->Class - After the dialog box is open, just name new class as
CCountingThread
. Following is the skeleton that would be produced by Wizard:
class CCountingThread : public CWinThread
{
DECLARE_DYNCREATE(CCountingThread)
protected:
CCountingThread(); virtual ~CCountingThread();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
protected:
DECLARE_MESSAGE_MAP()
};
- Now add three variables to the class to serve our purpose:
UINT_PTR
m_uTimerID
: will keep track of SetTimer
ID int
m_iCount
: will keep track of next incremented value CWnd*
m_pParentWnd
: will keep pointer to the main window. I know there is one more variable in CWinThread
itself to this task (m_pMainWnd
). However, I just want to keep it simple.
- Now since the Main Dialog must be updated every second with a new value, I thought of using
WM_TIMER
message with elapsed time of 1 second and on every timer message, post message to main window with new value. So now, add new function in CCountingThread
to handle WM_TIMER
message.
void CCountingThread::OnTimer(WPARAM wParam, LPARAM lParam)
{
m_pParentWnd->PostMessageW(WM_USER+2,0,++m_iCount); ---- (1)
}
Here, I have written code for posting message to main window with new value in (1).
- Now to make our
CCountingThread::OnTimer (…)
function visible to MessageLoop
for WM_TIMER
message, add the following code between BEGIN_MESSAGE_MAP()
and END_MESSAGE_MAP()
.
ON_THREAD_MESSAGE(WM_TIMER,&CCountingThread::OnTimer)
- Now, write the activation code for timer in
CCountingThread::InitInstance()
and de-activation code in CCountingThread::ExitInstance()
as follows:
BOOL CCountingThread::InitInstance(){
m_uTimerID = SetTimer(NULL,2001,1000,NULL);
return TRUE;
}
int CCountingThread::ExitInstance(){
KillTimer(NULL,m_uTimerID);
return CWinThread::ExitInstance();
}
After the above task for our WinThread
derived class is complete.
- Now, design main window UI with One Edit Box (for displaying value from
Thread
) and two buttons (For Starting and Stopping Thread) and add relevant handler and control variable for edit box. Also add pointer to CCountingThread
in class, before doing previous don’t forget to add CountingThread.h in your mainwindow
class header file. - Add the following code to your Start button handler:
void CUserThread1Dlg::OnBnClickedStartThread()
{
if(m_pRunningThread== NULL)
{
m_pRunningThread = (CCountingThread*)AfxBeginThread(
RUNTIME_CLASS(CCountingThread),
0,
0,
CREATE_SUSPENDED,
NULL); --- (a)
m_pRunningThread->m_pParentWnd = this; -- (b)
m_pRunningThread->ResumeThread(); --- (c)
}
}
- Create our UI thread in suspended mode using
AfxBeginThread
API, pass runtime class of CCountingThread
as the first parameter and CREATE_SUSPENDED
as the fourth parameter. - Provide
m_pRunningThread->m_pParentWnd
, our main window pointer for communication m_pRunningThread->ResumeThread()
: will start our thread
- Now write the following code for stopping our UI Thread:
void CUserThread1Dlg::OnBnClickedStopThread()
{
if(m_pRunningThread!= NULL)
{
m_pRunningThread->PostThreadMessageW(WM_QUIT,0,0); --- (a)
m_pRunningThread = NULL; --(b)
}
}
- The best way to close UI thread is to post
WM_QUIT
message to the thread, it would close down gracefully. Since WM_QUIT
makes message-pump of UI thread to exit. - I am making
m_pRunningThread
equal to NULL
, this is ok with DEMO scenario, however in real world problem, you need to program for synchronization and proper exit of thread, then assign m_pRunningThread
the value NULL
.
- Our UI thread is posting
WM_USER
+2 messages every second with updated counter value, so add function in our dialog class to handle it. So add the following code:
LRESULT CUserThread1Dlg::OnCountingIncrease(WPARAM wParam, LPARAM lParam)
{
CString strText;
strText.Format(_T("%d"),lParam);
m_edtCounting.SetWindowTextW(strText);
return LRESULT(0);
}
And add message listener in BEGIN_MESSAGE_MAP()
:
ON_MESSAGE(WM_USER+2, &CUserThread1Dlg::OnCountingIncrease)
- Build and run your application to see it work.
… Wait a minute, I told you there would be two way communication, however here it’s just one way, means UI thread is sending the message and Main window is listening. So implement this, i.e., Two Way communication, let's extend our problem statement #1 and derive the following new statement:
Problem Statement #2
“To add, reset counter button to reset count back to Zero”.
Step by Step Guide
- In main dialog box, add new button “Reset Counter” and add handler in your code.
- Now, add the following code into the
OnClick
handler to “Reset Counter” button:
void CUserThread1Dlg::OnBnClickedResetThreadcounter()
{
if(m_pRunningThread!= NULL) –(a)
{
m_pRunningThread->PostThreadMessageW(WM_USER+1,0,0); -- (b)
}
}
m_pRunningThread
is object of class CCountingThread
, created at the time of Start Thread button click. - using
PostThreadMessageW
we will post message to UI thread.
- Now, add function in class
CCountingThread
to handle WM_USER+1
user message sent by Main Dialog box:
void CCountingThread::ResetCounter(WPARAM wParam, LPARAM lParam){
m_iCount =0; -- reset the counter
}
- Also add the following message listener in
BEGIN_MESSAGE_MAP()
and END_MESSAGE_MAP()
:
ON_THREAD_MESSAGE(WM_USER+1,&CCountingThread::ResetCounter)
- Compile and run the application.
History
- 20-Jun-2012: First version