mfc把消息分为3大类

1.命令消息(wm_command)

一般来自工具栏和菜单栏,凡是派生自CCmdTarget的类,都可以接收命令消息。

2,标准消息(wm_)

凡是派生自cwnd类,都可以接收该消息。

3.notify

由控件产生,向其父窗口通知某种情况。

Windows 消息

消息可以分为系统定义消息和应用定义消息两大类。

  • 系统保留的消息标识符值的范围是 0x0000 到 0x03FF(WM_USER - 1)。应用不能使用这些值作为私有消息
  • 在 0x0400(WM_USER)到 0x7FFFF 范围内的值用于私有窗口类的消息标识符。

windows使用两种方法将消派发到一个窗口消息处理函数:一是将消息放到消息队列(先进先出队列),二是不放到消息队列,直接发送到窗口消息处理函数,让窗口处理函数来处理消息。

派发到消息队列的消息被称为排队消息(Queued messages)。它们主要是用户输入事件,比如说鼠标或键盘消息盘,有WM_MOUSEMOVE消息,WM_LBUTTONDOWN,WM_KEYDOWN,和WM_CHAR消息。还有一些其他的,包括WM_TIMER,WM_PAINT,以及WM_QUIT。大多数其他的消息息,这是直接发送到窗口过程,被称为非队列消息(non queued messages)。

(1) 队列(Queued)消息

windows可同时显示任意数量的窗口。此时,系统使用消息队列来将键盘和鼠标事件正确的派发到正确的窗口。

windows维护着一个系统消息队列,以及分别为每个GUI线程维护一个各自的线程消息队列。为了避免非GUI线程的创建线程消息队列的开销,所有线程创建初始化时,均不创建消息队列。只有当线程第一次调用GDI函数时,系统才会为线程创建消息队列。所以那些非GUI线程是没有消息队列的。

每当用户移动鼠标,点击按钮或键盘时,鼠标或键盘的设备驱动程序会将输入转换成消息,并将消息放在系统消息队列里。删windows会检查自己的消息队列,如果消息队列不为空,则每次取出并删除一个消息,然后确定消息的目标窗口,然后把消息放到创建这个窗口的线程的线程消息队列里。线程的消息队列接收由线程创建的窗口的所有的鼠标和键盘消息。然后线程会从队列中删除信息,并告诉系统把它们派发到对应的窗口消息处理函数。

除了WM_PAINT, WM_TIMER和WM_QUIT消息以外,系统总是派发放在在消息队列的末尾的消息。这将保证让一个窗口以first-in, first-out的顺序接收消息。WM_PAINT,WM_TIMER,和WM_QUIT消息,会一直被保存在队列中,只有在队列中没有其他消息时才会被派发到窗口消息处理函数。此外,同一个窗口的多个WM_PAINT消息被合并成一个WM_PAINT消息,客户区的所有无效部分也会被合并。这样是为了减少窗口重绘客户区的次数。

系统通过填充一个 MSG 结构来将消息投递到线程的消息队列,随后将其拷贝到消息队列中。 MSG 结构的信息包括:指定窗口的句柄,消息标识符,两个消息参数,消息投递的时间,以及鼠标光标的位置。通过使用 PostMessage (异步的)和 PostThreadMessage 函数,线程可以将一个消息投递到自己的消息队列或其他线程的消息队列。

应用可以使用 GetMessage 来删除队列中的消息。要在不删除消息的情况下检查队列消息,应用可以使用 PeekMessage 函数,该函数会使用消息填充 MSG 。

在从队列删除消息后,应用可以使用 DispatchMessage 函数来指示系统把消息发送给窗口过程进行处理。DispatchMessage 接收一个 MSG 结构的指针,该结构已经使用 GetMessage 或 PeekMessage 填充过。DispatchMessage 将窗口句柄,消息标识符,和两个消息参数传递给窗口过程,但它不会传递时间和鼠标光标位置。应用在处理消息时可以通过 GetMessageTime 和 GetMessagePos 函数检索时间和位置信息。

(2) 非队列(Nonqueued)消息

Nonqueued消息被立即送往目的地的窗口消息处理函数,绕过了系统的消息队列和线程消息队列。系统通常会发送nonqueued消息,来通知那些会影响窗口的事件。例如,当用户激活一个新的应用程序窗口时,系统会发送一些列消息到窗口,包括WM_ACTIVATE,WM_SETFOCUS,WM_SETCURSOR。这些消息通知窗口被激活,键盘输入被定向到窗口,并且鼠标光标也移到窗口的边界内。

Nonqueued消息也有可能来源于应用程序调用系统函数。例如,系统调用SetWindowPos函数移动一个窗口后会发送WM_WINDOWPOSCHANGED消息。 一些函数也发送nonqueued消息, 有BroadcastSystemMessage,BroadcastSystemMessageEx,SendMessage(同步),SendMessageTimeout,和SendNotifyMessage。

常见的消息

WM_CREATE

当应用通过调用 CreateWindow 或 CreateWindow 要求创建一个窗口时,会发送这个消息(在函数返回前消息就被发送)。新窗口的窗口过程在窗口创建后会接收到这个消息,但是是在窗口可见之前。

这是窗口过程接收到的第一个消息。

接收该消息时,窗口过程的 wParam 参数值不被使用,它的 lParam 是一个指向 CREATESTRUCT 结构的指针。这个结构包含了窗口初始化的参数。

处理该消息后,窗口过程应该返回 0 以继续窗口的创建。如果窗口过程返回 -1,窗口会被销毁,CreateWindow 或 CreateWindowEx 会返回空句柄。

WM_SIZE

在窗口尺寸改变后向窗口发送该消息。

wParam 是尺寸改变的类型,它可以是下列值中的一个;

  • SIZE_MAXHIDE,当其他窗口最大化时,该消息会发送到所有弹出(pop-up)窗口
  • SIZE_MAXIMIZED,窗口已经最大化了
  • SIZE_MAXSHOW,当其他窗口恢复到之前尺寸时,该消息会发送到所有弹出窗口
  • SIZE_MINIMIZED,窗口已经最小化了
  • SIZE_RESTORED,窗口的尺寸改变了,但不是最大化和最小化

lParam 的低位是客户区的新宽度,高位是客户区的新高度。虽然窗口的宽度和高度是 32 位值,lParam 只包含宽高值的低 16 位。

窗口过程处理该消息后应该返回 0。

WM_PAINT

当系统或其他应用要求对应用窗口的部分进行绘制时,会发送该消息。调用 UpdateWindow 或 RedrawWindow 函数时,或在应用通过使用 GetMessage 或 PeekMessage 获得 WM_PAINT 并调用 DispatchMessage 函数后,消息会被发送到窗口过程。

lParam 和 wParam 都不被使用。

Invalidate在消息队列中加入一条WM_PAINT消息,其无效区为整个客户区。而UpdateWindow直接发送一个WM_PAINT消息,其无效区范围就是消息队列中WM_PAINT消息(最多只有一条)的无效区。效果很明显,调用Invalidate之后,屏幕不一定马上更新,因为WM_PAINT消息不一定在队列头部,而调用UpdateWindow会使WM_PAINT消息马上执行的,绕过了消息队列。如果你调用Invalidate之后想马上更新屏幕,那就加上UpdateWindow()这条语句。

WM_DESTROY

当窗口将被销毁时发送该消息。在窗口被从屏幕删除后,它会被发送到删除的窗口的窗口过程。

该消息首先被发送到被销毁的窗口,之后发送到子窗口(如果有的话)。在消息的主窗口处理过程中,可以假设所有子窗口还是存在的。

wParam 和 lParam 不被使用。如果处理了该消息,窗口过程应返回 0。

如果被销毁的窗口是剪切板链的一部分,在 WM_DESTROY 消息处理返回前,窗口必须将它从链条中移除。

WM_COMMAND

当用户从菜单选中一个命令时会发送,当控件向它的父窗口发送提醒消息时会发送,当加速键被翻译时会发送。

如果应用处理了该消息,它应该返回 0。

在消息来源是菜单时,wParam 的高位是 0,低位是菜单标识符(IDM_*),lParam 是 0。

消息来源是加速键时,wParam 的高位是 1,低位是加速键标识符(IDM_*),lParam 是 0。

消息来源是控件时,wParam 的高位是控件特定的通知码,低位是控件标识符,lParam 是控件窗口句柄。

命令

ON_COMMAND用来响应相应工具栏和菜单栏的命令WM_COMMAND,不用自己解开WM_COMMAND中wParam和lParam中传送的控件ID。ON_COMMAND对应的消息ID一直都是WM_COMMAND.

