• 《windows程序设计》第三章学习心得


    第三章是基于对一个windows窗口的学习,来达到对windows程序运行机制的理解。

    从语言的角度看消息机制,Windows给程序发消息的本质就是调用“窗口过程”函数。
    Don't Call Me, I'll Call You!
    主动激励,被动响应。

    /*------------------------------------------------------------
       HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
                     (c) Charles Petzold, 1998
      ------------------------------------------------------------*/
    
    #include <windows.h>
    #include <string.h>
    #pragma comment(lib, "winmm.lib")
    #pragma warning(disable : 4996)
     
    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;//LRESULT等价于LONG WPARAM等价于UINT LPARAM等价于LONG
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow)
    {
         static TCHAR szAppName[] = TEXT ("HelloWin") ;
         HWND         hwnd ;//窗口句柄
         MSG          msg ;//消息结构
         WNDCLASS     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 = szAppName ;
    
         if (!RegisterClass (&wndclass))//注册窗口类
         {
              MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                          szAppName, MB_ICONERROR) ;
              return 0 ;
         }
         
    	 //基于窗口类创建窗口
         hwnd = CreateWindow (szAppName,                  // window class name
                              TEXT ("阡陌"), // window caption
                              WS_OVERLAPPEDWINDOW,        // window style
                              CW_USEDEFAULT,              // initial x position
                              CW_USEDEFAULT,              // initial y position
                              CW_USEDEFAULT,              // initial x size
                              CW_USEDEFAULT,              // initial y size
                              NULL,                       // parent window handle
                              NULL,                       // window menu handle
                              hInstance,                  // program instance handle
                              NULL) ;                     // creation parameters
         //发送WM_CREATE消息
    	ShowWindow (hwnd, iCmdShow) ;
    	//在屏幕显示窗口 
    	UpdateWindow (hwnd) ;
    	//指明窗口对其自身进行重绘 发送WM_PAINT消息 
    	while (GetMessage (&msg, NULL, 0, 0))//从消息队列获取消息 
    	{ 
    		TranslateMessage (&msg) ;//翻译键盘消息 
    		DispatchMessage (&msg) ;//将消息发送给窗口过程 
    	} 
    	return msg.wParam ;
    }
    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    { 
    	HDC hdc ;//设备环境句柄 
    	PAINTSTRUCT ps ;//绘制句柄 
    	RECT rect ;//矩形结构 
    	wchar_t str[1000] = {0};
    	wcscpy(str, TEXT("Hello")); 
    	switch (message) 
    	{ 
    		case WM_CREATE: 
    			PlaySound (TEXT ("ka.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP) ;//播放声音文件 
    			return 0; 
    		case WM_PAINT: 
    			hdc = BeginPaint (hwnd, &ps) ;//标明窗口绘制开始 
    			GetClientRect (hwnd, &rect) ;//获取窗口客户区的尺寸 
    			DrawText (hdc, str, -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;//显示文本字符串 
    			EndPaint (hwnd, &ps) ;//结束窗口绘制 
    			return 0 ; 
    		case WM_DESTROY: 
    			PostQuitMessage (0) ;//将退出消息插入消息队列 
    			return 0 ; 
    	} 
    	return DefWindowProc (hwnd, message, wParam, lParam) ;//执行默认的消息处理
    }


    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    在Win32中,wParam lParam是用来传递消息数据的最常用的手段.
    比如,对按键消息来说,鼠标的X和Y的坐标被压缩进lParam中
    对MFC来说,消息可以用多样的类型参数来传递,对用户自定义消息
    来说,只能用wParam和lParam来传递。
    在Win32 SDK中消息本身是作为一个结构体记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。这个记录类型叫做MSG,它在window中是这样声明的:
    typedef struct tagMSG {     // msg  
       HWND hwnd;    //窗口句柄
       UINT message;   //消息常量标识符
       WPARAM wParam;  //32位消息的特定附加信息,具体表示什么处决于message
       LPARAM lParam;  //32位消息的特定附加信息,具体表示什么处决于message
       DWORD time;    //消息创建时的时间
       POINT pt;       //消息创建时的鼠标位置
    } MSG;


    hwnd 接收消息的32位窗口句柄。窗口可以是任何类型的屏幕对象,
    因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。
    message 用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。
    wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
    lParam 通常是一个指向内存中数据的指针。
    --------------------------------------------------------------------------------------------------------------------------
    把 UpdateWindow (hwnd) ;注释掉,程序没有变化;
    把消息循环和UpdateWindow (hwnd) 一起注释掉没有客户区的“Hello”;
    只把消息循环注释掉,显示;
    说明updatewindow发出的 WM_PAINT 是不进入消息队列而直接处理。
    ------------------------------------------------------------------------------------------------------------------------
    PlaySound (TEXT ("ka.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP) ;//播放声音文件
    假如去掉SND_ASYNC | SND_LOOP会有什么后果呢?
    SND_ASYNC
    用异步方式播放声音,PlaySound函数在开始播放后立即返回。
    SND_LOOP
    重复播放声音,必须与SND_ASYNC标志一块使用。
    无论是PlaySound(SND_ASYNC)还是MCI(MM_MCINOTIFY)都必须异步方式执行播放声音文件代码,否则只能等到声音播放完成,才开始处理其他消息。
    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    队列消息与非队列消息:

     1、消息能够被分为「队列化的」和「非队列化的」。队列化的消息是由Windows放入程序消息队列中的。在程序的消息循环中,重新传回并分配给窗口消息处理程序。非队列化的消息在Windows呼叫窗口时直接送给窗口消息处理程序。也就是说,队列化的消息被「发送」给消息队列,而非队列化的消息则「发送」给窗口消息处理程序。任何情况下,窗口消息处理程序都将获得窗口所有的消息--包括队列化的和非队列化的。窗口消息处理程序是窗口的「消息中心」。


       2、 队列化消息基本上是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。队列化消息还包含时钟消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。


       3、非队列化消息则是其它消息。在许多情况下,非队列化消息来自呼叫特定的Windows函数。例如,当WinMain呼叫CreateWindow时,Windows将建立窗口并在处理中给窗口消息处理程序发送一个WM_CREATE消息。当WinMain呼叫ShowWindow时,Windows将给窗口消息处理程序发送WM_SIZE和WM_SHOWWINDOW消息。当WinMain呼叫UpdateWindow时,Windows将给窗口消息处理程序发送WM_PAINT消息。键盘或鼠标输入时发出的队列化消息信号,也能在非队列化消息中出现。


       SendMessage()与PostMessage()之间的区别是什么?
    它们两者是用于向应用程序发送消息的。PostMessagex()将消息直接加入到应用程序的消息队列中,不等程序返回就退出;而SendMessage()则刚好相反,应用程序处理完此消息后,它才返回。我想下图能够比较好的体现这两个函数的关系:

    程序详解:

    /*------------------------------------------------------------
       HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
                     (c) Charles Petzold, 1998
      ------------------------------------------------------------*/
    
    #include <windows.h>
    
    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;//窗口回调函数声明 LRESULT是函数返回值类型被宏定义为LONG
                                                           //CALLBACK说明的是函数参数的进栈顺序(从左到右)!
                                                           //4个函数参数为message结构体中的前4个参数!
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow) //主函数,int为函数返回值类型,WINAPI和CALLBACK一样都函数参数的进栈顺序!
                        //HINSTANCE hInstance 定义一个实例句柄变量,所谓句柄的概念和指针,引用差不多,但又不太一样!自己体会一下
                        //其他参数看书44页!
    {
         static TCHAR szAppName[] = TEXT ("HelloWin") ; //定义一个字符串数组szAppName[]并赋初值!这个“HelloWin”是程序的名字
                                                        //是要进操作系统的注册表的!是告诉操作系统你这个程序的名字是什么!
                                                       
         HWND         hwnd ;                            //定义窗口句柄变量!
         MSG          msg ;                             //MSG是一个结构体类型,所以msg是一个结构体变量!见书54页
         WNDCLASS     wndclass ;                        //WNDCLASS是一个结构体类型,所以wndclass是一个结构体变量!见书47,48页
    
         wndclass.style         = CS_HREDRAW | CS_VREDRAW ; //为窗口风格赋值,CS_HREDRAW 为垂直重画 CS_VREDRAW为水平重画!意思就                                                         //是如果你水平或竖直拖动窗口,他将重新显示窗口,即调用WM_PAINT消息! 
         wndclass.lpfnWndProc   = WndProc ;                 //把窗口回调函数的首地址赋给wndclass.lpfnWndProc
         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) ;  //初始化窗口的背景画刷,如果把WHITE_BRUSH改为
                                                                           //BLACK_BRUSH背景即为黑色!
         wndclass.lpszMenuName  = NULL ;                    //装载菜单,此程序没有!
         wndclass.lpszClassName = szAppName ;               //程序名字!
    
         if (!RegisterClass (&wndclass))      //向操作系统注册窗口类,也就是向操作系统申请内存!
         {
              MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                          szAppName, MB_ICONERROR) ;      //异常处理!
              return 0 ;
         }
         
         //创建窗口函数!
         hwnd = CreateWindow (szAppName,                  // window class name      应用程序在操作系统注册表中的名称
                              TEXT ("The Hello Program"), // window caption         应用程序标题栏名称
                              WS_OVERLAPPEDWINDOW,        // window style           窗口风格
                              CW_USEDEFAULT,              // initial x position     窗口显示时左上角x坐标
                              CW_USEDEFAULT,              // initial y position     窗口显示时左上角y坐标
                              CW_USEDEFAULT,              // initial x size         窗口显示时右下角x坐标
                              CW_USEDEFAULT,              // initial y size         窗口显示时右下角y坐标
                              NULL,                       // parent window handle   父窗口句柄,此程序没有
                              NULL,                       // window menu handle     菜单句柄
                              hInstance,                  // program instance handle程序实例句柄
                              NULL) ;                     // creation parameters    创建参数指针
         
         ShowWindow (hwnd, iCmdShow) ;  //第一次调用窗口回调函数Wndproc,注意Wndproc函数不是ShowWindow函数来调用,而是
                                        //ShowWindow函数向操作系统发送消息,是操作系统根据发送的消息来调用Wndproc函数!
                                        //操作系统调用Wndproc函数后并响应WM_CREATE消息!
                                        
         UpdateWindow (hwnd) ;          //刷新窗口,操作系统调用Wndproc函数,并响应WM_PAINT消息!
         
         while (GetMessage (&msg, NULL, 0, 0))// 这是所有WINDOWS程序的核心,消息循环处理过程!这里是接受消息和发送消息的地方!
         {                                    //GetMessage 函数从操作系统的消息队列中获得消息,一个一个的处理,来一个处理一个,
                                             //直到获得退出消息,也就是点击应用程序右上角的叉子!退出消息循环,并响应WM_QUIT消息
                                              
         
              TranslateMessage (&msg) ;  //翻译键盘消息
              DispatchMessage (&msg) ;   //发送消息函数,先把msg发送给操作系统,然后由操作系统再调用Wndproc函数!
         }
         return msg.wParam ;
    }//主函数结束
    
    //窗口回调函数,此函数只有声明和定义,没有调用!这说明此函数确实是由操作系统调用的!
    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)//函数的4个参数是消息结构体的前4个参数!
    {
         HDC         hdc ; //设备环境描述表句柄
         PAINTSTRUCT ps ;  //绘图结构体变量
         RECT        rect ;//矩形变量
         
         switch (message)   //message消息类型!分别处理不同的消息!
         {
         case WM_CREATE://此消息是一个应用程序发送的第一个消息,也是唯一的一次!
              PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
              return 0 ;
              
         case WM_PAINT://绘图函数,在窗口上画画儿!
              hdc = BeginPaint (hwnd, &ps) ;
              
              GetClientRect (hwnd, &rect) ;
              
              DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
                        DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
              
              EndPaint (hwnd, &ps) ;
              return 0 ;
              
         case WM_DESTROY://处理退出消息
              PostQuitMessage (0) ;//此消息直接进入消息队列的头部!
              return 0 ;
         }
         return DefWindowProc (hwnd, message, wParam, lParam) ;//处理一些闲杂以及一些不可预料的消息!
    }
    
    /*注意  1:所有的消息都由操作系统负责管理,所有的消息先进入总的系统消息队列,再由系统消息队列向各个小应用程序队列发消息!
            2:窗口回调函数Wndproc是由操作系统调用的,所有的消息进入消息循环,由消息循环把消息发到操作系统的队列中,在由操作系统
               根据消息来调用Wndproc函数,而Wndproc函数根据message的不同而调用相应的处理过程!
            3:上面这个程序你可以改造一下,在添加几个消息处理过程,比如鼠标左键按下WM_LBULLONDOWN,鼠标左键抬起WM_LBULLONUP
               定时器消息WM_TIMER,绘图消息WM_PAINT消息等等,然后把PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) 
               这个函数添加到以上各个消息处理过程中,看看结果是不是进行了相应的操作!比如,点击一下鼠标左键看看是不是发出声音了!
               可以自己是是!
            4:其实,WINDOWS程序也可以自己去创建函数!自己去自定义消息处理过程!比如,你自己定义了一个函数,想在鼠标左键点击后
               去调用这个函数,那就可以在WM_LBULLONDOWN这个消息处理过程中加上你自己定义的函数调用就可以了!
            5:上面这个程序是个非常简单的程序,但是他足以说明WINDOWS程序的消息机制!几乎所有的程序都包括上面这段代码(除了PLAYSOUND函数和
               DrawText函数),不管多么复杂的WINDOWS程序都必须包括上面这段代码,因为他是再简单不过的了,只是创建了一个窗口!但是,
               再复杂的程序无非也就是在Wndproc函数中的switch多加几个case 吗!无非就是加上什么鼠标,键盘,定时器等等,但原里都是
               一样的!他的消息处理过程都是一样的!主要是把他的消息机制弄懂,其他的都非常easy!
    */


    Keep it simple!
    作者:N3verL4nd
    知识共享,欢迎转载。
  • 相关阅读:
    [小经验]ASP.NET页面的编码问题
    mass Framework tabs插件 v2
    mass Framework lang模块 v4
    javascript模板系统 ejs v9
    mass Framework waterfall(瀑布流)插件
    mass Framework menu插件
    mass Framework pagination插件v2
    Firefox 12 Beta发布
    mass Framework pagination插件
    mass Framework switchable插件
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5834942.html
Copyright © 2020-2023  润新知