首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC.NET

Visual C++.NET 程式設計講座之七:文檔/視圖架構進階 — 一檔多視、CHtmlView 網頁視圖、視圖切換兩種方案深度比較

     編輯:VC.NET

摘要

本講深入 MFC 文檔/視圖架構的「一檔多視」機制,實作雙視圖切換:CView 文字視圖與 CHtmlView 網頁視圖。詳細對比兩種切換方案:動態創建視圖 + CCreateContext (方案一) 與 CDocument::AddView/RemoveView (方案二),分析文檔關聯、指針管理、效能、跨視圖訪問等關鍵差異,為複雜多視圖應用提供架構選型參考。

一、MFC 三種「一檔多視」模式

模式架構典型場景
多文檔多視圖 (MDI)同一文檔,多個框架窗口,各含一視圖 (Window → New Window)Word 多窗口編輯同一文件
動態分割視窗單一框架窗口,CSplitterWnd 動態分割,多視圖同步顯示同一文檔瀏覽器原始碼/預覽雙欄
靜態分割視窗單一框架窗口,靜態分割,各視圖**不同方式**顯示同一文檔圖形編輯器:頂部 3D 模型,底部屬性表

本講聚焦 **單文檔 (SDI) 手動切換視圖**:同一時刻僅一視圖可見,透過選單命令在「文字模式」與「HTML 瀏覽模式」間切換。

二、添加 HTML 瀏覽視圖 CHtmlView

  1. 專案 → 新增類別 → MFC 類別,類名 CWebView,基類 CHtmlView (封裝 WebBrowser ActiveX 控件)。
  2. WebView.h 頂部加入 #include 。
  3. ClassWizard 為 CWebView 添加 OnInitialUpdate 重寫:

void CWebView::OnInitialUpdate() {
    CHtmlView::OnInitialUpdate();
    // 導航至當前文檔路徑 (需文檔提供路徑)
    CString strPath = GetDocument()->GetPathName();
    if (!strPath.IsEmpty())
        Navigate2(strPath, NULL, NULL, NULL, NULL);
}
  1. ViewerApp.cpp 中 InitInstance 的 CSingleDocTemplate 第 4 參數暫時改為 RUNTIME_CLASS(CWebView) 測試運行 — 應能正確顯示 HTML 檔案。
  2. 改回 CViewerView,準備實作切換邏輯。

三、方案一:動態創建視圖 + CCreateContext (SwitchToView1)

核心代碼 (MainFrm.cpp)


// MainFrm.h 前置聲明 (解決循環包含)
class CViewerView;
class CWebView;

class CMainFrame : public CFrameWnd {
    // ...
    void SwitchToView1(CRuntimeClass* pViewClass);
    int m_nViewID; // 1=文字, 2=HTML
};

// MainFrm.cpp
void CMainFrame::SwitchToView1(CRuntimeClass* pViewClass) {
    CCreateContext context;
    context.m_pCurrentDoc = GetActiveDocument(); // 關鍵:傳遞當前文檔指針
    context.m_pNewViewClass = pViewClass;
    context.m_pNewDocTemplate = GetActiveDocument()->GetDocTemplate();

    CView* pNewView = (CView*)pViewClass->CreateObject();
    if (!pNewView) return;

    // 創建視圖窗口 (WS_CHILD | WS_VISIBLE, 0 矩形由框架調整)
    if (!pNewView->Create(NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST, &context)) {
        delete pNewView;
        return;
    }

    // 切換活動視圖
    SetActiveView(pNewView, FALSE);
    RecalcLayout();
    pNewView->OnInitialUpdate(); // 觸發初始化 (如 Navigate2)

    // 更新狀態
    m_nViewID = (pViewClass == RUNTIME_CLASS(CViewerView)) ? 1 : 2;
}

選單命令處理


void CMainFrame::OnViewText() {
    if (m_nViewID != 1) SwitchToView1(RUNTIME_CLASS(CViewerView));
}
void CMainFrame::OnUpdateViewText(CCmdUI* pCmdUI) {
    pCmdUI->SetCheck(m_nViewID == 1);
    pCmdUI->Enable(m_nViewID != 1);
}
void CMainFrame::OnViewHtml() {
    if (m_nViewID != 2) SwitchToView1(RUNTIME_CLASS(CWebView));
}
void CMainFrame::OnUpdateViewHtml(CCmdUI* pCmdUI) {
    pCmdUI->SetCheck(m_nViewID == 2);
    pCmdUI->Enable(m_nViewID != 2);
}

編譯關鍵:構造函數改為 public

CViewerView 和 CWebView 的構造函數預設為 protected (MFC 動態創建要求),動態 CreateObject 需為 public,需在頭文件手動修改:
public: CViewerView(); // 原為 protected

四、方案二:CDocument::AddView/RemoveView (SwitchToView2)

