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

VC++ 單文檔介面 (SDI) 實作多視圖動態切換:靜態建立與動態建立兩大方案

     編輯:關於VC++

標準 SDI (Single Document Interface) 僅支援單一視圖。若需如 Outlook Express 般在同一文檔下切換多種視圖 (列表/預覽/詳細資訊),需自行管理視圖物件生命週期並切換顯示。本文提供兩種成熟方案:靜態預建所有視圖並切換可見度、動態銷毀舊視圖並建立新視圖,含完整程式碼與關鍵步驟解析。

方案一:靜態建立切換法 (預建所有視圖)

適用於視圖數量固定、切換頻繁、追求零延遲的場景。

1. InitInstance 預建所有視圖


BOOL CMyApp::InitInstance() {
  // ... 標準文檔模板建立 ...
  CFrameWnd* pFrame = (CFrameWnd*)m_pMainWnd;
  CDocument* pDoc = pFrame->GetActiveDocument();

  CCreateContext ctx;
  ctx.m_pCurrentDoc = pDoc;
  ctx.m_pNewViewClass = NULL;
  ctx.m_pNewDocTemplate = NULL;
  ctx.m_pLastView = NULL;
  ctx.m_pCurrentFrame = pFrame;

  // 視圖 0 由文檔模板自動建立 (AFX_IDW_PANE_FIRST)
  m_pViews[0] = pFrame->GetActiveView();

  // 手動建立視圖 1, 2 ... 設為不可見
  for (int i = 1; i < NUM_VIEWS; i++) {
    m_pViews[i] = (CView*)new CView1(); // 或 CView2, CFormView 等
    UINT nID = AFX_IDW_PANE_FIRST + i;
    CRect rect(0,0,0,0);
    m_pViews[i]->Create(NULL, NULL,
        AFX_WS_DEFAULT_VIEW & ~WS_VISIBLE, // 初始隱藏
        rect, pFrame, nID, &ctx);
    // 手動發送 WM_INITIALUPDATE
    m_pViews[i]->OnInitialUpdate();
  }
  return TRUE;
}

2. 視圖切換核心函式


CView* CMyApp::SwitchView(int nIndex) {
  ASSERT(nIndex >= 0 && nIndex < NUM_VIEWS);
  CView* pNewView = m_pViews[nIndex];
  CFrameWnd* pFrame = (CFrameWnd*)m_pMainWnd;
  CView* pActiveView = pFrame->GetActiveView();

  if (!pActiveView || pNewView == pActiveView) return pActiveView;

  // 交換視窗 ID,讓 RecalcLayout 正確運作
  UINT idOld = ::GetWindowLong(pActiveView->m_hWnd, GWL_ID);
  UINT idNew = ::GetWindowLong(pNewView->m_hWnd, GWL_ID);
  ::SetWindowLong(pActiveView->m_hWnd, GWL_ID, idNew);
  ::SetWindowLong(pNewView->m_hWnd, GWL_ID, idOld);

  pActiveView->ShowWindow(SW_HIDE);
  pNewView->ShowWindow(SW_SHOW);
  pFrame->SetActiveView(pNewView);
  pFrame->RecalcLayout();
  pNewView->Invalidate();
  return pActiveView;
}

方案二:動態建立切換法 (銷毀舊視圖,建立新視圖)

適用於視圖類型繁多、記憶體敏感、或需依據文檔內容動態決定視圖類型的場景。

1. 刪除目前視圖

不可使用 GetActiveView() (可能為 NULL),需列舉子視窗尋找 CView 衍生類:


// 回呼函式
BOOL CALLBACK EnumViewProc(HWND hWnd, LPARAM lParam) {
  CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
  CView** ppView = (CView**)lParam;
  if (pWnd && pWnd->IsKindOf(RUNTIME_CLASS(CView))) {
    *ppView = static_cast<CView*>(pWnd);
    return FALSE; // 停止列舉
  }
  return TRUE;
}

// 刪除邏輯
CView* pView = NULL;
::EnumChildWindows(m_hWnd, EnumViewProc, (LPARAM)&pView);
if (pView) {
  CView* pTopView = pView;
  // 找到父視窗為 CMainFrame 的最上層視圖
  while (lstrcmp(pTopView->GetRuntimeClass()->m_lpszClassName, _T("CMainFrame")) != 0) {
    pView = pTopView;
    pTopView = (CView*)pTopView->GetParent();
  }
  CDocument* pDoc = pView->GetDocument();
  pDoc->m_bAutoDelete = FALSE; // 防止文檔被連帶刪除
  pView->DestroyWindow();
  pDoc->m_bAutoDelete = TRUE;
}

2. 建立新視圖


CCreateContext ctx;
ctx.m_pNewViewClass = RUNTIME_CLASS(CNewView); // 指定新視圖類別
ctx.m_pCurrentDoc = pDoc;
CView* pNewView = STATIC_DOWNCAST(CView, pFrame->CreateView(&ctx));
if (!pNewView) return FALSE;

pNewView->OnInitialUpdate(); // CreateView 不會自動呼叫
pNewView->ShowWindow(SW_SHOW);
pFrame->SetActiveView(pNewView);
pFrame->RecalcLayout();

關鍵技術點比較

特性靜態法動態法
記憶體佔用常駐所有視圖僅保留當前視圖
切換速度極快 (ShowWindow)較慢 (Destroy/Create)
視圖狀態保持自動保持 (滾動位置、選取)需自行序列化/還原
實作複雜度較低需處理列舉、ID 衝突、文檔關聯
適用場景固定 2-4 視圖、頻繁切換視圖類型多、條件式建立

常見陷阱與解決

  • 視圖 ID 衝突:靜態法必須手動交換 GWL_ID,動態法 CreateView 自動分配唯一 ID。
  • 文檔被誤刪:刪除視圖前務必設 m_bAutoDelete = FALSE。
  • WM_INITIALUPDATE 遺漏:手動建立視圖 (靜態法 Create、動態法 CreateView) 皆需自行呼叫。
  • SplitterWnd 不相容:本方案針對單一 CFrameWnd 客戶區,CSplitterWnd 另有機制。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved