《Windows 程序设计(第3版)》——6.4 消息映射

6.4 消息映射

6.4.1 消息映射表
直接在窗口函数WndProc中处理消息很烦琐。我们希望能够直接使用类的成员函数响应感兴趣的消息。比如现在有一个CMyWnd类,当处理某个消息的时候,只要在这个类中添加一个对应的成员函数就行了,如图6.4所示。

在CMyWnd类中,OnCreate函数响应的是WM_CREATE消息,即当窗口接收到WM_CREATE消息以后,想让框架程序自动调用OnCreate函数。函数的名称是任意的,这里命名为OnCreate只是为了有意义。OnPaint和OnDestroy函数分别响应WM_PAINT和WM_DESTROY消息。要是对其他消息感兴趣的话,你还可以随意在CMyWnd类中增加消息处理函数的数量。

图6.4中的消息和处理消息的成员是一一对应的,这就是所谓的消息映射。每一对消息和处理消息的成员组成一个映射项,类中所有的映射项连在一起形成消息映射表。窗口函数只有知道类的消息映射表,才能在消息到来的时候,主动调用表中此消息对应的处理函数。所以,我们必须想办法把映射表中的数据记录下来。

每个消息映射项最基本的内容应该包括消息的值和处理该消息的成员函数,下面用一个名称为AFX_MSGMAP_ENTRY的结构来描述它。

// 处理消息映射的代码    // 注意,这些代码在_AFXWIN.H文件的开头,要按顺序写下
class CCmdTarget;
typedef void (CCmdTarget::*AFX_PMSG)(void);

// 一个映射表项
struct AFX_MSGMAP_ENTRY
{
  UINT nMessage;    // 窗口消息
  UINT nCode;    // 控制代码或WM_NOTIFY通知码
  UINT nID;      // 控件ID,如果为窗口消息其值为0
  UINT nLastID;    // 一定范围的命令的最后一个命令或控件ID,用于支持组消息映射
  UINT nSig;      // 指定了消息处理函数的类型
  AFX_PMSG pfn;    // 消息处理函数
};

现在只注意nMessage和pfn两个成员就行了,其他的成员都是对nMessage消息的更具体的描述,以后再谈。你可能会对AFX_PMSG宏的定义感到奇怪,为什么要把消息处理函数都定义成CCmdTarget类的成员函数呢?事实上,我们对用户还有一个要求,就是所有有消息处理能力的类都要从CCmdTarget继承。既然无法预知用户定义的消息处理函数的具体类型,只好先统一转化成AFX_PMSG宏指定的类型了。

现在只要在CMyWnd类中添加一个AFX_MSGMAP_ENTRY类型的静态数组就即可将该类的消息和消息处理函数关联起来,如下代码所示。

class CMyWnd        // 这是示例代码,不属于类库的一部分
{
public:
  void OnCreate()    { /* 响应WM_CREAT消息的代码*/   }
  void OnPaint()    { /* 响应WM_PAINT 消息的代码*/  }
  void OnDestory()    { /* 响应WM_DESTROY 消息的代码*/}  
private:
  // 此数组记录了消息映射表中的数据
  static const AFX_MSGMAP_ENTRY _messageEntries[];
};

const AFX_MSGMAP_ENTRY CMyWnd::_messageEntries[] =
{
  {WM_CREATE, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnCreate},
  {WM_PAINT, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnPaint},
  {WM_DESTROY, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnDestory}
};

静态成员_messageEntries指向的数组记录了CMyWnd类要处理的消息和对应的消息处理函数。窗口函数接收到消息后,可以遍历此数组找到响应该消息的成员函数,然后把消息的处理权交给这个函数。

在类的继承结构中,对于CMyWnd类没有处理的消息,应该由CMyWnd类的基类来做默认处理。如果负责做默认处理的类是CCmdTarget的话,CMyWnd类就应该从CCmdTarget类继承,还应该记录下其基类中消息映射表的地址(这样消息才能向上传递)。所以我们最终用AFX_MSGMAP结构来描述类的消息映射表。

struct AFX_MSGMAP    // 继续在AFX_MSGMAP_ENTRY结构之后定义此结构,_AFXWIN.H文件中
{
  const AFX_MSGMAP* pBaseMap;    // 其基类的消息映射表的地址
  const AFX_MSGMAP_ENTRY* pEntries;  // 消息映射项的指针
};
下面是具有消息处理能力以后CMyWnd类的完整代码。

class CMyWnd : public CCmdTarget      // 示例代码
{
public:

  void OnCreate()    { /* 响应WM_CREAT消息的代码*/ }
  void OnPaint()    { /* 响应WM_PAINT 消息的代码*/}
  void OnDestory()    { /* 响应WM_DESTROY 消息的代码*/}

// 定义消息映射的代码
private:
  static const AFX_MSGMAP_ENTRY _messageEntries[];
protected:
  static const AFX_MSGMAP messageMap;
  virtual const AFX_MSGMAP* GetMessageMap() const;
};

// 实现消息映射的代码
const AFX_MSGMAP* CMyWnd::GetMessageMap() const
  { return &CMyWnd::messageMap; }
const AFX_MSGMAP CMyWnd::messageMap =
{ NULL/*&CCmdTarget::messageMap*/, &CMyWnd::_messageEntries[0] };
const AFX_MSGMAP_ENTRY CMyWnd::_messageEntries[] =
{
  {WM_CREATE, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnCreate},
  {WM_PAINT, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnPaint},
  {WM_DESTROY, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnDestory}
  // 在这里添加你要处理的消息
};

GetMessageMap是一个虚函数,用来取得当前类中消息映射表的地址。现在的CCmdTarget类中还没有消息映射表,所以在初始化messageMap变量的时候,为其成员pBaseMap传递的是NULL,否则就应该传递&CCmdTarget::messageMap了。

6.4.2 DECLARE_MESSAGE_MAP等宏的定义
框架程序就是采取上一小节所述的机制处理Windows消息的。CCmdTarget类位于消息映射的最顶层,这个类的消息映射表中pBaseMap成员的值是NULL。类要想具有消息处理能力的话,必须从CCmdTarget类继承,而且还必须有自己的消息映射表。与为CObject派生类添加运行时类信息类似,我们也可以通过一组宏简化添加消息映射表的过程。

CMyWnd类中定义消息映射的代码并不与具体的类相关,只用一个不带参数的宏就可以将它们代替。将此宏命名为DECLARE_MESSAGE_MAP,即“声明消息映射”的意思,下面是定义它的代码。

// 在消息映射表AFX_MSGMAP结构的定义之后定义下面的宏,_AFXWIN.H文件中
#define DECLARE_MESSAGE_MAP() \
private: \
  static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
  static const AFX_MSGMAP messageMap; \
  virtual const AFX_MSGMAP* GetMessageMap() const; \

消息映射表项中记录的数据是由用户填写的,所以再添加BEGIN_MESSAGE_MAP和END_MESSAGE_MAP两个宏代替实现消息映射的代码,在这两个宏中间用户填写初始化映射表项的代码。下面是这两个宏的定义。

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
  const AFX_MSGMAP* theClass::GetMessageMap() const \
    { return &theClass::messageMap; } \
  const AFX_MSGMAP theClass::messageMap = \
  { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
  const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
  { \

#define END_MESSAGE_MAP() \
    {0, 0, 0, 0, 0, (AFX_PMSG)0} \
  }; \

实现消息映射的代码中出现了当前类和基类的名字,所以替代它们的宏应该有这两个参数。“begin messge map”和“end message map”这两个名字比较能说明问题,一个是开始消息映射,一个是结束消息映射,它们中间的代码是真正的消息映射。

END_MESSAGE_MAP宏向映射表中添加的映射表项“{0, 0, 0, 0, 0, (AFX_PMSG)0}”是数组的结束标记。遍历类的映射表时,遇到此项就意味着映射表中已经没有数据了。

这两个宏必须配对使用,下面是利用它们初始化CMyWnd类的消息映射表的例子。

// 注意,必须当CCmdTarget中添加类消息映射表之后,这些代码才能提供编译    // 示例代码
BEGIN_MESSAGE_MAP(CMyWnd, CCmdTarget)
  {WM_CREATE, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnCreate},
  {WM_PAINT, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnPaint},
  {WM_DESTROY, 0, 0, 0, 0, (AFX_PMSG)CMyWnd::OnDestory},
END_MESSAGE_MAP()

现在该给类库中的类添加消息映射表了。首先是CCmdTarget类,在这个类的定义代码中加入DECLARE_MESSAGE_MAP宏。

class CCmdTarget : public CObject
{
  //......    // 其他成员
  DECLARE_MESSAGE_MAP()
};

因为CCmdTarget类位于消息映射的最顶层,在消息处理这一体系中,它是没有父类的,所以就不能直接用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP这一对宏,而只能手工添加实现消息映射的代码。下面是CMDTARG.CPP文件中相应的代码。

// 实现消息映射的代码
const AFX_MSGMAP* CCmdTarget::GetMessageMap () const
{
  return &CCmdTarget::messageMap ;
}
const AFX_MSGMAP CCmdTarget::messageMap =
  { NULL, &CCmdTarget::_messageEntries[0] };
const AFX_MSGMAP_ENTRY CCmdTarget::_messageEntries[] =
{
  // 一个消息也不处理
  { 0, 0, 0, 0, 0, (AFX_PMSG)0 }
};

现在按F7键编译连接做实验的工程,应该是不会出错的。

处理消息的核心是CWnd类,因为消息由此类的非静态成员函数WindowProc传入到类的对象中。由于CWnd类从CCmdTarget类继承,所以直接使用本小节设计的宏即可。

class CWnd : public CCmdTarget      // _AFXWIN.H文件
{
  ...    // 其他成员
  DECLARE_MESSAGE_MAP()
};

在WINCORE.CPP文件中添加如下代码。

BEGIN_MESSAGE_MAP(CWnd, CCmdTarget)
// 在此处添加CWnd类要处理的消息
END_MESSAGE_MAP()

消息映射表有了,下一节介绍如何向表中添加消息映射项。

时间: 2024-10-26 06:33:07

《Windows 程序设计(第3版)》——6.4 消息映射的相关文章

windows程序设计(第五版)第22章的drum.c中的问题?

问题描述 windows程序设计(第五版)第22章的drum.c中的问题? windows程序设计(第五版)第22章的drum.c中的这段,没弄明白作者的想法, " if (x >= 0 && x < 32 && y >= 0 && y < NUM_PERC) { if (message == WM_LBUTTONDOWN) drum.dwSeqPerc[y] ^= (1 << x) ;//这里不明白,作者为什么

学习方向-小弟最近学了【Windows程序设计(第五版)】但不知道学了有什么作用。

问题描述 小弟最近学了[Windows程序设计(第五版)]但不知道学了有什么作用. 小弟现在大二学生,物联网专业的专科.以后准备从事软件这个行业(具体什么我也不懂,反正即使IT啦),大一时学了C/C++的最最基本的语法,其他什么都不会,就像什么数据结构,算法,操作系统啊什么的都不会,现在都大二了,时间不多了,(专科都是两年半)想自己学点东西,但是不知道该学什么.就像那无头苍蝇,上几天听说了[Windows程序设计(第五版)] 就去图书馆借来看看了(暂时还没钱买,100多呢!),学的还行,但是不知

《Windows 程序设计(第3版)》——导读

前言 许多人在刚开始接触Windows编程时,或从VB开始,或从MFC开始,这使得大家虽然写出了程序,但自己都不知道程序是如何运行的,从而造成写程序"容易"修改难.设计程序"容易"维护难的状况.本书是为Windows程序设计入门的初学者和想从根本上提高自己编程水平的爱好者编写的,试图为他们提供一条由入门到深入.由简单到复杂的编程设计之路. API函数是Windows系统提供给应用程序的编程接口,任何用户应用程序必须运行在API函数之上.直接使用API编程是了解操作系

《Windows 程序设计(第3版)》——6.5 消息处理

6.5 消息处理 6.5.1 使用消息映射宏 Windows统一用WPARAM和LPARAM两个参数来描述消息的附加信息,例如WM_ CREATE消息的LPARAM参数是指向CREATESTRUCT结构的指针,WPARAM参数没有被使用:WM_LBUTTONDOWN消息的WPARAM参数指定了各虚拟键的状态(UINT类型),LPARAM参数指定了鼠标的坐标位置(POINT类型).很明显,消息附加参数的类型并不是完全相同的,如果CWnd类也定义一种统一形式的成员来处理所有的消息,将会丧失消息映射的

《Windows 程序设计(第3版)》——第6章 框架中的窗口 6.1 CWnd类的引出

第6章 框架中的窗口 前面讲述了类库框架管理应用程序的基本方式,以及它的执行顺序.本章将继续介绍如何在框架程序执行的过程中创建窗口和响应线程内发送给窗口的消息. 消息处理是Win32应用程序的灵魂,也是本章重点讨论的话题.本章最终要设计一个能够实现消息映射的基本构架. 6.1 CWnd类的引出 在类的体系结构中,框架程序提供了CWnd类来封装窗口的HWND句柄,即使用CWnd类来管理窗口的对象,这包括窗口的创建和销毁.窗口的一般行为和窗口所接受的消息. 为了使其他的类也有处理消息的机会,我们可以

Windows 8.1正式版来临

Win8.1看样子很快就要来了,根据最新消息,与之前国外媒体报道的情况吻合,目前微软已经开始对 Windows 8.1 正式版进行编译.在编译完毕并确定之后将很快发布给厂商进行镜像准备工作,并于 2013 年 10 月 17 日免费提供给所有 Windows 8 用户升级使用,随后上市销售的新一代电脑中也将预装这一版本.       Windows 8.1  RTM 的版本号与之前国外网友猜测的也十分吻合,版本号为 6.3.9600.16384.不过在这一版本编译完成之后,微软还将迅速对其进行测

语言-windows程序设计键盘问题

问题描述 windows程序设计键盘问题 /*-------------------------------------------------------- KEYVIEW1.C -- Displays Keyboard and Character Messages (c) Charles Petzold, 1998 --------------------------------------------------------*/ #include LRESULT CALLBACK WndP

求问-windows程序设计之对话框

问题描述 windows程序设计之对话框 在windows程序设计十一章ABOUT2中.要在对话框上绘图,实现过程是:程序在处理WM-COMMAND消息时调用painttheblock函数来绘图,painttheblock函数使子窗口控件无效并向控件窗口过程发送WM-PAINT消息,然后调用paintwindow函数.然而WM-PAINT处理过程也是调用painttheblock函数.当执行了painttheblock函数就会触发WM-PAINT,WM-PAINT消息代码又会调用paintthe

微软发出邀请函 9月30日有望推Windows 9预览版

微软宣布9月30日召开发布会 有望推Windows 9预览版新浪科技讯 北京时间9月16日凌晨消息,微软周一宣布,该公司将于9月30日在旧金山召开一次"Windows发布会",并已从今天开始发出媒体邀请函.微软Windows Phone业务主管乔·贝尔菲奥利(Joe Belfiore)和特里·迈尔森(Terry Myerson)将出席此次发布会,就Windows的未来进行讨论.该发布会将以Windows的企业用户和高级用户为重点关注对象.预计微软将在发布会上推出Windows 9操作系