【MFC】MFC消息处理和映射
发布日期:2021-06-29 20:50:25 浏览次数:2 分类:技术文章

本文共 15084 字,大约阅读时间需要 50 分钟。

00. 目录

文章目录

01. 概述

在传统的 Windows 程序中,Windows 消息在窗口过程中的大型 switch 语句中处理。 MFC 改为使用 消息映射 将直接消息映射到不同的类成员函数。 从这方面来看,消息映射比虚函数效率更高,并且前者可让消息由最合适的 C++ 对象(应用程序、文档、视图等)处理。 您可以映射单个消息,或者映射一系列消息、命令 ID 或控件 ID。

WM_COMMAND 消息(通常会由菜单、工具栏按钮或快捷键生成)也使用消息映射机制。 MFC 定义程序中应用程序、框架窗口、视图和活动文档之间的命令消息的标准 路由 。 如果需要,您可以重写此路由。

消息映射还提供了一种更新用户界面对象(如菜单和工具栏按钮)的方法,即启用或禁用这些对象以适应当前上下文。

有关 Windows 中的消息和消息队列的常规信息,请参阅 Windows SDK 中的 。

02. 框架中的消息和命令

为 Microsoft Windows 编写的应用程序是 “消息驱动的”。 为了响应鼠标单击、击键、窗口移动等事件,Windows 会将消息发送到正确的窗口。 框架应用程序像处理 windows 的任何其他应用程序一样处理 Windows 消息。 但框架还提供了一些增强功能,可更轻松、更易于维护和更好地封装处理消息。

2.1 消息

类的成员函数中的消息循环 Run CWinApp 检索由各种事件生成的已排队消息。 例如,当用户单击鼠标时,Windows 将发送多个与鼠标相关的消息,例如在按下鼠标左键时 WM_LBUTTONDOWN,并在释放鼠标左键时 WM_LBUTTONUP。 应用程序消息循环的框架的实现会将消息调度到合适的窗口。

2.2 消息处理程序

在 MFC 中,专用 处理程序 函数处理每个单独的消息。 消息处理程序函数是类的成员函数。 本文档使用术语 消息处理程序成员函数消息处理程序函数消息处理 程序和 处理程序 。 某些类型的消息处理程序也称为“命令处理程序”。

编写消息处理程序时,您的大部分精力都将耗在编写框架应用程序上。 本文章系列介绍消息处理机制的工作方式。

消息的处理程序执行的操作会执行您想要执行的任何操作来响应该消息。 您可以使用类的 类向导 创建处理程序,然后使用源代码编辑器填写处理程序的代码。

您可以使用 Microsoft Visual C++ 和 MFC 的所有工具编写处理程序。 有关所有类的列表,请参阅 MFC 参考 中的类库 概述。

2.3 消息类别

为哪些类型的消息编写处理程序主要有三个类别:

  1. Windows 消息

    这包括以除 WM_COMMAND 之外的 WM_ 前缀开头的那些消息。 Windows 消息由窗口和视图处理。 这些消息通常具有用于确定如何处理消息的参数。

  2. 控制通知

    这包括从控件和其他子窗口到其父窗口的 WM_COMMAND 通知消息。 例如,当用户已执行可能更改编辑控件中的文本的操作时,编辑控件会向其父级发送包含 EN_CHANGE 控件通知代码的 WM_COMMAND 消息。 窗口的消息处理程序将以某种合适的方式响应通知消息,如检索控件中的文本。

    框架路由控件通知消息,如其他 WM_ 消息。 但有一个例外,即用户单击按钮时由按钮发送的 BN_CLICKED 控件通知信息。 此消息将专门作为命令消息处理,并像传送其他命令一样传送。

  3. 命令消息

    这包括来自用户界面对象的 WM_COMMAND 通知消息:菜单、工具栏按钮和快捷键。 框架处理命令的方式与其他消息不同,它们可以由更多类型的对象处理,如 命令目标中所述。

Windows 消息和 Control-Notification 消息

类别 1 和 2 的消息(Windows 消息和控件通知)由窗口处理:派生自 CWnd 对象。 这包括 CFrameWndCMDIFrameWndCMDIChildWndCViewCDialog,并且您拥有派生自这些基类的类。 此类对象将封装 HWND(Windows 窗口的句柄)。

