140
第第第 第第 MFC 第第第第第第第第第第第

第三章 驱动 MFC 应用程序的引擎: 消息映射

  • Upload
    lucia

  • View
    262

  • Download
    0

Embed Size (px)

DESCRIPTION

第三章 驱动 MFC 应用程序的引擎: 消息映射. 众所周知, Windows 应用程序的运行是 消息驱动 。了解和掌握 API 应用程序的运行控制 —— 消息循环 接收、分发消息, 窗口 函数 处理消息,直至 退出 消息结束 消息循环 导致进程结束的 运 行机制 是理解 消息驱动 机制最直观的方法;同时如何设计 窗口 函数 实现消息的处理也是把握 API 应用程序设计、编程的关键 基础之一。 使用 MFC 编写的面向对象 Windows 应用程序的运行控制在本 - PowerPoint PPT Presentation

Citation preview

Page 1: 第三章 驱动  MFC  应用程序的引擎: 消息映射

第三章 驱动 MFC 应用程序的引擎:消息映射

Page 2: 第三章 驱动  MFC  应用程序的引擎: 消息映射

众所周知, Windows 应用程序的运行是消息驱动。了解和掌握

API 应用程序的运行控制 —— 消息循环接收、分发消息,窗口

函数处理消息,直至退出消息结束消息循环导致进程结束的运

行机制是理解消息驱动机制最直观的方法;同时如何设计窗口

函数实现消息的处理也是把握 API 应用程序设计、编程的关键

基础之一。

使用 MFC 编写的面向对象 Windows 应用程序的运行控制在本

质上与 API 应用程序是完全一致的。因此,掌握消息驱动机制的实现方法,如何实现消息的响应处理,从而控制进程、实现

程序设计需求也是在 VC 集成环境中使用 MFC 进行面向对象的

Windows 程序设计、编程的关键基础之一。

Page 3: 第三章 驱动  MFC  应用程序的引擎: 消息映射

在 MFC 应用程序中看不到 API 应用程序中实现消息驱动控制

的窗口函数 WndProc 和函数中用于处理消息的 switch-case 程序

结构,而是通过另一种方法实现的。这种方法是在每个需要响

应和处理消息的类(这些类必须是 CCmdTarget 类的直接或间接

从派生类)添加用于处理消息的成员函数,并在这些成员函数

和被处理的消息之间添加映射表从而实现消息驱动机制的。

这种消息映射的方法更符合面向对象程序设计。该方法是如

何实现将消息映射到处理函数的呢?我们将通过对消息的类别

和结构、消息映射的工作原理、处理消息的路径等内容的学习

来解决这一问题。

Page 4: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.1 消息类别及其描述消息:应用程序中所发生事件的通知,即事件的信息化表示。

事件:用户对交互界面进行操作(例如操作鼠标、键盘、改变

窗口、选择菜单项等);或者程序的某种特定运行(窗

口的创建显示、窗口客户区中被显示数据修改引起的重

绘操作)都会引起事件的产生。

基于事件触发而运行的应用程序必须是完全面向消息处

理的,即始终按如下步骤循环:

等待消息 接收消息 分析消息产生操作

完成操作返回

Page 5: 第三章 驱动  MFC  应用程序的引擎: 消息映射

消息的格式:typedef struct tagMSG

{

HWND hWnd; // 消息的目标窗口句柄UINT message; // 消息的标识符WPARAM wParam; // 32 位消息附加参数LPARAM lParam; // 32 位消息附加参数DWORD time; // 消息发至队列的时间POINT pt; // 消息发出时的鼠标位置

} MSG;

其中前四个成员是描述消息的主要参数: ① hWnd :消息的目标窗口句柄,即消息的接收者。所谓句

柄是一个特殊整数,作为 Windows 各类资源的索引,是访 问资源的唯一依据。不同资源的句柄类型也不同:

Page 6: 第三章 驱动  MFC  应用程序的引擎: 消息映射

窗口句柄 HWND, 进程实例句柄 HINSTANCE, 光标句柄 HCURSOR, 画笔句柄 HPEN, 画刷句柄 HBRUSH, 设备环境 句柄 HDC, 位图句柄 HBITMAP, 字体句柄 HFONT 等。

② message :消息的标识符,即消息的名称。 ③ wParam 和 lParam :消息的附加参数,即为消息的响应函

数传递参数。

消息队列:为管理和处理应用程序运行中所产生的驱动消息,Windows 平台提供了一个系统消息队列,用于存放分发给所有应用程序的消息; Windows 平台还为每一个进程建立一个程序消息队列,用于接收发到该进程的所有消息,以便分发到进程中的不同消息接收对象。

Page 7: 第三章 驱动  MFC  应用程序的引擎: 消息映射

面向对象程序的消息驱动相当于面向过程程序的函数调用,

但在调用目标、调用结果和参数传递三个方面存在不同:

调用目标:

函数调用:被调用的函数,由函数名确定。调用目标唯一。

消息驱动:能接收消息的对象,由消息的窗口句柄和消息标

识符确定。调用目标不唯一。

调用结果:

函数调用:被调用函数执行,并返回结果。调用无时延。

消息驱动:根据接收消息对象的状态决定消息对应的方法是

否执行,并返回响应状态。调用可能时延。

Page 8: 第三章 驱动  MFC  应用程序的引擎: 消息映射

参数传递:

函数调用:以实参形式传递给被调用函数,作为执行条件。

消息驱动:只在消息标识符对应的对象方法被调用时,参数

才被传递,传递方式与函数调用类似。

根据消息的接收对象、消息名(标识符)和附加参数的不

同, Win32 的消息可以分为四类:窗口消息、命令消息、控件

消息和用户界面更新消息。

Page 9: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.1.1 窗口消息相关操作:与窗口相关的操作,例如,创建窗口、绘制窗口、移动窗口、改变窗口尺寸、鼠标在窗口区域内的各种操作等。 CWnd 类或其派生类对象都能够响应处理这类消息。

形式: WM_XXX ,其中 WM 为窗口消息类型前缀, XXX 表示具体的窗口消息种类。例如:WM_CREATE

The WM_CREATE message is sent when an application requests that

a window be created by calling the CreateWindowEx or CreateWindow

function. The window procedure of the new window receives this

message after the window is created, but before the window becomes

visible. The message is sent before the CreateWindowEx or

CreateWindow function returns.

Page 10: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_PAINT

An application sends the WM_PAINT message when the system or

another application makes a request to paint a portion of an

application's window. The message is sent when the UpdateWindow

or RedrawWindow function is called, or by the DispatchMessage

function when the application obtains a WM_PAINT message by using

the GetMessage or PeekMessage function.

WM_LBUTTONDOWN

The WM_LBUTTONDOWN message is posted when the user presses

the left mouse button while the cursor is in the client area of a window.

If the mouse is not captured, the message is posted to the window

beneath the cursor. Otherwise, the message is posted to the window

that has captured the mouse.

Page 11: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_CLOSE

The WM_CLOSE message is sent as a signal that a window or an

application should terminate.

所有的窗口消息都可以在 MSDN 中查询到。主要参数:

hWnd : 接收消息的窗口句柄message : WM_XXX

wParam : 随 WM_XXX 而变lParam : 随 WM_XXX 而变

产生途径:· 窗口范围内的交互操作产生;·系统框架产生的窗口消息;· 程序根据需要发送窗口消息。

Page 12: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.1.2 命令消息相关操作:处理用户的某个请求或者执行用户的某个命令。

CCmdTarget 类或其派生类的对象都能够处理这类消息。形式: WM_COMMAND

主要参数:hWnd : 忽略message : WM_COMMAND

wParam : 低 16 位为命令 ID 高 16 位为 0

lParam : 0L

由于 wParam 的高、低 16 位表示不同的含义,需分解 wPara

m

来判断命令消息,也需要组装一个 wParam 用于生成命令消息。高、低字的分解和组合宏可以帮助完成这些工作。

Page 13: 第三章 驱动  MFC  应用程序的引擎: 消息映射

参数分解:

·HIGHWORD(wParam) 分解获取高 16 位,如果为 0 表示该

消息为命令消息。

·LOWWORD(wParam) 分解获取低 16 位,指示发出该命令

消息的资源 ID 。例如:菜单命令消息的菜单项资源 ID ;

工具栏按钮消息的按钮资源 ID 等。

参数组合:

使用宏 MAKEWPARAM 可以将两个 16 位字组合成一个 32

位字,例如生成一条打开文件的命令消息:打开文件的菜单项

资源 ID 为 ID_FILE_OPEN ,该命令消息为:

(WM_COMMAND, MAKEWPARAM(ID_FILE_OPEN, 0), 0L)

Page 14: 第三章 驱动  MFC  应用程序的引擎: 消息映射

产生途径:

· 选择菜单项;

· 单击工具栏按钮;

· 按加速键;

· 程序根据需要发送的命令消息。

Page 15: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.1.3 命令的用户界面更新消息相关操作:程序根据运行状态,在适当的时候更新某些命令的

用户界面(例如 菜单、工具栏等)的状态(例如,使能或禁用等)。当 CCmdTarget 派生类对象接收到这类消息后,则通过 CCmdUI 类对象来修改相应命令的用户界面状态。

形式: WM_COMMAND

主要参数:hWnd : 忽略message : WM_COMMAND

wParam : 低 16 位为命令 ID 高 16 位为 0

lParam : 0L

产生途径:· 下拉菜单列表;· 单击工具栏按钮;

Page 16: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.1.4 控件消息相关操作:此类消息与控件窗口中发生的某个事件相关,例

如:文本框控件窗口内的内容发生改变、列表框控件中某个

选项被选择、按钮控件被单击、滑尺控件的游标被移动等,

都会发出相应的控件事件消息。

形式:控件的变化是近年来软件开发中最为活跃的,随着控件

的种类不断增加,控件消息的类别也迅速增加,原来的控件

消息格式越来越不能满足控件消息的描述,因此,必须寻求

新的控件消息描述方法。由于历史的原因,控件消息的新旧

描述形式必须兼容。三种不同的控件消息形式、主要字段的

内容分别描述如下:

Page 17: 第三章 驱动  MFC  应用程序的引擎: 消息映射

1 第 1 种控件消息格式(仿窗口消息格式)

最早出现的控件消息形式,这种格式遵循窗口消息的格式

WM_XXX ,因此可视为窗口消息集的一部分。

主要参数:

hWnd : 接收消息的窗口句柄

message : WM_XXX

wParam : 随 WM_XXX 而变

lParam : 随 WM_XXX 而变

消息标识中的 XXX 表示不同控件消息,例如:

Page 18: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_HSCROLL

The WM_HSCROLL message is sent to a window when a scroll event

occurs in the window's standard horizontal scroll bar. This message is

also sent to the owner of a horizontal scroll bar control when a scroll

event occurs in the control.

WM_VSCROLL

The WM_VSCROLL message is sent to a window when a scroll event

occurs in the window's standard vertical scroll bar. This message is

also sent to the owner of a vertical scroll bar control when a scroll

event occurs in the control.

Page 19: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_PARENTNOTIFY

The WM_PARENTNOTIFY message is sent to the parent of a child

window when the child window is created or destroyed, or when the

user clicks a mouse button while the cursor is over the child window.

When the child window is being created, the system sends

WM_PARENTNOTIFY just before the CreateWindow or

CreateWindowEx function that creates the window returns. When the

child window is being destroyed, the system sends the message before

any processing to destroy the window takes place.

Page 20: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_CTLCOLOR

Windows controls frequently send notification messages to their parent

windows. For instance, many controls send a control color notification

message (WM_CTLCOLOR or one of its variants) to their parent to

allow the parent to supply a brush for painting the background of the

control.

WM_DRAWITEM

The WM_DRAWITEM message is sent to the owner window of an

owner-drawn button, combo box, list box, or menu when a visual

aspect of the button, combo box, list box, or menu has changed.

Page 21: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_DELETEITEM

The WM_DELETEITEM message is sent to the owner of a list box or

combo box when the list box or combo box is destroyed or when items

are removed by the LB_DELETESTRING, LB_RESETCONTENT,

CB_DELETESTRING, or CB_RESETCONTENT message. The

system sends a WM_DELETEITEM message for each deleted item.

The system sends the WM_DELETEITEM message for any deleted list

box or combo box item with nonzero item data. WM_MEASUREITEM

The WM_MEASUREITEM message is sent to the owner window of an

owner-drawn button, combo box, list box, list view control, or menu

item when the control or menu is created.

Page 22: 第三章 驱动  MFC  应用程序的引擎: 消息映射

WM_CHARTOITEM

The WM_CHARTOITEM message is sent by a list box with the

LBS_WANTKEYBOARDINPUT style to its owner in response to a

WM_CHAR message.

Page 23: 第三章 驱动  MFC  应用程序的引擎: 消息映射

2 第 2 种控件消息格式(仿命令消息格式)

这是继仿窗口消息之后出现的控件消息描述格式,它遵循

了命令消息格式,与命令消息的区别在于 wParam 高 16 位

为消息通知码,而不为 0 ;并且 lParam 不为 0L ,而是控件

窗口句柄。

主要参数:

hWnd : 忽略

message : WM_COMMAND

wParam : 低 16 位为命令 ID ,高 16 位为消息通知码

lParam : 发出消息的控件的窗口句柄

Page 24: 第三章 驱动  MFC  应用程序的引擎: 消息映射

参数分解:

·HIGHWORD(wParam) 获取消息通知码 XN_XXX 。

其中前缀 XN 表示某类控件的通知消息。例如:

EN_CHANGE 表示文本框控件窗口内容变化;

BN_CLICKED 表示按钮控件被单击。

·LOWWORD(wParam) 获取控件的 ID。

参数组合:例如生成对话框 OK 按钮的单击事件消息:

首先获取 OK 按钮的窗口句柄:HWND hwnd = GetDlgItem(IDOK)->GetSafeHwnd();

发出的消息为:(WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED), hwnd)

Page 25: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3 第 3 种控件消息格式(控件通知消息格式) 这是一种在 Win32 中新增加的适用于所有控件的事件

通知 消息的描述格式。这种消息描述格式是通过增加一个消息 类别 WM_NOTIFY ,表示由控件发出的事件通知;附加参数 wParam 被设置为控件的 ID ,而参数 lParam 指向一个描述 通知消息的数据结构 NMHDR 变量。这种格式可以满足日益 复杂的控件消息的需要。这是最流行的,具有很强描述能 力的消息描述格式。 主要参数:

hWnd : 忽略message : WM_NOTIFY

wParam : 低 16 位发出消息的控件的 ID ,高 16 位为 0

lParam : 描述消息的结构变量地址

Page 26: 第三章 驱动  MFC  应用程序的引擎: 消息映射

其中: lParam 所指向的数据结构分两种情况:

· 对于一些通用的事件通知消息(消息名前缀: NM )和

工具提示控件的通知消息 TTN_SHOW 和 TTN_POP

等,

lParam 指向数据结构 NMHDR ,该数据结构定义如下: typedef struct tagNMHDR

{   

HWND hwndFrom; // 发出消息的控件窗口的句柄。 UINT idFrom; // 发出消息的控件 ID 。

UINT code; // 通知码,例如工具栏提示显示消 // 息的通知码应为 TTN_SHOW 。

} NMHDR;

所有新增的控件都需要使用的通知消息码包括:

Page 27: 第三章 驱动  MFC  应用程序的引擎: 消息映射

Notification code Sent because

NM_CLICK User clicked left mouse button in the control

NM_DBLCLK User double-clicked left mouse button in the control

NM_RCLICK User clicked right mouse button in the control

NM_RDBLCLK User double-clicked right mouse button in the control

NM_RETURN User pressed the ENTER key while control has input focus

NM_SETFOCUS Control has been given input focus

NM_KILLFOCUS Control has lost input focus

NM_OUTOFMEMORY Control could not complete an operation because there was not enough memory available

Page 28: 第三章 驱动  MFC  应用程序的引擎: 消息映射

· 对于大多数情况, lParam 可以指向一个比 NMHDR 更 大的扩展数据结构,以便通过该结构变量传递更多的 需要扩展的消息信息,这类数据结构的第一个成员必 须是 NMHDR 类型成员。例如,在列表视图控件中按 下键盘按键的通知消息被命名为 LVN_KEYDOWN ,该 消息的 lParam 指向名为 LV_KEYDOWN 的数据结构变 量,该结构定义如下: typedef struct tagLV_KEYDOWN

{   

NMHDR hdr;   // 该结构成员中的通知码成员// hdr.code = LVN_KEYDOWN

WORD wVKey;  // 虚拟的键盘按键码

UINT flags;  // 该值必须总为 0

} LV_KEYDOWN;

Page 29: 第三章 驱动  MFC  应用程序的引擎: 消息映射

产生途径:

·由控件的各类事件发出;

· 程序根据需要模拟控件事件发送通知消息。

Page 30: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.2 MFC 消息映射原理3.2.1 应用程序类的 Run() 函数 MFC 应用程序中对消息的处理分为两个阶段:· 第一阶段:应用程序类的成员函数 Run 从消息队列中获取消 息,并将消息发送到相应的目标类对象 。· 第二阶段:在 MFC 消息机制的导引下,在目标类对象中为所 有消息寻找响应消息的处理函数。这些能响应消息的目标类 可以是 CCmdTarget 的任何派生类,例如主窗口类、视图类、 文档类对象等。 应用程序类的 Run 是一个虚函数,但在一般情况下,如果没

有特殊要求,不需要在 CXXXApp 类中进行重新定义,因此实际上运行的是其基类 CWinApp 对象的 Run 函数版本,它的定义代码可在系统源文件 “ Appcore.cpp” 中查询到。

Page 31: 第三章 驱动  MFC  应用程序的引擎: 消息映射

// Main running routine until application exits

int CWinApp::Run()

{

if (m_pMainWnd == NULL && AfxOleGetUserCtrl())

{ // Not launched /Embedding or /Automation, but has no main window!

TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");

AfxPostQuitMessage(0);

}

return CWinThread::Run();

}

分析代码,不难看出:只要 m_pMainWnd 指向的主窗口对象

被成功创建,或用户不在应用程序的控制之下,则 CWinApp

基类 CWinThread 的 Run 函数版本就被调用。该函数版本的代码可

在系统源文件 “ Thrdcore.cpp” 中查询到。

Page 32: 第三章 驱动  MFC  应用程序的引擎: 消息映射

int CWinThread::Run()

{

ASSERT_VALID(this); // 判断线程是否合法 // for tracking the idle time state

BOOL bIdle = TRUE;

LONG lIdleCount = 0;

// acquire and dispatch messages until a WM_QUIT message is received.

for (;;) // 消息循环入口 { // phase1: check to see if we can do idle work (空闲处理循环) while (bIdle &&

!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))

{ // call OnIdle while in bIdle state

if (!OnIdle(lIdleCount++))

bIdle = FALSE; // assume "no idle" state

}

Page 33: 第三章 驱动  MFC  应用程序的引擎: 消息映射

// phase2: pump messages while available (各类消息的获取和分发)

do

{ // pump message, but quit on WM_QUIT

if (!PumpMessage()) return ExitInstance(); // 进程结束退出

// reset "no idle" state after pumping "normal" message

if (IsIdleMessage(&m_msgCur))

{

bIdle = TRUE;

lIdleCount = 0;

}

} while ( ::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE) );

}

ASSERT(FALSE); // not reachable

}

Page 34: 第三章 驱动  MFC  应用程序的引擎: 消息映射

完成消息的获取和分发操作的核心功能函数是 CWinThread

成员函数 PumpMessage ,它是一个虚函数,因此在 CWinThrea

d

的派生类中可以重新定义该函数,但由于它是底层功能函数,

在没有必须的特殊底层操作,一般无须在派生类中重新定义,

其源代码可以在系统源文件 “ Thrdcore.cpp” 中查询到,如下所示:

Page 35: 第三章 驱动  MFC  应用程序的引擎: 消息映射

BOOL CWinThread::PumpMessage()

{

ASSERT_VALID(this);

if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))

