MFC實現MDI多檢視介面 (一)

crazyvoice發表於2011-01-17

MDI (Multiple Document Interface) 是Windows 界 面 的 一 種 規 範, 它 建 立 多 個 窗 口 來 瀏 覽 文 檔 數 據, 如Windows 中 的Program Manager 等 都 是 按MDI 規 範 實 現 的。 在 實 際 工 程 軟 件 開 發 中, 許 多 程 序 員 將 其 作 為 一 種 實 現 多 窗 口 的 標 準 方 法。 微 軟 基 礎 類 庫(Microsoft Foundation Class Library, 簡 稱MFC 庫), 是 微 軟 公 司 為 方 便Windows 程 序 開 發 所 提 供 的 一 個 功 能 強 大 的 通 用 類 庫。MFC 的 核 心 是 以 類 的 形 式 封 裝 了 大 量Windows API。 在 可 視 化 編 程 工具VC++ 下 應 用MFC 是 目 前 開 發Windows 程 序 最 方 便 的 途 徑 之 一。VC++ 提 供 的 各 種 開 發 工 具 如AppWizard、ClassWizard 和App Studio, 可 以 建 立 起 具 備 基 本 功 能 的Windows 框 架 程 序(Framework)。 而 程 序 員 所 需 要 做 的 工 作 就 是 將 自 己 特 有 的 代 碼 填 入 到 框 架 程 序 中 去, 從 而 極 大 地 減 少 了 用 戶 界 面 編 程 的 工 作 量, 加 快 了 開 發 速 度。 關 於MDI 的 標 準 開 發 方 法 可 參 考 一 般 的Windows 編 程 書 籍, 本 文 將 介 紹 利 用MFC 實 現MDI 界 面。

  MFC 2.0 以 上 版 本 支 持" 文 檔/ 瀏 覽 視 窗"(Document/View) 結 構 模 式。 由 文 檔 負 責 管 理 數 據, 瀏 覽 視 窗 負 責 數 據 顯 示 及 與 用 戶 的 交 互, 從 而 實 現 了 數 據 與 界 面 的 分 離, 使 整 個 程 序 設 計 更 具 規 範 化、 模 塊 化。MFC 中," 文 檔" 由 類CDocument 及 其 派 生 類 實 現( 簡 稱Doc 類);" 瀏 覽 視 窗" 由 類CView 及 其 派 生 類 實 現 ( 簡 稱View 類)。 二 者 都 包 含 於 應 用 程 序 的 框 架 窗 口 中, 並 由 其 管 理。 使 用 單 文 檔 時, 框 架 窗 口 由 類CFrameWnd 及 其 派 生 類 實 現; 使 用 多 文 檔 時, 框 架 窗 口 是 利 用 類CMDIFrameWnd 和CMDIChildWnd 實 現。 由 文 檔 模 板 將 文 檔、 瀏 覽 窗 口 和 框 架 窗 口 三 者 聯 系 起 來。


 

   當 程 序 員 在App Wizard 的Option 選 項 中 選 擇 Multiple Document Interface 時,MFC 構 架 程 序(Framework) 將 自 動 生 成 實 現MDI 基 本 功 能 的 代 碼。 類CMDIFrameWnd 負 責 整 個 應 用 程 序 的 主 框 架 窗 口; 類CMDIChildWnd 實 現MDI 的 子 窗 口 框 架, 它 不 帶 菜 單 項, 而 與 主 框 架 窗 口 共 享 菜 單。 主 框 架 窗 口 依 據 當 前 激 活 的 子 窗 口 自 動 更 換 菜 單 項。CView 則 負 責MDI 子 窗 口 客 戶 區 中 顯 示 的 具 體 內 容。 例 如,App Wizard 的 以M01 為Project 名 建 立 的 構 架 程 序(framework) 中 包 括 一 些 基 本 類: 主 框 架 窗 口CMainFrame: 派 生 自CMDIFrameWnd; 文 檔CM01Doc : 派 生 自CDocument; 瀏 覽 窗 口CM01View: 派 生 自CView; 其 中CM01Doc、CM01View 和CMDIChildWnd 由 多 文 檔 模 板CMultiDocTemplate 聯 系 在 一 起。 在CM01App::InitInstance() 函 數 中 代 碼 如 下:

  BOOL CM01App::InitInstance()

  {

  ......

   CMultiDocTemplate* pDocTemplate;

   // CMultiDocTemplate 用 於MDI 文 檔

   pDocTemplate = new CMultiDocTemplate(

   IDR_M01TYPE,// 資 源 標 識

   RUNTIME_CLASS(CM01Doc),

   // 文 檔 類

   RUNTIME_CLASS(CMDIChildWnd),

   // 標 準MDI 子 窗 口 框 架

   RUNTIME_CLASS(CM01View));

   // 瀏 覽 視 窗 類

   AddDocTemplate(pDocTemplate);

   // 為 整 個 應 用 程 序 添 加 新 模 板

  ......

  }

   此 時, 數 據Doc 類 僅 與 一 種View 類 相 關 聯,MDI 每 個 子 窗 口 顯 示 的 內 容 是 一 致 的。 如 果 用 戶 希 望 不 同 的 子 窗 口 顯 示 不 同 的 文 檔, 則 需 要 分 別 建 立 新 的 資 源 項、 新 的 文 檔 類、 新 的View 類, 並 且 用 新 模 板 將 他 們 與CMDIChildWnd 聯 系 起 來 即 可。MFC 框 架 程 序 將 復 雜 的 消 息 發 送 和 接 收 機 制 隱 藏 起 來, 自 動 實 現 子 窗 口 的 調 度 安 排。 程 序 員 只 需 設 定 自 己 的 數 據, 並 在 各 個View 中 重 載OnDraw() 函 數, 完 成 所 需 的 繪 制。


 

   然 而 在 實 際 開 發 應 用 程 序 中, 常 常 希 望 對 某 一 類 數 據 進 行 不 同 方 式 的 顯 示, 既 可 觀 察 數 值, 又 可 有 圖 形 顯 示。 這 就 要 求 同 一 種Doc 類 與 多 個View 類 相 關 聯, 而 每 個View 類 對 應 一 個 不 同 的MDI 子 窗 口。CMultiDocTemplate 的 典 型 用 法 是 建 立 獨 立 的 文 檔 結 構 和View 對 象。 而 下 面CMultiDocTemplate 將 使 用 同 一 文 檔 和 多 個View 類。

  (1) 用ClassWizard 建 立 一 新 的View 類:CM02View。

  (2) 建 立 新 模 板:

  CMultiDocTemplate* pDocTemplate02=new CMultiDocTemplate(

   IDR_M01TYPE, // 使 用 同 一 資 源

   RUNTIME_CLASS(CM01Doc), // 同 一 文 檔

   RUNTIME_CLASS(CMDIChildWnd), // 標 準MDI 子 窗 口 框 架

   RUNTIME_CLASS(CM02View)); // 新View

   然 後 使 用CApp::AddDocTemplate 函 數 添 加 新 模 板。

   如 果 此 時 仍 然 在CM01App::InitInstance() 函 數 中 添 加 新 模 板, 則 構 架 程 序 會 錯 誤 地 認 為 程 序 支 持 兩 種 文 檔 類 型, 從 而 在 編 譯 產 生 的EXE 文 件 執 行 時 彈 出 對 話 框, 要 求 用 戶 選 擇 文 檔 類 型。 而 實 際 上 兩 種 文 檔 類 型 是 一 樣 的。

   為 避 免 此 種 情 況, 可 使 用MFC 開 發 者 建 議 的 方 法: 在 前 例 情 況 下, 首 先, 應 在App Studio 中 將 字 串 資 源IDR_M01TYPE 復 制 為 一 個 新 字 串 資 源IDR_M02TYPE。 然 後, 刪 去 字 串 資 源IDR_M02TYPE 中 第 二 個/n 後 的 字 符 串M01 Document( 該 字 串 即 為CDocTemplate::fileNewName 項)。 之 後, 用 新 資 源IDR_M02TYPE 來 建 立 第 二 個 模 板。 這 樣 編 譯 的EXE 文 件 將 不 會 彈 出 對 話 框。 在 研 究MFC 的 源 碼 之 後, 發 現 之 所 以 彈 出 文 檔 類 型 對 話 框, 是 由 於CM01App::InitInstance() 函 數 中 調 用 了OnFileNew() 函 數。OnFileNew() 函 數 檢 查 文 檔 模 板 數 量; 當 不 止 一 個 模 板 時, 則 彈 出 對 話 框; 待 用 戶 選 擇 之 後, 按 所 選 的 文 檔 類 型 建 立MDI 窗 口。 由 於 刪 去 了 第 二 個 模 板 的fileNewName 項, 無 法 顯 示 文 檔 類 型, 就 自 動 停 止 對 話 框, 而 將 第 一 種 類 型 作 為 缺 省 文 檔 類 型 建 立MDI 窗 口。

   在 工 程 應 用 程 序 中,OnFileNew() 函 數 一 般 只 在 程 序 初 始 化 時 調 用 一 次( 至 於 菜 單File |New 的 響 應, 用 戶 可 接 管 處 理), 所 以 可 以 不 在CMyApp::InitInstance() 函 數 中 添 加 新 文 檔 模 板, 躲 過OnFileNew() 函 數 的 檢 查, 而 在 需 要 的 時 候 添 加 所 需 的 文 檔 模 板, 建 立 新 的 子 窗 口。 這 樣 既 避 免 了 文 檔 類 型 對 話 框, 又 不 必 增 加 字 串 資 源。


 

   一 種 簡 單 的 例 子 如 下: 第 一 個 子 窗 口 仍 由 構 架 程 序 自 動 建 立; 設 定 一 個 新 的 菜 單 項" 新 窗 口(NewWindow)", 在CMainFrame 中 處 理 該 菜 單 消 息, 消 息 響 應 函 數 中 顯 示 第 二 個 子 窗 口。

  void CMainFrame::OnNewWindow()

  {

   // 添 加 新 的 文 檔 模 板

   static CMultiDocTemplate* pDocTemplate_New;

   static BOOL bChildCreated=FALSE;

   // 標 志, 新 窗 口 是 否 建 立; 如 已 建, 將 不 重 建

   if(bChildCreated==FALSE)

   {

   pDocTemplate_New = new CMultiDocTemplate(

   IDR_M01TYPE, // 使 用 同 一 資 源

   RUNTIME_CLASS(CM01Doc),

   RUNTIME_CLASS(CMDIChildWnd),

   // 標 準MDI 子 窗 口 框 架

   RUNTIME_CLASS(CM02View));

   AfxGetApp()->AddDocTemplate(pdocTemplate_New);

   // 創 建 新 的 子 窗 口

   CMDIChildWnd* pMDIActive = MDIGetActive(); // 獲 得 當 前 活 動 子 窗 口 的 指 針

   CMpvDoc* pDoc = (CMpvDoc*)pMDIActive->GetActiveDocument(); // 獲 得 文 檔 指 針

   CMDIChildWnd* pNewFrame=(CMDIChildWnd*) (pDocTemplate_New ->CreateNewFrame(pDoc, NULL));

  // 建 立 新 的 框 架 窗 口

   if (pNewFrame == NULL)

   {

   AfxMessageBox(" 新 窗 口 不 能 建 立",MB_OK,0);

   return; // not created

   }

  pDocTemplate_New ->InitialUpdateFrame(pNewFrame, pDoc); // 顯 示 窗 口

  MDITile(MDITILE_HORIZONTAL); // 將 多 個 窗 口 平 鋪

  bChildCreated=TRUE;

  }

   不 同 的View 在OnDraw() 函 數 中 有 各 自 的 繪 制 代 碼, 當 數 據 更 新 時, 只 要 調 用CDocument::UpdateAllViews() 函 數, 即 可 更 新 全 部 的MDI 子 窗 口。

 

相關文章