MFC-第一节 源生C语言创建一个窗口

想要开发能够在windows平台上运行的应用,那么就要使用windows系统提供的API windows系统提供的api都是纯C语言的 直接用windows的api创建一个窗口,就需要大量的代码,耗时耗力 因此微软采用C++面向对象的思想把纯C语言的windows API封装成面向对象的形式,这样简化使用—-这个封装好的东西就叫MFC。 本节主要介绍,不是用MFC,即直接使用windows API如何创建一个窗口

为什么学习MFC ?

  1. 如果你是在Windows平台上做GUI开发,MFC(微软基础类库)是一个很好的选择,毕竟Windows累积用户群庞大,市场接受程度高
    1. GUI: 图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。
    2. 与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受

windows的消息机制

  1. 要想熟练掌握 Windows 应用程序的开发, 首先需要理解 Windows 平台下程序运行的内部机制。
  2. 如果想要更好的学习掌握 MFC,必须要先了解Windows 程序的内部运行机制

基本概念解释

  1. 我们在编写标准C程序的时候,经常会调用各种库函数来辅助完成某些功能:初学者使用得最多的C库函数就是printf了,这些库函数是由你所使用的编译器厂商提供的
  2. 在Windows平台下,也有类似的函数可供调用:不同的是,这些函数是由Windows操作系统本身提供的

SDK和API

  1. SDK: 软件开发工具包(Software Development Kit),一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。
  2. API函数:
    1. Windows操作系统提供给应用程序编程的接口(Application Programming Interface)。
    2. Windows应用程序API函数是通过C语言实现的,所有主要的 Windows 函数都在 Windows.h 头文件中进行了声明。
    3. Windows 操作系统提供了 1000 多种 API函数。