{

#ifdef _DEBUG

if (afxTraceFlags & traceAppMsg)

TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n");

m_nDisablePumpCount++; // application must die

// Note: prevents calling message loop things in 'ExitInstance'

// will never be decremented

#endif

return FALSE; // 接收到退出消息 WM_QUIT

}

Page 36: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#ifdef _DEBUG

if (m_nDisablePumpCount != 0)

{

TRACE0( "Error: CWinThread::PumpMessage called when not permitted.\n" );

ASSERT(FALSE);

}

#endif

#ifdef _DEBUG

if (afxTraceFlags & traceAppMsg)

_AfxTraceMsg(_T("PumpMessage"), &m_msgCur);

#endif

Page 37: 第三章 驱动  MFC  应用程序的引擎: 消息映射

// process this message

if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMe

ssage(&m_msgCur))

{

::TranslateMessage(&m_msgCur); // 消息预处理::DispatchMessage(&m_msgCur); // 消息分发

}

return TRUE;

}

CWinThread::Run 中实现消息获取和分发的另一个辅助操作是CWinThread 的成员函数 IsIdleMessage 完成的,它的功能是检测不会引起“空闲”操作的消息,常用于检测鼠标在原位置发出

的鼠标移动事件消息。该函数是一个虚函数,但一般无须在派生类中重定义,其代码可在系统源文件 “ Thrdcore.cpp” 中查询到。

Page 38: 第三章 驱动  MFC  应用程序的引擎: 消息映射

BOOL CWinThread::IsIdleMessage(MSG* pMsg)

{

if ( pMsg->message == WM_MOUSEMOVE ||

pMsg->message == WM_NCMOUSEMOVE)

{ // mouse move at same position as last mouse move?

if ( m_ptCursorLast == pMsg->pt && pMsg->message == m_nMsgLast )

return FALSE;

m_ptCursorLast = pMsg->pt; // remember for next time

m_nMsgLast = pMsg->message;

return TRUE;

}

// WM_PAINT and WM_SYSTIMER (caret blink)

return pMsg->message != WM_PAINT && pMsg->message != 0x0118;

}

Page 39: 第三章 驱动  MFC  应用程序的引擎: 消息映射

执行进程结束退出的成员函数 ExitInstance 是结束 Run 函数执

行导致跳出消息循环的唯一操作。该函数也是一个虚函数,但

一般情况下,也无须在派生类中重定义 ExitInstance ,其代码可

在系统源文件 “ Thrdcore.cpp” 中查询到。int CWinThread::ExitInstance()

{

ASSERT_VALID(this);

ASSERT(AfxGetApp() != this);

int nResult = m_msgCur.wParam;

// returns the value from PostQuitMessage

return nResult;

}

归纳上述分析, CWinThread::Run 的运行流程如下图所示:

Page 40: 第三章 驱动  MFC  应用程序的引擎: 消息映射

开始

结束

将虚键码翻译成字符消息并发送到消息队列中 ::TranslateMessage(&m_msgCur);

发送消息到窗口函数 ::DispatchMessage(&m_msgCur);

消息队列出现新的消息?

while(::PeekMessage(&m_msgCur,NULL, NULL, NULL, PM_NOREMOVE));

WM_QUIT?

需要空闲处理? 初始化空闲状态 bIdle=TRUE; lIdleCount=0;

YES

YES

if (!OnIdle(lIdleCount++)) bIdle = FALSE;

连续空闲处理?YES

YES

for(;;)循环接收消息入口

消息队列为空? while (bIdle && !::PeekMessage(&m_msgCur, NULL,NULL,NULL, PM_NOREMOVE))

NO

NO

return ExitInstance();退出进程

NO

NOYES

NO

PumpMessage():消息处理

消息预翻译处理 PreTranslateMessage(&m_msgCur)

从消息队列获取消息 GetMessage(&m_msgCur,NULL,NULL,NULL)

Page 41: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.2.2 SendMessage 和 PostMessage 函数 把消息发给各类窗口对象的方法除了在 Run 函数中调用

的::DispatchMessage 外,还可调用 SendMessage 和 PostMessage

函数在程序的任何位置向指定窗口对象发送一条确定消息。1 SendMessage 和 PostMessage 函数的区别⑴ SendMessage 用于向指定窗口发送确定消息,导致接收该 消息的窗口函数被调用,并等待窗口函数处理完该消息,

返 回一个操作结果标志后,进程继续。⑵ PostMessage 用于向指定窗口寄送确定消息,就是把消息发 送到进程的消息队列中,并返回消息队列接收消息的标志, 而不等待消息的处理结果。进程通过消息循环 Run 函数获取

发送到消息队列中的消息,并把它发送到指定窗口进行相应

的处理。

Page 42: 第三章 驱动  MFC  应用程序的引擎: 消息映射

归纳发送消息和寄送消息的区别如下:

发送消息 寄送消息

消息直接交给窗口函数,不存在通讯延时。 将消息放在队列中,通讯可能被延时。

消息交给窗口函数并等待处理后才返回。 只把消息放进消息队列便返回。

反馈信息表示消息处理是否成功。 反馈信息表示消息放入消息队列是否成功。

调用 Win32 API 函数 SendMessage 的形式为:int ::SendMessage(hwnd,message,wParam,lParam);

调用 Win32 API 函数 PostMessage 的形式为:int ::PostMessage(hwnd,message,wParam,lParam);

用于除鼠标和键盘消息以外的其他消息以及用

户自定义消息。 用于鼠标和键盘消息以及用户自定义消息,注

意必须考虑通讯延时。

Page 43: 第三章 驱动  MFC  应用程序的引擎: 消息映射

2 在 MFC 应用程序中如何发送和寄送消息

⑴ 首先要获取接收消息的目标窗口对象(例如 wnd )或指向窗

口对象的指针(例如 pWnd );

⑵ 使用所获取的窗口对象或指向窗口对象的指针调用 CWnd 类

的成员函数 SendMessage 或 PostMessage 。例如:int res = wnd.SendMessage( message, wParam, lParam );

int res = wnd.PostMessage( message, wParam, lParam );

或int res = pWnd->SendMessage( message, wParam, lParam );

int res = pWnd->PostMessage( message, wParam, lParam );

Page 44: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.2.3 如何检索消息队列中的消息 需要从消息队列中检索消息并处理时,可调用 PeekMessa

ge

和 GetMessage 函数,这两个函数的原型分别如下:BOOL PeekMessage( LPMSG lpMsg, // pointer to structure for message

HWND hWnd, // handle to window

UINT wMsgFilterMin, // first message

UINT wMsgFilterMax, // last message

UINT wRemoveMsg // removal flags

);

Page 45: 第三章 驱动  MFC  应用程序的引擎: 消息映射

功能:

·窥视和查询一下消息队列,如果消息队列中有指定范围

[wMsgFilterMin, wMsgFilterMax] 内的消息,则获取一个消息保存

在 lpMsg 指向的消息结构变量中。

· 被获取的消息是否从消息队列中删除,取决于删除标志

wRemoveMsg ( = PM_REMOVE ,删除; = PM_NOREMOVE ,

删除)。

· 如果消息队列有可获取的消息,则返回非 0 ,否则返回 0 ,即

该函数的调用不阻塞当前线程运行。

Page 46: 第三章 驱动  MFC  应用程序的引擎: 消息映射

BOOL GetMessage( LPMSG lpMsg, // address of structure with message

HWND hWnd, // handle of window

UINT wMsgFilterMin, // first message

UINT wMsgFilterMax // last message

);

功能:

· 如果消息队列中有指定范围 [wMsgFilterMin, wMsgFilterMax] 内的

消息,则获取一个消息保存在 lpMsg 指向的消息结构变量中

并从队列中删除该消息后返回。

· 如果当前队列中无可获取的消息,则一直等待消息到达,该

函数所在的线程的运行被阻塞,直到新消息到达。

Page 47: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.2.4 消息映射表 消息被分发到目标对象后,如何与对应的功能匹配呢?⑴ 在 Win32 API 应用程序中,这种匹配是通过接收到消息的

窗口 函数中的 switch-case 结构实现的。⑵ 在 MFC 框架应用程序中,这种匹配的实现方法 ① 既不是 switch-case 结构,因为不符合面向对象的设计。 ② 也不是虚函数,因为消息数量非常多,并且还会随着操作

平台的发展而增多。如果将每个消息响应函数定义为虚函数,则会使每个能够处理消息的类对象都需要一个结构庞大的虚函数表 vtab 。这对于同一类的多个对象是冗余的,况且一个特定的类并不需要响应所有的消息。显然从程序的空间复杂度上考虑使用虚函数技术是不合理的。

Page 48: 第三章 驱动  MFC  应用程序的引擎: 消息映射

③ 而是消息映射,即在每个能接收和处理消息的类中,定义

一个静态对照表 —— 消息映射表,该表中只需要包含所属

类需要处理的消息和对应的消息处理函数的映射信息。进

程只要搜索该表就可以完成:

·判断能否响应所收到的消息;

· 如果能响应,立即找到相应的消息处理函数。

由于消息映射表是通过一对宏增加到能够处理消息的类中

的,这意味着能够根据需要,非常方便地确定所有能处理

消息的类是否处理消息和处理哪些消息。

以一个 Hello 应用程序的主窗口类 CMainFrame 为例,说明

如何在类中增加消息映射表:

Page 49: 第三章 驱动  MFC  应用程序的引擎: 消息映射

在类定义文件中增加消息映射定义宏:class CMainFrame : public CFrameWnd

{

DECLARE_MESSAGE_MAP()

};

在类实现文件中增加消息映射实现宏:…

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

WM_PAINT

ON_COMMAND(ID_MY_ITEM, OnMyItem)

END_MESSAGE_MAP()

Page 50: 第三章 驱动  MFC  应用程序的引擎: 消息映射

1 代码宏 DECLARE_MESSAGE_MAP 定义了是什麽

#define DECLARE_MESSAGE_MAP()\

private:\

static const AFX_MSGMAP_ENTRY _messageEntries[ ];\

protected:\

static const AFX_MSGMAP messageMap;\

static const AFX_MSGMAP* PASCAL _GetBaseMessageMap();\