命令消息

类别 3 的消息(命令)可由更多类型的对象处理:文档、文档模板和应用程序对象本身以及窗口和视图。 当命令直接影响了某个特定对象时,让该对象处理命令很有意义。 例如,“文件”菜单上的“打开”命令与应用程序逻辑关联:应用程序在收到命令时打开指定的文档。 因此,“打开”命令的处理程序是应用程序类的成员函数。 有关命令以及如何将其路由到对象的详细信息,请参阅 框架如何调用处理程序。

2.4 映射消息

每个可以接收消息或命令的框架类都有自己的“消息映射”。 框架使用消息映射来将消息和命令连接到其处理程序函数。 派生自类 CCmdTarget 的所有类都可拥有消息映射。 其他文章详细解释了消息映射并说明了其使用方式。

尽管名称为 “消息映射”,但消息映射会同时处理消息和命令 消息类别中列出的所有三类消息。

2.5 命令ID

命令只通过其命令 ID 进行完全描述, (在 WM_COMMAND message) 中进行编码。 此 ID 分配给生成命令的用户界面对象。 通常,Id 是为其分配到的用户界面对象的功能命名的。

例如,可以为 “编辑” 菜单中的 “全部清除” 项分配 ID,如 ID_EDIT_CLEAR_ALL。 类库预定义一些 Id,特别是对于框架处理自身的命令,例如 ID_EDIT_CLEAR_ALLID_FILE_OPEN。 你将自行创建其他命令 Id。

在 “Visual C++ 菜单编辑器” 中创建自己的菜单时,最好遵循类库的命名约定,如 ID_FILE_OPEN 所示。 标准命令 说明类库定义的标准命令。

2.6 标准命令

框架定义许多标准命令消息。 这些命令的 ID 通常采用以下形式:

ID_ 源 _ 项

其中, Source 通常是菜单名称, 是菜单项。 例如,“文件” 菜单上的 “新建” 命令的命令 ID 是 ID_FILE_NEW。 标准命令 ID 将以粗体类型显示在文档中。 程序员定义的 ID 将以不同于周围文本的字体显示。

下面是支持的部分最重要的命令的列表:

"文件" 菜单命令

新建、打开、关闭、保存、另存为、页面设置、打印设置、打印、打印预览、退出和最近使用的文件。

编辑菜单命令

清除、全部清除、复制、剪切、查找、粘贴、重复、替换、全选、撤消和重做。

"查看" 菜单命令

工具栏和状态栏。

"窗口" 菜单命令

新建、排列、层叠、水平平铺、垂直平铺和拆分。

"帮助" 菜单命令

“索引”、“使用帮助”和“关于”。

OLE 命令(编辑菜单)

插入新对象、编辑链接、粘贴链接、粘贴特殊对象和 typename 对象 (谓词命令) 。

框架将为这些命令提供各种级别的支持。 一些命令仅作为定义的命令 ID 支持,而其他命令则通过实现支持。 例如,框架通过新建文档对象,显示一个“打开”对话框并打开和读取文件来实现“文件”菜单上的“打开”命令。 相反,您必须自己实现“编辑”菜单命令,因为 ID_EDIT_COPY 等命令取决于要复制的数据的性质。

有关支持的命令以及提供的实现级别的详细信息,请参阅 。 标准命令是在文件 AFXRES.H. 中定义的。

2.7 命令目标

框架中的图形命令显示了用户界面对象(如菜单项)与框架调用的处理程序函数之间的连接,以在单击对象时执行生成的命令。

Windows 直接将不是命令消息的消息发送到某个窗口,随后将调用该窗口的针对该消息的处理程序。 但是,框架会将命令传送到很多候选对象(称为“命令目标”),其中一个对象通常会调用命令的处理程序。 处理程序函数对于命令和标准 Windows 消息的工作方式相同,但调用它们的机制不同,如 框架如何调用处理程序中所述。

03. 框架如何调用处理程序

3.1 消息发送和接收

考虑进程的发送部分以及框架响应的方式。

大多数消息来源于用户与程序的交互。 命令通过以下方式生成:用鼠标单击菜单项或工具栏按钮或按快捷键。 用户还通过一些操作来生成 Windows 消息,例如,通过移动窗口或调整窗口大小。 当发生程序启动或终止、窗口获取或丢失焦点等情况时,会发送其他 Windows 消息。 通过鼠标单击或与用户与控件(如对话框中的按钮或列表框控件)的其他交互来生成控件通知消息。

Run类的成员函数 CWinApp 检索消息,并将消息调度到相应的窗口。 大多数命令消息将发送到应用程序的主框架窗口。 由类库预定义的 WindowProc 将获取消息并以不同方式传送这些消息,具体取决于所接收消息的类别。

现在考虑过程的接收部分。

消息的初始接收器必须为窗口对象。 窗口消息通常由该窗口对象直接处理。 命令消息(通常源自应用程序的主框架窗口)将路由到 命令传送中所述的命令目标链。

能够接收消息或命令的每个对象都有其自己的消息映射,此映射会将消息或命令与其处理程序的名称配对。

当命令目标对象接收消息或命令时,它会在其消息映射中搜索匹配项。 如果它找到此消息的处理程序,则会调用该处理程序。 有关如何搜索消息映射的详细信息,请参阅 框架如何搜索消息映射。 再次引用 框架中的图形命令。

3.2 非命令消息如何到达其处理程序

与命令不同,标准 Windows 消息不通过命令目标链进行路由,但通常由 Windows 发送消息的窗口处理。 窗口可能是主框架窗口、MDI 子窗口、标准控件、对话框、视图或某些其他类型的子窗口。

在运行时,每个窗口窗口均附加到一个窗口对象, (直接或间接从 CWnd 具有其自己的关联消息映射和处理函数的) 派生。 框架使用消息映射(对于命令)将传入消息映射到处理程序。

3.3 命令路由

使用命令的责任仅限于在命令及其处理程序函数之间建立消息映射连接,这是使用 MFC 类向导的任务。 还必须为命令处理程序编写代码。

Windows 消息通常发送到主框架窗口中,但命令消息则传送到其他对象。 框架通过命令目标对象的标准顺序传送命令,这些对象中应包含命令的处理程序。 每个命令目标对象检查其消息映射,看看是否能处理传入的消息。

不同的命令目标类检查其自身不同时间的消息映射。 通常情况下,类将命令传送到其他某些对象,让对象首次尝试处理命令。 如果没有对象处理该命令,原始类将检查其自己的消息映射。 然后,如果它自己无法提供处理程序,则可将命令传送到更多的命令目标。 下表 标准命令传送 显示了每个类如何构成此顺序。 命令目标传送命令的一般顺序是:

  1. 到其当前活动子命令目标对象。
  2. 到自身。
  3. 到其他命令目标。

此路由机制与处理程序响应命令所执行的操作相比,此路由机制的代价是很低。 请记住,仅当用户与用户界面对象交互时,框架才生成命令。

标准命令路由

当此类型的对象收到命令时。 . . 它给自身和其他命令目标对象一个机会以此顺序处理命令:
MDI 框架窗口 (CMDIFrameWnd) 1. 活动 CMDIChildWnd 2. 此框架窗口 3. 应用程序 (CWinApp 对象)
文档框架窗口(CFrameWndCMDIChildWnd 1. 活动视图 2. 此框架窗口 3. 应用程序 (CWinApp 对象)
查看 1. 此视图 2. 附加到视图的文档
文档 1. 此文档 2. 附加到文档的文档模板
对话框 1. 此对话框 2. 拥有对话框的窗口 3. 应用程序 (CWinApp 对象)

如果前述表第二列中带编号的项提到其他对象(例如文档),请参见第一列中相应的项。 例如,当你在第二列中看到视图将命令转发到其文档,则参阅第一列中的“文档”项了解进一步的传送。

3.4 命令路由示例

为了说明,请考虑来自 MDI 应用程序的“编辑”菜单中“全部清除”菜单项的命令消息。 假设此命令的处理程序函数恰好是应用程序的文档类的成员函数。 下面演示了在用户选择菜单项后命令如何到达其处理程序:

  1. 主框架窗口首先收到命令消息。
  2. 主 MDI 框架窗口为当前处于活动状态的 MDI 子窗口提供处理命令的机会。
  3. MDI 子框架窗口的标准传送在检查自己的消息映射前为其视图提供了一个执行命令的机会。
  4. 视图首先检查自己的消息映射,如果未找到处理程序,则会将命令传送到其关联文档。
  5. 该文档检查其消息映射并找到一个处理程序。 将调用此文档成员函数,然后传送停止。

如果文档没有处理程序,它会将命令传送到其文档模板。 接下来,命令将返回到视图,然后返回到框架窗口。 最后,框架窗口将检查其消息映射。 如果该检查也失败,命令将传送回主 MDI 框架窗口,然后传送到应用程序对象(未处理的命令的最终目标)。

3.5 OnCmdMsg 处理程序

为了完成命令路由,每个命令目标均将调用序列中下一命令目标的 OnCmdMsg 成员函数。 命令目标将使用 OnCmdMsg 确定它们是否可以处理命令并在无法处理命令时将其路由到其他命令目标。

每个命令目标类都可能重写 OnCmdMsg 成员函数。 通过重写,每个类会将命令路由到下一特定目标。 例如,框架窗口始终将命令路由到其当前子窗口或视图,如表 标准命令路由中所示。

CCmdTarget 的默认 OnCmdMsg 实现使用命令目标类的消息映射搜索收到的每条命令消息的处理程序函数 - 通过搜索标准消息的同一方式。 如果找到匹配项,则将调用找到的处理程序。 框架如何搜索消息映射中介绍了消息映射搜索。

3.6 重写标准命令传送

在您必须实现标准框架路由的某个变体的罕见情况下,您可重写它。 意图是通过重写这些类中的 OnCmdMsg 来更改一个或多个类的路由。 如此做:

  • 在打乱到非默认对象的传递顺序的类中。
  • 在新的非默认对象或它可能依次将命令传递到的命令目标中。

如果您将某个新的对象插入路由,则其类必须是命令目标类。 在 OnCmdMsg 的重写版本中,请确保调用您要重写的版本。 有关示例,请参阅 MFC 参考中的类的OnCmdMsg成员函数 CCmdTarget ,以及此类类中的 CView 和和 CDocument 中提供的版本。

04. 框架如何搜索消息映射

框架在消息映射表中搜索与传入消息匹配的消息。 为每条消息编写消息映射项后,需要类处理并写入相应的处理程序,框架会自动调用处理程序。

4.1 消息映射的位置

使用应用程序向导创建新的主干应用程序时,应用程序向导将为您创建的每个命令目标类写入消息映射。 这包括派生的应用程序、文档、视图和框架窗口类。 其中一些消息映射已经具有应用程序向导为某些消息和预定义的命令提供的条目,而有些则只是要添加的处理程序的占位符。

类的消息映射位于中。类的 CPP 文件。 使用应用程序向导创建的基本消息映射,可使用 类向导 添加每个类将处理的消息和命令的条目。 添加一些条目后,典型的消息映射可能类似于以下内容:

BEGIN_MESSAGE_MAP(CMyView, CFormView)ON_WM_MOUSEACTIVATE()ON_COMMAND(ID_EDIT_CUT, &CMyView::OnEditCut)ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, &CMyView::OnUpdateEditCut)ON_BN_CLICKED(IDC_MYBUTTON, &CMyView::OnBnClickedMybutton)ON_WM_CREATE()END_MESSAGE_MAP()

消息映射包含宏的集合。 两个宏, BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP,用方括号将消息映射括起来。 其他宏(如 ON_COMMAND )填写消息映射的内容。

备注:

消息映射宏后面不跟分号。

使用 “添加类向导” 创建新类时,它将为类提供消息映射。 或者,您可以使用源代码编辑器手动创建消息映射。

4.2 派生消息映射

消息处理期间,检查类自己的消息映射是否是消息映射描述的结尾。 如果 CMyView 从) 派生的类 (CView 没有相应的消息匹配项,会发生什么情况?

记住,CViewCMyView 的基类)是依次从 CWnd 派生的。 因此 CMyView CView CWnd 。 这些类都有其自己的消息映射。 下图显示了类的层次结构关系,但请记住, CMyView 对象是具有所有三个类的特征的单个对象。

如果消息无法在类 CMyView 的消息映射中匹配,则框架还会搜索其直接基类的消息映射。 位于消息映射开头的 BEGIN_MESSAGE_MAP 宏将指定两个类名称作为其参数:

BEGIN_MESSAGE_MAP(CMyView, CView)

第一个自变量命名消息映射所属的类。 第二个自变量提供与即时基类的连接(在本例中为 CView ),以便框架也可搜索其消息映射。

基类中提供的消息处理程序将由派生类集成。 这非常类似于标准虚拟成员函数,无需使所有处理程序成员函数成为虚拟。

如果在任何基类消息映射中都未找到处理程序,则将对消息执行默认处理。 如果消息是命令,则框架会将其路由至下一命令目标。 如果消息是标准 Windows 消息,则消息将传递给适当的默认窗口程序。

为了加快消息映射匹配,框架将根据再次收到相同的消息的可能性缓存最近的匹配项。 这样做的一个结果就是框架将非常高效地处理未处理的消息。 消息映射还比使用虚函数的实现更省空间。

05. 声明消息处理程序函数

5.1 标准 Windows 消息的处理程序

标准 Windows 消息 (WM_) 的默认处理程序是在类中预定义的 CWnd 。 类库基于消息名称来命名这些处理程序的名称。 例如,在中声明 WM_PAINT 消息的处理程序,如下所示 CWnd

afx_msg void OnPaint();

Afx_msg 关键字 virtual 通过区分来自其他成员函数的处理程序来建议 c + + 关键字的影响 CWnd 。 但请注意,这些函数实际上都不是虚函数;它们是通过消息映射实现的。 消息映射仅取决于标准预处理器宏,而不是 C++ 语言的任何扩展。 在预处理后, afx_msg 关键字解析为空白

若要重写基类中定义的处理程序,只需使用派生类中相同的原型定义一个函数,并为处理程序生成一个消息映射条目。 您的处理程序将“重写”任何类的基类中名称相同的任意处理程序。

在某些情况下,您的处理程序应当调用基类中的已重写处理程序,以便基类和 Windows 可处理消息。 在重写中调用基类处理程序的位置取决于环境。 有时您必须首先调用基类,有时最后调用。 有时如果您选择不处理消息,则将有条件地调用基类处理程序。 有时你应调用基类处理程序,然后根据基类处理程序返回的值或状态有条件地执行你自己的处理程序代码。

注意

如果您打算将传入处理程序的参数传递到基类处理程序,则修改这些参数不安全。 例如,你可能想要将处理程序的 nChar 参数修改 OnChar (转换为大写形式,例如) 。 此行为相当难懂,但如果您需要实现此效果,请改用 CWnd 成员函数 SendMessage

如何在 类向导 写入给定消息的处理程序函数的主干时确定重写给定消息的正确方法( OnCreate 例如 WM_CREATE 的处理程序),以建议的重写成员函数形式进行草绘。 下面的示例建议处理程序首先调用基类处理程序,并仅在不返回-1 的情况下继续。

int CMyView::OnCreate(LPCREATESTRUCT lpCreateStruct){
if (CFormView::OnCreate(lpCreateStruct) == -1) return -1; // TODO: Add your specialized creation code here return 0;}

按照约定,这些处理程序的名称以前缀“On”开头。 其中一些处理程序不采用任何自变量,而另一些处理程序采用几个自变量。 有些也有一个返回类型,而不是 void 。 所有 WM_ 消息的默认处理程序在 MFC 引用 中记录为类的成员函数, CWnd 其名称以 “On” 开头。 中的成员函数声明以 CWnd afx_msg 为前缀。

5.2 命令和控件通知的处理程序

命令或控件通知消息没有默认的处理程序。 因此,您将仅受为这些消息类别命名处理程序的约定约束。 当您将命令或控件通知映射到处理程序时, 类向导 将基于命令 ID 或控件通知代码建议一个名称。 您可以接受建议的名称、更改或替换该名称。

约定建议您按照处理程序表示的用户界面对象的两个类别为其命名。 因此,可能为“编辑”菜单上的“剪贴”命令对应的处理程序命名

afx_msg void OnEditCut();

由于 Cut 命令通常在应用程序中实现,因此框架会将 Cut 命令的命令 ID 预定义为 ID_EDIT_CUT。 有关所有预定义命令 ID 的列表,请参阅 AFXRES.H 文件。 有关详细信息,请参阅 标准命令。

此外,约定建议从标记为 “我的按钮” 的按钮获取 BN_CLICKED 通知消息的处理程序。

afx_msg void OnBnClickedMybutton();

你可以为此命令分配 IDC_MY_BUTTON ID,因为它等效于特定于应用程序的用户界面对象。

所有消息类别不采用参数也不返回值。

5.3 消息映射范围的处理程序

本文介绍如何将一系列消息映射到单个消息处理程序函数 (,而不是将一条消息只映射到一个函数) 。

有时,您需要以完全相同的方式处理多个消息或控件通知。 在这种情况下,你可能希望将所有消息映射到一个处理程序函数。 消息映射范围允许您为一系列连续的消息执行此操作:

  • 可以将命令 Id 的范围映射到:
    • 命令处理程序函数。
    • 命令更新处理程序函数。
  • 您可以将一系列控件 Id 的控制通知消息映射到消息处理程序函数。

写入 Message-Map 条目

在CPP 文件中,添加消息映射条目,如以下示例中所示:

ON_COMMAND_RANGE(ID_MYCMD_ONE, ID_MYCMD_TEN, &OnDoSomething)

消息映射项由以下项组成:

  • 消息映射范围宏:

    • ON_COMMAND_RANGE
    • ON_UPDATE_COMMAND_UI_RANGE
    • ON_CONTROL_RANGE
  • 宏的参数:

    前两个宏采用三个参数:

    • 启动范围的命令 ID
    • 结束范围的命令 ID
    • 消息处理程序函数的名称

    命令 Id 的范围必须是连续的。

    第三个宏 ON_CONTROL_RANGE 使用额外的第一个参数:控件通知消息,如 EN_CHANGE

声明处理函数

在中添加处理函数声明。H 文件。 下面的代码演示了这种情况的外观,如下所示:

public:   afx_msg void OnDoSomething(UINT nID);

用于单个命令的处理程序函数通常不采用任何参数。 除了更新处理程序函数之外,消息映射范围的处理程序函数需要 UINT 类型的额外参数 nID。 此参数是第一个参数。 额外参数可容纳指定用户实际选择的命令所需的额外命令 ID。

有关更新处理程序函数的参数要求的详细信息,请参阅 有关一系列命令 id 的示例。

命令 Id 范围的示例

用范围时,一个示例就是处理类似于 MFC 示例 HIERSVR中的 Zoom 命令的命令。 此命令会缩放视图,并在其正常大小的25% 到300% 之间进行缩放。 HIERSVR 的视图类使用范围来处理包含类似于以下内容的消息映射项的缩放命令:

ON_COMMAND_RANGE(ID_VIEW_ZOOM25, ID_VIEW_ZOOM300, &OnZoom)

写入消息映射项时,指定:

  • 开始和结束连续范围的两个命令 Id。

    此处 ID_VIEW_ZOOM25ID_VIEW_ZOOM300

  • 命令的处理程序函数的名称。

    这就是 OnZoom

函数声明如下所示:

public:   afx_msg void OnZoom(UINT nID);

更新处理程序函数的情况与此类似,并且可能更广泛地发挥作用。 为 ON_UPDATE_COMMAND_UI 多个命令编写处理程序很常见,并发现您自己编写或复制了相同的代码。 解决方案是使用宏将一系列命令 Id 映射到一个更新处理程序函数 ON_UPDATE_COMMAND_UI_RANGE 。 命令 Id 必须构成一个连续的范围。 有关示例,请参阅 OnUpdateZoom ON_UPDATE_COMMAND_UI_RANGE HIERSVR 示例的视图类中的处理程序及其消息映射项。

用于单个命令的更新处理程序函数通常采用类型为的单个参数 pCmdUI CCmdUI* 。 与处理程序函数不同,消息映射范围的更新处理程序函数不需要 UINT 类型的额外参数 nID。 在对象中找到指定用户实际选择的命令所需的命令 ID CCmdUI

控制 Id 范围的示例

另一个有趣的情况是将一系列控件 Id 的控件通知消息映射到单个处理程序。 假设用户可以单击10个按钮中的任意一个。 若要将所有10个按钮映射到一个处理程序,消息映射条目应如下所示:

ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON1, IDC_BUTTON10, OnButtonClicked)

当您在 ON_CONTROL_RANGE 消息映射中编写宏时,您指定:

  • 特定的控件通知消息。

    这里 BN_CLICKED

  • 与控件的连续范围关联的控件 ID 值。

    此处 IDC_BUTTON1IDC_BUTTON10

  • 消息处理程序函数的名称。

    这就是 OnButtonClicked

编写处理程序函数时,请指定额外的 UINT 参数,如下所示:

void CRangesView::OnButtonClicked(UINT nID){
int nButton = nID - IDC_BUTTON1; ASSERT(nButton >= 0 && nButton < 10); // ...}

OnButtonClicked单个 BN_CLICKED 消息的处理程序不采用任何参数。 一系列按钮的相同处理程序采用一个 UINT。 额外参数允许标识负责生成 BN_CLICKED 消息的特定控件。

示例中所示的代码是典型的:将传递给的值转换为 int 消息范围内的,并断言这种情况。 然后,你可能需要执行一些不同的操作,具体取决于所单击的按钮。

5.4 处理反射消息

消息反射使你可以在控件本身中处理控件的消息,如 WM_CTLCOLORWM_COMMANDWM_NOTIFY。 这使得控件更独立和可移植。 此机制适用于 Windows 公共控件以及 ActiveX 控件(之前称为 OLE 控件)。

消息反射可以让您轻松地重用 CWnd 派生类。 消息反射通过 CWnd:: OnChildNotify使用特殊 ON_XXX_REFLECT 消息映射项,例如 ON_CTLCOLOR_REFLECT 和 ON_CONTROL_REFLECT。 技术说明 62 更详细地说明了消息反射。

06. 在状态栏中显示命令信息

当你运行应用程序向导来创建应用程序的主干时,可以支持工具栏和状态栏。 应用程序向导中只有一个选项支持两者。 如果存在状态栏,则当用户将指针移到菜单上的项上方时,应用程序会自动提供有用的反馈。 突出显示菜单项时,应用程序会自动在状态栏中显示提示字符串。 例如,当用户将指针移到 “编辑” 菜单上的 “剪切” 命令上时,状态栏可能会在状态栏的消息区域中显示 “剪切选定内容并将其放在剪贴板上”。 该提示有助于用户了解菜单项的用途。 这也适用于用户单击工具栏按钮的情况。

可以通过为添加到程序的菜单项定义提示字符串,将添加到此状态栏帮助。 为此,请在菜单编辑器中编辑菜单项的属性时提供提示字符串。 你定义的字符串存储在应用程序资源文件中;它们具有与它们说明的命令相同的 Id。

默认情况下,应用程序向导添加 AFX_IDS_IDLEMESSAGE,即标准 “Ready” 消息的 ID,该消息会在程序等待新消息时显示。 如果在应用程序向导中指定 Context-Sensitive “帮助” 选项,则该消息将更改为 “获取帮助,请按 F1”。

07. 为模板类创建消息映射

MFC 中的消息映射提供了一种将 Windows 消息定向到合适的 C++ 对象实例的有效方法。 MFC 消息映射目标的示例包括应用程序类、文档和视图类、控件类等。

使用 宏声明传统 MFC 消息映射,以声明消息映射的开头、每个消息处理程序类方法的宏条目,最后 宏声明消息映射的结尾。

当与包含模板参数的类结合使用时,将会出现 宏的一个限制。 在与模板类一起使用时,此宏会导致编译时错误,因为宏展开过程中缺少模板参数。 宏旨在允许包含单个模板自变量的类声明自己的消息映射。

示例

假设有一个示例,其中 MFC 类已扩展以提供与外部数据源的同步。 虚拟 CSyncListBox 类的声明方式如下:

// Extends the CListBox class to provide synchronization with // an external data sourcetemplate 
class CSyncListBox : public CListBox{
public: CSyncListBox(); virtual ~CSyncListBox(); afx_msg void OnPaint(); afx_msg void OnDestroy(); afx_msg LRESULT OnSynchronize(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() // ...additional functionality as needed};

CSyncListBox类在一种类型上模板化,该类型描述它将与之同步的数据源。 它还声明了三个将参与类的消息映射的方法: OnPaintOnDestroyOnSynchronizeOnSynchronize方法的实现方式如下:

template 
LRESULT CSyncListBox
::OnSynchronize(WPARAM, LPARAM lParam){
CollectionT* pCollection = (CollectionT*)(lParam); ResetContent(); if(pCollection != NULL) {
INT nCount = (INT)pCollection->GetCount(); for(INT n = 0; n < nCount; n++) {
CString s = StringizeElement(pCollection, n); AddString(s); } } return 0L;}

上述实现允许 CSyncListBox 在实现方法的任何类类型上特殊化类 GetCount ,如 CArrayCListCMapStringizeElement函数是原型的模板函数,如下所示:

// Template function for converting an element within a collection// to a CString objecttemplate
CString StringizeElement(CollectionT* pCollection, INT iIndex);

通常,此类的消息映射的定义方式如下:

BEGIN_MESSAGE_MAP(CSyncListBox, CListBox)  ON_WM_PAINT()  ON_WM_DESTROY()  ON_MESSAGE(LBN_SYNCHRONIZE, OnSynchronize)END_MESSAGE_MAP()

其中 LBN_SYNCHRONIZE 是应用程序定义的自定义用户消息,如:

#define LBN_SYNCHRONIZE (WM_USER + 1)

由于类的模板规范 CSyncListBox 在宏扩展过程中将丢失,因此以上宏映射将不会编译。 BEGIN_TEMPLATE_MESSAGE_MAP 宏通过将指定的模板参数合并到扩展的宏映射中来解决此情况。 此类的消息映射将成为:

BEGIN_TEMPLATE_MESSAGE_MAP(CSyncListBox, CollectionT, CListBox)   ON_WM_PAINT()   ON_WM_DESTROY()   ON_MESSAGE(LBN_SYNCHRONIZE, OnSynchronize)END_MESSAGE_MAP()

下面演示使用对象的类的示例用法 CSyncListBox CStringList

void CSyncListBox_Test(CWnd* pParentWnd){
CSyncListBox
ctlStringLB; ctlStringLB.Create(WS_CHILD | WS_VISIBLE | LBS_STANDARD | WS_HSCROLL, CRect(10,10,200,200), pParentWnd, IDC_MYSYNCLISTBOX); // Create a CStringList object and add a few strings CStringList stringList; stringList.AddTail(_T("A")); stringList.AddTail(_T("B")); stringList.AddTail(_T("C")); // Send a message to the list box control to synchronize its // contents with the string list ctlStringLB.SendMessage(LBN_SYNCHRONIZE, 0, (LPARAM)&stringList); // Verify the contents of the list box by printing out its contents INT nCount = ctlStringLB.GetCount(); for( INT n = 0; n < nCount; n++ ) {
TCHAR szText[256]; ctlStringLB.GetText(n, szText); TRACE(_T("%s\n"), szText); }}

若要完成测试, StringizeElement 函数必须专门用于 CStringList 类:

template<>CString StringizeElement(CStringList* pStringList, INT iIndex){
if (pStringList != NULL && iIndex < pStringList->GetCount()) {
POSITION pos = pStringList->GetHeadPosition(); for( INT i = 0; i < iIndex; i++ ) {
pStringList->GetNext(pos); } return pStringList->GetAt(pos); } return CString(); // or throw, depending on application requirements}

08. 附录

转载地址:https://dengjin.blog.csdn.net/article/details/113520121 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:【MFC】MFC消息映射(二)
下一篇:【MFC】类的层次结构图

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月12日 23时43分03秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章