窗口和句柄

  1. 窗口:
    1. 窗口是 Windows 应用程序中一个非常重要的元素,一个 Windows 应用程序至少要有一个窗口,称为主窗口
    2. 窗口是屏幕上的一块矩形区域,是 Windows 应用程序与用户进行交互的接口。利用窗口可以接收用户的输入、以及显示输出。
    3. 一个应用程序窗口通常都包含标题栏、菜单栏、系统菜单、最小化框、最大化框、 可调边框,有的还有滚动条。如下图: 图4
    4. 窗口可以分为客户区和非客户区, 如上图。
      1. 客户区是窗口的一部分, 应用程序通常在客户区中显示文字或者绘制图形。
      2. 标题栏、 菜单栏、 系统菜单、 最小化框和最大化框、 可调边框统称为窗口的非客户区它们由 Windows 系统来管理, 而应用程序则主要管理客户区的外观及操作
    5. 窗口可以有一个父窗口, 有父窗口的窗口称为子窗口。除了上图所示类型的窗口外, 对话框和消息框也是一种窗口。 在对话框上通常还包含许多子窗口, 这些子窗口的形式有按钮、 单选按钮、 复选框、 组框、 文本编辑框等。(万物皆窗口
  2. 句柄
    1. 在 Windows 应用程序中, 窗口是通过窗口句柄( HWND) 来标识的。 我们要对某个窗口进行操作, 首先就要得到这个窗口的句柄。

    2. 句柄( HANDLE) 是 Windows 程序中一个重要的概念, 使用也非常频繁。(资源号

      1. 在 Windows 程序中, 有各种各样的资源( 窗口、 图标、光标,画刷等),系统在创建这些资源时会为它们分配内存, 并返回标识这些资源的标识号, 即句柄
      2. 在后面的内容中我们还会看到图标句柄( HICON)、 光标句柄( HCURSOR) 和画刷句柄( HBRUSH)。

消息与消息队列

  1. Windows 程序设计是一种完全不同于传统的 DOS 方式的程序设计方法。它是一种事件驱动方式的程序设计模式,主要是基于消息的。
  2. 每一个 Windows 应用程序开始执行后, 系统都会为该程序创建一个消息队列, 这个消息队列用来存放该程序创建的窗口的消息。
    1. 例如,当用户在窗口中画图的时候,按下鼠标左键,此时,操作系统会感知到这一事件,于是将这个事件包装成一个消息,投递到应用程序的消息队列中,等待应用程序的处理。
    2. 然后应用程序通过一个消息循环不断地从消息队列中取出消息,并进行响应、分发给操作系统处理
    3. 操作系统处理过程中,也会给应用程序“ 发送消息”。所谓“ 发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程。
    4. 如下图: 图4

WinMain函数

  1. 当Windows操作系统启动一个程序时,它调用的就是该程序的WinMain函数( 实际是由插入到可执行文件中的启动代码调用的)。
  2. WinMain是Windows程序的入口点函数,与DOS程序的入口点函数main的作用相同,当WinMain 函数结束或返回时,Windows应用程序结束。
  3. 由于是在windows平台上开发,所以因该由windows规定入口

Windows 编程模型

  1. 一个完整的Win32程序(#include <windows.h>),该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序的实现步骤为:
    1. WinMain函数的定义
    2. 创建一个窗口
    3. 进行消息循环
    4. 编写窗口过程函数

创建项目

  1. 打开vs2017->新建->项目
  2. 点击Visual C++ -> win32项目
  3. 重名项目名、选择路径->确定
  4. 选择windows应用程序、空项目(其他选项默认)->完成 (以上步骤,或者之间选择一个空项目即可,默认就是win32的)
  5. 添加源文件
    1. 右击源文件->新建项目->创建一个windows.c文件(因为底层是C语言的API)

WinMain函数的定义

int WINAPI WinMain(
	HINSTANCE hInstance,	//应用程序实例
	HINSTANCE hPrevInstance,	//上一个应用程序实例
	LPSTR lpCmdLine,		//命令行参数
	int nShowCmd);		//窗口显示的样式

创建一个窗口

  1. 创建一个完整的窗口,需要经过下面几个步骤:
    1. 设计窗口类
    2. 注册窗口类
    3. 创建窗口类
    4. 显示和更新窗口
    5. 通过循环取消息
    6. 处理消息(窗口过程)
设计窗口类
  1. 一个完整的窗口具有许多特征, 包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。
  2. 我们在生产一个型号的汽车之前, 首先要对该型号的汽车进行设计, 在图纸上画出汽车的结构图, 设计各个零部件, 同时还要给该型号的汽车取一个响亮的名字, 例如“宝马 x6”。
  3. 类似地, 在创建一个窗口前, 也必须对该类型的窗口进行设计, 指定窗口的特征。在Windows中,窗口的特征就是由WNDCLASS结构体来定义的,我们只需给WNDCLASS结构体对应的成员赋值,即可完成窗口类的设计。
  4. WNDCLASS结构体的定义如下:

     typedef struct _WNDCLASS{
     	UINT        style;
     	WNDPROC     lpfnWndProc;
     	int         cbClsExtra;
     	int         cbWndExtra;
     	HINSTANCE   hInstance;
     	HICON       hIcon;
     	HCURSOR     hCursor;
     	HBRUSH      hbrBackground;
     	LPCWSTR     lpszMenuName;
     	LPCWSTR     lpszClassName;
     } WNDCLASS;
    
    
    1. tyle:指定窗口的样式(风格),常用的样式如下:

       CS_HREDRAW : 当窗口水平方向上的宽度发生变化时, 将重新绘制整个窗口。 当窗口发生重绘时, 窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。
       CS_VREDRAW: 当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。
       CS_NOCLOSE: 禁用系统菜单的 Close 命令,这将导致窗口没有关闭按钮。
       CS_DBLCLKS: 当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。
      
    2. lpfnWndProc:指定一个窗口回调函数,是一个函数的指针。
      1. 当应用程序收到给某一窗口的消息时,就应该调用某一函数来处理这条消息。
      2. 这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。
      3. 对于一条消息,操作系统调用的是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。
      4. 回调函数的定义形式如下:

         LRESULT CALLBACK WindowProc(
         	HWND hWnd,		//信息所属的窗口句柄
         	UINT uMsg,		//消息类型
         	WPARAM wParam,	//附加信息(如键盘哪个键按下)
         	LPARAM lParam	//附加信息(如鼠标点击坐标)
         	);
        
    3. cbClsExtra:类的附加内存,通常数情况下为0。
    4. cbWndExtra:窗口附加内存,通常情况下为0。
    5. hInstance:当前实例句柄,用WinMain中的形参hInstance为其赋值。
    6. hIcon:指定窗口类的图标句柄,设置为NULL,则使用默认图标,也可用如下函数进行赋值:

       HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
       如:LoadIcon(NULL, IDI_WARNING); //第一个参数为NULL,加载系统默认图标
      
    7. hCursor:指定窗口类的光标句柄,设置为NULL,则使用默认图标,也可用如下函数进行赋值:

       HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
       如:LoadCursor(NULL, IDC_HELP); //第一个参数为NULL,加载系统默认光标
      
    8. hbrBackground:指示窗口的背景颜色,可用如下函数进行赋值:

       HGDIOBJ GetStockObject(int fnObject);
       如:GetStockObject(WHITE_BRUSH);
      
    9. lpszMenuName:指定菜单资源的名字。如果设置为NULL,那么基于这个窗口类创建的窗口将没有默认菜单。
    10. lpszClassName:指定窗口类的名字。
注册窗口类
  1. 设计完窗口类(WNDCLASS)后, 需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。
  2. 注册函数的原型声明如下:

     ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
     使用示例:RegisterClass(&wc);
    
创建窗口类
  1. 设计好窗口类并且将其成功注册之后, 即可用CreateWindow函数产生这种类型的窗口了。
  2. CreateWindow函数的原型声明如下:

     HWND CreateWindow(
         LPCTSTR lpClassName,
         LPCTSTR lpWindowName,
         DWORD dwStyle,
         int x,
         int y,
         int nWidth,
         int nHeight,
         HWND hWndParent,
         HMENU hMenu,
         HINSTANCE hInstance,
         LPVOID lpParam);
    
    • lpClassName:指定窗口类的名称,此名字必须和WNDCLASS的lpszClassName成员指定的名称一样。
    • lpWindowName:指定窗口的名字,即窗口的标题。
    • dwStyle:指定创建的窗口的样式,常指定为指WS_OVERLAPPEDWINDOW类型,这是一种多种窗口类型的组合类型。
    • x, y:指定窗口左上角的x,y坐标。如果参数x被设为CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y参数。
    • nWidth,nHeight:指定窗口窗口的宽度,高度。如果参数nWidth被设为 CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。
    • hWndParent:指定被创建窗口的父窗口句柄,没有父窗口,则设置NULL。
    • hMenu:指定窗口菜单的句柄,没有,则设置为NULL。
    • hInstance:窗口所属的应用程序实例的句柄,用WinMain中的形参hInstance为其赋值。
    • lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。通常设置为NULL。
    • 返回值说明:如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。
  3. 示例代码:

     HWND  hWnd = CreateWindow(
     TEXT("MyWin"),  //窗口类名字
     TEXT("测试"),      //窗口标题
     WS_OVERLAPPEDWINDOW,  //窗口风格	
     CW_USEDEFAULT, CW_USEDEFAULT,  //窗口x,y坐标,使用默认值
     CW_USEDEFAULT, CW_USEDEFAULT,  //窗口宽度,高度,使用默认值
     NULL,  	//无父窗口
     NULL, 	//无菜单
     hInstance, 	//应用程序实例句柄,为WinMain第1个形参
     NULL);	//附件信息,通常设置为NULL
    
显示和更新窗口
  1. 显示窗口函数原型:

     BOOL ShowWindow(HWND hWnd, int nCmdShow);
    
  2. 更新窗口函数原型:

     BOOL UpdateWindow(HWND hWnd);
    
  3. 示例代码

     ShowWindow(hWnd, SW_SHOWNORMAL); //SW_SHOWNORMAL为普通模式
     UpdateWindow(hWnd);
    
通过循环取消息
  1. 在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。
  2. 消息结构体
    1. 在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下:

       typedef struct tagMSG {
       	HWND hWnd;   
       	UINT message;   
       	WPARAM wParam;
       	LPARAM lParam;   
       	DWORD time;   
       	POINT pt;
       } MSG;
      
      • hWnd:消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。
      • message:消息的标识符,是由一个数值来表示的,不同的消息对应不同的数值。Windows将消息对应的数值定义为WM_XXX宏(WM是Windows Message的缩写)的形式, XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是 WM_CHAR……。
      • wParam: 指定消息的附加信息,如键盘按下会触发WM_KEYDOWN消息,但是,具体按下哪个按键需要wParam区分。
      • lParam:指定消息的附加信息,如鼠标左击会触发WM_LBUTTONDOWN消息,但是,具体点击的坐标需要lParam区分。
      • time:标识一个消息产生时的时间。
      • pt:表示产生这个消息时光标或鼠标的坐标。
  3. 取消息
    1. 要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下:

       BOOL GetMessage(
       	LPMSG lpMsg,
       	HWND hWnd,
       	UINT wMsgFilterMin,
       	UINT wMsgFilterMax);
      
      • lpMsg:指向一个消息结构体(MSG),GetMessage从线程的消息队列中取出的消息信息将保存在该结构体变量中。
      • hWnd:指定接收属于哪一个窗口的消息。通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。
      • wMsgFilterMin:指定消息的最小值。
      • wMsgFilterMax:指定消息的最大值。如果wMsgFilterMin和wMsgFilterMax都设置为0, 则接收所有消息。
      • 返回值说明:GetMessage函数接收到除 WM_QUIT 外的消息均返回非零值。对于WM_QUIT消息,该函数返回零。如果出现了错误,该函数返回-1,例如,当参数hWnd是无效的窗口句柄或lpMsg是无效的指针时。
  4. 建立消息循环

     MSG msg;
     while (GetMessage(&msg, NULL, 0, 0))
     {
     	TranslateMessage(&msg);
     	DispatchMessage(&msg);
     }
    
    • TranslateMessage:用于翻译、处理和转换消息并把新消息投放到消息队列中,并且此过程不会影响原来的消息队列。
    • DispatechMessage:用于把收到的消息传到窗口回调函数进行分析和处理。即将消息传递给操作系统,让操作系统调用窗口回调函数,来对信息进行处理。
  5. 消息处理机制
    1. 操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
    2. 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。
    3. 应用程序调用DispatchMessage,将消息回传给操作系统。消息是由 MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此, DispatchMessage函数总能进行正确的传递。
    4. 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理。

      图4

处理消息(窗口过程)
  1. 在完成上述步骤后,剩下的工作就是编写一个窗口过程函数,用于处理发送给窗口的消息。
  2. 窗口过程函数的名字可以随便取, 如WinProc, 但函数定义的形式必须和下面声明的形式相同:

     LRESULT CALLBACK WinProc( //CALLBACK 和WINAPI 作用一样
     HWND hWnd,		//信息所属的窗口句柄
     UINT uMsg,		//消息类型
     WPARAM wParam,	//附加信息(如键盘哪个键按下)
     LPARAM lParam	//附加信息(如鼠标点击坐标)
     );
    
  3. 示例代码

     LRESULT CALLBACK WinProc(
     	HWND hWnd,		//信息所属的窗口句柄
     	UINT uMsg,		//消息类型
     	WPARAM wParam,	//附加信息(如键盘按键)
     	LPARAM lParam	//附加信息(如鼠标点击坐标)
     	)
     {
     	switch (uMsg)
     	{
     	case WM_KEYDOWN: //键盘按下
     		//……
     		break;
     	case WM_LBUTTONDOWN: //鼠标右键按下
     		//……
     		break;
     	case WM_PAINT: //绘图事件
     		//……
     		break;
     	case WM_DESTROY:
     		PostQuitMessage(0);
     		break;
     	case WM_CLOSE: 
     		DestroyWindow(hWnd); 
     		break;
     	default:
     		//以windows默认方式处理
     		return DefWindowProc(hWnd, uMsg, wParam, lParam);
     		break;
     	}
        
     	return 0;
     }
    
    • DefWindowProc函数:DefWindowProc函数调用默认的窗口过程,对应用程序没有处理的其他消息提供默认处理。
    • WM_CLOSE:对WM_CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc函数而 DefWindowProc函数则调用DestroyWindow函数来响应这条WM_CLOSE消息。
    • WM_DESTROY:DestroyWindow函数在销毁窗口后,会给窗口过程发送 WM_DESTROY消息,我们在该消息的响应代码中调用PostQuitMessage函数。

    • PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回。WinMain函数中,GetMessage 函数只有在收到WM_QUIT消息时才返回0,此时消息循环才结束,程序退出。传递给 PostQuitMessage函数的参数值将作为WM_QUIT消息的wParam参数,这个值通常用做WinMain函数的返回值。

示例代码

#include<Windows.h>

//6. 处理消息(窗口过程) windowproc函数的实现
/*
CALLBACK: 代表__stdcall,代表参数传递的顺序,从右往左依次入栈,并且在函数返回前自动清空堆栈内容
*/
LRESULT CALLBACK windowProc(
	HWND hWnd,  //句柄,消息所处的窗口句柄
	UINT uMsg,//具体的消息名称
	WPARAM wParam,//键盘附加消息
	LPARAM lParam //鼠标附加消息
) {
	//处理消息
	switch (uMsg)
	{
	case WM_CLOSE://关闭窗口

		DestroyWindow(hWnd);//销毁当前窗口
		break;
	case WM_DESTROY://退出应用程序
		//这个方法会让GetMessage返回值为0,那么就退出下面的while(1)循环了。
		PostQuitMessage(0);//发送退出消息
		break;
	case WM_LBUTTONDOWN: //鼠标左键按下
	{
		int xpos = 0;
		int ypos = 0;
		xpos = LOWORD(lParam);
		ypos = HIWORD(lParam);
		//打印操作
		TCHAR buf[1024];
		wsprintf(buf, TEXT("x=%d,y=%d"), xpos, ypos);
		//参数:所属窗口、显示内容、标题内容、关联按键类型
		MessageBox(hWnd, buf, TEXT("鼠标左键按下"), MB_OK);
	}
		break;
	case WM_KEYDOWN://键盘消息
		MessageBox(hWnd, TEXT("键盘按下"), TEXT("键盘消息"), MB_OK);
		break;
	case WM_PAINT: //绘图消息
	{
		PAINTSTRUCT ps;//绘图结构体
		//开始绘图
		//hdc就相当于画家
		HDC hdc = BeginPaint(hWnd,&ps);
		//绘制文字
		TextOut(hdc, 100, 100, TEXT("hello world !"), strlen("hello world !"));
		EndPaint(hWnd, &ps);
	}
		
		break;
	default:
		break;
	}
	//默认的处理函数
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

/*
WINAPI:__stdcall的宏定义,代表参数传递的顺序,从右往左依次入栈,并且在函数返回前自动清空堆栈内容
winmain:函数入口
hInstance:应用程序的实例句柄,相当于iOS的UIAPPlication对象
hPrevInstance:前一个应用程序的实例句柄,后期没什么作用了,基本为null
lpCmdLine: 相当于argv[],命令行参数数组
nShowCmd: 窗口的显示命令,最大化、最小化
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
	//实现底层窗口
	//1. 设计窗口类
	WNDCLASS wc;
	wc.cbClsExtra = 0;//类额外的内存,通常设置为0;
	wc.cbWndExtra = 0;//窗口额外的内存,通常也为0;
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITENESS);  //设置背景,必须设置
	wc.hCursor = LoadCursor(NULL, IDC_HAND); //设置光标,如果第一个参数为NULL,代表使用系统提供的默认光标。
	wc.hIcon = LoadIcon(NULL, IDI_WARNING); //设置图标
	wc.hInstance = hInstance;//直接使用当前的实例句柄
	wc.lpfnWndProc = windowProc; //窗口过程函数,即回调函数,可以先随便起个名
	wc.lpszClassName = TEXT("WINDOW"); //窗口的名称
	wc.lpszMenuName = NULL; //菜单名称,没有填NULL
	wc.style = 0;//窗口风格:0位默认风格
	//2. 注册窗口类
	RegisterClass(&wc);

	//3. 创建窗口类
	/*
	lpClassName :类名
	lpWindowName:窗口名称
	dwStyle: 显示风格
	x, y :窗口坐标
	nWidth: 窗口宽度 (显示时默认)
	nHeight: 窗口高度(显示时默认)
	hWndParent:父窗口
	hMenu: 菜单
	hInstance: 实例句柄
	lpParam: 其他参数
	*/
	HWND wnd = CreateWindow(wc.lpszClassName, TEXT("TEXT WINDOW"), WS_OVERLAPPEDWINDOW, CWMO_DEFAULT, CWMO_DEFAULT, CWMO_DEFAULT, CWMO_DEFAULT, NULL, NULL, hInstance, NULL);
	//4. 显示和更新窗口,SW_NORMAL:正常方式显示
	ShowWindow(wnd, SW_NORMAL);
	UpdateWindow(wnd);
	//5. 通过循环取消息
	/*
	typedef struct tagMSG {
	HWND        hwnd; //主窗口
	UINT        message; //消息名称 WM_XXX
	WPARAM      wParam;  //附加消息 键盘
	LPARAM      lParam; //附加消息 鼠标
	DWORD       time;  //消息产生时间
	POINT       pt;    //附加消息,鼠标,xy坐标
	} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

	GetMessage参数:
	_Out_ LPMSG lpMsg, //消息结构体MSG
	_In_opt_ HWND hWnd, //要捕获消息的窗口,NULL:表示捕获所有窗口的消息
	_In_ UINT wMsgFilterMin, //过滤的最小消息量
	_In_ UINT wMsgFilterMax); //过滤的最大消息量 如果都为0,捕获所有消息
	*/
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		/*BOOL isget= GetMessage(&msg, NULL, 0, 0);
		if (!isget)
		{
			break;
		}*/
		//翻译消息
		TranslateMessage(&msg);
		//获取到消息,分发消息给window系统
		DispatchMessage(&msg);
	}
	//6. 处理消息(窗口过程)

	return 0;
}
Table of Contents