virtual const AFX_MSGMAP* GetMessageMap() const;\

其中数据类型 AFX_MSGMAP 描述一个消息映射表,其定义:

Page 51: 第三章 驱动  MFC  应用程序的引擎: 消息映射

struct AFX_MSGMAP

{

const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();

// 能返回该类基类的映射表指针的成员函数

const AFX_MSGMAP_ENTRY* lpEntries; // 消息条目表入口

};

该结构中成员类型 AFX_MSGMAP_ENTRY 是描述映射表中的

一个条目的数据结构,其定义:

Page 52: 第三章 驱动  MFC  应用程序的引擎: 消息映射

struct AFX_MSGMAP_ENTRY {

UINT nMessage; // windows message

UINT nCode; // control code or WM_NOTIFY code

UINT nID; // control ID (or 0 for windows messages)

UINT nLastID; // used for entries specifying a range of control id’s

UINT nSig; // signature type (action) or pointer to message #

AFX_PMSG pfn; // routine to call (or special value)

};

Page 53: 第三章 驱动  MFC  应用程序的引擎: 消息映射

可见代码宏 DECLARE_MESSAGE_MAP() 在类定义中添加了:

· 定义消息映射条目表 ( 数组 ) _messageEntries[]

· 定义消息映射 messageMap

·声明获取基类消息映射的成员函数 _GetBaseMessageMap()

·声明获取消息映射的虚成员函数 GetMessageMap()

例如 Hello 程序的 CMainFrame 类定义中添加了该宏定义:class CMainFrame : public CFrameWnd

{

DECLARE_MESSAGE_MAP()

};

经过预编译后, DECLARE_MESSAGE_MAP() 被替换为:

Page 54: 第三章 驱动  MFC  应用程序的引擎: 消息映射

class CMainFrame : public CFrameWnd

{

private:\

static const AFX_MSGMAP_ENTRY _messageEntries[ ];\

protected:\

static const AFX_MSGMAP messageMap;\

static const AFX_MSGMAP* PASCAL _GetBaseMessageMa

p();\

virtual const AFX_MSGMAP* GetMessageMap() const;\

};

Page 55: 第三章 驱动  MFC  应用程序的引擎: 消息映射

2 揭开 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 宏的秘密

MFC AppWizard 会在添加了 DECLARE_MESSAGE_MAP 的类的

实现代码中添加这对宏实现消息映射表。这对宏定义如下:

#define BEGIN_MESSAGE_MAP( theClass, baseClass ) \

const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \

{ return &baseClass::messageMap; } \

const AFX_MSGMAP* theClass::GetMessageMap() const \

{ return &theClass::messagMap; } \

const AFX_MSGMAP theClass::messageMap = \

{&theClass::GetBaseMessageMap, &theClass::_messageEntries[0]};\

const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \

{ \ // 消息条目表初始化开始

Page 56: 第三章 驱动  MFC  应用程序的引擎: 消息映射

从上述定义不难看出:

· 在 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间,可

以按照 AFX_MSGMAP_ENTRY 初始化格式要求,加入该类

需要响应的消息映射条目。

·包含这对宏的类实现文件在预编译时将被上述代码替换,

为类添加了一个实现消息响应的消息映射结构。

#define END_MESSAGE_MAP() \

{ 0, 0, 0, 0, AfxSig_end, ( AFX_PMSG )0 } \ // 结束进程消息的条目

}; \ // 消息映射表结束

Page 57: 第三章 驱动  MFC  应用程序的引擎: 消息映射

宏 DECLARE_MESSAGE_MAP 以及 BEGIN_MESSAGE_MAP

和 END_MESSAGE_MAP 可以在具有消息响应能力的 MFC 类的

派 生层次之间建立起如下的消息映射关联,例如:

消息映射表中每一条映射条目描述了从一个消息到响应函数

被调用的全部信息。 MFC 为所有消息类型和对应的响应函数

类型之间的映射条目定义了宏代码(在 “ Afxmsg_.h” 中)。

CCmdTarget CWnd

massegeMapGetBaseMessageMap()massegeEntries

………0,0,0,0,AfxSig_end,(AFX_PMSG)0

CFrameWnd

massegeMapGetBaseMessageMap()massegeEntries

………0,0,0,0,AfxSig_end,(AFX_PMSG)0

CMainFrame

massegeMapGetBaseMessageMap()massegeEntries

………0,0,0,0,AfxSig_end,(AFX_PMSG)0

Page 58: 第三章 驱动  MFC  应用程序的引擎: 消息映射

例如:

对应命令消息的映射项定义宏:

#define ON_COMMAND( id, memberFxn ) \

{ WM_COMMAND, CN_COMMAND, ( WORD)id, ( WORD)id, AfxSig_vv, \

( AFX_PMSG )&memberFxn }

对应通用控件消息的映射项定义宏:

#define ON_CONTROL( wNotifyCode, id, memberFxn ) \

{ WM_COMMAND, ( WORD)wNotifyCode, ( WORD )id, ( WORD )id, \

AfxSig_vv, ( AFX_PMSG )&memberFxn }

Page 59: 第三章 驱动  MFC  应用程序的引擎: 消息映射

对应窗口消息 WM_CREATE 的映射项定义宏:

#define ON_WM_CREATE() \

{ WM_CREATE, 0, 0, 0, AfxSig_is, \

( AFX_PMSG )( AFX_PMSGW )( int ( AFX_MSG_CALL CWnd::*)

( LPCREATESTRUCT ) ) &OnCreate }

Page 60: 第三章 驱动  MFC  应用程序的引擎: 消息映射

本例 CMainFrame 的实现文件经预编译后,这对宏被替换为:

const AFX_MSGMAP* PASCAL CMainFrame::_GetBaseMessageMap()

{ return &CFrameWnd::messageMap; }

const AFX_MSGMAP* CMainFrame::GetMessageMap() const

{ return & CMainFrame::messagMap; }

const AFX_MSGMAP CMainFrame::messageMap =

{

&CMainFrame::GetBaseMessageMap,

& CMainFrame::_messageEntries[0]

};

Page 61: 第三章 驱动  MFC  应用程序的引擎: 消息映射

const AFX_MSGMAP_ENTRY CMainFrame::_messageEntries[] =

{

WM_PAINT, 0, 0, 0, AfxSig_vv, ( AFX_PMSG)(AFX_PMSGW )

( void ( AFX_MSG_CALL CWnd::* )( void ) ) &OnPaint

WM_COMMAND,CN_COMMAND, ( WORD ) ID_MY_ITEM,

( WORD ) ID_MY_ITEM, AfxSig_vv, ( AFX_PMSG ) &OnMyItem

0, 0, 0, 0, AfxSig_end, ( AFX_PMSG )0

};

Page 62: 第三章 驱动  MFC  应用程序的引擎: 消息映射

归纳所有消息所对应的映射项定义宏,可以进行以下的分类:

⑴ 命令消息和仿命令通知消息(单一响应)

例如:#define ON_COMMAND( id, memberFxn )

{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,

(AFX_PMSG)&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD wNotifyCode 命令或控件 id 命令或控件 id signature type memberFxn

Page 63: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#define ON_COMMAND_EX( id, memberFxn )

{ WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_bw,

(AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)(UINT))

&memberFxn }

#define ON_CONTROL( wNotifyCode, id, memberFxn )

{ WM_COMMAND, (WORD)wNotifyCode, (WORD)id, (WORD)id,

AfxSig_vv,(AFX_PMSG)&memberFxn }

Page 64: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑵ 命令消息和仿命令通知消息(指定范围响应)

例如:

#define ON_COMMAND_RANGE(id, idLast, memberFxn)

{ WM_COMMAND, CN_COMMAND,

(WORD)id, (WORD)idLast, AfxSig_vw,

(AFX_PMSG)(void(AFX_MSG_CALL CmdTarget::*)(UINT))

&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD wNotifyCode 命令或控件 id命令或控件 idLas

tsignature type memberFxn

Page 65: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#define ON_COMMAND_EX_RANGE(id, idLast, memberFxn)

{ WM_COMMAND, CN_COMMAND,

(WORD)id, (WORD)idLast, AfxSig_bw,

(AFX_PMSG)(BOOL(AFX_MSG_CALL CmdTarget::*)(UINT))

&memberFxn }

#define ON_CONTROL_RANGE(wNotifyCode, id, idLast, memberFxn)

{ WM_COMMAND, (WORD)wNotifyCode,

(WORD)id, (WORD)idLast, AfxSig_vw,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)(UINT))

&memberFxn }

注意, id 至 idLast 必须是连续值。

Page 66: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑶ 更新(单一)用户界面命令

例如:

#define ON_UPDATE_COMMAND_UI(id, memberFxn)

{ WM_COMMAND, CN_UPDATE_COMMAND_UI,

(WORD)id, (WORD)id, AfxSig_cmdui,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)(CCmdUI*))

&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMADCN_UPDATE_

COMMAND_UI命令或控件 id 命令或控件 id signature type memberFxn

Page 67: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑷ 更新(指定范围)用户界面命令

例如:

#define ON_UPDATE_COMMAND_UI_RANGE(id, idLast, memberFxn)

{ WM_COMMAND,CN_UPDATE_COMMAND_UI,

(WORD) id, (WORD) idLast, AfxSig_cmdui,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)(CCmdUI*))

&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD

CN_UPDATE_

COMMAND_UI_

RANGE

命令或控件 id命令或控件 idLas

tsignature type memberFxn

Page 68: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑸ (单一) WM_NOTIFY 通知消息

例如:

#define ON_NOTIFY( wNotifyCode, id, memberFxn )

{ WM_NOTIFY, (WORD)(int) wNotifyCode,

(WORD) id, (WORD) id, AfxSig_vNMHDRpl,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)

(NMHDR*, LRESULT*)) &memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_NOTIFY wNotifyCode 控件 id 控件 id signature type memberFxn

Page 69: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#define ON_NOTIFY_EX(wNotifyCode, id, memberFxn)

{ WM_NOTIFY, (WORD)(int) wNotifyCode,

(WORD) id, (WORD) id, AfxSig_bwNMHDRpl,

(AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)

(UINT, NMHDR*, LRESULT*)) &memberFxn }