操作方法:在类向导中,命令tab页 对象id为选择需要的控件ID,消息为command.然后添加处理程序。相当于在

在头文件添加了 afx_msg void func();

在源文件的BEGIN_MESSAGE_MAP添加了ON_COMMAND(控件ID,func);

消息

如果是系统已定义好的消息,其格式为ON_WM_XX的形式。

操作办法是:在类向导中,消息tab页选择某一个WM_XXX即可。

相当于在在头文件添加了 afx_msg void func();

在源文件的BEGIN_MESSAGE_MAP添加了ON_WM_XX。

ON_MESSAGE用来响应自定义消息,能够处理所有的消息响应,在程序中需要自己设定相应的消息响应函数。

ON_MESSAGE(message, memberFxn ) 参数:

message:消息的ID。

memberFxn :映射message的消息函数,该函数的类型必须是以下类型的

afx_msg LRESULT (CWnd::*)(WPARAM, LPARAM)

操作办法

1.在头文件定义 #define WM_MYMESSAGE (WM_USER + 100)  //消息id

2.在头文件定义afx_msg LRESULT  func( WPARAM wparam, LPARAM lparam)

3.在BEGIN_MESSAGE_MAP 添加ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)

4.在源文件添加实现

LRESULT 类名::OnMyMessage(WPARAM wParam, LPARAM lParam)

{

return 0;

}

需要调用postMessage 或者 SendMessage 实现关联。

ON_NOTIFY是控件向其父窗口发送消息处理的宏,扩展了ON_COMMAND的功能,使用了相应的NMHDR结构.

ON_NOTIFY( wNotifyCode, id, memberFxn )

wNotifyCode:要被处理的通告消息代码,如 LVN_KEYDOWN。

id:发送通告消息的控件ID。

memberFxn:通告消息发送后被调用的成员函数。

做法

1在头文件定义

afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result );

2.在BEGIN_MESSAGE_MAP 添加 ON_NOTIFY( wNotifyCode, id, memberFxn)

3.在源文件实现void 类名::memberFxn(NMHDR* pNMHDR, LRESULT* pResult)

{

*pResult = 0;

}

虚函数OnCommand

virtual BOOL OnCommand(WPARAM wParam,LPARAM lParam);

相当于一个“接口”实现多个命令的处理。可以直接在类向导操作。

wParam:表示具体是哪个控件ID.

虚函数OnOntify

virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)

 NMHDR* pNMHDR = (NMHDR*)lParam;

NMHDR {

HWnd hWndFrom  相当于原WM_COMMAND传递方式的lParam

UINT idFrom  相当于原WM_COMMAND传递方式的wParam(low-order)

UINT code  相当于原WM_COMMAND传递方式的Notify Code(wParam"s high-order)

};

WindowProc   //回调函数,处理发送给窗口的消息   手段更丰富了。        是一个虚函数,可以通过类向导添加。

LRESULT CALLBACK WindowProc(          HWND hwnd,

UINT uMsg,

WPARAM wParam,

LPARAM lParam

);

hwnd:指向窗口的句柄。

uMsg:指定消息类型。uMsg可以是WM_COMMAND、 WM_NOTIFY 从而可以实现ON_NOTIFY  ON_COMMAND的处理。

wParam:指定其余的、消息特定的信息。该参数的内容与UMsg参数值有关。

IParam:指定其余的、消息特定的信息。该参数的内容与uMsg参数值有关

CWnd::WindowProc 调用 OnWndMsg 用来分辨并处理消息;如果是命令消息,交给 OnCommand 处理,如果是通知消息(Notification),交给 OnNotify 处理。而一般的 Windows 消息,就直接在消息映射表中上溯,寻找其归宿(消息处理程序)

在CWnd中,MFC使用OnWndMsg来分别处理各类消息:

如果是WM_COMMAND消息,交给OnCommand处理;然后返回。

如果是WM_NOTIFY消息,交给OnNotify处理;然后返回。

其中:OnCommand、OnNotify也是虚函数

一般 windows 消息:直线上溯,即从派生类流向到基类。

WM_COMMAND命令消息:拐弯上溯

DECLARE_MESSAGE_MAP、BEGIN_MESSAGE_MAP、END_MESSAGE_MAP宏使用及解析

具体方式是在类或者结构体末尾添加DECLARE_MESSAGE_MAP(无分号),然后在定义类成员函数的.CPP文件中,使用BEGIN_MESSAGE_MAP()宏和 END_MESSAGE_MAP()宏来实现对消息的处理。

其中BEGIN_MESSAGE_MAP(参数1,参数2),参数1为该类的类名,参数2为该类基类的类名。

#define DECLARE_MESSAGE_MAP() \
protected: \
    static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
    virtual const AFX_MSGMAP* GetMessageMap() const; \

struct AFX_MSGMAP
 {
     const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
     const AFX_MSGMAP_ENTRY* lpEntries;
 };
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_PTR nSig;       // signature type (action) or pointer to message #
   AFX_PMSG pfn;    // routine to call (or special value)
 };
typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void);

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
    PTM_WARNING_DISABLE \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
        { return GetThisMessageMap(); } \
    const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
    { \
        typedef theClass ThisClass;                           \
        typedef baseClass TheBaseClass;                       \
        static const AFX_MSGMAP_ENTRY _messageEntries[] =  \
        {

#define END_MESSAGE_MAP() \
        {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
    }; \
        static const AFX_MSGMAP messageMap = \
        { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
        return &messageMap; \
    }

可以通过类的成员函数GetMessageMap获取到消息映射表的信息。

在MFC中消息映射表是从CCmdTarget开始继承下去的。

会发现这和 RTTI 一样,还是一个链表。将子类的消息映射表和父类的消息映射表联系起来。

下图为 MFC 消息映射表

传递

1.如果是一般的Windows消息(wm_xx),则一定是由派生类流向基类。没有旁流的可能。

2.如果是命令消息WM_COMMAND,就有奇特的路线。

3.cwinthread 没有declare、begin、end 宏组。所以BEGIN_MESSAGE_MAP(CWinApp, CCmdTarget)是CCmdTarget而不是cwinthread,所以CWinApp可以跳过cwinthread直接连上CCmdTarget。

4.通过pbasemap 实现消息映射的继承性。即子类可以继承基类的消息。类似于虚函数,但不是虚函数的机制,可以减少额外的内存负担。

5.  在cwinapp::run 调用pumpmessage,而pumpmessage先调用GetMessage函数获得或者TranslateMessage函数,然后又调用DispatchMessage传递消息,从而把消息推送到afxwndproc,最后流向pwnd->windowproc。消息被分发到回调函数(过程函数),作用是消息传递给操作系统,然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息

在mfc2.5时代(九几年代),所有窗口类共享同一个窗口函数(即afxwndproc)。

但现在使用的是钩子技术(即hook),所以要关联hook章节一起看。

hook操作是在每一个cwnd派生类之对象产生之际发生。表示每一个从cwnd派生的类都有一个钩子函数。如frame、view。

即:每个窗口类有一个窗口消息处理函数。

所以DispatchMessage把消息推送到hook技术中的afxwndproc.然后afxwndproc -> AfxCallWndProc -> WindowProc(该函数是虚函数,可重写实现自己的内容) ->OnWndMsg(用来分辨并处理消息,

如果是命令消息,就调用OnCommand(该函数也是虚函数,好多派生类都可以重写,如cwnd、cframe);

如果是通知消息,就调用OnNotify。

而一般Windows消息,直接在消息映射表中上溯,即在OnWndMsg中可以实现)

下面图是:一般 windows 消息:直线上溯。

说明了基类的函数先执行,派生类的重写函数、或者重载函数后执行。

能够上溯,使用的技术是消息映射机制,因为在消息映射中就和基类(pbasemap)进行关联

下面图是:WM_COMMAND命令消息

在cframewnd::oncmdmsg处分3路处理。

说明:当framewnd收到wm_command时,消息传递的路线是 cview -> cdoc  -> cframewnd -> cwinapp 这样的先后顺序。

延伸:

除了message map还有 data  、dispatchmap、event map.

深入浅出MFC之6大技术 消息映射及命令传递 DECLARE_MESSAGE_MAP 和 ON_NOTIFY ON_COMMAND ON_MESSAGE 三大难点解析相关推荐

  1. MFC六大核心机制之五、六:消息映射和命令传递

    作为C++程序员,我们总是希望自己程序的所有代码都是自己写出来的,如果使用了其他的一些库,也总是千方百计想弄清楚其中的类和函数的原理,否则就会感觉不踏实.所以,我们对于在进行MFC视窗程序设计时经常要 ...

  2. MFC消息映射与命令传递

    独酌逸醉(博客搬至http://www.perfect-is-shit.com/,本博不再更新!) Keep It Simple,Stupid! 博客园 首页 博问 闪存 联系 管理 随笔-63  文 ...

  3. MFC关键技术-消息映射机制

  4. MFC消息映射与消息传递内幕

    MFC消息映射与消息传递内幕 Windows操作系统是以消息为基础,事件驱动的.作为程序员了解操作系统的消息传递机制是非常必要的.Microsoft的MFC有它自己的一套支持Windows操作系统消息 ...

  5. 深入浅出MFC学习笔记 消息

    本章将会对MFC的消息映射和 命令传递机制做深入探讨. MFC规定了消息传递的路线,消息会按照这个路线传递下去,找不到归宿的话就交给DefWindowProc. 在产生窗口之前,如果我们在创建窗口时指 ...

  6. 《深入浅出MFC》观后有感

    <深入浅出MFC>观后有感 本文原创,如需转载,请注明出处! 好几年前我曾经买过这本书,知道它是本好书,在匆匆走马观看一遍后,便将它束之高阁,后来有友人借之,不想几经辗转,最终不知我的这本 ...

  7. 深入浅出MFC 读书笔记1

    第六章 MFC程序的生死因果 1.不同类的PreCreateWindow()成员函数都是在窗口产生之前一刻被调用,准备用来注册窗口类.如果我们指定的窗口类是NULL,那么就使用系统默认类. 第九章 消 ...

  8. MFC 教程【4_消息映射的实现】

    消息映射的实现 Windows消息概述 Windows应用程序的输入由Windows系统以消息的形式发送给应用程序的窗口.这些窗口通过窗口过程来接收和处理消息,然后把控制返还给Windows. 消息的 ...

  9. 【转】MFC消息映射详解(整理转载)

    消息:主要指由用户操作而向应用程序发出的信息,也包括操作系统内部产生的消息.例如,单击鼠标左按钮,windows将产WM_LBUTTONDOWN消息,而释放鼠标左按钮将产生WM_LBUTTONUP消息 ...

  10. 【MFC】MFC消息映射(二)

    00. 目录 文章目录 00. 目录 01. 概述 02. 消息映射宏 2.1 BEGIN_MESSAGE_MAP 2.2 DECLARE_MESSAGE_MAP 2.3 END_MESSAGE_MA ...

最新文章

  1. R语言描述性统计分析:假设检验
  2. qt跟随鼠标动态绘制_采用Qt快速绘制多条曲线(折线),跟随鼠标动态显示线上点的值(基于Qt的开源绘图控件QCustomPlot进行二次开发)...
  3. java提高篇(十六)-----异常(一)
  4. php self this static,PHP 中 self、static、$this 的区别和后期静态绑定详解
  5. cocos2dx Auto-batching的使用
  6. mysql opendrowset,java,mysql、jdbc
  7. 【动态规划笔记】背包问题:搬寝室
  8. 【备忘录】sublime text的使用
  9. dependencyManagement 失效
  10. 姑娘美丽人体日志博客_60个美丽诱人的博客设计
  11. could not find function 函数名
  12. mysql2008jar包下载_sqljdbc.jar2008
  13. 通过Python爬虫技术获取小说信息
  14. element ui 表格头部内容不换行
  15. 在mysql lt_amp;lt;转amp;gt;mysql 树查询话语
  16. 程序员长见识需要关注的牛人
  17. python基础:web =html+ python
  18. l05173芯片针脚图_芯片引脚定义
  19. Soul Dance
  20. 两个数字交换的三种方法

热门文章

  1. Elasticsearch 备份数据到 AWS S3
  2. 第 4 周 关于 startActivityForResult
  3. 从Oracle Database 角度来看浪潮天梭K1主机的操作系统选择
  4. ios获取设备的唯一标识mac address
  5. Starship Troopers
  6. npm:no such file /usr/local/lib/node_modules/vue-cli/node_modules/get-stream
  7. vue 方法回调通知执行下一个方法
  8. python 随机密码生成_python生成随机密码,包含大小写和数字
  9. ES6学习笔记五(对象)
  10. 5.abp框架code first方式删除表