Document/View是MFC的基石,負責程序數據的管理和顯示,Doculent和Viewd的關系有一檔一視,一檔多視和多檔多視,下面將分別對實現過程中的重點知識進行總結。
1. 視圖的同步更新
2. 序列化和反序列化
3. 雙緩存繪圖
4. CSplitterWnd視圖切割
5. 映射模式(SetMapMode)
6. 使用CMDIFrameWnd::OnWindowNew實現多視圖顯示
7. 多重文件類型
1. 視圖的同步更新
UpdateAllViews----->OnUpDate----->WM_PAINT------->OnDraw
UpdateAllView(NULL) 表示更新所有視圖
同時也可以根據UpdateAllViews中的第二個第三個參數實現只更新需要重繪區域,提高效率
UpdateAllViews源碼如下:
void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint) // walk through all views { ASSERT(pSender == NULL || !m_viewList.IsEmpty()); // must have views if sent by one of them POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); if (pView != pSender) pView->OnUpdate(pSender, lHint, pHint); } }
重載OnUpdate,實現無效區域重繪代碼
void CScribbleView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { // TODO: 在此添加專用代碼和/或調用基類 if (pHint != NULL) { if (pHint->IsKindOf(RUNTIME_CLASS(CStroke))) { CStroke *pStroke = (CStroke *)pHint; CClientDC dc(this); OnPrepareDC(&dc); CRect rectInvalid = pStroke->GetBoundingRect(); dc.LPtoDP(&rectInvalid); InvalidateRect(&rectInvalid); return; } }return; }
OnDraw關鍵代碼
void CScribbleView::OnDraw(CDC* pDC) { CScribbleDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // 得到窗口的無效區域 CRect rectClip; pDC->GetClipBox(&rectClip);
// 得到已變化的區域
rectStroke = pStroke->GetBoundingRect();
// 判斷兩區域是否有交集,有則更新視圖
if (!rectStroke.IntersectRect(&rectStroke, &rectClip)){continue;}
}
2. 序列化和反序列化
Serialize機制作為MFC六大關鍵技術之一,實現對象、資料的序列化和反序列化
所有派生於CObject的類都擁有RunTimeClass、IsKindof和IsSerialBle功能,Serialize機制的具體實現這里不作說明,只介紹如何使用。
繼承於CObject的類若要具備Serialize機制需具備以下條件:
(1)類必須要有默認的無參數構造函數
(2)類的聲明中添加DECLARE_SERIAL(CNewClass)
(3) 類的實現中添加IMPLEMENT_SERIAL(CNewClass, CObject, 1),注意第三個參數為版本號,版本號如為0XFFFF,則無Serialize
(4) 類中重載Serialize(CArchive &ar)虛函數
void CStroke::Serialize(CArchive &ar) { if (ar.IsStoring()) // 寫文件 { ar << a; } else // 讀文件 { ar >> a; } }
3. 雙緩存繪圖,防止閃爍
(1)在OnDraw函數中創建內存DC,在內存DC上繪圖,然后再將內存DC通過BitBlt或StrechBlt貼到原DC上
(2)重寫OnEraseBkgnd虛函數,return TRUE;
CRect rc; GetClientRect(&rc); CDC MemDC; MemDC.CreateCompatibleDC(pDC); CBitmap BitMap; BitMap.CreateCompatibleBitmap(pDC,rc.Width(),rc.Height()); CBitmap* pOldBitMap = MemDC.SelectObject(&BitMap); MemDC.FillSolidRect(rc, pDC->GetBkColor()); .....繪圖 pDC->BitBlt(0,0,rc.Width(),rc.Height(), &MemDC,0,0,SRCCOPY); MemDC.DeleteDC(); BitMap.DeleteObject();
BOOL CScribbleView::OnEraseBkgnd(CDC* pDC) { // TODO: 在此添加消息處理程序代碼和/或調用默認值 //return CView::OnEraseBkgnd(pDC); return TRUE; }
4. CSplitterWnd視圖切割
(1)視圖創建過程

從MFC源碼看出View產生的整個流程如下:
OpenDocumentFile----->CreateNewDocument----->CreateNewFrame----->LoadFrame
LoadFrame的最后一個參數為CCreateContext
struct CCreateContext // Creation information structure // All fields are optional and may be NULL { // for creating new views CRuntimeClass* m_pNewViewClass; // runtime class of view to create or NULL CDocument* m_pCurrentDoc; // for creating MDI children (CMDIChildWnd::LoadFrame) CDocTemplate* m_pNewDocTemplate; // for sharing view/frame state from the original view/frame CView* m_pLastView; CFrameWnd* m_pCurrentFrame; // Implementation CCreateContext(); };

Document Frame窗口產生之際由於WM_CREATE的發生導致
CFrameWnd::OnCreate------>CFrameWnd::OnCreateHelper----->CFrameWnd::OnCreateClient----->CFrameWnd::CreateView
(2)視圖切割
由上我們看出視圖的產生在虛函數OnCreateClient中,所以進行視圖的切割操作應在該函數中發生
// 重寫OnCreateClient虛函數,創建視圖 BOOL CChildFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { m_wndSplit1.CreateStatic(this, 1, 2); m_wndSplit1.CreateView(0,0,RUNTIME_CLASS(CTextView), CSize(300, 0), pContext); m_wndSplit2.CreateStatic(&m_wndSplit1, 2, 1, WS_CHILD|WS_VISIBLE, m_wndSplit1.IdFromRowCol(0,1)); m_wndSplit2.CreateView(0, 0, RUNTIME_CLASS(CBarView), CSize(0, 260), pContext); //m_wndSplit2.CreateView(1, 0, RUNTIME_CLASS(CGraphView), CSize(0, 0), pContext); // 為了不添加CGraphView頭文件引起編譯錯誤 m_wndSplit2.CreateView(1, 0, pContext->m_pNewViewClass, CSize(0, 0), pContext); SetActiveView((CView* )m_wndSplit1.GetPane(0,0)); return TRUE; }

5. 映射模式(SetMapMode)
具體的映射模式種類可以百度或參考映射模式之SetMapMode,下面只是總結SetMapMode(MM_ANISOTROPIC)的使用,使用MM_ANISOTROPIC並結合SetWindoworg、SetViewportorg、SetWindowExt和SetViewportExt我們可以自定義坐標系(比例和方向)
CRect rc; GetClientRect(&rc); // 設置坐標系 pDC->SetMapMode(MM_ANISOTROPIC); // 設置窗口原點 pDC->SetWindowOrg(0, 0); // 對應視口的右下角 pDC->SetViewportOrg(rc.left + 20, rc.bottom - 20); pDC->SetWindowExt(100, 100); pDC->SetViewportExt(rc.Width()- 40, -(rc.Height() - 40));
上面代碼將CSize(100,100)映射到(rc.Width()- 40, -(rc.Height() - 40)上,坐標系如下:

將邏輯坐標轉換為設備坐標:LPtoDP
將設備坐標轉換為邏輯坐標:DPtoLP
6. 使用CMDIFrameWnd::OnWindowNew實現多視圖顯示
如果你不喜歡分裂窗口,我們可以來點新鮮的,重寫OnWindowNew函數
(1)添加新的CMultiDocTemplate
m_pTemplateTxt= new CMultiDocTemplate(IDR_TextTYPE, RUNTIME_CLASS(CTextDoc), RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架 RUNTIME_CLASS(CTextView));
m_pTemplateHex = new CMultiDocTemplate(IDR_TextTYPE,
RUNTIME_CLASS(CTextDoc),
RUNTIME_CLASS(CChildFrame), // 自定義 MDI 子框架
RUNTIME_CLASS(CHexView));
(2)添加菜單項進行視圖創建測試
void CMainFrame::OnTesta() { CMDIChildWnd *pActiveChild= MDIGetActive(); CDocument *pDocument; if (pActiveChild == NULL || (pDocument = pActiveChild->GetActiveDocument()) == NULL) { return; } CDocTemplate *pTemplate = ((CTextApp *)AfxGetApp())->m_pTemplateTxt; if (pTemplate == NULL) { return; } CFrameWnd *pFrame = pTemplate->CreateNewFrame(pDocument, pActiveChild); if (pFrame == NULL) { return; } pTemplate->InitialUpdateFrame(pFrame, pDocument); }
// 關鍵點:重寫CMDIFrameWnd::OnWindowNew創建視圖 void CMainFrame::OnTest01() { CMDIChildWnd *pActiveChild = MDIGetActive(); CDocument *pDocument; if (pActiveChild == NULL || (pDocument = pActiveChild->GetActiveDocument()) == NULL) { return; } CDocTemplate *pTemplate = ((CTextApp *)AfxGetApp())->m_pTemplateHex; if (pTemplate == NULL) { return; } CFrameWnd *pFrame = pTemplate->CreateNewFrame(pDocument, pActiveChild); if (pFrame == NULL) { return; } pTemplate->InitialUpdateFrame(pFrame, pDocument); }
(3)效果如下

7. 多重文件類型
以下為在多文檔視圖中實現多文檔類型:
(1)添加新建UI系統
CMultiDocTemplate(UINT nIDResource,CRuntimeClass* pDocClass,
CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass);
nIDResource是一個資源ID,表示此一文件類型(文件格式)所使用的資源。內容都在.rc文件中,包含ICON/MENU等等信息,可以自己查看
(2)添加新DOC
(3)添加新View,添加相應顯示內容
(4)添加CMultiDocPlate
pDocTemplate = new CMultiDocTemplate(IDR_NEWTYPE, RUNTIME_CLASS(CNewDoc), RUNTIME_CLASS(CMDIChildWnd), // 自定義 MDI 子框架 RUNTIME_CLASS(CNewDocView)); if (!pDocTemplate) return FALSE; AddDocTemplate(pDocTemplate);
(5)效果


上述內容代碼鏈接: https://pan.baidu.com/s/117eij5osBlopdjVdUbXT1A,純為測試代碼!