Page 70: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑹ (指定范围) WM_NOTIFY 通知消息

例如:

#define ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn)

{ WM_NOTIFY,(WORD)(int) wNotifyCode,

(WORD) id,(WORD) idLast, AfxSig_vwNMHDRpl,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)

(UINT, NMHDR*, LRESULT*)) &memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_NOTIFY wNotifyCode 控件 id 控件 idLast signature type memberFxn

Page 71: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#define ON_NOTIFY_EX_RANGE( wNotifyCode, id, idLast, memberFxn)

{ WM_NOTIFY,(WORD)(int) wNotifyCode,

(WORD) id,(WORD) idLast, AfxSig_bwNMHDRpl,

(AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)

(UINT, NMHDR*, LRESULT*)) &memberFxn }

Page 72: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑺ 通用控件(单一)消息

例如:

#define ON_CONTROL( wNotifyCode, id, memberFxn)

{ WM_COMMAND, (WORD) wNotifyCode,

(WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD wNotifyCode 控件 id 控件 id signature type memberFxn

Page 73: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑻ 通用控件(指定范围)消息

例如:

#define ON_CONTROL_RANGE( wNotifyCode, id, idLast, memberFxn)

{ WM_COMMAND, (WORD) wNotifyCode,

(WORD) id, (WORD) idLast, AfxSig_vw,

(AFX_PMSG)(void (AFX_MSG_CALL CmdTarget::*)(UINT))

&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD wNotifyCode 控件 id 控件 idLast signature type memberFxn

Page 74: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑼ 控件仿命令通知反射消息

例如:

#define ON_CONTROL_REFLECT( wNotifyCode, memberFxn)

{ WM_COMMAND+WM_REFLECT_BASE, (WORD) wNotifyCode, 0, 0,

AfxSig_vv, (AFX_PMSG)&memberFxn }

#define ON_CONTROL_REFLECT_EX( wNotifyCode, memberFxn)

{ WM_COMMAND+WM_REFLECT_BASE,

(WORD) wNotifyCode, 0, 0, AfxSig_bv,

(AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)())&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD+

WM_REFLECT_BASEwNotifyCode 0 0 signature type memberFxn

Page 75: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑽ 控件通知反射消息

例如:

#define ON_NOTIFY_REFLECT( wNotifyCode, memberFxn)

{ WM_NOTIFY+WM_REFLECT_BASE, (WORD)(int) wNotifyCode,

0, 0, AfxSig_vNMHDRpl,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)

(NMHDR*, LRESULT*)) &memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_NOTIFY+

WM_REFLECT_BASEwNotifyCode 0 0 signature type memberFxn

Page 76: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#define ON_NOTIFY_REFLECT_EX( wNotifyCode, memberFxn)

{ WM_NOTIFY+WM_REFLECT_BASE, (WORD)(int) wNotifyCode,

0, 0,AfxSig_bNMHDRpl,

(AFX_PMSG)(BOOL (AFX_MSG_CALL CCmdTarget::*)

(NMHDR*, LRESULT*)) &memberFxn }

Page 77: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑾ 更新(单一)反射消息用户界面命令

例如:

#define ON_UPDATE_COMMAND_UI_REFLECT( memberFxn)

{ WM_COMMAND+WM_REFLECT_BASE,

(WORD)CN_UPDATE_COMMAND_UI, 0,0, AfxSig_cmdui,

(AFX_PMSG)(void (AFX_MSG_CALL CCmdTarget::*)(CCmdUI*))

&memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_COMMAD+

WM_REFLECT_BASE

CN_UPDATE_

COMMAND_UI0 0 signature type memberFxn

Page 78: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑿ 窗口消息常量

例如:

#define ON_WM_CREATE()

{ WM_CREATE, 0, 0, 0, AfxSig_is,

(AFX_PMSG)(AFX_PMSGW)(int (AFX_MSG_CALL CWnd::*)

(LPCREATESTRUCT)) &OnCreate }

nMessage nCode nID nLastID nSig AFX_PMSG

WM_XXX 0 0 0 signature type对应的窗口消息

函数

Page 79: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#define ON_WM_PAINT()

{ WM_PAINT, 0, 0, 0, AfxSig_vv,

(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))

&OnPaint }

Page 80: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⒀ 用于窗口的用户扩展消息

例如:

#define ON_MESSAGE( message, memberFxn)

{ message, 0, 0, 0, AfxSig_lwl,

(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)

(WPARAM, LPARAM)) &memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

message 0 0 0 signature type memberFxn

Page 81: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⒁ 用于注册窗口的用户扩展消息

例如:

#define ON_REGISTERED_MESSAGE( nMessageVariable, memberFxn)

{ 0xC000, 0, 0, 0,

(UINT)(UINT*)(&nMessageVariable), /*implied 'AfxSig_lwl'*/

(AFX_PMSG)(AFX_PMSGW)(LRESULT (AFX_MSG_CALL CWnd::*)

(WPARAM, LPARAM)) &memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

0xC000 0 0 0 nMessageVarable memberFxn

Page 82: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⒂ 用于线程的用户扩展消息

例如:

#define ON_THREAD_MESSAGE( message, memberFxn)

{ message, 0, 0, 0, AfxSig_vwl,

(AFX_PMSG)(AFX_PMSGT)(void (AFX_MSG_CALL CWinThread::*)

(WPARAM, LPARAM)) &memberFxn }

nMessage nCode nID nLastID nSig AFX_PMSG

message 0 0 0 signature type memberFxn

Page 83: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⒃ 用于注册线程的用户扩展消息

例如:

#define ON_REGISTERED_THREAD_MESSAGE( nMessageVariable,

memberFxn)

{ 0xC000, 0, 0, 0,

(UINT)(UINT*)(&nMessageVariable), /*implied 'AfxSig_vwl'*/

(AFX_PMSG)(AFX_PMSGT)(void (AFX_MSG_CALL CWinThread::*)

(WPARAM, LPARAM)) &memberFxn },

nMessage nCode nID nLastID nSig AFX_PMSG

0xC000 0 0 0 nMessageVarable memberFxn

Page 84: 第三章 驱动  MFC  应用程序的引擎: 消息映射

注意,消息映射表中的最后一条映射条目必须是:

0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 ,即窗口消息常量 WM_QUIT 的

映射条目(这一工作是由宏 END_MESSAGE_MAP() 完成的)。

映射条目的 nSig 域内容指定了消息响应函数的签名类型(函数

类型、参数个数和类型),说明消息响应函数指针的类型。

MFC 使用枚举 AfxSig 定义了所有签名类型:enum AfxSig { AfxSig_end = 0, // [marks end of message map]

AfxSig_vv, // void(void)

AfxSig_vw, // void(UINT)

AfxSig_vwW, // void(UINT, CWnd*)

… };

详细信息查询 Afxmsg.h 中 AfxSig 的定义内容。

Page 85: 第三章 驱动  MFC  应用程序的引擎: 消息映射

消息映射表中,消息与所对应的响应函数指针是通过映射条目

成对出现的。在执行被搜索到的消息函数之前,必须根据映射

条目中 AfxSig 的不同值,将响应函数指针的类型强制转换为相

应的函数签名类型。在 MFC 应用程序框架中,是通过一个 un

ion

类型变量 MessageMapFunctions 为所有的函数签名类型提供这种

强制转换(见系统头文件 “ Afximpl.h” ):

Page 86: 第三章 驱动  MFC  应用程序的引擎: 消息映射

union MessageMapFunctions

{

AFX_PMSG pfn; // generic member function pointer

// specific type safe variants for WM_COMMAND and WM_NOTIFY messages

void (AFX_MSG_CALL CCmdTarget::*pfn_COMMAND)();

BOOL (AFX_MSG_CALL CCmdTarget::*pfn_bCOMMAND)();

void (AFX_MSG_CALL CWnd::*pfn_vv)(void);

void (AFX_MSG_CALL CWnd::*pfn_vw)(UINT);

void (AFX_MSG_CALL CWnd::*pfn_vbWW)(BOOL, CWnd*, CWnd*);

int (AFX_MSG_CALL CWnd::*pfn_is)(LPTSTR);

LRESULT (AFX_MSG_CALL CWnd::*pfn_lwl)(WPARAM, LPARAM);

};

查询 “ Afximpl.h” 中 MessageMapFunctions 的定义内容可以获得

所有消息响应函数的签名类型信息。

Page 87: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3 如何使用消息映射表

以本例中 CMainFrame 的消息映射表为例,对于要响应的窗口

消息 WM_PAINT ,对应映射表中的第一个映射条目。通过消

息映射表实现该消息的响应的操作过程可以用下面的简化代

码描述:

union MessageMapFunctions mmf; // 创建一个消息 - 函数指针映射

AFX_MESSAGE_ENTRY *lpEntry; // 指向映射表项的指针

lpEntry = &CMainFrame::_messageEntries[0]; // 获取第一表项

mmf.pfn = lpEntry->pfn; // 获取第一表项的函数指针

Page 88: 第三章 驱动  MFC  应用程序的引擎: 消息映射

// 判断该条目的 AfxSig值,以便调用相应的处理函数 switch(lpEntry->nSig)

{

case AfxSig_is:

(this->*mmf.pfn_is)((LPTSTR)lparam); // 转换为相应类型执行。 bre

ak;

case AfxSig_vv:

(this->*mmf.pfn_vv)(); // 转换为相应类型执行。 br

eak;

case AfxSig_lwl:

(this->*mmf.pfn_lwl)(wParam, lParam); // 转换为相应类型执行。 break;

}

Page 89: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.2.4 使用 MFC 应用程序框架寻找消息处理函数 SendMessage(…) AfxWndProc(…)

PostMessage(…)

消息队列

CXXXApp::Run()

GetMessage(…)DisPatchMessage(…)

AfxCallWndProc(…)

窗口类 ::WindowProc(…)

窗口类 :: OnWndMsg(…)

窗口类 ::DefWndProc(…)

窗口类 ::OnCommand(…) 窗口类 ::OnNotify(…) 搜索窗口类及其基类消息映射表

窗口类 ::OnCmdMsg(…)

其他类 ::OnCmdMsg(…)

Page 90: 第三章 驱动  MFC  应用程序的引擎: 消息映射

其中各个函数的原型及其作用如下: ⑴ LRESULT CALLBACK

AfxWndProc( HWND hwnd, UINT nmsg, WPARAM wParam,

LPARAM lParam);

根据 hwnd 寻找目标窗口类对象,然后调用 AfxCallWndPr

oc 。 ⑵ LRESULT AFXAPI

AfxCallWndProc( CWnd* pWnd, HWND hwnd, UINT nmsg,

WPARAM wParam, LPARAM lParam);

存储消息标志符和参数,然后调用目标窗口对象的虚函数 WindowProc 。

⑶ virtual LRESULT

CWnd::WindowProc(UINT message, WPARAM wParam,

LPARAM lParam );

将消息发送给虚函数 OnWndMsg ,并接收其反馈。如果消息

不能被响应,则调用 DefWindowProc 进行缺省处理。

Page 91: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑷ virtual BOOL CWnd::OnWndMsg( UINT message, WPARAM wParam,

LPARAM lParam, LRESULT* pResult );

· 将消息分为窗口消息、命令消息和控件消息三类。 · 对于窗口消息调用对应不同窗口消息的响应函数,并接收 这些调用的反馈。 ·调用 OnCommand 处理命令消息,并接收其反馈。 ·调用 OnNotify 处理控件消息,并接收其反馈。 · 向 WindowProc 发回的反馈通知其对消息的处理情况。

⑸ virtual BOOL CWnd::OnCommand( WPARAM wParam, LPARAM lParam );

· 如果 lParam 不为 0 ,则按控件消息通知控件处理该消息, 并接收其反馈。 · 如果 lParam 为 0 ,则调用 OnCmdMsg ,并接收其反馈。

· 向 OnWndMsg 发回的反馈通知其对消息的处理情况。

Page 92: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑹ virtual BOOL CWnd::OnNotify( WPARAM wParam, LPARAM lParam, LRESULT* pResult );

· 通知控件处理该消息,并接收其反馈。 · 如果控件不能处理该消息,则调用 OnCmdMsg 。 · 向 OnWndMsg 发回的反馈通知其对消息的处理情况。

⑺ virtual BOOL

CCmdTarget::OnCmdMsg( UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo );

· 按已规定好的路径搜索相应类的消息映射表,以便找到相 应的消息处理函数并执行。 · 如果找不到,则将相应的界面元素变灰。 · 向 OnCommand 或 OnNotify 发回的反馈通知其对消息的处

理 情况。

⑻ virtual LRESULT

DefWindowProc( UINT message, WPARAM wParam, LPARAM lParam );

Page 93: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.3 MFC 程序处理消息的路径3.3.1 如何处理窗口消息 virtual BOOL OnWndMsg( UINT message, WPARAM wParam,

LPARAM lParam, LRESULT* pResult );

搜索窗口类的消息映射表

找到消息处理函数?

继续搜索基类的消息映射表

窗口类 ::DefWindowProc(…)

执行消息处理函数

NO

YES

找到消息处理函数

所有基类均没有定义该消息的处理函数

是窗口消息?

窗口类 ::OnWndMsg(…)

命令消息或者控件消息处理NO

YES

Page 94: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.3.2 如何处理命令消息 由于命令消息反映了用户的请求,其包含的信息最多,也

最为复杂,加之能够响应这类消息的类包括了从 CCmdTarget 派

生的所有类,所以响应这类消息是按照一个特定顺序进行的。1 搜索处理命令消息的重要函数 OnCmdMsg

virtual BOOL

CCmdTarget::OnCmdMsg( UINT nID,

int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerI

nfo );

参数说明: ·nID 命令消息 ID 。 ·nCode 消息的通知码。例如,标准命令消息的通知码为

CN_COMMAND 。

Page 95: 第三章 驱动  MFC  应用程序的引擎: 消息映射

·pExtra 指向消息附加信息,根据 nCode 的不同而不同:① 对于菜单和工具栏界面更新命令消息,为 CCmdUI 的 派生类指针;② 对于 WM_NOTIFY 类型消息,指向 NMHDR 结构变量或 包含 NMHDR 的结构变量;

③ 其他消息时,为 0 。 ·pHandlerInfo 其类型 AFX_CMDHANDLERINFO 定义如下:

struct AFX_CMDHANDLERINFO {

CCmdTarget *pTarget; // 包含消息处理函数的对象void (AFX_MSG_CALL CCmdTarget::*pmf)(void); //

消息处理函数} ;

典型情况下,例如,当被 OnCommand(…) 和 OnNotify

(…) 调用时,该参数为 NULL 。

返回值: 找到一个消息响应函数,则返回 TRUE ;否则返回 FALS

E 。

Page 96: 第三章 驱动  MFC  应用程序的引擎: 消息映射

2 MFC 应用程序框架处理命令消息的路径追踪

如果消息为 WM_COMMAND ,主框架窗口的 OnWndMsg 将调用

虚函数 OnCommand 。如果应用程序的主框架窗口类没有重载

此虚函数,则 CFrameWnd::OnCommand 将被调用。该函数的

定义代码如下:(见系统源文件 “ WinFrm.cpp” ) BOOL CFrameWnd::OnCommand(WPARAM wParam,LPARAM lParam)

// return TRUE if command invocation was attempted

{

HWND hWndCtrl = (HWND)lParam; // 获取控件窗口句柄

UINT nID = LOWORD(wParam); // 获取命令消息 ID 值

CFrameWnd* pFrameWnd=GetTopLevelFrame(); // 获取顶层框架窗口

ASSERT_VALID(pFrameWnd);

Page 97: 第三章 驱动  MFC  应用程序的引擎: 消息映射

if (pFrameWnd->m_bHelpMode && hWndCtrl == NULL &&

nID != ID_HELP && nID != ID_DEFAULT_HELP &&

nID != ID_CONTEXT_HELP)

{

// route as help

if ( !SendMessage(WM_COMMANDHELP, 0, HID_BASE_COMMAND+nID) )

SendMessage(WM_COMMAND, ID_DEFAULT_HELP);

return TRUE;

}

// route as normal command

return CWnd::OnCommand(wParam, lParam);

}

Page 98: 第三章 驱动  MFC  应用程序的引擎: 消息映射

BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)

{

return OnCmdMsg(nID, nCode, 0, 0);

}

指向 CMainFrame 类对象的指针调用 OnCmdMsg 时,通过动态

联编, CMainFrame 的 OnCmdMsg 被调用,如果 CMainFra

me 类

中 OnCmdMsg 没有重定义,则 CFrameWnd::OnCmdMsg 被调

用。该函数的定义代码如下:

Page 99: 第三章 驱动  MFC  应用程序的引擎: 消息映射

BOOL CFrameWnd::OnCmdMsg( UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandle

rInfo)

{

CPushRoutingFrame push(this); // pump through current view FIRST

CView* pView = GetActiveView();

if (pView != NULL &&

pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE; // then pump through frame

if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE; // last but not least, pump through app

CWinApp* pApp = AfxGetApp();

if (pApp != NULL &&

pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;

return FALSE;

}

Page 100: 第三章 驱动  MFC  应用程序的引擎: 消息映射

· 如果在视图类对象的消息映射表中找到了消息响应函数,则返回 TRUE ,结束消息搜索操作。

· 如果在视图类对象的消息映射表中未找到了消息响应函数,并且文档类对象存在,则调用 CDocument::OnCmdMsg

继续消息搜索操作。(见系统源文件“ DocCore.cpp” ) BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo)

{ // first pump through pane

if (CWnd::OnCmdMsg(nID,nCode,pExtra,pHandlerInfo)) return TRUE;

// then pump through document

if (m_pDocument != NULL)

{ // special state for saving view before routing to document

CPushRoutingView push(this);

return m_pDocument->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo);

}

return FALSE;

}

Page 101: 第三章 驱动  MFC  应用程序的引擎: 消息映射

CDocument::OnCmdMsg 的定义代码如下:(见系统源文件

“DocCore.cpp” ) BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandlerInfo)

