MFC 文檔/視圖架構核心在於三者協作:文檔管理數據、視圖處理交互、框架提供窗口容器。本文深度解析消息路由規律、多視圖同步更新機制、視圖初始化/文檔清理生命週期、文檔模板作用及修改標記自動提示保存。
ON_COMMAND 映射時自動禁用 (灰顯)ON_UPDATE_COMMAND_UI 決定CWinApp 或 CFrameWnd 中映射// 在 CMainFrame 中映射全局命令
ON_COMMAND(ID_FILE_SAVE_ALL, OnFileSaveAll)
void CMainFrame::OnFileSaveAll() {
// 遍歷所有文檔保存
POSITION pos = m_pDocTemplate->GetFirstDocPosition();
while (pos) {
CDocument* pDoc = m_pDocTemplate->GetNextDoc(pos);
pDoc->OnSaveDocument(pDoc->GetPathName());
}
}
單文檔多視圖 (如同一文檔同時在表格視圖和圖表視圖顯示) 時,一視圖修改數據需通知其他視圖刷新。
| 函數 | 調用方 | 參數 | 作用 |
|---|---|---|---|
CDocument::UpdateAllViews | 修改數據的視圖/文檔 | pSender (發送者視圖, 可排除自身)lHint (長整型提示)pHint (對象指針提示) | 通知所有關聯視圖調用 OnUpdate |
CView::OnUpdate | 框架自動調用 | 同上 | 視圖響應更新,重繪/重新查詢數據 |
// 視圖中修改數據後
void CMyView::OnDataChanged() {
CMyDoc* pDoc = GetDocument();
pDoc->ModifyData(...); // 修改文檔數據
pDoc->SetModifiedFlag(TRUE); // 標記已修改
// 通知其他視圖更新 (排除自己)
pDoc->UpdateAllViews(this, 0, NULL); // this = pSender
}
// 其他視圖響應
void CAnotherView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) {
if (pSender == this) return; // 可選:排除發送者
// 根據 Hint 精準更新
if (lHint == HINT_DATA_CHANGED) {
Invalidate(); // 簡單重繪
} else if (pHint) {
// 針對性更新特定數據
CDataHint* pData = (CDataHint*)pHint;
UpdateSpecificItem(pData->index);
}
}
lHint:枚舉值,如 HINT_ADD_ITEM, HINT_DELETE_ITEM, HINT_REFRESH_ALLpHint:指向自定義結構,可攜帶索引、舊值、新值、矩形區域等豐富信息pDoc->UpdateAllViews(this, HINT_SELECTION_CHANGED, (CObject*)this),接收端可識別發送者OnInitialUpdatevoid CMyView::OnInitialUpdate() {
CView::OnInitialUpdate(); // 必須調用基類!
// 1. 獲取文檔指針
CMyDoc* pDoc = GetDocument();
// 2. 根據文檔數據初始化視圖狀態
m_listCtrl.SetItemCount(pDoc->GetItemCount());
// ... 設置滾動範圍、選中項、視圖模式等
// 基類 OnInitialUpdate 內部會調用 OnUpdate(NULL, 0, NULL)
// 確保初始狀態同步
}
關鍵點:
OnInitialUpdate在視圖創建、文檔打開/新建時調用一次。區別於構造函數:此時文檔已關聯,可安全調用GetDocument()。
DeleteContentsvoid CMyDoc::DeleteContents() {
// 1. 釋放文檔持有的所有動態資源
m_dataArray.RemoveAll(); // 清空數組
while (m_pHead) { // 清空鏈表
Node* p = m_pHead;
m_pHead = p->next;
delete p;
}
// 2. 重置狀態變量
m_bModified = FALSE;
m_strPathName.Empty();
// 3. 調用基類 (通常為空)
CDocument::DeleteContents();
}
調用時機:文檔關閉前、新建文檔前 (舊文檔清理)、應用退出時。單文檔應用中文檔對象不銷毀,僅反複調用
DeleteContents/OnNewDocument。
| 特性 | SDI (單文檔) | MDI (多文檔) |
|---|---|---|
| 文檔對象 | 應用生命週期內唯一實例,反覆復用 | 每個子窗口獨立文檔實例,創建/銷毀 |
| 視圖對象 | 唯一實例,反覆復用 | 每個子窗口獨立視圖實例 |
| 框架窗口 | 主框架唯一 | 主框架 + 多個 MDI 子框架 |
| 關鍵函數 | OnNewDocument, DeleteContents | 構造/析構, OnNewDocument |
CDocTemplate 記錄三者對應關係:
CreateNewDocument, OpenDocumentFile, CreateNewFrame 等工廠方法// 字符串資源 IDR_MAINFRAME
"項目名\n\n項目\n項目文件 (*.myext)\n.myext\n項目.Document\n項目 Document"
/* 解析:
行1: 應用名
行2: (空)
行3: 文件描述
行4: 擴展名
行5: 註冊表文檔類名
行6: 文檔類型顯示名 */
// 視圖修改數據時
GetDocument()->SetModifiedFlag(TRUE);
// 框架關閉文檔時自動檢查
// CDocument::SaveModified() 內部邏輯:
if (IsModified()) {
int response = AfxMessageBox(IDS_ASK_TO_SAVE, MB_YESNOCANCEL);
if (response == IDYES) return OnSaveDocument(GetPathName());
if (response == IDCANCEL) return FALSE; // 取消關閉
return TRUE; // 不保存關閉
}
return TRUE; // 未修改直接關閉
| 需求 | 代碼 | 上下文 |
|---|---|---|
| 應用對象 | AfxGetApp() | 全局 |
| 主框架窗口 | AfxGetApp()->m_pMainWnd | 全局 |
| 活動文檔 (框架中) | GetActiveDocument() | CFrameWnd |
| 活動視圖 (框架中) | GetActiveView() | CFrameWnd |
| 對應文檔 (視圖中) | GetDocument() | CView |
| 遍歷文檔視圖 | GetFirstViewPosition()/GetNextView() | CDocument |
| 文檔模板 | GetDocTemplate() | CDocument |
| 活動 MDI 子窗口 | MDIGetActive() | CMDIFrameWnd |
本文整理自 MFC 經典技術文檔,適用於 VC6~VS2022 所有版本。