MFC MDI
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 中 第 二 个
后 的 字 符 串 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 子 窗 口。
以 上 所 讨 论 的 程 序 在Windows 3.1、 中 文 之 星2.0、 VC++ 1.52、MFC 2.50 环 境 下 通 过。 从 中可 以 看 到: 利 用MFC 实 现MDI, 将 复 杂 的 多 窗 口 安 排 交 给 框 架 程 序(Framework) 来 承 担, 编 程 人 员可 将 精 力 集 中 于 自 己 特 有 的 任 务, 极 大 地 提 高 了 编 程 的 效 率。
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 中 第 二 个
后 的 字 符 串 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 子 窗 口。
以 上 所 讨 论 的 程 序 在Windows 3.1、 中 文 之 星2.0、 VC++ 1.52、MFC 2.50 环 境 下 通 过。 从 中可 以 看 到: 利 用MFC 实 现MDI, 将 复 杂 的 多 窗 口 安 排 交 给 框 架 程 序(Framework) 来 承 担, 编 程 人 员可 将 精 力 集 中 于 自 己 特 有 的 任 务, 极 大 地 提 高 了 编 程 的 效 率。