{

if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE; // otherwise check template

if (m_pDocTemplate != NULL &&

m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))

return TRUE;

return FALSE;

}

Page 102: 第三章 驱动  MFC  应用程序的引擎: 消息映射

· 如果在文档类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。

· 如果在文档类对象的消息映射表中未找到了响应函数,并且文档模板类对象存在,则调用文档模板类的 OnCmdMsg

继续消息响应搜索。 · 如果在文档模板类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。

· 如果在文档模板类对象的消息映射表中未找到了响应函数,则结束第一阶段的搜索进入第二阶段的搜索:从程序框架窗口类对象的消息映射表搜索,调用 CWnd::OnCmdMs

g

继续消息响应搜索。 · 如果在框架窗口类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。

Page 103: 第三章 驱动  MFC  应用程序的引擎: 消息映射

· 如果在框架窗口类对象的消息映射表中未找到了响应函数,则结束第二阶段的搜索进入第三阶段的搜索:从应用程序类对象的消息映射表搜索,调用 CWinApp::OnCmdMsg

继续消息响应搜索。 · 如果在应用程序类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。

· 如果在应用程序类对象的消息映射表中未找到了响应函数,则结束第三阶段的搜索,返回 FALSE 指示命令消息无响应。

注意,在 CWnd 类和 CWinApp 类中都未对 OnCmdMsg

进行重新 定义,可见上述所有的搜索都要归结到调用 CCmdTarget

类的 OnCmdMsg ,其定义代码如下:(见 “ CmdTarg.cpp” )

Page 104: 第三章 驱动  MFC  应用程序的引擎: 消息映射

BOOL CCmdTarget::OnCmdMsg (UINT nID, int nCode, void* pExtra,

AFX_CMDHANDLERINFO* pHandle

rInfo)

