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中执行。

           

你可能感兴趣的:(windows)