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...