{ …

// determine the message number and code (packed into nCode)

const AFX_MSGMAP* pMessageMap;

const AFX_MSGMAP_ENTRY* lpEntry;

UINT nMsg = 0;

for (pMessageMap = GetMessageMap(); pMessageMap != NULL;

pMessageMap = (*pMessageMap->pfnGetBaseMap)())

{ …

// Note: catches BEGIN_MESSAGE_MAP(CMyClass, CMyClass)!

lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries,

nMsg, nCode, nID);

Page 105: 第三章 驱动  MFC  应用程序的引擎: 消息映射

if (lpEntry != NULL)

{

return _AfxDispatchCmdMsg (this, nID, nCode, lpEntry->pfn,

pExtra, lpEntry->nSig, pHandlerInfo);

}

}

return FALSE; // not handled

}

其中 AfxFindMessageEntry 的代码如下:(见 “ Wincore.cp

p” ) const AFX_MSGMAP_ENTRY* AFXAPI

AfxFindMessageEntry( const AFX_MSGMAP_ENTRY*

lpEntry,UINT nMsg, UINT nCode, UINT nID )

{

#if defined(_M_IX86) && !defined(_AFX_PORTABLE)

// 32-bit Intel 386/486 version.

Page 106: 第三章 驱动  MFC  应用程序的引擎: 消息映射

#else // _AFX_PORTABLE

// C version of search routine

while (lpEntry->nSig != AfxSig_end)

{

if (lpEntry->nMessage == nMsg && lpEntry->nCode == nCode &&

nID >= lpEntry->nID && nID <= lpEntry->nLastID)

{ return lpEntry; }

lpEntry++;

}

return NULL; // not found

#endif // _AFX_PORTABLE

}

Page 107: 第三章 驱动  MFC  应用程序的引擎: 消息映射

处理消息的函数 AfxDispatchCmdMsg 的定义代码如下:(见系

统源文件 “ CmdTarg.cpp” ) AFX_STATIC BOOL AFXAPI

_AfxDispatchCmdMsg (CCmdTarget* pTarget, UINT nID, int nCode,

AFX_PMSG pfn, void* pExtra, UINT

nSig, AFX_CMDHANDLERINFO* pHandler

Info ) // return TRUE to stop routing

{

ASSERT_VALID(pTarget);

UNUSED(nCode); // unused in release builds

union MessageMapFunctions mmf;

mmf.pfn = pfn;

BOOL bResult = TRUE; // default is ok

Page 108: 第三章 驱动  MFC  应用程序的引擎: 消息映射

if (pHandlerInfo != NULL)

{

// just fill in the information, don't do it

pHandlerInfo->pTarget = pTarget;

pHandlerInfo->pmf = mmf.pfn;

return TRUE;

}

Page 109: 第三章 驱动  MFC  应用程序的引擎: 消息映射

switch (nSig)

{

case AfxSig_vv: // normal command or control notification

ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED

ASSERT(pExtra == NULL);

(pTarget->*mmf.pfn_COMMAND)();

break;

case AfxSig_bv: // normal command or control notification

ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED

ASSERT(pExtra == NULL);

bResult = (pTarget->*mmf.pfn_bCOMMAND)();

break;

Page 110: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_vw: // normal command or control notification in a range

ASSERT(CN_COMMAND == 0); // CN_COMMAND same as BN_CLICKED

ASSERT(pExtra == NULL);

(pTarget->*mmf.pfn_COMMAND_RANGE)(nID);

break;

case AfxSig_bw: // extended command (passed ID, returns bContinue)

ASSERT(pExtra == NULL);

bResult = (pTarget->*mmf.pfn_COMMAND_EX)(nID);

break;

Page 111: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_vNMHDRpl:

{

AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;

ASSERT(pNotify != NULL);

ASSERT(pNotify->pResult != NULL);

ASSERT(pNotify->pNMHDR != NULL);

(pTarget->*mmf.pfn_NOTIFY)

(pNotify->pNMHDR,pNotify->pResult);

break;

}

Page 112: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_bNMHDRpl:

{

AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;

ASSERT(pNotify != NULL);

ASSERT(pNotify->pResult != NULL);

ASSERT(pNotify->pNMHDR != NULL);

bResult = (pTarget->*mmf.pfn_bNOTIFY)

(pNotify->pNMHDR,pNotify->pResult);

break;

}

Page 113: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_vwNMHDRpl:

{

AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;

ASSERT(pNotify != NULL);

ASSERT(pNotify->pResult != NULL);

ASSERT(pNotify->pNMHDR != NULL);

(pTarget->*mmf.pfn_NOTIFY_RANGE)

(nID, pNotify->pNMHDR,pNotify->pResult);

break;

}

Page 114: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_bwNMHDRpl:

{

AFX_NOTIFY* pNotify = (AFX_NOTIFY*)pExtra;

ASSERT(pNotify != NULL);

ASSERT(pNotify->pResult != NULL);

ASSERT(pNotify->pNMHDR != NULL);

bResult = (pTarget->*mmf.pfn_NOTIFY_EX)(nID,

pNotify->pNMHDR,pNotify->pResult);

}

break;

Page 115: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_cmdui:

{ // ON_UPDATE_COMMAND_UI or ON_UPDATE_COMMAND_UI_REFLECT case

ASSERT(CN_UPDATE_COMMAND_UI == (UINT)-1);

ASSERT(nCode == CN_UPDATE_COMMAND_UI ||

nCode == 0xFFFF);

ASSERT(pExtra != NULL);

CCmdUI* pCmdUI = (CCmdUI*)pExtra;

ASSERT(!pCmdUI->m_bContinueRouting);

// idle - not set

(pTarget->*mmf.pfn_UPDATE_COMMAND_UI)(pCmdUI);

bResult = !pCmdUI->m_bContinueRouting;

pCmdUI->m_bContinueRouting = FALSE; // go back to idle

}

break;

Page 116: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_cmduiw:

{ // ON_UPDATE_COMMAND_UI case

ASSERT(nCode == CN_UPDATE_COMMAND_UI);

ASSERT(pExtra != NULL);

CCmdUI* pCmdUI = (CCmdUI*)pExtra;

ASSERT(pCmdUI->m_nID == nID); // sanity assert

ASSERT(!pCmdUI->m_bContinueRouting); // idle - not set

(pTarget->*mmf.pfn_UPDATE_COMMAND_UI_RANGE)

(pCmdUI, nID);

bResult = !pCmdUI->m_bContinueRouting;

pCmdUI->m_bContinueRouting = FALSE; // go back to idle

}

break; // general extensibility hooks

Page 117: 第三章 驱动  MFC  应用程序的引擎: 消息映射

case AfxSig_vpv:

(pTarget->*mmf.pfn_OTHER)(pExtra);

break;

case AfxSig_bpv:

bResult = (pTarget->*mmf.pfn_OTHER_EX)(pExtra);

break;

default: // illegal

ASSERT(FALSE);

return 0;

}

return bResult;

}

3 MFC 应用程序框架中命令消息的响应传递顺序 视图类对象→文档类对象→文档模板类对象→框架窗口类对

象→应用程序类对象

Page 118: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.3.3 如何处理控件消息 virtual BOOL CWnd::OnNotify( WPARAM wParam, LPARAM lParam,

LRESULT* pResult );

OnWndMsg(…)

OnNotify(…)

控件窗口类能处理该消息?

控件的父窗口类 ::OnCmdMsg(…)

控件窗口类处理消息YES

NO

Page 119: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.4 自定义消息处理 通过前面的章节的学习,我们已经清楚地知道 Windows

系统为各种操作驱动的需要定义了各类窗口消息、命令消息、通知消息、控件消息等,并为每个消息分配了一个唯一的无符号整数作为消息的标识值。同时系统还保留了足够的标识值空间为用户根据需要定义自己的消息提供了可能。在应用程序中可以使用 CWnd 类的成员函数 SendMessage 和 PostMessage 或同

名 API

函数向各种窗口类对象发送或寄送自定义消息,还可以使用线程类 CWinThread 的成员函数 PostThreadMessage 或同名 API

函数向各种线程或进程类对象发送或寄送自定义消息。 为了区分系统定义消息和用户自定义消息以及为将来的扩展

保留充分的余地,系统将所有可以使用的标识值进行了明确的划分:

Page 120: 第三章 驱动  MFC  应用程序的引擎: 消息映射

其中:#define WM_USER 0x00400

#define WM_APP 0x08000

Range Meaning

0 through WM_USER – 1 Messages reserved for use by the system.

WM_USER through 0x7FFFInteger messages for use by private window classes.

WM_APP through 0xBFFF Messages available for use by applications.

0xC000 through 0xFFFF String messages for use by applications.

Greater than 0xFFFF Reserved by the system for future use.

Page 121: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.4.1 自定义静态窗口消息1 在需要使用该自定义窗口消息的类实现文件中手工添加自定

义窗口消息。例如: #define WM_MYSAYHELLO WM_USER + 100

或 const WM_MYSAYHELLO = WM_USER + 100;

Page 122: 第三章 驱动  MFC  应用程序的引擎: 消息映射

2 在相应的类定义中手工添加该消息的响应函数声明。例如: class CMyView : public CView

{

protected:

afx_msg LRESULT OnSayHello(WPARM, LPARM);

DECLARE MESSAGE_MAP

};

注意,该函数的返回类型、参数类型和个数,并必须添加前

缀 afx_msg 。

Page 123: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3 在相应类的消息映射表中手工添加该消息映射条目。例如: BEGIN_MESSAGE_MAP(CMyView, CView)

//{{AFX_MSG_MAP(CMyView)

//}}AFX_MSG_MAP

ON_MESSAGE( WM_MYSAYHELLO, OnSayHello )

END_MESSAGE_MAP()

Page 124: 第三章 驱动  MFC  应用程序的引擎: 消息映射

4 在相应类的实现文件中手工添加消息响应函数定义。例如: LRESULT CMyView::OnMySayHello(WPARAM wp, LPARAM lp)

{

AfxMessageBox(“Hello I am in CMyView.”);

return 0L;

}

在获得了接收消息的窗口类对象指针后,可通过如下代码向

窗口类对象发送消息: pView->SendMessage( WM_MYSAYHELLO, 0L, 0L );

或寄送消息:

pView->PostMessage( WM_MYSAYHELLO, 0L, 0L );

Page 125: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.4.2 自定义动态窗口消息1 在需要使用该自定义窗口消息的类实现文件中添加自定义消

息名(标识符)。例如: #define MESSAGE_NAME “2005-9-21-IS-A-MESSAGE-TEST”

或 const MESSAGE_NAME = “2005-9-21-IS-A-MESSAGE-TEST”;

然后注册该用户消息:

UINT WM_MYSAYHELLO

= ::RegisterWndMessage(MESSAGE_NAME);

Page 126: 第三章 驱动  MFC  应用程序的引擎: 消息映射

2 在相应的类定义中手工添加该消息的响应函数声明。例如: class CMyView : public CView {

protected:

afx_msg LRESULT OnSayHello(WPARM, LPARM);

DECLARE MESSAGE_MAP

};

注意,该函数的返回类型、参数类型和个数,并必须添加前

缀 afx_msg 。

Page 127: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3 在相应类的消息映射表中手工添加该消息映射条目。例如: BEGIN_MESSAGE_MAP(CMyView, CView)

//{{AFX_MSG_MAP(CMyView)

//}}AFX_MSG_MAP

ON_MESSAGE( WM_MYSAYHELLO, OnSayHello )

END_MESSAGE_MAP()

Page 128: 第三章 驱动  MFC  应用程序的引擎: 消息映射

4 在相应类的实现文件中手工添加消息响应函数定义。例如: LRESULT CMyView::OnMySayHello(WPARAM wp, LPARAM lp)

{

AfxMessageBox(“Hello I am in CMyView.”);

return 0L;

}

在获得了接收消息的窗口类对象指针后,可通过如下代码向

窗口类对象发送消息: pView->SendMessage( WM_MYSAYHELLO, 0L, 0L );

或寄送消息:

pView->PostMessage( WM_MYSAYHELLO, 0L, 0L );

Page 129: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.5 实例 1 :自定义类如何响应命令消息 在缺省情况下,应用程序中的命令消息只能在应用程序框架

规定的途径中顺序传递,而自定义类对象是不包括在内的。而在应用程序中定义自定义类是不可避免的,如何使这些自定义类对象能够加入到命令消息的传递途径中,使自定义类对象能够响应由程序框架发来的命令消息是非常必要的。本例的主要目的:如何定义一个能处理消息的自定义类,如何将自定义类加入到命令消息传递途径中。 本例的基本功能是通过一个对话框界面实现:· 对个人信息编辑和管理(保存信息,装入已保存的信息);· 个人信息的保存和管理由一个自定义类 CPerson 实现;·保存信息操作是通过对话框中的按钮控件发出命令消息,由 CPerson 类对象接收后,由相应的成员函数完成。

Page 130: 第三章 驱动  MFC  应用程序的引擎: 消息映射

1 创建一个名为 “ MSG1MSG1” 的对话框( Dialog based )类型的应用程

序项目。2 修改对话框( IDD_MSG1_DIALOG )资源:⑴ 删除原有的静态文本框;⑵ 加入下列控件:

3 定义 CPerson 类 ⑴ 使用 ClassWizard ,以 CCmdTarget 为基类。⑵ 将构造函数和析构函数的访问权限修改为公有。

控件类型 名称 属性 ID

静态控件静态控件编辑控件编辑控件按钮

Name

Age

默认默认保存

默认默认默认默认默认

IDC_STATIC

IDC_STATIC

IDC_EDIT_NAME

IDC_EDIT_AGE

IDC_SAVE

Page 131: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑶ 添加公有数据成员,用于存放个人信息“姓名” 和 “年龄”

CString m_szName;

UINT m_nAge;

⑷ 重载 Serialize 虚函数,实现个人信息数据的保存和装载。⑸ 增加一个公有成员函数 UINT CPerson::Load() ,用于从磁盘装

入数据。⑹ 为控件 IDC_SAVE 发出的消息增加命令消息映射代码: · 在定义文件中加入处理函数声明:

afx_msg void OnSave();

· 在实现文件中的消息映射表中加入映射项: ON_

COMMAND(IDC_SAVE, OnSave)

· 在实现文件中实现 OnSave() 的定义。 注意,以上工作可以通过 ClassWizard 或手工完成添加。

Page 132: 第三章 驱动  MFC  应用程序的引擎: 消息映射

4 为框架窗口类 CMSG1Dlg 增加和修改成员:

⑴ 增加三个数据成员

CPerson m_person; // 定义 CPerson 类对象,用于与主框架的联系。

UINT m_nAge; // 文本编辑框 IDC_NAME 的数据缓冲

CString m_szName; // 文本编辑框 IDC_AGE 的数据缓冲

⑵ 在成员函数 OnInitDialog() 中加入调用 CPerson::Load ,以便将

磁盘文件中已保存的个人信息数据读入控件显示。

⑶ 重载虚函数 OnCmdMsg 使 CPerson 类对象能够从命令消息的

传递途径中截获需要响应的消息。

⑸ 添加 OK 按钮的响应函数,在函数中加入向 CMSG1Dlg 发送

保存个人信息数据的消息,以便在结束对话框之前保存个人

信息数据。

Page 133: 第三章 驱动  MFC  应用程序的引擎: 消息映射

⑷ 如果想为 IDC_SAVE 命令增加一个加速键,则需要重载虚函

数 PreTranslateMessage ,增加将一个确定的加速键组合转换

为 IDC_SAVE 命令。

5 编译运行。

小结:使自定义类能响应处理命令消息的编程关键

·自定义类必须是 CCmdTarget 类或其派生类的派生类;

· 在自定义类中定义消息响应函数和添加消息映射;

· 在自定义类对象的所属类中重载虚函数 OnCmdMsg 使自定义

类对象能接收到命令消息。

Page 134: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.6 实例 2 :自定义消息处理 本例的目的是如何借助自定义静态窗口消息的方法,为应

程序定义和响应自定义窗口消息。

1 创建一个名为 “ MSG2MSG2” 的对话框( Dialog based )类型的应用

程序项目。

2 修改对话框( IDD_MSG2_DIALOG )资源:

⑴ 删除原有的静态文本框和“确定”按钮;

⑵ 加入下列控件:控件类型 名称 属性 ID

静态控件编辑控件按钮

消息次数统计:默认发消息

默认默认默认

IDC_STATIC

IDC_PROMT

IDC_SENDMSG

Page 135: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3 消息及其处理函数定义⑴ 在 CMsg2Dlg 的实现文件中增加自定义消息: #define WM_COUNT_MSG WM_USER + 100

⑵ 在 CMsg2Dlg 的定义文件中增加自定义消息响应函数声明:

afx_msg LRESULT OnCountMsg(WPARAM, LPARAM);

⑶ 在 CMsg2Dlg 的实现文件的消息映射表中增加映射项: ON_MESSAGE(WM_COUNT_MSG, OnCountMsg)

⑷ 在 CMsg2Dlg 的实现文件中增加 OnCountMsg 的定义代码。⑸ 使用 ClassWizard 为按钮 ID_SENDMSG 发出的控件消息添

加映 射代码,并为添加的处理函数 OnSendmsg() 增加发送自

定义 消息的代码。4 编译运行。

Page 136: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3.7 实例 3 :使用菜单、工具栏和加速键 本例将介绍如何对 AppWizard 生成程序的菜单、工具栏和

加速键资源以及进行修改和添加,并为生成的命令定义命令响应函数。1 创建一个名为 “ HelloHello” 的 SDI 类型的应用程序项目。2 编辑菜单⑴ 在 Edit 和 View 子菜单之间加入下拉式子菜单 Test 。⑵ 在 Test 子菜单中加入下列菜单项: 菜单项名称 菜单项 ID 菜单项提示 (prompt)

&Say Hello

&Red

&Blue

&Yellow

ID_SAY_HELLO

ID_SELECT_RED

ID_SELECT_BLUE

ID_SELECT_YELLOW

Say hello to you!\nHello

Red is Selected.\nRed

Blue is Selected.\nBlue

Yellow is Selected.\nYellow

Page 137: 第三章 驱动  MFC  应用程序的引擎: 消息映射

3 修改工具栏

⑴ 删除缺省工具栏中除 help 按钮以外的所有按钮。

⑵ 增加与菜单项 ID_SELECT_RED 、 ID_SELECT_BLUE 和

ID_SELECT_YELLOW 相关的工具栏按钮,并添加相应提示。

修改后的工具栏如下:

4 修改 CHelloView::OnDraw ,加入如下代码:pDC->TextOut(0, 0, “Hello World!”);

Page 138: 第三章 驱动  MFC  应用程序的引擎: 消息映射

5 在 CMainFrame 类中为新增菜单项定义命令处理函数

⑴ 为 CMainFrame 增加数据成员和枚举: int m_nColor;

enum{RED = 0, BLUE = 1, YELLOW = 2};

用于标识由菜单或工具栏按钮选择的颜色。

⑵ 修改 CMainFrame 的构造函数,加入 m_nColor = RED;

指定选定的缺省颜色为红,即标识值 = RED 。

⑶ 使用 ClassWizard 为各个新增菜单项添加命令消息响应函数

OnSayHello 、 OnSelectRed 、 OnSelectBlue 和 OnSelectYellow 的声

明、定义体和映射项。

⑷ 在各个响应函数定义体内加入具体的功能代码。

Page 139: 第三章 驱动  MFC  应用程序的引擎: 消息映射

6 增加更新命令用户界面( UI )消息

菜单用户的界面的更新是在选择菜单时,由系统框架发出的

WM_INITMENUPOPUP 消息引起对菜单界面的更新操作完成

的。这一操作发生在菜单项弹出之前,对顶层主菜单不起作

用。工具栏用户界面的更新操作发生在空闲处理中。

⑴ 用 ClassWizard 为菜单项 ID_SELECT_RED 、 ID_SELECT_BLUE

和 ID_SELECT_YELLOW 添加用户界面更新命令消息响应函数

OnUpdateSelectRed 、 OnUpdateSelectBlue 和 OnUpdateSelectYellow

的声明、定义体和映射项。

⑵ 在各个处理函数定义体内加入具体的功能代码。

Page 140: 第三章 驱动  MFC  应用程序的引擎: 消息映射

7 为菜单项 ID_SAY_HELLO 定义加速键 CTRL+H 。

8 编译运行。