你好,我已准备好沟通!
使用窗口消息进行双向通信的 CWinThread。
引言
昨天晚上,我正在做一个个人项目,其中一个需求是使用UI线程。由于我可以使用MFC,所以我考虑使用CWinThread
。当我深入研究它的概念时,我萌生了写一篇关于这项非常宏伟的旧技术的文章的念头,它与.NET的世界相去甚远。
Using the Code
现在,这里是我们的问题陈述。
问题陈述 #1
“创建一个UI线程,该线程每秒向主线程发送消息,将计数器加1。”
解决问题 #1 的分步指南
- 如今,VS向导非常强大,您无需手动编写MFC派生类,只需在类视图中右键单击项目,然后添加派生自
CWinThread
的类即可。所以,只需右键单击项目名称 -> 添加 -> 类 - 打开对话框后,将新类命名为
CCountingThread
。以下是向导将生成的骨架class CCountingThread : public CWinThread { DECLARE_DYNCREATE(CCountingThread) protected: CCountingThread(); // protected constructor used by dynamic creation virtual ~CCountingThread(); public: virtual BOOL InitInstance(); virtual int ExitInstance(); protected: DECLARE_MESSAGE_MAP() };
- 现在,向类添加三个变量以服务我们的目的
UINT_PTR
m_uTimerID
:将跟踪SetTimer
的IDint
m_iCount
:将跟踪下一个增量值CWnd*
m_pParentWnd
:将指向主窗口的指针。我知道CWinThread
本身还有一个变量用于此任务(m_pMainWnd
)。但是,我只想保持简单。
- 现在,由于主对话框必须每秒用新值更新,我想使用
WM_TIMER
消息,设置1秒的 elapsed time,并在每次收到timer消息时,向主窗口发送一条带有新值的新消息。所以,现在,在CCountingThread
中添加新函数来处理WM_TIMER
消息。void CCountingThread::OnTimer(WPARAM wParam, LPARAM lParam) { m_pParentWnd->PostMessageW(WM_USER+2,0,++m_iCount); ---- (1) }
在这里,我为向主窗口发送新值的消息编写了代码(1)。
- 现在,为了使我们的
CCountingThread::OnTimer (…)
函数对WM_TIMER
消息的MessageLoop
可见,请在BEGIN_MESSAGE_MAP()
和END_MESSAGE_MAP()
之间添加以下代码。ON_THREAD_MESSAGE(WM_TIMER,&CCountingThread::OnTimer)
- 现在,在
CCountingThread::InitInstance()
中编写timer的激活代码,并在CCountingThread::ExitInstance()
中编写de-activation代码,如下所示:BOOL CCountingThread::InitInstance(){ m_uTimerID = SetTimer(NULL,2001,1000,NULL); return TRUE; } int CCountingThread::ExitInstance(){ KillTimer(NULL,m_uTimerID); return CWinThread::ExitInstance(); }
在为我们的
WinThread
派生类完成上述任务后。 - 现在,设计主窗口UI,包含一个编辑框(用于显示来自
Thread
的值)和两个按钮(用于启动和停止线程),并为主编辑框添加相关的处理程序和控件变量。还要在类中添加CCountingThread
的指针。在执行以上操作之前,不要忘记在您的mainwindow
类头文件中添加CountingThread.h。 - 将以下代码添加到您的Start按钮处理程序中
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) } }
- 使用
AfxBeginThread
API以挂起模式创建我们的UI线程,将CCountingThread
的运行时类作为第一个参数,将CREATE_SUSPENDED
作为第四个参数传递。 - 为通信提供
m_pRunningThread->m_pParentWnd
,即我们的主窗口指针。 m_pRunningThread->ResumeThread()
:将启动我们的线程。
- 使用
- 现在,编写以下代码来停止我们的UI线程。
void CUserThread1Dlg::OnBnClickedStopThread() { if(m_pRunningThread!= NULL) { m_pRunningThread->PostThreadMessageW(WM_QUIT,0,0); --- (a) m_pRunningThread = NULL; --(b) } }
- 关闭UI线程的最佳方法是向线程发送
WM_QUIT
消息,它将优雅地关闭。因为WM_QUIT
会使UI线程的消息泵退出。 - 我将
m_pRunningThread
设置为NULL
,这在DEMO场景中是可以的,但在实际问题中,您需要编程实现同步和线程的正确退出,然后将m_pRunningThread
赋值为NULL
。
- 关闭UI线程的最佳方法是向线程发送
- 我们的UI线程每秒发送
WM_USER
+2消息,其中包含更新的计数器值,因此请在我们的对话框类中添加一个函数来处理它。所以添加以下代码LRESULT CUserThread1Dlg::OnCountingIncrease(WPARAM wParam, LPARAM lParam) { CString strText; strText.Format(_T("%d"),lParam); m_edtCounting.SetWindowTextW(strText); return LRESULT(0); }
并在
BEGIN_MESSAGE_MAP()
中添加消息监听器。ON_MESSAGE(WM_USER+2, &CUserThread1Dlg::OnCountingIncrease)
- 构建并运行您的应用程序,看看它是否正常工作。
……等等,我告诉过您会有双向通信,但在这里它只是一向通信,即UI线程发送消息,主窗口监听。所以,实现这一点,即双向通信,让我们扩展问题陈述#1并得出以下新陈述:
问题陈述 #2
“添加重置计数器按钮,将计数重置为零。”
分步指南
- 在主对话框中,添加一个新的按钮“重置计数器”,并在您的代码中添加处理程序。
- 现在,将以下代码添加到“重置计数器”按钮的
OnClick
处理程序中。void CUserThread1Dlg::OnBnClickedResetThreadcounter() { if(m_pRunningThread!= NULL) –(a) { m_pRunningThread->PostThreadMessageW(WM_USER+1,0,0); -- (b) } }
m_pRunningThread
是CCountingThread
类的对象,在单击“开始线程”按钮时创建。- 使用
PostThreadMessageW
我们将向UI线程发送消息。
- 现在,在
CCountingThread
类中添加函数来处理主对话框发送的WM_USER+1
用户消息。void CCountingThread::ResetCounter(WPARAM wParam, LPARAM lParam){ m_iCount =0; -- reset the counter }
- 还在
BEGIN_MESSAGE_MAP()
和END_MESSAGE_MAP()
中添加以下消息监听器。ON_THREAD_MESSAGE(WM_USER+1,&CCountingThread::ResetCounter)
- 编译并运行应用程序。
历史
- 2012年6月20日:第一个版本