zoukankan      html  css  js  c++  java
  • Windows程序设计零基础自学_1_Windows程序消息循环机制

           第一次接触计算机的时候,已经是2005年,我记得当时在学校的机房还有98和2000的操作系统, 当时学C语言后,知道了怎么在cmd一样的模式下编制程序,当时一直迷惑就是怎么样编制一个和IE和Word一样具有点击鼠标操作的应用程序, 后来过了大一,大二时选择了自动化(我们学习大一不分专业)就一直没有机会学习如何编制Windows下的应用程序。

         出于兴趣,现在重新开始学习Windows下的图形界面应用程序编写。

         看了孙老师的视频教程,讲的很好,很可惜我基础太差,听不懂, 于是就从网上下载了那本经典的教程开始自学Windows程序设计。最难的是入门,入门后再加上自己的学习,编制一个简单的程序基本是不成问题的了。

          下面开始这次的闲扯, Windows消息机制:

           早DOS下编程,程序的执行过程是按照程序任意的意愿来执行的, 那时应用程序干什么,以及什么时候干什么那时由程序员说了算的, 在windows下情况就不那么妙了, windows程序是面向使用者的, windows应用程序的执行过程基本上是由使用人员的操作过程来决定的。

           windows会为应用程序建立和维护一个消息队列,这个消息队列会存储预定义的消息(windows预定义了一大堆使用者使用过程中产生的消息,反正很多,没有数,估计上万吧);当操作一个应用程序的时候,windows操作系统感知使用者的操作并将这个消息放到应用程序的消息队列,应用程序根据消息进行相应的处理。这就是大体的windows消息机制,这个是我的简单理解,如果需要理解可以参考那本经典的教材.......

    一、windows程序的过程

       windows程序的过程是:

                 程序入口

                 定义窗口类

                 注册窗口类

                 生成窗口

                 更新窗口

                 显示窗口

                 消息循环

                 窗口消息处理(回调函数, 我不知道callback函数是不是回调函数, 不过从call back上看估计就是......哈哈哈  )

                 程序执行实体结束

    1、windows程序的入口:

         与Dos下的程序入口不一样,windows下的程序入口如下:

    int WINAPI WinMain(HINSTANCE hInstance,  //本程序的实例句柄
    				   HINSTANCE hPrevInstance,  //程序的前一个实例的句柄
    				   LPSTR lpCmdLine,  
    				   int iCmdShow)
    其中WINAPI是一个预定义的宏:
    #define WINAPI __stdcall
    这个宏定义指明函数的参数的入栈和出栈顺序。

    2、 定义窗口类型:

         windows程序主要是以窗口为对象的, 应用程序的一个按钮可以称作窗口,一个滚动条和状态条同样是窗口,而整个程序显示出来的也是窗口。在windows为窗口定义了一个结构体:

         typedef struct tagWNDCLASSA

    {
        UINT        style;
        WNDPROC     lpfnWndProc;
        int         cbClsExtra;
        int         cbWndExtra;
        HINSTANCE   hInstance;
        HICON       hIcon;
        HCURSOR     hCursor;
        HBRUSH      hbrBackground;
        LPCSTR      lpszMenuName;
        LPCSTR      lpszClassName;
    } WNDCLASSA;

    typedef WNDCLASSA WNDCLASS;

    为了要使用窗口需要定义窗口变量(结构体变量);如:

    Exp:

         WNDCLASS wndclass;

    定义完后需要进行初始化,如下:

       wndclass.cbClsExtra =0;   //声明程序额外空间用变量
        wndclass.cbWndExtra=0;    //额外变量
        wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //GetStockObject()获取一个对象,这里为获取一个画刷对象
                                                                   //hbrBackGround字段名称中的hbr前缀代表画刷句柄,画刷是个绘图词汇,指
                                                                   //用来填充一个区域的着色样式,Windows有几个标准的画刷,也称为备用Stock画刷
                                                                   //这里的GetStockObject()函数呼叫返回一个白色画刷句柄,这就表示窗口的显示区域
                                                                   //背景完全为白色
        wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);  //加载光标
                                                      //LoadCursor()函数第一个参数是句柄,第二个参数是要加载的光标类型
        wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //加载程序图标
                                                       //LoadIcon()函数第一个参数是句柄,第二个参数是要加载的图标类型,这里加载预定义图标
                                                       //对于预定义的图标,第二个参数是IDI开头的标识符,在Winuser.h中定义
                                                       //当要加载用户自定义图标时,第一个参数必须设定为程序执行实体的句柄hInstance
        wndclass.hInstance=hInstance;   //系统分配给应用程序的实例句柄
        wndclass.lpfnWndProc=WndProc;    //a pointer which point to the main window's callback function函数的指针
        wndclass.lpszClassName=szAppName;  //窗口名称,创建窗口的时候需要用到
        wndclass.lpszMenuName=NULL; //菜单资源名称
        wndclass.style =CS_HREDRAW | CS_VREDRAW;  //窗口的类型,或者风格

    3、注册窗口类

         为了可以显示一个窗口,必须先向windows注册一个窗口类, 以此来通知windows,应用程序需要用这样一个窗口类来显示。在windows中通过 RegisterClass函数来注册窗口类。如下:

    Exp:

        //注册窗口类型
        if(!RegisterClass(&wndclass))                 
            //int registerclass(WNDCALSS *wndclass)
            //函数注册窗口类型,这个函数有两个类型定义
            //RegisterClassA:  返回一个指向WNDCLASSA结构的指针
            //RegisterClassW:  返回一个指向WNDCLASSW结构的指针
            //如果用定义的UNICODE标识符编译了程序,程序将呼叫RegisterClassW,改程序可以在NT上执行,但是在98上就不能真正执行,
            //函数有一个进入点,但是会执行错误,返回一个0值,这就是为什么要进行判断的原因,
            //为了防止错误,就需要判断后退出程序。
        {
            MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR);
            return 0;
        }

    4、生成窗口

        一个窗口里面包含很多的内容,在计算机里面需要很多的内存空间来保存这些内容。同时在注册窗口类型后,并没有生成一个窗口实体,就像有了一个模具,但是还没有用模具制作工件一样。为了得到工件我们需要一个制作工件的过程。在这里就是需要生成工件,如下:

         //生成窗口
        hwnd=CreateWindow(szAppName,   //窗口类名, 在匈牙利命名规则下以sz字符开头的标识符表示的是以'\0'结束的字符串
                          TEXT("The Hello Program"), //窗口的标题栏的提示
                          WS_OVERLAPPEDWINDOW, //窗口样式,标注样式窗口,具有标题栏,并且标题栏有缩小、放大和关闭按钮。
                                                //WS_OVERLAPPEDWINDOW,其实几个位旗标的组合值
                                                // #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | \
                                                //                              WS_CAPTION | \
                                                //                              WS_SYSMENU | \
                                                //                              WS_THICKFRAME | \
                                                //                              WS_MINIMIZEBOX | \
                                                //                              WS_MAXIMIZEBOX )
                          CW_USEDEFAULT,  //初始化窗口的X轴起始位置,
                          CW_USEDEFAULT,  //初始化窗口的Y轴起始位置
                          CW_USEDEFAULT,  // initial x size 初始的X方向的大小
                          CW_USEDEFAULT,  // initial y size 初始的Y方向的大小
                          NULL, //父窗口的句柄,通常子窗口显示在父窗口的前面
                                //应用程序的窗口显示在桌面的上面,应用程序不必为呼叫CreateWindow函数获取桌面的句柄
                          NULL, //窗口的菜单句柄
                          hInstance, //程序的执行实例句柄
                          NULL //附加参数信息
                          );
          //在调用窗口产生函数createwindow函数后,系统会为窗口分配内存,用来保存createwindow函数制定的窗口信息和其他信息,
          //createwindow返回的句柄值是程序后面获取存储在内存信息的指针值。

    5、显示和更新窗口

        在制作完工件后,我们需要将它拿出来才会有用;在windows下,生成窗体后,他并不会立即显示,而是在内存中占用了一段空间,我们需要利用一个工具将它显示在桌面: showwindow和updatewindow。如下:

       ShowWindow(hwnd,iCmdShow);  //显式窗口,两个参数,第一个参数是CreateWindow函数返回的窗口句柄
                                     //第二个参数,确定窗口如何显示在屏幕上,例如:最大化、最小化还是一般化
                                     //通常iCmdShow: SW_SHOWNORMAL
                                     //               SW_SHOWMAXIMIZED
                                     //               SW_SHOWMINNOACTIVE   窗口仅显示在工作列上
                                     //如果icmdshow=sw_shownormal,则窗口的显示区域会被窗口类别中定义的背景画刷所覆盖。

      UpdateWindow(hwnd);  //更新窗口, 利用updatewindow函数会重画显示区域,这里是通过给窗口处理函数发送一个WM_PAINT消息完成这一过程

    6、消息循环

         窗口显示到桌面上后,程序的使用者对窗口进行操作,会产生一大堆的消息, 应用程序通过消息循环从消息队列取出消息,然后进行处理,就形成了应用程序的处理过程。

    消息循环通过下面的样式进行处理:

    Exp:

         //消息循环
         //windows为每一个应用程序维护一个消息队列,在发生输入事件后,windows将事件转化为一个消息并将消息放到消息队列中
         //应用程序通过消息循环取系统发给应用程序的消息,并对之做出响应
         //消息结构体MSG
         //
         //  typedef  struct tagMSG
         //         {
         //             HWND hwnd;   接受消息的窗口的句柄
         //             UINT message;  消息标识符,表示具体的消息信息
         //             WPARAM wParam;  32位的附加消息参数。根据消息的不同而不同
         //             LPARAM lParam;  32位的附加消息参数,其值与消息有关
         //             DWORD time; 消息放入消息队列的时间
         //             POINT pt; 消息放入消息队列时鼠标的坐标
         //         }
         //   MSG, *PMSG;
         //其中POINT也是一个结构体
         //  typedef struct tagPOINT
         //        {
         //           LONG x;
         //           LONG y;
         //         }
         //  POINT, *PPOINT;
         while(GetMessage(&msg,NULL,0,0))
             //getmessage函数取得windows系统发送给程序的消息,每次从消息队列里面取一个值
             //第二、第三、第四个参数设定为NULL,0,0 表示程序接收自己建立的所有窗口的所有消息
             //当从消息队列取回的MSG结构体的message子段位WM_QUIT消息是,GetMessage就返回一个 0 值
             //否则就返回非零值
             //在匈牙利命名法中:
             //                  WM_  开头的标识符表示的是消息
         {
             TranslateMessage(&msg); // 将消息结构体MSG回传给Windows,进行一些键盘转化
             DispatchMessage(&msg);  //将msg结构体回传给Windows。然后,Windows将该消息发送给适当的窗口处理程序,让其进行处理
                                     //即: Windows呼叫回调函数,回调函数处理完消息后,返回到Windows,此时
                                     // Windows还停留在DispatchMessage呼叫中,在结束DispatchMessage呼叫处理之后,Windows回到程序执行
                                     //实体,并进行下一轮的消息循环
         }
         return msg.wParam;
    }

    7、窗口消息处理函数

        前面说到应用程序可以取得消息,但是我们看到消息循环并不对消息进行处理,因此为了处理消息,我们就需要建立一种机制,这就是窗口消息处理程序。

    我们通过下面的方式来进行窗口消息处理:

    Exp:

       LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
            //关于窗口处理函数的参数的意义
            //第一个参数:HWND hwnd, 表示的是接收消息的窗口的句柄,这个句柄值与CreateWindow函数返回的值相等
            //若用同一WNDCLASS建立多个窗口,则hwnd标识特定的某个窗体
            //第二个参数:UINT message, 为标识窗体的数值,最后两个参数都是32位的消息的附加信息参数
            //程序本身通常自己不呼叫窗口消息处理程序,而由系统呼叫窗口消息处理函数
            //通过SendMessage()函数可以实现程序自己呼叫窗口消息处理函数
            //Windows程序写作者使用switch和case结构处理消息; 窗口消息处理程序在处理消息时,必须回传一个0.
            //窗口消息处理程序不予处理的所有消息应该被传给名为DefWindowProc的Windows函数,而且从DefWindowProc传回的值必须由窗口消息处理程序传回
    {
        HDC hdc;
        PAINTSTRUCT ps;
        RECT rect;

        switch (message)
        {
        //case WM_CREATE:
       
        case WM_PAINT:
            hdc=BeginPaint(hwnd,&ps);
            GetClientRect(hwnd,&rect);
            DrawText(hdc,TEXT("Hello win"),-1,&rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER);
            EndPaint(hwnd,&ps);
            return 0; //消息处理完后必须返回0值到系统
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        }
        return DefWindowProc(hwnd,message,wParam,lParam);  //这个函数的返回值必须由窗口消息处理程序返回到系统
    }

        我们看不到这个函数的调用,但是他是怎么执行的呢?是这样的: 当使用者进行操作后,windows操作系统感知这个操作,然后将消息投递到消息队列,同时windows会呼叫用户编制的窗口消息处理程序。

       Tip:

             注意是windows呼叫用户编制的窗口消息处理程序。

    通过我自己的写的一个简单的windows程序来看看一个完整的windows应用程序:

    // 第一个Win32 Program
    
    #include <windows.h>
    LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);  //declare a callback function
    
    int WINAPI WinMain(HINSTANCE hInstance,   //本程序的实例句柄
    				   HINSTANCE hPrevInstance,  //程序的前一个实例的句柄
    				   LPSTR lpCmdLine,  //
    				   int iCmdShow)
    {
    	static TCHAR szAppName[]=TEXT("Hellowin"); //   这个就是窗口的caption
    	HWND hwnd;  //声明句柄变量
    	MSG msg;    //声明消息结构体 定义消息结构体变量
    	
    	WNDCLASS wndclass; //声明窗口结构体变量
         
    	//声明定义窗口结构体变量后初始化结构体
    	wndclass.cbClsExtra =0;   //声明程序额外空间用变量
    	wndclass.cbWndExtra=0;    //额外变量
    	wndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); //GetStockObject()获取一个对象,这里为获取一个画刷对象
    	                                                           //hbrBackGround字段名称中的hbr前缀代表画刷句柄,画刷是个绘图词汇,指
    	                                                           //用来填充一个区域的着色样式,Windows有几个标准的画刷,也称为备用Stock画刷
    	                                                           //这里的GetStockObject()函数呼叫返回一个白色画刷句柄,这就表示窗口的显示区域
    	                                                           //背景完全为白色
    	wndclass.hCursor=LoadCursor(NULL,IDC_ARROW);  //加载光标
    	                                              //LoadCursor()函数第一个参数是句柄,第二个参数是要加载的光标类型
    	wndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //加载程序图标
    	                                               //LoadIcon()函数第一个参数是句柄,第二个参数是要加载的图标类型,这里加载预定义图标
    	                                               //对于预定义的图标,第二个参数是IDI开头的标识符,在Winuser.h中定义
    	                                               //当要加载用户自定义图标时,第一个参数必须设定为程序执行实体的句柄hInstance
    	wndclass.hInstance=hInstance;   //系统分配给应用程序的实例句柄
    	wndclass.lpfnWndProc=WndProc;    //a pointer which point to the main window's callback function函数的指针
    	wndclass.lpszClassName=szAppName;  //窗口名称,创建窗口的时候需要用到
    	wndclass.lpszMenuName=NULL; //菜单资源名称
    	wndclass.style =CS_HREDRAW | CS_VREDRAW;  //窗口的类型,或者风格
    
    	//注册窗口类型
    	if(!RegisterClass(&wndclass))                  
    		//int registerclass(WNDCALSS *wndclass)
    		//函数注册窗口类型,这个函数有两个类型定义
    		//RegisterClassA:  返回一个指向WNDCLASSA结构的指针
    		//RegisterClassW:  返回一个指向WNDCLASSW结构的指针
    		//如果用定义的UNICODE标识符编译了程序,程序将呼叫RegisterClassW,改程序可以在NT上执行,但是在98上就不能真正执行,
    		//函数有一个进入点,但是会执行错误,返回一个0值,这就是为什么要进行判断的原因,
    		//为了防止错误,就需要判断后退出程序。
    	{
    		MessageBox(NULL,TEXT("This program requires Windows NT!"),szAppName,MB_ICONERROR);
    		return 0;
    	}
    
    	 //生成窗口
    	hwnd=CreateWindow(szAppName,   //窗口类名, 在匈牙利命名规则下以sz字符开头的标识符表示的是以'\0'结束的字符串
    		              TEXT("The Hello Program"), //窗口的标题栏的提示
    					  WS_OVERLAPPEDWINDOW, //窗口样式,标注样式窗口,具有标题栏,并且标题栏有缩小、放大和关闭按钮。
    					                        //WS_OVERLAPPEDWINDOW,其实几个位旗标的组合值
    											// #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | \ 
    											//                              WS_CAPTION | \
    											//                              WS_SYSMENU | \
    											//                              WS_THICKFRAME | \
    											//                              WS_MINIMIZEBOX | \
    											//                              WS_MAXIMIZEBOX )
    					  CW_USEDEFAULT,  //初始化窗口的X轴起始位置,
    					  CW_USEDEFAULT,  //初始化窗口的Y轴起始位置
    					  CW_USEDEFAULT,  // initial x size 初始的X方向的大小
    					  CW_USEDEFAULT,  // initial y size 初始的Y方向的大小
    					  NULL, //父窗口的句柄,通常子窗口显示在父窗口的前面
    					        //应用程序的窗口显示在桌面的上面,应用程序不必为呼叫CreateWindow函数获取桌面的句柄
    					  NULL, //窗口的菜单句柄
    					  hInstance, //程序的执行实例句柄
    					  NULL //附加参数信息
    					  );
    	  //在调用窗口产生函数createwindow函数后,系统会为窗口分配内存,用来保存createwindow函数制定的窗口信息和其他信息,
    	  //createwindow返回的句柄值是程序后面获取存储在内存信息的指针值。
    
    	 ShowWindow(hwnd,iCmdShow);  //显式窗口,两个参数,第一个参数是CreateWindow函数返回的窗口句柄
    	                             //第二个参数,确定窗口如何显示在屏幕上,例如:最大化、最小化还是一般化
    	                             //通常iCmdShow: SW_SHOWNORMAL 
    	                             //               SW_SHOWMAXIMIZED
    	                             //               SW_SHOWMINNOACTIVE   窗口仅显示在工作列上
    	                             //如果icmdshow=sw_shownormal,则窗口的显示区域会被窗口类别中定义的背景画刷所覆盖。
    
    	 UpdateWindow(hwnd);  //更新窗口, 利用updatewindow函数会重画显示区域,这里是通过给窗口处理函数发送一个WM_PAINT消息完成这一过程
        
    	 //消息循环
    	 //windows为每一个应用程序维护一个消息队列,在发生输入事件后,windows将事件转化为一个消息并将消息放到消息队列中
    	 //应用程序通过消息循环取系统发给应用程序的消息,并对之做出响应
    	 //消息结构体MSG
    	 //
    	 //  typedef  struct tagMSG
    	 //         {
    	 //             HWND hwnd;   接受消息的窗口的句柄
    	 //             UINT message;  消息标识符,表示具体的消息信息
    	 //             WPARAM wParam;  32位的附加消息参数。根据消息的不同而不同
    	 //             LPARAM lParam;  32位的附加消息参数,其值与消息有关
    	 //             DWORD time; 消息放入消息队列的时间
    	 //             POINT pt; 消息放入消息队列时鼠标的坐标
    	 //         }
    	 //   MSG, *PMSG;
    	 //其中POINT也是一个结构体
    	 //  typedef struct tagPOINT
    	 //        {
    	 //           LONG x;
    	 //           LONG y;
    	 //         }
    	 //  POINT, *PPOINT;
    	 while(GetMessage(&msg,NULL,0,0))
    		 //getmessage函数取得windows系统发送给程序的消息,每次从消息队列里面取一个值
    		 //第二、第三、第四个参数设定为NULL,0,0 表示程序接收自己建立的所有窗口的所有消息
    		 //当从消息队列取回的MSG结构体的message子段位WM_QUIT消息是,GetMessage就返回一个 0 值
    		 //否则就返回非零值
    		 //在匈牙利命名法中:
    		 //                  WM_  开头的标识符表示的是消息
    	 {
    		 TranslateMessage(&msg); // 将消息结构体MSG回传给Windows,进行一些键盘转化
    		 DispatchMessage(&msg);  //将msg结构体回传给Windows。然后,Windows将该消息发送给适当的窗口处理程序,让其进行处理
    		                         //即: Windows呼叫回调函数,回调函数处理完消息后,返回到Windows,此时
    		                         // Windows还停留在DispatchMessage呼叫中,在结束DispatchMessage呼叫处理之后,Windows回到程序执行
    		                         //实体,并进行下一轮的消息循环
    	 }
    	 return msg.wParam;
    }
    
    LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
            //关于窗口处理函数的参数的意义
    		//第一个参数:HWND hwnd, 表示的是接收消息的窗口的句柄,这个句柄值与CreateWindow函数返回的值相等
    		//若用同一WNDCLASS建立多个窗口,则hwnd标识特定的某个窗体
    		//第二个参数:UINT message, 为标识窗体的数值,最后两个参数都是32位的消息的附加信息参数
    		//程序本身通常自己不呼叫窗口消息处理程序,而由系统呼叫窗口消息处理函数
    		//通过SendMessage()函数可以实现程序自己呼叫窗口消息处理函数
    		//Windows程序写作者使用switch和case结构处理消息; 窗口消息处理程序在处理消息时,必须回传一个0.
    		//窗口消息处理程序不予处理的所有消息应该被传给名为DefWindowProc的Windows函数,而且从DefWindowProc传回的值必须由窗口消息处理程序传回
    {
    	HDC hdc;
    	PAINTSTRUCT ps;
    	RECT rect;
    
    	switch (message)
    	{
    	//case WM_CREATE:
    	
    	case WM_PAINT:
    		hdc=BeginPaint(hwnd,&ps);
    		GetClientRect(hwnd,&rect);
    		DrawText(hdc,TEXT("Hello win"),-1,&rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER);
    		EndPaint(hwnd,&ps);
    	    return 0; //消息处理完后必须返回0值到系统
    	case WM_DESTROY:
    		PostQuitMessage(0);
    		return 0;
    	}
    	return DefWindowProc(hwnd,message,wParam,lParam);  //这个函数的返回值必须由窗口消息处理程序返回到系统
    
    }
    

         执行上面的程序需要建立一个win32 应用程序工程,最好是个空的工程, 建议在VC 6.0中执行。

               

  • 相关阅读:
    Python TIPS上一道关于人民币金额小写转大写的题
    C Primer Plus--C预处理器和C库(1)
    C Primer Plus--位操作
    C Primer Plus--结构和其他数据类型(2)
    计算机缓存方式对于程序运行的影响
    7. 整数反转
    服务器初始化安装docker、docker-compose
    nacos爬坑史-1
    Kafka 报错: Error Uncaught exception in scheduled task 'flush-log'
    CentOS中docker部署的项目CPU占用率持续100%以上
  • 原文地址:https://www.cnblogs.com/volcanol/p/2078237.html
Copyright © 2011-2022 走看看