核心代碼


// MainFrm.h 成員指針 (需手動添加,ClassWizard 不支援指針類型直接添加)
CViewerView* m_pMainView;
CWebView*    m_pWebView;

// MainFrm.cpp
void CMainFrame::SwitchToView2(CRuntimeClass* pViewClass) {
    CDocument* pDoc = GetActiveDocument();
    if (!pDoc) return;

    CView* pOldView = GetActiveView();
    if (pOldView) pDoc->RemoveView(pOldView); // 移除舊視圖關聯

    CView* pNewView = NULL;
    if (pViewClass == RUNTIME_CLASS(CViewerView)) {
        if (!m_pMainView) {
            m_pMainView = new CViewerView();
            m_pMainView->Create(NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST, NULL);
        }
        pNewView = m_pMainView;
    } else if (pViewClass == RUNTIME_CLASS(CWebView)) {
        if (!m_pWebView) {
            m_pWebView = new CWebView();
            m_pWebView->Create(NULL, NULL, WS_CHILD | WS_VISIBLE, CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST, NULL);
            // 需傳遞文檔路徑給 WebView (因無 CCreateContext)
            m_pWebView->m_strFileName = pDoc->GetPathName();
        }
        pNewView = m_pWebView;
    }

    if (pNewView) {
        pDoc->AddView(pNewView); // 建立文檔關聯
        SetActiveView(pNewView, FALSE);
        RecalcLayout();
        pNewView->OnInitialUpdate();
    }
}

CWebView 配合修改


// WebView.h
public:
    CString m_strFileName; // 文檔路徑

// WebView.cpp
void CWebView::OnInitialUpdate() {
    CHtmlView::OnInitialUpdate();
    if (!m_strFileName.IsEmpty())
        Navigate2(m_strFileName, NULL, NULL, NULL, NULL);
    // 移除原本 GetDocument()->GetPathName() (因無文檔關聯時 GetDocument 返回 NULL)
}

五、兩種方案深度比較

維度方案一:CCreateContext 動態創建方案二:AddView/RemoveView 手動管理
文檔關聯**創建時自動建立** (context.m_pCurrentDoc),GetDocument() 直接可用**需手動 AddView/RemoveView**,創建時無關聯,GetDocument() 返回 NULL
視圖實例每次切換 **創建新實例** (舊視圖銷毀),CViewerView 會被創建多次**複用實例** (成員指針保存),CViewerView 僅創建一次
跨視圖訪問視圖指針在函數內部,**無法直接互訪**指針為主框架成員,**視圖間可透過 AfxGetMainWnd() 互訪**
效能頻繁創建/銷毀視圖窗口、DC、GDI 物件,切換較慢僅切換顯示/隱藏,極快
狀態保持視圖銷毀導致滾動位置、選取狀態丟失 (需手動序列化)視圖持久化,狀態自動保持
代碼複雜度較低 (MFC 內建機制)需手動管理指針、生命週期、文檔關聯
適用場景視圖輕量、狀態無需保持、切換頻率低視圖重量級 (如 CHtmlView 載入網頁)、需保持狀態、高頻切換

六、第三種方案:靜態分割器 CSplitterWnd (簡述)

  • 在 CMainFrame::OnCreateClient 中創建靜態分割器,兩行一列,分別放入 CViewerView 和 CWebView。
  • 兩視圖**同時可見**,同步滾動/選取需自行協調。
  • 涉及底層分割窗口管理,對初學者難度較大,此處不展開。

七、實戰建議與最佳實踐

  1. **優先選擇方案二** (AddView/RemoveView + 成員指針複用) 用於重量級視圖切換 (WebView、報表預覽、3D 渲染視圖)。
  2. **方案一** 適合輕量級視圖 (純文字、簡單圖形)、原型驗證、臨時切換。
  3. **跨視圖數據同步**:文檔為單一事實來源,視圖透過 GetDocument() (方案一) 或主框架指針 (方案二) 訪問數據,修改後調用 UpdateAllViews(NULL) 通知所有視圖 (含隱藏視圖) 刷新。
  4. **資源清理**:主框架析構時 delete m_pMainView; delete m_pWebView; (若非 NULL)。
  5. **高 DPI / 縮放**:CHtmlView 基於 IE 控件,受系統縮放影響;現代專案建議遷移至 CWebView2 (Edge WebView2) 獲得 Chromium 內核、高 DPI 原生支援、ES6+ 支援。

結語

MFC 文檔/視圖架構的靈活性在於「一檔多視」的多種實現方式。理解 CCreateContext、AddView/RemoveView、CSplitterWnd 三大機制的本質差異 — **文檔關聯建立時機、視圖實例生命週期、指針可達性** — 是設計複雜多視圖應用 (如 IDE、CAD、瀏覽器、報表系統) 的關鍵架構決策能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved