牛骨文教育服务平台(让学习变的简单)
博文笔记

深入理解windows 消息机制

创建时间:2016-02-07 投稿人: 浏览次数:113

深入理解Windows消息机制

       今天我们来学一学Windows消息机制,我们知道在传统的C语音程序中,当我们需要打开一个文件时,我们可以调用fopen()函数,这个函数最后又会调用操作系统提供的函数以此来打开文件。而在Windows编程中,不仅用户可以调用系统的API函数,反之,系统也可以调用应用程序,而这些调用就是通过Windows的消息机制来实现的。Windows程序设计是一种完全不同于传统的DOS方式的程序设计方法,它是一种事件驱动的程序设计模式,主要是基于消息的。

一、那么消息究竟是What 嘞?

       消息系统对于一个win32程序来说十分重要,它是一个程序运行的动力源泉。一个消息,是系统定义的一个32位的值,他唯一的定义了一个事件,向 Windows发出一个通知,告诉应用程序某个事情发生了。例如,单击鼠标、改变窗口尺寸、按下键盘上的一个键都会使Windows发送一个消息给应用程序的消息队列(下面会讲到)中,然后应用程序再从消息队列中取出消息并进行相应的响应。在这个处理的过程中,操作系统也会给应用程序“发送消息”,而所谓的发送消息--------实际上就是操作系统调用程序中的一个专门负责处理消息的函数,这个函数称为窗口过程
        消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。例如,对于单击鼠标所产生的消息来说,这个记录中包含了单击鼠标时的坐标。这个记录类型叫做MSG,MSG含有来自windows应用程序消息队列的消息信息,在Windows中MSG结构体定义如下:

typedef struct tagMsg
{
       HWND    hwnd;            //接受该消息的窗口句柄
       UINT    message;         //消息常量标识符,也就是我们通常所说的消息号
       WPARAM  wParam;     //32位消息的特定附加信息,确切含义依赖于消息值
       LPARAM  lParam;        //32位消息的特定附加信息,确切含义依赖于消息值
       DWORD   time;            //消息创建时的时间
       POINT   pt;                  //消息创建时的鼠标/光标在屏幕坐标系中的位置
}MSG;


二、What  is消息队列?

在Windows编程中,每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该应用程序所创建的窗口的信息。例如,当我们按下鼠标右键的时候,这时会产生一个WM_RBUTTONDOWN消息,系统会自动将这个消息放进当前窗口所属的应用程序的消息队列中,等待应用程序的结束。Windows将产生的消息以此放进消息队列中,应用程序则通过一个消息循环不断的从该消息队列中读取消息,并做出响应(后面会详细讲述消息处理过程。。。。)


三、消息中的家庭成员?
        通过前面所罗列的MSG结构体,
我们是不是会对消息结构里边含有的东东有了一个比较清楚的认识呢?如果还没有,没关系!!呵呵,那么我再次对那些咋一看就会泪奔的变量做出详细的解释:
        hwnd - - - 一个32位的窗口句柄(我的PC是32 位的^_^),它表示的是消息所属的窗口。我们通常开发的程序都是窗口应用程序,一般一个消息都是和某个窗口相关联的。比如我们在某个活动窗口按下鼠标右键,此时产生的消息就是发送给该活动窗口的。窗口可以是任何类型的屏幕对象,因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。

(补充一下:“句柄”---在Windows程序中,有各种各样的资源,系统在创建这些资源的时候,都会为他们分配内存,并返回标识这些资源的标识号,这个标识号就是句柄)
       message- - - -一个消息的标识符,用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。在Windows中消息是由一个数值表示的,不同的消息对应不同的数值。但由于当这些消息种类多到足以挑战我们的IQ,所以聪明的程序开发者便想到将这些数值定义为WM_XXX宏的形式。例如,鼠标左键按下的消息--WM_LBUTTONDOWN,键盘按下消息--WM_KEYDOWN,字符消息--WM_CHAR,等等。。。。消息标识符以常量命名的方式指出消息的含义。当窗口过程接收到消息之后,他就会使用消息标识符来决定如何处理消息。例如、WM_PAINT告诉窗口过程窗体客户区被改变了需要重绘。符号常量指定系统消息属于的类别,其前缀指明了处理解释消息的窗体的类型。
        wParam和lParam- - - 用于指定消息的附加信息。例如,当我们收到一个键盘按下消息的时候,message成员变量的值就是WM_KEYDOWN,但是用户到底按下的是哪一个按键,我们就得拜托这二位,由他们来告知我们具体的信息。

time和pt- - -这俩兄弟分别被用来表示消息投递到消息队列中的时间和鼠标当前的位置,一般情况下不怎么使用(但不代表没用)


