利 用MFC 的 消 息 映 像 实 现 动 态 菜 单
---- 当 我 们 提 到 动 态 菜 单 的 实 现 时, 我 们 通 常 的 做 法 是 使 用GetMenu() 函 数 获 取 一 个Cmenu 类 指 针, 然 后 调 用CMenu 类 方 法AppendMenu, InsertMenu, ModifyMenu, RemoveMenu 等。 本 文 介 绍 一 种 更 加 简 洁 的 方 法, 它 利 用MFC 的 消 息 映 像 机 制 及CCmdUI 类 方 法 来 实 现。
---- 首 先, 我 们 简 要 说 说VC 中MFC 的 消 息 映 像。 每 个Windows 程 序 员 大 概 都 对 以 前 使 用 的 窗 口 函 数WindowProc 记 忆 犹 新, 当 我 们 面 对 各 种 消 息 时, 我 们 别 无 他 方, 只 能 使 用 庞 大 而 机 械 的switch-case 语 句 来 实 现 不 同 的 分 支 选 择。 在VC5.0 中 使 用V4.2 版 的MFC 基 本 类 库, 你 将 告 别switch-case 语 句, 代 之 以 透 明 的 消 息 映 像。 要 在 一 个 类 中 使 用 消 息 映 像, 在 类 声 明 中, 必 须 显 式 的 加 入 宏DECLARE_MESSAGE_MAP:
class CMyClass: public CBaseClass { DECLARE_MESSAGE_MAP() }
---- 在 类 实 现 中, 必 须 使 用 两 个 宏BEGIN_MESSAGE_MAP 和END_MESSAGE_MAP,BEGIN_MESSAGE_MAP 带 两 个 参 数: 当 前 类 和 直 接 父 类:
---- BEGIN_MESSAGE_MAP(CMyClass, CBaseClass)
---- // 消 息 映 像 项
---- ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
---- // 消 息 映 像 项
---- END_MESSAGE_MAP()
---- 消 息 映 像 项 使 用 下 列 基 本 语 法:
---- ON_MessageName(ID, ClassMethod)
---- MessageName 是 需 要 处 理 的 消 息,ID 是 发 送 消 息 的 标 识 符, 而ClassMethod 为 处 理 此 消 息 的 类 方 法 名。MessageName 是MFC 预 定 义 的, 可 分 为 以 下 三 种:
---- 命 令 消 息
---- 子 窗 口 通 知 消 息
---- Windows 消 息
---- 共 一 百 多 个, 用 户 不 必 记 住 它 们, 因 为 消 息 映 像 可 以 很 简 单 的 利 用ClassWizard 加 入。 处 理 一 个 消 息 的 类 方 法ClassMethod 必 须 在 类 定 义 中 声 明, 且 有 实 现 代 码。 其 原 型 为:
---- Afx_msg return_type ClassMethod(paras table)
---- 类CCmdUI 专 门( 且 仅 仅) 与ON_UPDATE_COMMAND_UI 消 息 映 像 宏 配 套 使 用, 用 于 管 理 菜 单( 还 有 工 具 栏 按 扭 等) 的 实 时 状 态, 如 是 否 变 灰, 是 否 加 选 中 标 记 等。
---- ON_UPDATE_COMMAND_UI 消 息 映 像 宏 原 型 为:
---- ON_UPDATE_COMMAND_UI(Menu_Item_ID, Menu_Proc)
---- ON_UPDATE_COMMAND_UI 消 息 映 像 宏 将 一 个 菜 单 项( 命 令 项) 和 一 个 更 新 处 理 过 程 联 结, 从 而 在 适 当 的 时 机 自 动 调 用 此 更 新 处 理 过 程 来 完 成 对 菜 单 项 状 态 的 更 新。
---- Menu_Item_ID 为 菜 单 项 的ID 号,Menu_Proc 为 此 菜 单 项 的 更 新 处 理 函 数, 原 型 为:
---- afx_msg void Menu_Proc (CCmdUI* pCmdUI)
---- 它 带 有 一 个CCmdUI 类 指 针, 使 用 它 可 调 用CCmdUI 的 类 方 法。 与 菜 单 有 关 的 类 方 法 有:
Enable(BOOL) 使菜单项有效或无效SetText(LPCTSTR) 设置菜单项的文本SetCheck(int) 加上或去掉选中标记“X”SetRadio(BOOL) 加上或去掉选中标记“.”
---- MenuProc 被 调 用 的 时 机 有 以 下 几 种 情 况:
---- 用 鼠 标 选 中 包 含 该 菜 单 项 的 菜 单 条
---- 用 热 键 选 中 包 含 该 菜 单 项 的 菜 单 条
---- 用 快 捷 键 选 中 与 该 菜 单 项 在 同 一 菜 单 条 下 的 任 一 菜 单 项
---- 我 们 以 下 面 菜 单 结 构 为 例:
Test menu Item One ID_ITEM_ONE Ctrl+1 Item Two ID_ITEM_TWO Ctrl+2 Popup Popup One ID_POPUP_ONE Ctrl+3 Popup Two ID_POPUP_TWO Ctrl+4
---- 当 用 鼠 标 左 键 点 按Test menu 菜 单 条 或 按Alt+t 或 按Ctrl+1/2/3/4 时, 四 个 菜 单 项 的 更 新 处 理 过 程MenuProc 都 将 被 调 用。
---- 当 我 们 考 察 上 面 这 个 具 有 嵌 套 结 构 的 菜 单 时, 我 们 面 临 这 样 一 个 问 题: 菜 单 项Item One/Item Two 的 更 新 函 数 和Popup One/Popup Two 的 更 新 函 数 形 式 上 是 否 一 致 ? 当Popup One 和Popup Two 都 变 灰 时Popup 是 否 自 动 变 灰 ?
---- 根 据MFC 的 内 部 机 制, 仅 仅 弹 出 菜 单 的 第 一 项 应 附 加 一 些 代 码, 其 余 项 的 形 式 基 本 是 一 致 的。 也 就 是 说 在 上 例 中, 除 菜 单 项Popup One 外, 其 他 菜 单 项 更 新 函 数 的 代 码 基 本 一 致, 即 根 据 条 件, 简 单 调 用CCmdUI 类 方 法 即 可。 菜 单 项Popup One 由 于 是 弹 出 式 菜 单Popup 的 第 一 项, 它 的 更 新 函 数 在 以 下 两 种 情 况 下 都 会 被 调 用:
---- 当 弹 出 式 菜 单(Popup) 的 菜 单 项(Popup One 和Popup Two) 要 被 绘 出 时
---- 当 此 弹 出 式 菜 单 即Popup 本 身 要 被 绘 出 时
---- 第 一 种 情 况 很 好 理 解, 正 如 我 们 选 中Test menu 而Item One 和Item Two 的 更 新 函 数 会 自 动 执 行 一 样。 第 二 种 情 况 其 实 也 很 自 然, 因 为Popup 和Item One/Item Two 不 一 样, 它 没 有ID 号, 不 能 添 加 消 息 映 像 项, 那 么 它 的 状 态 如 何 更 新 呢 ? 于 是 它 的 第 一 项 的 更 新 函 数 被 调 用, 为 了 区 分 是 不 同 的 调 用, 它 将CCmdUI 的 类 成 员 变 量m_pSubMenu 设 置 为 不 同 的 值。 在 第 一 种 情 况 下,m_pSubMenu 等 于NULL, 第 二 种 情 况 下,m_pSubMenu 不 等 于NULL。
---- 以 下 我 们 给 出 一 个 实 际 的 编 程 范 例。 由 于 篇 幅 关 系, 我 们 仅 仅 给 出 一 些 关 键 的 语 句, 其 余 的 则 一 并 略 去。
---- 在 头 文 件 的 类 声 明 中:
BOOL m_bItemOne, m_bItemTwo, m_bPopupOne, m_bPopupTwo; //用于决定各个菜单项的状态protected:afx_msg void OnUpdateMenuitemOne(CCmdUI* pCmdUI); afx_msg void OnUpdateMenuitemTwo(CCmdUI* pCmdUI);afx_msg void OnUpdatePopupOne(CCmdUI* pCmdUI); afx_msg void OnUpdatePopupTwo(CCmdUI* pCmdUI); //各菜单项的更新函数DECLARE_MESSAGE_MAP()在源文件中: BEGIN_MESSAGE_MAP(CMyDoc, CDocument) ON_UPDATE_COMMAND_UI (ID_ITEM_ONE, OnUpdateMenuitemOne) ON_UPDATE_COMMAND_UI (ID_ITEM_TWO, OnUpdateMenuitemTwo) ON_UPDATE_COMMAND_UI (ID_POPUP_ONE, OnUpdatePopupOne) ON_UPDATE_COMMAND_UI (ID_ POPUP_TWO, OnUpdatePopupTwo) END_MESSAGE_MAP()void CMyApp::OnUpdatetMenuitemOne (CCmdUI* pCmdUI){pCmdUI->Enable(m_bItemOne); if(m_bItemOne) pCmdUI->SetText("Item One"); else pCmdUI->SetText("Item One is now disabled");}void CMyApp::OnUpdatetMenuitemTwo (CCmdUI* pCmdUI){ pCmdUI->Enable(m_bItemTwo); if(m_bItemTwo) pCmdUI->SetText("Item Two"); else pCmdUI->SetText("Item Two is now disabled");}void CMyApp::OnUpdatePopupOne(CCmdUI* pCmdUI){ if (pCmdUI->m_pSubMenu != NULL) { BOOL b_Popup = m_bPopupOne m_bPopupTwo; pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex, MF_BYPOSITION (bEnable ? MF_ENABLED : (MF_DISABLED MF_GRAYED))); return; } pCmdUI->Enable(m_bPopupOne);}void CMyApp::OnUpdatePopupTwo(CCmdUI* pCmdUI){ pCmdUI->Enable(m_bPopupTwo);