Upload
lucia
View
267
Download
0
Embed Size (px)
DESCRIPTION
第三章 驱动 MFC 应用程序的引擎: 消息映射. 众所周知, Windows 应用程序的运行是 消息驱动 。了解和掌握 API 应用程序的运行控制 —— 消息循环 接收、分发消息, 窗口 函数 处理消息,直至 退出 消息结束 消息循环 导致进程结束的 运 行机制 是理解 消息驱动 机制最直观的方法;同时如何设计 窗口 函数 实现消息的处理也是把握 API 应用程序设计、编程的关键 基础之一。 使用 MFC 编写的面向对象 Windows 应用程序的运行控制在本 - PowerPoint PPT Presentation
Citation preview
第三章 驱动 MFC 应用程序的引擎:消息映射
众所周知, Windows 应用程序的运行是消息驱动。了解和掌握
API 应用程序的运行控制 —— 消息循环接收、分发消息,窗口
函数处理消息,直至退出消息结束消息循环导致进程结束的运
行机制是理解消息驱动机制最直观的方法;同时如何设计窗口
函数实现消息的处理也是把握 API 应用程序设计、编程的关键
基础之一。
使用 MFC 编写的面向对象 Windows 应用程序的运行控制在本
质上与 API 应用程序是完全一致的。因此,掌握消息驱动机制的实现方法,如何实现消息的响应处理,从而控制进程、实现
程序设计需求也是在 VC 集成环境中使用 MFC 进行面向对象的
Windows 程序设计、编程的关键基础之一。
在 MFC 应用程序中看不到 API 应用程序中实现消息驱动控制
的窗口函数 WndProc 和函数中用于处理消息的 switch-case 程序
结构,而是通过另一种方法实现的。这种方法是在每个需要响
应和处理消息的类(这些类必须是 CCmdTarget 类的直接或间接
从派生类)添加用于处理消息的成员函数,并在这些成员函数
和被处理的消息之间添加映射表从而实现消息驱动机制的。
这种消息映射的方法更符合面向对象程序设计。该方法是如
何实现将消息映射到处理函数的呢?我们将通过对消息的类别
和结构、消息映射的工作原理、处理消息的路径等内容的学习
来解决这一问题。
3.1 消息类别及其描述消息:应用程序中所发生事件的通知,即事件的信息化表示。
事件:用户对交互界面进行操作(例如操作鼠标、键盘、改变
窗口、选择菜单项等);或者程序的某种特定运行(窗
口的创建显示、窗口客户区中被显示数据修改引起的重
绘操作)都会引起事件的产生。
基于事件触发而运行的应用程序必须是完全面向消息处
理的,即始终按如下步骤循环:
等待消息 接收消息 分析消息产生操作
完成操作返回
消息的格式:typedef struct tagMSG
{
HWND hWnd; // 消息的目标窗口句柄UINT message; // 消息的标识符WPARAM wParam; // 32 位消息附加参数LPARAM lParam; // 32 位消息附加参数DWORD time; // 消息发至队列的时间POINT pt; // 消息发出时的鼠标位置
} MSG;
其中前四个成员是描述消息的主要参数: ① hWnd :消息的目标窗口句柄,即消息的接收者。所谓句
柄是一个特殊整数,作为 Windows 各类资源的索引,是访 问资源的唯一依据。不同资源的句柄类型也不同:
窗口句柄 HWND, 进程实例句柄 HINSTANCE, 光标句柄 HCURSOR, 画笔句柄 HPEN, 画刷句柄 HBRUSH, 设备环境 句柄 HDC, 位图句柄 HBITMAP, 字体句柄 HFONT 等。
② message :消息的标识符,即消息的名称。 ③ wParam 和 lParam :消息的附加参数,即为消息的响应函
数传递参数。
消息队列:为管理和处理应用程序运行中所产生的驱动消息,Windows 平台提供了一个系统消息队列,用于存放分发给所有应用程序的消息; Windows 平台还为每一个进程建立一个程序消息队列,用于接收发到该进程的所有消息,以便分发到进程中的不同消息接收对象。
面向对象程序的消息驱动相当于面向过程程序的函数调用,
但在调用目标、调用结果和参数传递三个方面存在不同:
调用目标:
函数调用:被调用的函数,由函数名确定。调用目标唯一。
消息驱动:能接收消息的对象,由消息的窗口句柄和消息标
识符确定。调用目标不唯一。
调用结果:
函数调用:被调用函数执行,并返回结果。调用无时延。
消息驱动:根据接收消息对象的状态决定消息对应的方法是
否执行,并返回响应状态。调用可能时延。
参数传递:
函数调用:以实参形式传递给被调用函数,作为执行条件。
消息驱动:只在消息标识符对应的对象方法被调用时,参数
才被传递,传递方式与函数调用类似。
根据消息的接收对象、消息名(标识符)和附加参数的不
同, Win32 的消息可以分为四类:窗口消息、命令消息、控件
消息和用户界面更新消息。
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.
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.
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 而变
产生途径:· 窗口范围内的交互操作产生;·系统框架产生的窗口消息;· 程序根据需要发送窗口消息。
3.1.2 命令消息相关操作:处理用户的某个请求或者执行用户的某个命令。
CCmdTarget 类或其派生类的对象都能够处理这类消息。形式: WM_COMMAND
主要参数:hWnd : 忽略message : WM_COMMAND
wParam : 低 16 位为命令 ID 高 16 位为 0
lParam : 0L
由于 wParam 的高、低 16 位表示不同的含义,需分解 wPara
m
来判断命令消息,也需要组装一个 wParam 用于生成命令消息。高、低字的分解和组合宏可以帮助完成这些工作。
参数分解:
·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)
产生途径:
· 选择菜单项;
· 单击工具栏按钮;
· 按加速键;
· 程序根据需要发送的命令消息。
3.1.3 命令的用户界面更新消息相关操作:程序根据运行状态,在适当的时候更新某些命令的
用户界面(例如 菜单、工具栏等)的状态(例如,使能或禁用等)。当 CCmdTarget 派生类对象接收到这类消息后,则通过 CCmdUI 类对象来修改相应命令的用户界面状态。
形式: WM_COMMAND
主要参数:hWnd : 忽略message : WM_COMMAND
wParam : 低 16 位为命令 ID 高 16 位为 0
lParam : 0L
产生途径:· 下拉菜单列表;· 单击工具栏按钮;
3.1.4 控件消息相关操作:此类消息与控件窗口中发生的某个事件相关,例
如:文本框控件窗口内的内容发生改变、列表框控件中某个
选项被选择、按钮控件被单击、滑尺控件的游标被移动等,
都会发出相应的控件事件消息。
形式:控件的变化是近年来软件开发中最为活跃的,随着控件
的种类不断增加,控件消息的类别也迅速增加,原来的控件
消息格式越来越不能满足控件消息的描述,因此,必须寻求
新的控件消息描述方法。由于历史的原因,控件消息的新旧
描述形式必须兼容。三种不同的控件消息形式、主要字段的
内容分别描述如下:
1 第 1 种控件消息格式(仿窗口消息格式)
最早出现的控件消息形式,这种格式遵循窗口消息的格式
WM_XXX ,因此可视为窗口消息集的一部分。
主要参数:
hWnd : 接收消息的窗口句柄
message : WM_XXX
wParam : 随 WM_XXX 而变
lParam : 随 WM_XXX 而变
消息标识中的 XXX 表示不同控件消息,例如:
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.
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.
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.
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.
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.
2 第 2 种控件消息格式(仿命令消息格式)
这是继仿窗口消息之后出现的控件消息描述格式,它遵循
了命令消息格式,与命令消息的区别在于 wParam 高 16 位
为消息通知码,而不为 0 ;并且 lParam 不为 0L ,而是控件
窗口句柄。
主要参数:
hWnd : 忽略
message : WM_COMMAND
wParam : 低 16 位为命令 ID ,高 16 位为消息通知码
lParam : 发出消息的控件的窗口句柄
参数分解:
·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)
3 第 3 种控件消息格式(控件通知消息格式) 这是一种在 Win32 中新增加的适用于所有控件的事件
通知 消息的描述格式。这种消息描述格式是通过增加一个消息 类别 WM_NOTIFY ,表示由控件发出的事件通知;附加参数 wParam 被设置为控件的 ID ,而参数 lParam 指向一个描述 通知消息的数据结构 NMHDR 变量。这种格式可以满足日益 复杂的控件消息的需要。这是最流行的,具有很强描述能 力的消息描述格式。 主要参数:
hWnd : 忽略message : WM_NOTIFY
wParam : 低 16 位发出消息的控件的 ID ,高 16 位为 0
lParam : 描述消息的结构变量地址
其中: lParam 所指向的数据结构分两种情况:
· 对于一些通用的事件通知消息(消息名前缀: NM )和
工具提示控件的通知消息 TTN_SHOW 和 TTN_POP
等,
lParam 指向数据结构 NMHDR ,该数据结构定义如下: typedef struct tagNMHDR
{
HWND hwndFrom; // 发出消息的控件窗口的句柄。 UINT idFrom; // 发出消息的控件 ID 。
UINT code; // 通知码,例如工具栏提示显示消 // 息的通知码应为 TTN_SHOW 。
} NMHDR;
所有新增的控件都需要使用的通知消息码包括:
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
· 对于大多数情况, 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;
产生途径:
·由控件的各类事件发出;
· 程序根据需要模拟控件事件发送通知消息。
3.2 MFC 消息映射原理3.2.1 应用程序类的 Run() 函数 MFC 应用程序中对消息的处理分为两个阶段:· 第一阶段:应用程序类的成员函数 Run 从消息队列中获取消 息,并将消息发送到相应的目标类对象 。· 第二阶段:在 MFC 消息机制的导引下,在目标类对象中为所 有消息寻找响应消息的处理函数。这些能响应消息的目标类 可以是 CCmdTarget 的任何派生类,例如主窗口类、视图类、 文档类对象等。 应用程序类的 Run 是一个虚函数,但在一般情况下,如果没
有特殊要求,不需要在 CXXXApp 类中进行重新定义,因此实际上运行的是其基类 CWinApp 对象的 Run 函数版本,它的定义代码可在系统源文件 “ Appcore.cpp” 中查询到。
// 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” 中查询到。
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
}
// 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
}
完成消息的获取和分发操作的核心功能函数是 CWinThread
的
成员函数 PumpMessage ,它是一个虚函数,因此在 CWinThrea
d
的派生类中可以重新定义该函数,但由于它是底层功能函数,
在没有必须的特殊底层操作,一般无须在派生类中重新定义,
其源代码可以在系统源文件 “ Thrdcore.cpp” 中查询到,如下所示:
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
}
#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
// 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” 中查询到。
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;
}
执行进程结束退出的成员函数 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 的运行流程如下图所示:
开始
结束
将虚键码翻译成字符消息并发送到消息队列中 ::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)
3.2.2 SendMessage 和 PostMessage 函数 把消息发给各类窗口对象的方法除了在 Run 函数中调用
的::DispatchMessage 外,还可调用 SendMessage 和 PostMessage
函数在程序的任何位置向指定窗口对象发送一条确定消息。1 SendMessage 和 PostMessage 函数的区别⑴ SendMessage 用于向指定窗口发送确定消息,导致接收该 消息的窗口函数被调用,并等待窗口函数处理完该消息,
返 回一个操作结果标志后,进程继续。⑵ PostMessage 用于向指定窗口寄送确定消息,就是把消息发 送到进程的消息队列中,并返回消息队列接收消息的标志, 而不等待消息的处理结果。进程通过消息循环 Run 函数获取
发送到消息队列中的消息,并把它发送到指定窗口进行相应
的处理。
归纳发送消息和寄送消息的区别如下:
发送消息 寄送消息
消息直接交给窗口函数,不存在通讯延时。 将消息放在队列中,通讯可能被延时。
消息交给窗口函数并等待处理后才返回。 只把消息放进消息队列便返回。
反馈信息表示消息处理是否成功。 反馈信息表示消息放入消息队列是否成功。
调用 Win32 API 函数 SendMessage 的形式为:int ::SendMessage(hwnd,message,wParam,lParam);
调用 Win32 API 函数 PostMessage 的形式为:int ::PostMessage(hwnd,message,wParam,lParam);
用于除鼠标和键盘消息以外的其他消息以及用
户自定义消息。 用于鼠标和键盘消息以及用户自定义消息,注
意必须考虑通讯延时。
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 );
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
);
功能:
·窥视和查询一下消息队列,如果消息队列中有指定范围
[wMsgFilterMin, wMsgFilterMax] 内的消息,则获取一个消息保存
在 lpMsg 指向的消息结构变量中。
· 被获取的消息是否从消息队列中删除,取决于删除标志
wRemoveMsg ( = PM_REMOVE ,删除; = PM_NOREMOVE ,
不
删除)。
· 如果消息队列有可获取的消息,则返回非 0 ,否则返回 0 ,即
该函数的调用不阻塞当前线程运行。
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 指向的消息结构变量中
并从队列中删除该消息后返回。
· 如果当前队列中无可获取的消息,则一直等待消息到达,该
函数所在的线程的运行被阻塞,直到新消息到达。
3.2.4 消息映射表 消息被分发到目标对象后,如何与对应的功能匹配呢?⑴ 在 Win32 API 应用程序中,这种匹配是通过接收到消息的
窗口 函数中的 switch-case 结构实现的。⑵ 在 MFC 框架应用程序中,这种匹配的实现方法 ① 既不是 switch-case 结构,因为不符合面向对象的设计。 ② 也不是虚函数,因为消息数量非常多,并且还会随着操作
平台的发展而增多。如果将每个消息响应函数定义为虚函数,则会使每个能够处理消息的类对象都需要一个结构庞大的虚函数表 vtab 。这对于同一类的多个对象是冗余的,况且一个特定的类并不需要响应所有的消息。显然从程序的空间复杂度上考虑使用虚函数技术是不合理的。
③ 而是消息映射,即在每个能接收和处理消息的类中,定义
一个静态对照表 —— 消息映射表,该表中只需要包含所属
类需要处理的消息和对应的消息处理函数的映射信息。进
程只要搜索该表就可以完成:
·判断能否响应所收到的消息;
· 如果能响应,立即找到相应的消息处理函数。
由于消息映射表是通过一对宏增加到能够处理消息的类中
的,这意味着能够根据需要,非常方便地确定所有能处理
消息的类是否处理消息和处理哪些消息。
以一个 Hello 应用程序的主窗口类 CMainFrame 为例,说明
如何在类中增加消息映射表:
在类定义文件中增加消息映射定义宏:class CMainFrame : public CFrameWnd
{
…
DECLARE_MESSAGE_MAP()
};
在类实现文件中增加消息映射实现宏:…
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
WM_PAINT
ON_COMMAND(ID_MY_ITEM, OnMyItem)
END_MESSAGE_MAP()
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 描述一个消息映射表,其定义:
struct AFX_MSGMAP
{
const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
// 能返回该类基类的映射表指针的成员函数
const AFX_MSGMAP_ENTRY* lpEntries; // 消息条目表入口
};
该结构中成员类型 AFX_MSGMAP_ENTRY 是描述映射表中的
一个条目的数据结构,其定义:
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)
};
可见代码宏 DECLARE_MESSAGE_MAP() 在类定义中添加了:
· 定义消息映射条目表 ( 数组 ) _messageEntries[]
· 定义消息映射 messageMap
·声明获取基类消息映射的成员函数 _GetBaseMessageMap()
·声明获取消息映射的虚成员函数 GetMessageMap()
例如 Hello 程序的 CMainFrame 类定义中添加了该宏定义:class CMainFrame : public CFrameWnd
{
…
DECLARE_MESSAGE_MAP()
};
经过预编译后, DECLARE_MESSAGE_MAP() 被替换为:
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;\
};
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[] = \
{ \ // 消息条目表初始化开始
从上述定义不难看出:
· 在 BEGIN_MESSAGE_MAP 和 END_MESSAGE_MAP 之间,可
以按照 AFX_MSGMAP_ENTRY 初始化格式要求,加入该类
需要响应的消息映射条目。
·包含这对宏的类实现文件在预编译时将被上述代码替换,
为类添加了一个实现消息响应的消息映射结构。
#define END_MESSAGE_MAP() \
{ 0, 0, 0, 0, AfxSig_end, ( AFX_PMSG )0 } \ // 结束进程消息的条目
}; \ // 消息映射表结束
宏 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
例如:
对应命令消息的映射项定义宏:
#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 }
对应窗口消息 WM_CREATE 的映射项定义宏:
#define ON_WM_CREATE() \
{ WM_CREATE, 0, 0, 0, AfxSig_is, \
( AFX_PMSG )( AFX_PMSGW )( int ( AFX_MSG_CALL CWnd::*)
( LPCREATESTRUCT ) ) &OnCreate }
本例 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]
};
…
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
};
归纳所有消息所对应的映射项定义宏,可以进行以下的分类:
⑴ 命令消息和仿命令通知消息(单一响应)
例如:#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
#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 }
⑵ 命令消息和仿命令通知消息(指定范围响应)
例如:
#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
#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 必须是连续值。
⑶ 更新(单一)用户界面命令
例如:
#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
⑷ 更新(指定范围)用户界面命令
例如:
#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
⑸ (单一) 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
#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 }
⑹ (指定范围) 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
#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 }
⑺ 通用控件(单一)消息
例如:
#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
⑻ 通用控件(指定范围)消息
例如:
#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
⑼ 控件仿命令通知反射消息
例如:
#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
⑽ 控件通知反射消息
例如:
#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
#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 }
⑾ 更新(单一)反射消息用户界面命令
例如:
#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
⑿ 窗口消息常量
例如:
#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对应的窗口消息
函数
#define ON_WM_PAINT()
{ WM_PAINT, 0, 0, 0, AfxSig_vv,
(AFX_PMSG)(AFX_PMSGW)(void (AFX_MSG_CALL CWnd::*)(void))
&OnPaint }
⒀ 用于窗口的用户扩展消息
例如:
#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
⒁ 用于注册窗口的用户扩展消息
例如:
#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
⒂ 用于线程的用户扩展消息
例如:
#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
⒃ 用于注册线程的用户扩展消息
例如:
#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
注意,消息映射表中的最后一条映射条目必须是:
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 的定义内容。
消息映射表中,消息与所对应的响应函数指针是通过映射条目
成对出现的。在执行被搜索到的消息函数之前,必须根据映射
条目中 AfxSig 的不同值,将响应函数指针的类型强制转换为相
应的函数签名类型。在 MFC 应用程序框架中,是通过一个 un
ion
类型变量 MessageMapFunctions 为所有的函数签名类型提供这种
强制转换(见系统头文件 “ Afximpl.h” ):
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 的定义内容可以获得
所有消息响应函数的签名类型信息。
3 如何使用消息映射表
以本例中 CMainFrame 的消息映射表为例,对于要响应的窗口
消息 WM_PAINT ,对应映射表中的第一个映射条目。通过消
息映射表实现该消息的响应的操作过程可以用下面的简化代
码描述:
…
union MessageMapFunctions mmf; // 创建一个消息 - 函数指针映射
AFX_MESSAGE_ENTRY *lpEntry; // 指向映射表项的指针
lpEntry = &CMainFrame::_messageEntries[0]; // 获取第一表项
mmf.pfn = lpEntry->pfn; // 获取第一表项的函数指针
…
// 判断该条目的 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;
…
}
…
3.2.4 使用 MFC 应用程序框架寻找消息处理函数 SendMessage(…) AfxWndProc(…)
PostMessage(…)
消息队列
CXXXApp::Run()
GetMessage(…)DisPatchMessage(…)
AfxCallWndProc(…)
窗口类 ::WindowProc(…)
窗口类 :: OnWndMsg(…)
窗口类 ::DefWndProc(…)
窗口类 ::OnCommand(…) 窗口类 ::OnNotify(…) 搜索窗口类及其基类消息映射表
窗口类 ::OnCmdMsg(…)
其他类 ::OnCmdMsg(…)
…
其中各个函数的原型及其作用如下: ⑴ 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 进行缺省处理。
⑷ 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 发回的反馈通知其对消息的处理情况。
⑹ 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 );
3.3 MFC 程序处理消息的路径3.3.1 如何处理窗口消息 virtual BOOL OnWndMsg( UINT message, WPARAM wParam,
LPARAM lParam, LRESULT* pResult );
搜索窗口类的消息映射表
找到消息处理函数?
继续搜索基类的消息映射表
窗口类 ::DefWindowProc(…)
执行消息处理函数
NO
YES
找到消息处理函数
所有基类均没有定义该消息的处理函数
是窗口消息?
窗口类 ::OnWndMsg(…)
命令消息或者控件消息处理NO
YES
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 。
·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 。
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);
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);
}
BOOL CWnd::OnCommand(WPARAM wParam, LPARAM lParam)
{
…
return OnCmdMsg(nID, nCode, 0, 0);
}
指向 CMainFrame 类对象的指针调用 OnCmdMsg 时,通过动态
联编, CMainFrame 的 OnCmdMsg 被调用,如果 CMainFra
me 类
中 OnCmdMsg 没有重定义,则 CFrameWnd::OnCmdMsg 被调
用。该函数的定义代码如下:
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;
}
· 如果在视图类对象的消息映射表中找到了消息响应函数,则返回 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;
}
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;
}
· 如果在文档类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。
· 如果在文档类对象的消息映射表中未找到了响应函数,并且文档模板类对象存在,则调用文档模板类的 OnCmdMsg
继续消息响应搜索。 · 如果在文档模板类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。
· 如果在文档模板类对象的消息映射表中未找到了响应函数,则结束第一阶段的搜索进入第二阶段的搜索:从程序框架窗口类对象的消息映射表搜索,调用 CWnd::OnCmdMs
g
继续消息响应搜索。 · 如果在框架窗口类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。
· 如果在框架窗口类对象的消息映射表中未找到了响应函数,则结束第二阶段的搜索进入第三阶段的搜索:从应用程序类对象的消息映射表搜索,调用 CWinApp::OnCmdMsg
继续消息响应搜索。 · 如果在应用程序类对象的消息映射表中找到了响应函数,则返回 TRUE ,结束消息响应搜索。
· 如果在应用程序类对象的消息映射表中未找到了响应函数,则结束第三阶段的搜索,返回 FALSE 指示命令消息无响应。
注意,在 CWnd 类和 CWinApp 类中都未对 OnCmdMsg
进行重新 定义,可见上述所有的搜索都要归结到调用 CCmdTarget
类的 OnCmdMsg ,其定义代码如下:(见 “ CmdTarg.cpp” )
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);
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.
…
#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
}
处理消息的函数 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
if (pHandlerInfo != NULL)
{
// just fill in the information, don't do it
pHandlerInfo->pTarget = pTarget;
pHandlerInfo->pmf = mmf.pfn;
return TRUE;
}
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;
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;
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;
}
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;
}
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;
}
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;
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;
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
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 应用程序框架中命令消息的响应传递顺序 视图类对象→文档类对象→文档模板类对象→框架窗口类对
象→应用程序类对象
3.3.3 如何处理控件消息 virtual BOOL CWnd::OnNotify( WPARAM wParam, LPARAM lParam,
LRESULT* pResult );
OnWndMsg(…)
OnNotify(…)
控件窗口类能处理该消息?
控件的父窗口类 ::OnCmdMsg(…)
控件窗口类处理消息YES
NO
3.4 自定义消息处理 通过前面的章节的学习,我们已经清楚地知道 Windows
系统为各种操作驱动的需要定义了各类窗口消息、命令消息、通知消息、控件消息等,并为每个消息分配了一个唯一的无符号整数作为消息的标识值。同时系统还保留了足够的标识值空间为用户根据需要定义自己的消息提供了可能。在应用程序中可以使用 CWnd 类的成员函数 SendMessage 和 PostMessage 或同
名 API
函数向各种窗口类对象发送或寄送自定义消息,还可以使用线程类 CWinThread 的成员函数 PostThreadMessage 或同名 API
函数向各种线程或进程类对象发送或寄送自定义消息。 为了区分系统定义消息和用户自定义消息以及为将来的扩展
保留充分的余地,系统将所有可以使用的标识值进行了明确的划分:
其中:#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.
3.4.1 自定义静态窗口消息1 在需要使用该自定义窗口消息的类实现文件中手工添加自定
义窗口消息。例如: #define WM_MYSAYHELLO WM_USER + 100
或 const WM_MYSAYHELLO = WM_USER + 100;
2 在相应的类定义中手工添加该消息的响应函数声明。例如: class CMyView : public CView
{
…
protected:
…
afx_msg LRESULT OnSayHello(WPARM, LPARM);
DECLARE MESSAGE_MAP
};
注意,该函数的返回类型、参数类型和个数,并必须添加前
缀 afx_msg 。
3 在相应类的消息映射表中手工添加该消息映射条目。例如: BEGIN_MESSAGE_MAP(CMyView, CView)
//{{AFX_MSG_MAP(CMyView)
…
//}}AFX_MSG_MAP
ON_MESSAGE( WM_MYSAYHELLO, OnSayHello )
END_MESSAGE_MAP()
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 );
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);
2 在相应的类定义中手工添加该消息的响应函数声明。例如: class CMyView : public CView {
…
protected:
…
afx_msg LRESULT OnSayHello(WPARM, LPARM);
DECLARE MESSAGE_MAP
};
注意,该函数的返回类型、参数类型和个数,并必须添加前
缀 afx_msg 。
3 在相应类的消息映射表中手工添加该消息映射条目。例如: BEGIN_MESSAGE_MAP(CMyView, CView)
//{{AFX_MSG_MAP(CMyView)
…
//}}AFX_MSG_MAP
ON_MESSAGE( WM_MYSAYHELLO, OnSayHello )
END_MESSAGE_MAP()
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 );
3.5 实例 1 :自定义类如何响应命令消息 在缺省情况下,应用程序中的命令消息只能在应用程序框架
规定的途径中顺序传递,而自定义类对象是不包括在内的。而在应用程序中定义自定义类是不可避免的,如何使这些自定义类对象能够加入到命令消息的传递途径中,使自定义类对象能够响应由程序框架发来的命令消息是非常必要的。本例的主要目的:如何定义一个能处理消息的自定义类,如何将自定义类加入到命令消息传递途径中。 本例的基本功能是通过一个对话框界面实现:· 对个人信息编辑和管理(保存信息,装入已保存的信息);· 个人信息的保存和管理由一个自定义类 CPerson 实现;·保存信息操作是通过对话框中的按钮控件发出命令消息,由 CPerson 类对象接收后,由相应的成员函数完成。
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
⑶ 添加公有数据成员,用于存放个人信息“姓名” 和 “年龄”
CString m_szName;
UINT m_nAge;
⑷ 重载 Serialize 虚函数,实现个人信息数据的保存和装载。⑸ 增加一个公有成员函数 UINT CPerson::Load() ,用于从磁盘装
入数据。⑹ 为控件 IDC_SAVE 发出的消息增加命令消息映射代码: · 在定义文件中加入处理函数声明:
afx_msg void OnSave();
· 在实现文件中的消息映射表中加入映射项: ON_
COMMAND(IDC_SAVE, OnSave)
· 在实现文件中实现 OnSave() 的定义。 注意,以上工作可以通过 ClassWizard 或手工完成添加。
4 为框架窗口类 CMSG1Dlg 增加和修改成员:
⑴ 增加三个数据成员
CPerson m_person; // 定义 CPerson 类对象,用于与主框架的联系。
UINT m_nAge; // 文本编辑框 IDC_NAME 的数据缓冲
CString m_szName; // 文本编辑框 IDC_AGE 的数据缓冲
⑵ 在成员函数 OnInitDialog() 中加入调用 CPerson::Load ,以便将
磁盘文件中已保存的个人信息数据读入控件显示。
⑶ 重载虚函数 OnCmdMsg 使 CPerson 类对象能够从命令消息的
传递途径中截获需要响应的消息。
⑸ 添加 OK 按钮的响应函数,在函数中加入向 CMSG1Dlg 发送
保存个人信息数据的消息,以便在结束对话框之前保存个人
信息数据。
⑷ 如果想为 IDC_SAVE 命令增加一个加速键,则需要重载虚函
数 PreTranslateMessage ,增加将一个确定的加速键组合转换
为 IDC_SAVE 命令。
5 编译运行。
小结:使自定义类能响应处理命令消息的编程关键
·自定义类必须是 CCmdTarget 类或其派生类的派生类;
· 在自定义类中定义消息响应函数和添加消息映射;
· 在自定义类对象的所属类中重载虚函数 OnCmdMsg 使自定义
类对象能接收到命令消息。
3.6 实例 2 :自定义消息处理 本例的目的是如何借助自定义静态窗口消息的方法,为应
用
程序定义和响应自定义窗口消息。
1 创建一个名为 “ MSG2MSG2” 的对话框( Dialog based )类型的应用
程序项目。
2 修改对话框( IDD_MSG2_DIALOG )资源:
⑴ 删除原有的静态文本框和“确定”按钮;
⑵ 加入下列控件:控件类型 名称 属性 ID
静态控件编辑控件按钮
消息次数统计:默认发消息
默认默认默认
IDC_STATIC
IDC_PROMT
IDC_SENDMSG
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 编译运行。
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
3 修改工具栏
⑴ 删除缺省工具栏中除 help 按钮以外的所有按钮。
⑵ 增加与菜单项 ID_SELECT_RED 、 ID_SELECT_BLUE 和
ID_SELECT_YELLOW 相关的工具栏按钮,并添加相应提示。
修改后的工具栏如下:
4 修改 CHelloView::OnDraw ,加入如下代码:pDC->TextOut(0, 0, “Hello World!”);
5 在 CMainFrame 类中为新增菜单项定义命令处理函数
⑴ 为 CMainFrame 增加数据成员和枚举: int m_nColor;
enum{RED = 0, BLUE = 1, YELLOW = 2};
用于标识由菜单或工具栏按钮选择的颜色。
⑵ 修改 CMainFrame 的构造函数,加入 m_nColor = RED;
指定选定的缺省颜色为红,即标识值 = RED 。
⑶ 使用 ClassWizard 为各个新增菜单项添加命令消息响应函数
OnSayHello 、 OnSelectRed 、 OnSelectBlue 和 OnSelectYellow 的声
明、定义体和映射项。
⑷ 在各个响应函数定义体内加入具体的功能代码。
6 增加更新命令用户界面( UI )消息
菜单用户的界面的更新是在选择菜单时,由系统框架发出的
WM_INITMENUPOPUP 消息引起对菜单界面的更新操作完成
的。这一操作发生在菜单项弹出之前,对顶层主菜单不起作
用。工具栏用户界面的更新操作发生在空闲处理中。
⑴ 用 ClassWizard 为菜单项 ID_SELECT_RED 、 ID_SELECT_BLUE
和 ID_SELECT_YELLOW 添加用户界面更新命令消息响应函数
OnUpdateSelectRed 、 OnUpdateSelectBlue 和 OnUpdateSelectYellow
的声明、定义体和映射项。
⑵ 在各个处理函数定义体内加入具体的功能代码。
7 为菜单项 ID_SAY_HELLO 定义加速键 CTRL+H 。
8 编译运行。