四、see see 消息标识符
        系统保留消息标识符的值在0x0000在0x03ff(WM_USER-1)范围。这些值被系统定义消息使用。应用程序不能使用这些值给自己的消息。应用程序消息从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到 0X7FFF范围的消息由应用程序自己使用;0XC000到0XFFFF范围的消息用来和其他应用程序通信,在此只是罗列一些具有标志性的消息值:
     WM_NULL---0x0000    空消息。
     0x0001----0x0087    主要是窗口消息。
     0x00A0----0x00A9    非客户区消息 
     0x0100----0x0108    键盘消息
     0x0111----0x0126    菜单消息
     0x0132----0x0138    颜色控制消息
     0x0200----0x020A    鼠标消息
     0x0211----0x0213    菜单循环消息
     0x0220----0x0230    多文档消息
     0x03E0----0x03E8    DDE消息
    0x0400             WM_USER
    0x8000             WM_APP
     0x0400----0x7FFF    应用程序自定义私有消息


五、原来消息也有分类啊
        
windows中的消息虽然很多,但是种类并不繁杂,

//*************************************

大体上有3种: 窗口消息、       ******

                        命令消息、       ******

  控件通知消息。*****

    ****************************************//
       (1)   窗口消息- - - -大概是系统中最为常见的消息,它是指由操作系统和控制其他窗口的窗口所使用的消息。例如CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息,还有我们在上面谈到的单击鼠标所产生的消息也是一种窗口消息。
       (2) 命令消息- - - - 这是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息。
       (3)   控件通知消息- - - 其实它是这样滴,当一个窗口内的子控件发生了一些事情,而这些是需要通知父窗口的,此刻它就上场啦。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框,以及Windows公共控件如树状视图、列表视图等。

例如,单击或双击一个控件、在控件中选择部分文本、操作控件的滚动条都会产生通知消息-------她类似于命令消息,那么控件通知消息就会从控件窗口发送到它的主窗口。但是这种消息的存在并不是为了处理用户命令,而是为了让主窗口能够改变控件,例如加载、显示数据。

再例如,按下一个按钮,他向父窗口发送的消息也可以看作是一个控件通知消息;单击鼠标所产生的消息可以由主窗口直接处理,然后交给控件窗口处理。其中窗口消息及控件通知消息主要由窗口类即直接或间接由CWND类派生类处理。相对窗口消息及控件通知消息而言,命令消息的处理对象范围就广得多,它不仅可以由窗口类处理,还可以由文挡类,文档模板类及应用类所处理。
    由于控件通知消息很重要的,编程者用的也比较多,但是具体的含义往往令初学者晕头转向,所以我决定把常见的几个列出来供大家参考:
按扭控件
BN_CLICKED        用户单击了按钮
 BN_DISABLE 按钮被禁止
 BN_DOUBLECLICKED  用户双击了按钮
 BN_HILITE  用/户加亮了按钮
 BN_PAINT  按钮应当重画
 BN_UNHILITE 加亮应当去掉

组合框控件
 CBN_CLOSEUP 组合框的列表框被关闭
 CBN_DBLCLK 用户双击了一个字符串
 CBN_DROPDOWN 组合框的列表框被拉出
 CBN_EDITCHANGE 用户修改了编辑框中的文本
 CBN_EDITUPDATE 编辑框内的文本即将更新
 CBN_ERRSPACE 组合框内存不足
 CBN_KILLFOCUS 组合框失去输入焦点
 CBN_SELCHANGE 在组合框中选择了一项
 CBN_SELENDCANCEL 用户的选择应当被取消
 CBN_SELENDOK 用户的选择是合法的
 CBN_SETFOCUS 组合框获得输入焦点

编辑框控件
 EN_CHANGE 编辑框中的文本己更新
 EN_ERRSPACE 编辑框内存不足
 EN_HSCROLL 用户点击了水平滚动条
 EN_KILLFOCUS 编辑框正在失去输入焦点
 EN_MAXTEXT 插入的内容被截断
 EN_SETFOCUS 编辑框获得输入焦点
 EN_UPDATE 编辑框中的文本将要更新
 EN_VSCROLL 用户点击了垂直滚动条消息含义

列表框控件
 LBN_DBLCLK 用户双击了一项
 LBN_ERRSPACE 列表框内存不够
 LBN_KILLFOCUS 列表框正在失去输入焦点
 LBN_SELCANCEL 选择被取消
 LBN_SELCHANGE 选择了另一项
 LBN_SETFOCUS 列表框获得输入焦点


/***************************

*****眼睛疼,休息下,做做眼保操吧【上下左右,左右上下,】

*****话说前面我们已经讲过了消息队列,那么OK,下面来讲讲队列消息和非队列消息。注意啦!注意啦!!队列消息和消息队列不要搞混了(此刻我想到了函数指针和指针*****函数,多么淡淡疼的问题)。FH不多说,开始吧。。。。

***************************/


六、队列消息和非队列消息
从消息的发送途径来看,Windows程序中的消息可以分成2种:队列消息和非队列消息,也有叫“进队消息”和“不进队消息”。

消息队列可以分成系统消息队列线程消息队列。系统消息队列由Windows维护,线程消息队列则由每个GUI线程自己进行维护,为避免给non-GUI现成创建消息队列,所有线程产生时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列。(本段内容貌似应该放在消息队列时讲,但个人觉得放在这里很方便理解下面的内容

(1)、队列消息送到系统消息队列,然后到线程消息队列;

        对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,还有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的相应队列,下面的事情就该由线程消息队列操心了,Windows开始忙自己的事情去了。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
     一般来讲,系统总是将消息Post在消息队列的末尾。这样保证窗口以先进先出的顺序接受消息。然而,WM_PAINT是一个例外,同一个窗口的多个 WM_PAINT被合并成一个 WM_PAINT 消息, 合并所有的无效区域到一个无效区域。合并WM_PAIN的目的是为了减少刷新窗口的次数。

(2)、非队列消息直接送给目的窗口过程。
        非队列消息将会绕过系统队列和消息队列,直接将消息发送到窗口过程,。系统发送非队列消息通知窗口,系统发送消息通知窗口。例如,当用户激活一个窗口系统发送WM_ACTIVATE,WM_SETFOCUS, and WM_SETCURSOR。这些消息通知窗口它被激活了。非队列消息也可以由当应用程序调用系统函数产生。例如,当程序调用SetWindowPos系统发送WM_WINDOWPOSCHANGED消息。一些函数也发送非队列消息,例如下面我们要谈到的函数。


七、一个简单的Win32程序

Now,让我们通过这个程序来更进一步的理解Windows消息!!! 


//一个简单的Win32应用程序

//通过这个简单的实例讲解Windows消息是如何传递的

#include <windows.h>

//声明窗口过程函数

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

//定义一个全局变量,作为窗口类名

TCHAR szClassName[] = TEXT("SimpleWin32");

//应用程序主函数

int WINAPI WinMain (HINSTANCE hInstance,
                                  HINSTANCE hPrevInstance,
                                  LPSTR szCmdLine,
                                  int iCmdShow)
{
 
/***********注意以下几步是windows窗口创建的流程*********************/
//****1.设计一个窗口类****
    //
(说明:在这里需要自己查一下 _WNDCLASS结构体,不过里边的成员就是以下被初始化的那些变量)
    WNDCLASS wndclass; 
//typedef struct _WNDCLASS{
    wndclass.style = CS_HREDRAW|CS_VREDRAW; //当窗口水平方向的宽度和垂直方向的高度变化时重绘整个窗口
    wndclass.lpfnWndProc = WndProc;//关联窗口过程函数
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;//实例句柄
    wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);//图标
    wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);//光标
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);//画刷
    wndclass.lpszMenuName  = NULL;//菜单
    wndclass.lpszClassName = szClassName;//类名称
//};
    //****2.注册窗口类
    if(!RegisterClass (&wndclass))
    {
           MessageBox (NULL, TEXT ("RegisterClass Fail!"), 
                   szClassName, MB_ICONERROR);
           return 0;
    }

    //****3.创建一个窗口
    HWND hwnd;
    hwnd = CreateWindow(szClassName,//窗口类名称
           TEXT ("The Simple Win32 Application"),//窗口标题 
           WS_OVERLAPPEDWINDOW,//窗口风格,即通常我们使用的windows窗口样式
           CW_USEDEFAULT,//指定窗口的初始水平位置,即屏幕坐标系的窗口的左上角的X坐标
           CW_USEDEFAULT,//指定窗口的初始垂直位置,即屏幕坐标系的窗口的左上角的Y坐标
           CW_USEDEFAULT,//窗口的宽度
           CW_USEDEFAULT,//窗口的高度
           NULL,//父窗口句柄
           NULL,//窗口菜单句柄
           hInstance,//实例句柄
           NULL);
    //****4.显示窗口
ShowWindow(hwnd,iCmdShow);
    //**** 5.更新窗口
    UpdateWindow(hwnd);
/***********************以上为整个窗口创建的流程**************************/

    //消息循环
    MSG msg;
    while(GetMessage(&msg,NULL,0,0))//从消息队列中取消息 
    {
           TranslateMessage (&msg);              //转换消息
           DispatchMessage (&msg);               //派发消息
    }
    return msg.wParam;
}

//消息处理函数

//参数:窗口句柄,消息,消息参数,消息参数

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    //处理感兴趣的消息
    switch (message)
    {
    case WM_DESTROY:
           //当用户关闭窗口,窗口销毁,程序需结束,发退出消息,以退出消息循环
           PostQuitMessage(0);
           return 0;
    }
    //其他消息交给由系统提供的缺省处理函数
    return ::DefWindowProc (hwnd, message, wParam, lParam);
}
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。