文檔/視圖架構是 MFC 的核心設計模式,實現了數據(文檔)與表現(視圖)的分離,是觀察者模式在框架層的經典應用。然而,AppWizard 生成的龐大代碼往往讓初學者望而生畏。本文旨在提供一條「輕量級」學習路徑,結合理論與實戰,幫助開發者快速掌握文檔/視圖架構的核心機制並靈活應用於實際專案。
使用 AppWizard 建立 MDI 專案(專案名為 Demo)預設會產生六個核心類別:
CDemoApp(CWinApp 派生):應用程式物件,管理文檔模板列表CMainFrame(CMDIFrameWnd 派生):主框架視窗,掛載選單、工具列、狀態列CChildFrame(CMDIChildWnd 派生):子框架視窗,容納視圖CDemoDoc(CDocument 派生):文檔類別,負責資料儲存與業務邏輯CDemoView(CView 派生):視圖類別,負責資料顯示與用戶互動CAboutDlg:關於對話框| 類別 | 關鍵成員 | 用途 |
|---|---|---|
CWinApp | m_pDocManager | 指向 CDocManager,管理所有文檔模板 |
CDocManager | m_templateList (CPtrList) | 文檔模板鏈表 |
CDocTemplate | m_pDocClass, m_pViewClass, m_pFrameClass, m_nIDResource | 文檔/視圖/框架的運行時類別指標與資源 ID |
CDocument | m_viewList (CPtrList) | 關聯的所有視圖 |
CFrameWnd | m_pActiveView | 當前活動視圖 |
CView | m_pDocument | 所屬文檔 |
實戰中最常用的「從 A 找 B」模式整理如下:
// 取得應用程式指標
AfxGetApp(); // 或 extern CMyApp theApp;
// 取得主框架
AfxGetMainWnd(); // 或 AfxGetApp()->m_pMainWnd;
// 文檔 → 視圖
POSITION pos = GetFirstViewPosition();
while (pos) { CView* pView = GetNextView(pos); }
// 視圖 → 文檔
CDocument* pDoc = GetDocument();
// 框架 → 活動文檔/視圖
CDocument* pDoc = GetActiveDocument();
CView* pView = GetActiveView();
// MDI 子框架 → 主框架
CMDIChildWnd* pChild = MDIGetActive(); // 或 GetActiveFrame()
// 視圖 → 父框架
CFrameWnd* pFrame = GetParentFrame();
// 文檔 → 文檔模板
CDocTemplate* pTemplate = GetDocTemplate();
// 應用程式 → 所有文檔模板
POSITION pos = GetFirstDocTemplatePosition();
while (pos) { CDocTemplate* p = GetNextDocTemplate(pos); }
CDocument、CView、CMDIChildWnd 派生類。IDR_NEW_TYPE)。InitInstance 中註冊:CMultiDocTemplate* pTpl = new CMultiDocTemplate(
IDR_NEW_TYPE,
RUNTIME_CLASS(CNewDoc),
RUNTIME_CLASS(CNewChildFrame),
RUNTIME_CLASS(CNewView));
AddDocTemplate(pTpl);
m_pNewTemplate = pTpl; // 保存指標以便後續使用
CNewView* pView = new CNewView();
CDocument* pDoc = ((CFrameWnd*)m_pMainWnd)->GetActiveDocument();
CCreateContext ctx;
ctx.m_pCurrentDoc = pDoc;
ctx.m_pNewViewClass = RUNTIME_CLASS(CNewView);
CRect rect(0,0,0,0);
UINT nID = AFX_IDW_PANE_FIRST + 1;
pView->Create(NULL, NULL, WS_CHILD, rect, m_pMainWnd, nID, &ctx);
CChildFrame* pFrame = new CChildFrame();
CCreateContext ctx;
ctx.m_pNewViewClass = RUNTIME_CLASS(CDemoView);
pFrame->LoadFrame(IDR_MAINFRAME, WS_CHILD | WS_OVERLAPPEDWINDOW, this, &ctx);
pFrame->ShowWindow(SW_SHOWMAXIMIZED);
pFrame->InitialUpdateFrame(NULL, TRUE); // 觸發 OnInitialUpdate & ActivateFrame
// 若需關聯現有文檔
ctx.m_pCurrentDoc = pExistingDoc;
IDR_MAINFRAME(未登入)→ IDR_DEMOTYPE(已登入)CDemoApp 保存兩個選單控制碼,OnLogin/OnLogout 切換選單與視窗標題。CDemoFrame(CMDIChildWnd)、CFuncOneView(CView)、CFuncTwoView(CFormView)。CMainFrame 以指標成員 m_pFuncOneFrame/m_pFuncTwoFrame 追蹤子視窗,避免重複建立。CCreateContext 動態建立框架+視圖:void CMainFrame::OnFuncOne() {
if (m_pFuncOneFrame) { m_pFuncOneFrame->MDIActivate(); return; }
m_pFuncOneFrame = new CDemoFrame();
CCreateContext ctx; ctx.m_pNewViewClass = RUNTIME_CLASS(CFuncOneView);
m_pFuncOneFrame->LoadFrame(IDR_MAINFRAME, WS_MAXIMIZE|WS_OVERLAPPEDWINDOW, this, &ctx);
m_pFuncOneFrame->SetWindowText(_T("業務邏輯一"));
m_pFuncOneFrame->ShowWindow(SW_SHOWMAXIMIZED);
m_pFuncOneFrame->InitialUpdateFrame(NULL, TRUE);
}
CDemoDoc(CDocument),建構函數設為 public,提供 GetData()。CFuncOneView 覆寫 GetDocument() 回傳 CDemoDoc*,OnDraw 中呼叫 pDoc->GetData() 繪製。CCreateContext 指定 m_pCurrentDoc = new CDemoDoc()。InitInstance 追加模板即可,無需重寫。CMDIChildWnd 派生類,避免狀態汙染。WM_CLOSE 關閉子視窗,防止資源洩漏。文檔/視圖架構雖複雜,但掌握「文檔模板三要素(Doc/View/Frame)」與「CCreateContext 動態綁定」兩大核心機制,即可靈活構建從傳統 MDI 到輕量級彈出視窗的各種界面形態。本文提供的 MIS 案例可直接作為專案骨架擴展,祝開發順利!