Creating MDI Apps
在 Multi-SDI 之前就有了 MDI , WTL 基於 CFramewindowImplBase基類提供了一系列相關類使得程式員編寫 MDI 應用程式尤其的簡單。Figure9 展示了 WTL 的 MDI 視窗架構.
Figure 9: MDI Windowing Hierarchy of WTL
CMDIWwindow 類是 MDI 應用程式的介面類, 繼承自 CWindow 基類並添加了兩個資料成員分別儲存 MDI 客戶區視窗控制代碼和 MDI 架構菜單控制代碼:
class CMDIWindow : public ATL::CWindow
{
public:
// Data members
HWND m_hWndMDIClient;
HMENU m_hMenu;
···
}
該類也封裝了 MDI 視窗特有的 WM_MDIXXX 訊息家族的發送處理請求, 以成員函數形式把相關功能提供給程式員使用, 成員函數命名為 MDIXXX().
MDI 應用程式由視窗架構, 管理 MDI 子視窗的架構客戶區以及 MDI 子視窗組成.(如 Figure 10 所示)
Figure 10: MDI Application Consisting of Frame, Client Area, and MDI Children
為了建立 MDI 子視窗, 需要從 CMDIChildWindowImpl 類派生自己的視窗類別.
class CChildFrame : public CMDIChildWindowImpl<CChildFrame> {
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MDICHILD)
CHtmlView m_view;
virtual void OnFinalMessage(HWND /*hWnd*/) {
delete this;
}
BEGIN_MSG_MAP(CChildFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
CHAIN_MSG_MAP(CMDIChildWindowImpl<CChildFrame>)
END_MSG_MAP()
LRESULT OnCreate(UINT,WPARAM,LPARAM,BOOL&) {
m_hWndClient = m_view.Create(m_hWnd, rcDefault,
_T("http://www.microsoft.com"),
WS_CHILD | WS_VISIBLE, WS_EX_CLIENTEDGE);
bHandled = FALSE; // Let base class have a crack
return 1;
}
…
};
MDI 子架構視窗與 SDI 架構視窗很相似(MDI 子架構視窗類別一般命名為 CChildFrame). 比如, 通過傳遞菜單資源 ID 給 DECLARE_FRAME_WND_CLASS 宏來管理相關資源的建立, 正如在 SDI 架構視窗中所做的那樣. 同樣地, 我們在 WM_CREATE 訊息映射函數中建立視圖. 然而有所不同的是, 建立了視圖之後, 必須設定 bHandled 為 FALSE, 因為我們需要 MDI 子視窗基類繼續處理 WM_CREATE 訊息.
LRESULT CChildFrame::OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
{
bHandled = FALSE;
···
}
最後, 在虛擬函數 OnFinalMessage() 中, MDI 子架構視窗類別對其自身調用 delete 操作符, 以此釋放相關資源.
void CChildFrame::OnFinalMessage(HWND /*hWnd*/)
{
delete this;
}
按照我們的設計, CMDIFrameWindowImpl 類作為所有 MDI 架構視窗類別的基類, 實現了 MDI 架構和 MDI 客戶區. 如下樣本:
class CMainFrame : public CMDIFrameWindowImpl<CMainFrame> {
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
BEGIN_MSG_MAP(CMainFrame)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
COMMAND_ID_HANDLER(ID_WINDOW_CASCADE, OnWindowCascade)
…
CHAIN_MSG_MAP(CMDIFrameWindowImpl<CMainFrame>)
END_MSG_MAP()
LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
// Create command bar window, toolbar and statusbar
…
// Create MDI client
CreateMDIClient();
m_CmdBar.SetMDIClient(m_hWndMDIClient);
…
return 0;
}
LRESULT OnFileNew(WORD, WORD, HWND, BOOL&)
{
// Create MDI child
CChildFrame* pChild = new CChildFrame;
pChild->CreateEx(m_hWndMDIClient);
return 0;
}
LRESULT OnWindowCascade(WORD,WORD,HWND, BOOL&)
{
MDICascade();
return 0;
}
…
};
在 WM_CREATE 訊息映射函數中, 調用了基類 CMDIFrameWindowImpl 的成員函數 CreateMDIClient() 建立 MDI 客戶區並設定 m_hWndMDIClient 資料成員. MDI 客戶區是作為所有 MDI 子架構視窗的父視窗存在的. 而所有 MDI 子視窗都是在訊息處理函數 OnFileNew() 中被建立的, 每個 MDI 子視窗都是 MDI 子視窗類的一個執行個體, 並且以 MDI 客戶區作為其父視窗. 最後, 在 WinMain() 中我們建立 MDI 架構視窗, 這與 SDI 相似.
Divide and Conquer with Splitters
To be continued...