TN021:命令和消息路由

注释

自联机文档中首次包含此说明以来,尚未更新以下技术说明。 因此,某些过程和主题可能过期或不正确。 有关最新信息,建议在在线文档索引中搜索您感兴趣的主题。

本说明介绍常规窗口消息路由中的命令路由和调度体系结构以及高级主题。

有关此处所述的体系结构的一般详细信息,请参阅 Visual C++,尤其是 Windows 消息、控件通知和命令之间的区别。 此说明假定你非常熟悉打印文档中介绍的问题,并且仅处理非常高级的主题。

命令路由和调度 MFC 1.0 功能演变为 MFC 2.0 体系结构

Windows 具有重载的WM_COMMAND消息,用于提供菜单命令、快捷键和对话框控制通知的通知。

MFC 1.0 通过允许派生类中的 CWnd 命令处理程序(例如“OnFileNew”)在响应特定WM_COMMAND时调用它而构建。 这与称为消息映射的数据结构粘附在一起,并产生非常空间高效的命令机制。

MFC 1.0 还提供了用于将控件通知与命令消息分开的其他功能。 命令由 16 位 ID 表示,有时称为命令 ID。 命令通常从 CFrameWnd (即菜单选择或翻译的加速器)开始,并路由到各种其他窗口。

MFC 1.0 在有限意义上使用命令路由实现多个文档接口(MDI)。 (MDI 帧窗口将命令委托给其活动的 MDI 子窗口。)

此功能已在 MFC 2.0 中通用化和扩展,以允许由更广泛的对象(而不仅仅是窗口对象)处理命令。 它提供了一个更正式和可扩展的体系结构,用于路由消息,并重复使用命令目标路由,不仅处理命令,还用于更新 UI 对象(如菜单项和工具栏按钮),以反映命令的当前可用性。

命令 ID

有关命令路由和绑定过程的说明,请参阅 Visual C++。 技术说明 20 包含有关 ID 命名的信息。

我们将泛型前缀“ID_”用于命令 ID。 命令 ID 为 >= 0x8000。 如果存在与命令 ID 相同的 STRINGTABLE 资源,则消息行或状态栏将显示命令说明字符串。

在应用程序的资源中,命令 ID 可以在多个位置显示:

  • 在一个 ID 与消息行提示符相同的 STRINGTABLE 资源中。

  • 在可能许多附加到调用相同命令的菜单项的 MENU 资源中。

  • (ADVANCED)在 GOSUB 命令的对话框按钮中。

在应用程序的源代码中,命令 ID 可在多个位置显示:

  • 在 RESOURCE 中。用于定义特定于应用程序的命令 ID 的 H(或其他主符号头文件)。

  • 也许在用于创建工具栏的 ID 数组中。

  • 在ON_COMMAND宏中。

  • 也许在ON_UPDATE_COMMAND_UI宏中。

目前,MFC 中要求命令 ID 为 >= 0x8000的唯一实现是 GOSUB 对话/命令的实现。

GOSUB 命令,在对话框中使用命令体系结构

路由和启用命令的命令体系结构适用于框架窗口、菜单项、工具栏按钮、对话框栏按钮、其他控制栏和其他用户界面元素,这些元素旨在按需更新,并将命令或控件 ID 路由到主命令目标(通常是主框架窗口)。 该主命令目标可能会根据需要将命令或控制通知路由到其他命令目标对象。

如果将对话控件的控件 ID 分配给相应的命令 ID,则对话框(模式或无模式)可以从命令体系结构的某些功能中受益。 对对话框的支持不是自动的,因此可能需要编写一些额外的代码。

请注意,若要使所有这些功能正常工作,命令 ID 应为 >= 0x8000。 由于许多对话可以路由到同一帧,因此共享命令应为 >= 0x8000,而特定对话框中的非共享 IDC 应为 <= 0x7FFF。

可以将普通按钮置于正常模式对话框中,该按钮的 IDC 设置为相应的命令 ID。 当用户选择按钮时,对话框(通常是主框架窗口)的所有者获取命令就像任何其他命令一样。 这称为 GOSUB 命令,因为它通常用于打开另一个对话(第一个对话框的 GOSUB)。

还可以在对话框中调用该函数 CWnd::UpdateDialogControls ,并将其传递给主框架窗口的地址。 此函数将基于它们是否在帧中具有命令处理程序来启用或禁用对话控件。 对于应用程序的空闲循环中的控制栏,会自动调用此函数,但必须直接为希望具有此功能的正常对话调用它。

调用ON_UPDATE_COMMAND_UI时

始终维护程序的所有菜单项的启用/检查状态可能是一个计算成本高昂的问题。 一种常见方法是仅在用户选择 POPUP 时启用/检查菜单项。 WM_INITMENUPOPUP消息的 CFrameWnd MFC 2.0 实现,并使用命令路由体系结构通过ON_UPDATE_COMMAND_UI处理程序确定菜单的状态。

CFrameWnd 还处理WM_ENTERIDLE消息,以描述状态栏(也称为消息行)上选择的当前菜单项。

由 Visual C++ 编辑的应用程序菜单结构用于表示WM_INITMENUPOPUP时可用的潜在命令。 ON_UPDATE_COMMAND_UI处理程序可以修改菜单的状态或文本,或者用于高级用途(如文件 MRU 列表或 OLE 谓词弹出菜单),实际上在绘制菜单之前修改菜单结构。

当应用程序进入其空闲循环时,对工具栏(和其他控制栏)执行相同类型的ON_UPDATE_COMMAND_UI处理。 有关控制栏的详细信息,请参阅 类库参考 和技术 说明 31

嵌套弹出菜单

如果使用嵌套菜单结构,你会注意到弹出菜单中第一个菜单项的ON_UPDATE_COMMAND_UI处理程序在两个不同的情况下调用。

首先,它被称为弹出菜单本身。 这是必要的,因为弹出菜单没有 ID,我们使用弹出菜单的第一个菜单项的 ID 来引用整个弹出菜单。 在这种情况下,对象的 m_pSubMenu 成员变量 CCmdUI 将为非 NULL,并指向弹出菜单。

其次,在弹出菜单中的菜单项要绘制之前调用它。 在这种情况下,ID 仅引用第一个菜单项,并且该对象的 m_pSubMenu 成员变量 CCmdUI 将为 NULL。

这样,就可以启用与菜单项不同的弹出菜单,但需要编写一些菜单感知代码。 例如,在具有以下结构的嵌套菜单中:

File>
    New>
    Sheet (ID_NEW_SHEET)
    Chart (ID_NEW_CHART)

可以独立启用或禁用ID_NEW_SHEET和ID_NEW_CHART命令。 如果启用了这两个菜单之一,则应启用 “新建 ”弹出菜单。

ID_NEW_SHEET的命令处理程序(弹出窗口中的第一个命令)如下所示:

void CMyApp::OnUpdateNewSheet(CCmdUI* pCmdUI)
{
    if (pCmdUI->m_pSubMenu != NULL)
    {
        // enable entire pop-up for "New" sheet and chart
        BOOL bEnable = m_bCanCreateSheet || m_bCanCreateChart;
        // CCmdUI::Enable is a no-op for this case, so we
        // must do what it would have done.
        pCmdUI->m_pMenu->EnableMenuItem(pCmdUI->m_nIndex,
            MF_BYPOSITION |
            (bEnable  MF_ENABLED : (MF_DISABLED | MF_GRAYED)));

        return;
    }
    // otherwise just the New Sheet command
    pCmdUI->Enable(m_bCanCreateSheet);
}

ID_NEW_CHART的命令处理程序是正常的更新命令处理程序,如下所示:

void CMyApp::OnUpdateNewChart(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(m_bCanCreateChart);
}

ON_COMMAND和ON_BN_CLICKED

ON_COMMANDON_BN_CLICKED的消息映射宏相同。 MFC 命令和控制通知路由机制仅使用命令 ID 来确定要路由到的位置。 控制通知代码为零(BN_CLICKED)的控件通知被解释为命令。

注释

事实上,所有控件通知消息都经过命令处理程序链。 例如,从技术上讲,可以在文档类中为 EN_CHANGE 编写控件通知处理程序。 通常不建议这样做,因为此功能的实际应用程序很少,ClassWizard 不支持该功能,并且使用该功能可能会导致代码脆弱。

禁用按钮控件的自动禁用

如果在对话框栏上放置按钮控件,或者使用自己调用 CWnd::UpdateDialogControls 的对话框,你会注意到框架会自动禁用没有 ON_COMMANDON_UPDATE_COMMAND_UI 处理程序的按钮。 在某些情况下,无需具有处理程序,但希望按钮保持启用状态。 实现此目的的最简单方法是添加虚拟命令处理程序(可以轻松地使用 ClassWizard),并在其中执行任何作。

窗口消息路由

下面介绍了 MFC 类上的一些更高级主题,以及 Windows 消息路由和其他主题如何影响它们。 此处的信息仅简要介绍。 有关公共 API 的详细信息,请参阅 类库参考 。 有关实现详细信息的详细信息,请参阅 MFC 库源代码。

有关窗口清理的详细信息,请参阅 技术说明 17 ,这是所有 CWnd 派生类非常重要的主题。

CWnd 问题

实现成员函数 CWnd::OnChildNotify 为子窗口(也称为控件)提供强大且可扩展的体系结构,用于挂钩或通知发送到其父窗口(或“所有者”)的消息、命令和控制通知。 如果子窗口(/control)是C++ CWnd 对象本身,则首先使用原始消息中的参数(即 MSG 结构)调用虚拟函数 OnChildNotify。 子窗口可以单独离开邮件,吃它,或修改父级的消息(很少见)。

默认 CWnd 实现处理以下消息,并使用 OnChildNotify 挂钩允许子窗口(控件)首先访问消息:

  • WM_MEASUREITEMWM_DRAWITEM (自画)

  • WM_COMPAREITEMWM_DELETEITEM (自画)

  • WM_HSCROLLWM_VSCROLL

  • WM_CTLCOLOR

  • WM_PARENTNOTIFY

你会注意到 OnChildNotify 挂钩用于将所有者绘制消息更改为自画消息。

除了 OnChildNotify 挂钩之外,滚动消息还具有进一步的路由行为。 有关滚动条和 WM_HSCROLLWM_VSCROLL 消息来源的更多详细信息,请参阅下文。

CFrameWnd 问题

CFrameWnd 类提供大部分命令路由和用户界面更新实现。 这主要用于应用程序的主框架窗口(CWinApp::m_pMainWnd),但适用于所有框架窗口。

主框架窗口是带有菜单栏的窗口,是状态栏或消息行的父级。 请参阅上述有关命令路由和 WM_INITMENUPOPUP的讨论。

CFrameWnd 类提供活动视图的管理。 以下消息通过活动视图路由:

  • 所有命令消息(活动视图将首先访问它们)。

  • 同级滚动条 WM_HSCROLL和WM_VSCROLL 消息(请参阅下文)。

  • WM_ACTIVATE (和 MDI WM_MDIACTIVATE )转换为对虚拟函数 CView::OnActivateView 的调用。

CMDIFrameWnd/CMDIChildWnd 问题

这两个 MDI 帧窗口类都派生自 CFrameWnd ,因此都为 CFrameWnd 中提供的相同命令路由和用户界面更新启用。 在典型的 MDI 应用程序中,只有主框架窗口(即 CMDIFrameWnd 对象)保留菜单栏和状态栏,因此是命令路由实现的主要源。

常规路由方案是活动 MDI 子窗口首次访问命令。 默认 PreTranslateMessage 函数处理 MDI 子窗口(第一个)和 MDI 帧(第二个)的加速器表,以及通常由 TranslateMDISysAccel (last)处理的标准 MDI 系统命令加速器。

滚动条问题

处理滚动消息(WM_HSCROLL/OnHScroll 和/或 WM_VSCROLL/OnVScroll)时,应尝试编写处理程序代码,使其不依赖于滚动条消息来自何处。 这不仅是一个常规的 Windows 问题,因为滚动消息可能来自真正的滚动条控件或 WS_HSCROLL/WS_VSCROLL 滚动条,而不是滚动条控件。

MFC 扩展允许滚动条控件是滚动窗口的子级或同级窗口(事实上,滚动条和滚动窗口之间的父/子关系可以是任何内容)。 这对于使用拆分器窗口的共享滚动条尤其重要。 有关 CSplitterWnd 实现的详细信息,请参阅技术说明 29,包括有关共享滚动条问题的详细信息。

在侧注中,有两个 CWnd 派生类,其中创建时指定的滚动条样式被捕获,而不是传递到 Windows。 传递给创建例程时,可以独立设置 WS_HSCROLLWS_VSCROLL ,但在创建后无法更改。 当然,不应直接测试或设置它们创建的窗口的WS_SCROLL样式位。

对于 CMDIFrameWnd, 传入到 CreateLoadFrame 的滚动条样式用于创建 MDICLIENT。 如果希望具有可滚动的 MDICLIENT 区域(如 Windows 程序管理器),请务必为用于创建 CMDIFrameWnd 的样式设置两个滚动条样式(WS_HSCROLL | WS_VSCROLL)。

对于 CSplitterWnd ,滚动条样式适用于拆分器区域的特殊共享滚动条。 对于静态拆分器窗口,通常不会设置任一滚动条样式。 对于动态拆分器窗口,通常为要拆分的方向设置滚动条样式,即 如果可以拆分行, WS_HSCROLL,WS_VSCROLL 可以拆分列。

另请参阅

按编号列出的技术说明
按类别列出的技术说明