对于初学MFC多线程的,我建议看一看《Windows环境下的多线程编程原理与应用--王险峰》一书,里面代码实例较多,适合初学者理解MFC里的工作、界面多线程及线程间通信。
对于MFC里多线程的原理,不做过多介绍,可以看书获得。下面是我在MFC里,使用MDI框架,多文档/多视图(View)里同步绘制动态数据曲线的实现。
对应MFC里多文档/多视图的学校,推荐看侯捷的《深入浅出MFC》。
(本实例主要完成以下工作:
(1)、利用VS2010创建MDI工程;
(2)、在主.cpp文件中注册多个文档模板;
(3)、重新OnFileNew()函数,使得重新执行或New时显示自己要求的View视图窗口;
(4)、利用MFC的界面多线程CWinThread类进行界面多线程的建立;
(5)、利用全局数组的形式在和类之间传递数据;
该工程是基于VS2010的,完整源代码可在我得Github里下载:https://github.com/LuoKuanH/MultiThread
1、 利用VS2010创建MDI工程项目
2、在主cpp里注册多文档模板
// Draw.h : Draw 应用程序的主头文件 // #pragma once #ifndef __AFXWIN_H__ #error "在包含此文件之前包含“stdafx.h”以生成 PCH 文件" #endif #include "resource.h" // 主符号 // CDrawApp: // 有关此类的实现,请参阅 Draw.cpp // class CDrawApp : public CWinAppEx { public: CDrawApp(); CMultiDocTemplate* m_pTemplateTime;//创建文档模板1,用于绘制时域图 CMultiDocTemplate* m_pTemplateDFT;//创建文档模板2,用于绘制实时DFT图 // 重写 public: virtual BOOL InitInstance(); virtual int ExitInstance(); // 实现 UINT m_nAppLook; BOOL m_bHiColorIcons; virtual void PreLoadState(); virtual void LoadCustomState(); virtual void SaveCustomState(); afx_msg void OnAppAbout(); afx_msg void OnFileNew(); DECLARE_MESSAGE_MAP() afx_msg void OnDraw(); }; extern CDrawApp theApp;
BOOL CDrawApp::InitInstance() { ...... // 注册应用程序的文档模板。文档模板 // 将用作文档、框架窗口和视图之间的连接 //CMultiDocTemplate* pDocTemplate; m_pTemplateTime = new CMultiDocTemplate(IDR_DrawTYPE, //注册时域视图模板 RUNTIME_CLASS(CDrawDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CMainView)); if (!m_pTemplateTime) return FALSE; AddDocTemplate(m_pTemplateTime); m_pTemplateDFT = new CMultiDocTemplate(IDR_DrawTYPE, //注册频域视图域模板 RUNTIME_CLASS(CMainDoc), RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CDFTView)); if (!m_pTemplateDFT) return FALSE; AddDocTemplate(m_pTemplateDFT); ...... }
然后在主cpp里重写OnFileNew()函数,使得新建文件时,同时打开两个用于绘图的View视图
ON_COMMAND(ID_FILE_NEW, OnFileNew) void CDrawApp::OnFileNew() { //CDrawApp *pMyApp = (CDrawApp * )AfxGetApp(); //CMultiDocTemplate* m_pTemplateTime = pMyApp->m_pTemplateTime; m_pTemplateTime->OpenDocumentFile(NULL); m_pTemplateDFT->OpenDocumentFile(NULL); }此时当开始执行程序,或者新建时,灰同时显示两个View视图:
3、在菜单栏添加菜单命令:
Caption | ID | 设置 | Prompt |
更新数据 | ID_REDATA | 默认 | 两个View同时绘图 |
DrawDoc.cpp
BEGIN_MESSAGE_MAP(CDrawDoc, CDocument) ON_COMMAND(ID_REDATA, &CDrawDoc::OnRedata) END_MESSAGE_MAP() void CDrawDoc::OnRedata() { // TODO: 在此添加命令处理程序代码 pDFTThread = AfxBeginThread(RUNTIME_CLASS(CMainThread)); pDrawThread = AfxBeginThread(RUNTIME_CLASS(CViewThread)); pThread = AfxBeginThread(Calculate,this);
UINT Calculate(LPVOID pParam) { CDrawDoc *pdoc = (CDrawDoc*)pParam; CViewThread *pdrawThrd = (CViewThread*)(pdoc->pDrawThread); CMainThread *pUIThrdd = (CMainThread*)(pdoc->pDFTThread); //CViewThread *pdlgThrd = (CViewThread*)(pdoc->pPROSThread); for (int i =0; i<100; i++) { datax[i] = i; datay[i] = i*i/2; data = i; pdrawThrd->PostThreadMessage(WM_SHOW_VI,NULL,NULL); //pdlgThrd->PostThreadMessage(WM_PROS,NULL,NULL); pUIThrdd->PostThreadMessage(WM_SHOW_DFT,NULL,NULL); Sleep(10); } return 0; }
响应该菜单命令的函数主要是创建两个界面多线程和一个工作线程,其中Calculate()函数是工作线程,用于实时计算数据,然后利用两个界面线程,实现显示计算出来的数据。
4、由MFC的界面多线程类CWinThread派生class CMainThread : public CWinThread,线程间通信采用消息传递法方式。
线程间通信采用消息传递法,在两个派生的界面线程中,添加对应的消息响应函数
CMainThread
#define WM_SHOW_DFT WM_USER + 3 ... afx_msg void ShowDFT(WPARAM wParam,LPARAM lParam); ... BEGIN_MESSAGE_MAP(CMainThread, CWinThread) ON_THREAD_MESSAGE(WM_SHOW_DFT,ShowDFT) END_MESSAGE_MAP() ... // CMainThread 消息处理程序 void CMainThread::ShowDFT(WPARAM wParam,LPARAM lParam) { POSITION curTemplatePos = theApp.GetFirstDocTemplatePosition(); CDocTemplate *m_doc=theApp.GetNextDocTemplate(curTemplatePos); CDocTemplate *m_doc1=theApp.GetNextDocTemplate(curTemplatePos);//文档模板 //获得文档: curTemplatePos=m_doc1->GetFirstDocPosition(); CMainDoc *m_pdoc=(CMainDoc*)m_doc1->GetNextDoc(curTemplatePos); //获得视图: curTemplatePos=m_pdoc->GetFirstViewPosition(); CDFTView *m_dview; while(curTemplatePos !=NULL) { m_dview = (CDFTView*)m_pdoc->GetNextView(curTemplatePos); if (m_dview->IsKindOf(RUNTIME_CLASS(CDFTView))) { m_dview->drawline();//调用相应视图的绘图函数进行曲线绘制 } else { } } }上述的ShowDFT()函数里,主要功能是遍历程序的文档模板及文档视图,遍历找到需要的View类,并利用对应的View类调用该View对应的drawline()函数进行曲线绘制。
线程间的数据传递采用全局变量的方式:
定义两个全局数组int datax[100];int datay[100];用于保存计算的数据,同时用于数据传递至界面线程里面进行曲线绘制(本实例较简单,通过Calculate()函数实现更新数据,然后两个界面线程利用此数据进行简单的直线绘制);
本例中,两个界面线程分别使用MainView和DFTView进行曲线绘制,因此,在这两个View中,分别加入函数drawline();
MainView.cppj及DFTview.cpp
// CMainView extern int datax[100]; extern int datay[100]; void CMainView::drawline() { CClientDC dc (this); CPen pen1(PS_SOLID,1,RGB(0,255,0)); ////建立一个画笔类对象,构造时设置画笔属性 dc.SelectObject(&pen1); for (int i=0; i<100; i++) { dc.MoveTo(datax[i],150); dc.LineTo(datay[i],150); //Sleep(10); } }
5、结果显示
当我们把鼠标单击上面一个视图后,点击菜单栏的更新数据命令,便可以看到两个View里的直线同步绘制,从左至右。
注意:由于本实例代码中使用了两个文档注册文档模板,而菜单命令只在DrawDoc文档里进行的响应,因此,我们绘图是需要将焦点定位于与DrawDoc关联的View(及上面一个View里,才能响应我们的“更新数据”菜单命令);总结:这只是我自己实现的一个例子,可能有很多问题,希望指正。刚开始学习写博客,感觉好多话说不清楚。该实例的完整源码可在我的GitHub里下载,链接在上方。
对应MFC实现多文档多视图,首先需要在主cpp里注册多个文档模板,然后如果想要程序开始时或新建时显示特定View,需要重新主cpp里的OnFileOpen()和OnFileNew()函数。对应变量的传递,我苦于只想到使用全局变量的方式,应该还有更好的方式。