MFC界面编程基础(01):Windows 编程模型

上一篇:MFC界面编程基础(00):基本概念 下一篇:MFC界面编程基础(02):MFC窗口的创建

WinMain函数

当 Windows 操作系统启动一个程序时,它调用的就是该程序的 WinMain 函数( 实际是由插入到可执行文件中的启动代码调用的)。 WinMain 是 Windows程序的入口点函数,与 DOS 程序的入口点函数 main 的作用相同,当 WinMain 函数结束或返回时,Windows 应用程序结束。

Windows 编程模型

一个完整的Win32程序,该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序的实现步骤为:

  • WinMain函数的定义
  • 创建一个窗口
  • 进行消息循环
  • 编写窗口过程函数

WinMain函数的定义

WinMain函数的原型声明如下:
MFC界面编程基础(01):Windows 编程模型_第1张图片

知识点补充:
在我们以后的学习中会经常遇到以下宏定义:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
__stdcall和__cdecl是两种函数名字修饰(注意是两个下划线),规定了函数参数的入栈方式
相同点:

  • __stdcall还是__cdecl函数参数都是从右向左入栈
  • 并且由调用者完成入栈操作
    不同点:
  • __stdcall方式在函数返回前自动清空堆栈
  • __cdecl则由调用者维护内存堆栈
  • 由__cdecl约定的函数只能被C/C++调用。
    Windows上不管是C还是C++,默认使用的都是__stdcall方式。
    WinMain 函数接收 4 个参数,这些参数都是在系统调用 WinMain 函数时,传递给应用程序的。

第一个参数 hInstance 表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows 下运行时,它唯一标识运行中的实例( 注意,只有运行中的程序实例, 才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过 hInstance 参数传递给 WinMain 函数。
第二个参数 hPrevInstance 表示当前实例的前一个实例的句柄。通过查看 MSDN 我们可以知道,在 Win32 环境下,这个参数总是 NULL,即在 Win32 环境下,这个参数不再起作用
第三个参数 lpCmdLine 是一个以空终止的字符串, 指定传递给应用程序的命令行参数。
第四个参数 nCmdShow 指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。

创建一个窗口

创建一个完整的窗口,需要经过下面几个步骤:

  • 设计一个窗口类
  • 注册窗口类
  • 创建窗口
  • 显示及更新窗口

下面详细对创建窗口的过程进行介绍:

1. 设计一个窗口类

一个完整的窗口具有许多特征, 包括光标( 鼠标进入该窗口时的形状)、 图标、 背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前, 首先要对该型号的汽车进行设计, 在图纸上画出汽车的结构图, 设计各个零部件, 同时还要给该型号的汽车取一个响亮的名字, 例如“ 奥迪 A6”。
类似地, 在创建一个窗口前, 也必须对该类型的窗口进行设计, 指定窗口的特征。 当然, 在我们设计一个窗口时, 不像汽车的设计这么复杂, 因为 Windows 已经为我们定义好了一个窗口所应具有的基本属性, 我们只需要像考试时做填空题一样, 将需要我们填充的部分填写完整, 一种窗口就设计好了。 在 Windows 中, 要达到作填空题的效果, 只能通过结构体来完成, 窗口的特征就是由 WNDCLASS 结构体来定义的。 WNDCLASS 结构体的定义如下:
MFC界面编程基础(01):Windows 编程模型_第2张图片

第一个成员变量 style 指定这一类型窗口的样式,常用的样式如下:

  • CS_HREDRAW
    当窗口水平方向上的宽度发生变化时, 将重新绘制整个窗口。 当窗口发生重绘时, 窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。
  • CS_VREDRAW
    当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。
  • CS_NOCLOSE
    禁用系统菜单的 Close 命令,这将导致窗口没有关闭按钮。
  • CS_DBLCLKS
    当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。

第二个成员变量 lpfnWndProc 是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。
针对 Windows 的消息处理机制, 窗口过程函数被调用的过程如下(了解即可)

  • 在设计窗口类的时候,将窗口过程函数的地址赋值给 lpfnWndProc 成员变量。
  • 调用 RegsiterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。
  • 当应用程序接收到某一窗口的消息时,调用 DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。

一个 Windows 程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联( 通过 WNDCLASS 结构体中的 lpfnWndProc 成员变量指定), 基于该窗口类创建的窗口使用同一个窗口过程。

lpfnWndProc 成员变量的类型是 WNDPROC,WNDPROC 的定义如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);

在这里又出现了两个新的数据类型 LRESULT 和 CALLBACK,它们实际上是 long 和__stdcall。从 WNDPROC 的定义可以知道, WNDPROC 实际上是函数指针类型。

第三个成员变量 cbClsExtra: 用于存储类的附加信息,一般我们将这个参数设置为 0。
第四个成员变量 cbWndExtra:窗口附加内存,一般我们将这个参数设置为 0。
第五个成员变量 hInstance 指定包含窗口过程的程序的实例句柄。
第六个成员变量 hIcon 指定窗口类的图标句柄。这个成员变量必须是一个图标资源的句柄,如果这个成员为 NULL,那么系统将提供一个默认的图标。在为 hIcon 变量赋值时,可以调用 LoadIcon 函数来加载一个图标资源,返回系统分配给该图标的句柄。 该函数的原型声明如下所示:
HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName)
第七个成员变量 hCursor 指定窗口类的光标句柄。 这个成员变量必须是一个光标资源的句柄, 如果这个成员为 NULL, 那么无论何时鼠标进入到应用程序窗口中, 应用程序都必须明确地设置光标的形状。在为 hCursor 变量赋值时,可以调用 LoadCursor 函数来加载一个光标资源, 返回系统分配给该光标的句柄。该函数的原型声明如下所示:
HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
第八个成员变量 hbrBackground 指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们可以调用 GetStockObject 函数来得到系统的标准画刷。 GetStockObject 函数的原型声明如下所示:
HGDIOBJ GetStockObject( int fnObject);
GetStockObject 函数不仅可以用于获取画刷的句柄, 还可以用于获取画笔、字体和调色板的句柄。由于 GetStockObject 函数可以返回多种资源对象的句柄,在实际调用该函数前无法确定它返回哪一种资源对象的句柄,因此它的返回值的类型定义为 HGDIOBJ, 在实际使用时,需要进行类型转换。
第九个成员变量 lpszMenuName 是一个以空终止的字符串, 指定菜单资源的名字。如果将lpszMenuName 成员设置为 NULL,那么基于这个窗口类创建的窗口将没有默认的菜单。要注意,菜单并不是一个窗口,很多初学者都误以为菜单是一个窗口。
第十个成员变量 lpszClassName是一个以空终止的字符串,指定窗口类的名字。

核心代码如下:
MFC界面编程基础(01):Windows 编程模型_第3张图片

2.注册窗口类

设计完窗口类( WNDCLASS) 后, 需要调用 RegisterClass 函数对其进行注册,注册成功后, 才可以创建该类型的窗口。 注册函数的原型声明如下:
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
该函数只有一个参数, 即上一步骤中所设计的窗口类对象的指针。
核心代码:
RegisterClass(&wc);

3.创建窗口

设计好窗口类并且将其成功注册之后, 就可以用 CreateWindow 函数产生这种类型的窗口了。 CreateWindow 函数的原型声明如下:
MFC界面编程基础(01):Windows 编程模型_第4张图片

参数 lpClassName 指定窗口类的名称,即我们在步骤 1 设计一个窗口类中为 WNDCLASS的 lpszClassName 成员指定的名称。
参数 lpWindowName 指定窗口的名字。 如果窗口样式指定了标题栏, 那么这里指定的窗口名字将显示在标题栏上。
参数 dwStyle 指定创建的窗口的样式。要注意区分 WNDCLASS 中的 style 成员与 CreateWindow 函数的 dwStyle 参数, 前者是指定窗口类的样式, 基于该窗口类创建的窗口都具有这些样式, 后者是指定某个具体的窗口的样式。我们可以给创建的窗口指定WS_OVERLAPPEDWINDOW类型,这是一种多种窗口类型的组合类型。
参数 x,y,nWidth,nHeight 分别指定窗口左上角的 x,y 坐标,窗口的宽度,高度。如果参数 x 被设为 CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略 y 参数。如果参数 nWidth 被设为 CW_USEDEFAULT, 那么系统为窗口选择默认的宽度和高度, 参数 nHeight 被忽略。
参数 hWndParent 指定被创建窗口的父窗口句柄。
参数 hMenu 指定窗口菜单的句柄。
参数 hInstance 指定窗口所属的应用程序实例的句柄。
参数 lpParam:作为 WM_CREATE 消息的附加参数 lParam 传入的数据指针。
在创建多文档界面的客户窗口时, lpParam 必须指向 CLIENTCREATESTRUCT 结构体。多数窗口将这个参数设置为 NULL。

如果窗口创建成功,CreateWindow 函数将返回系统为该窗口分配的句柄,否则,返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。

核心代码:
在这里插入图片描述

4.显示及更新窗口

显示窗口
窗口创建之后,我们要让它显示出来,这就跟汽车生产出来后要推向市场一样。调用函数 ShowWindow 来显示窗口,该函数的原型声明如下所示:
BOOL ShowWindow( HWND hWnd, int nCmdShow );
ShowWindow 函数有两个参数, 第一个参数 hWnd 就是在上一步骤中成功创建窗口后返回的那个窗口句柄;第二个参数 nCmdShow 指定了窗口显示的状态,常用的有以下几种。

  • SW_HIDE: 隐藏窗口并激活其他窗口。
  • SW_SHOW: 在窗口原来的位置以原来的尺寸激活和显示窗口。
  • SW_SHOWMAXIMIZED: 激活窗口并将其最大化显示。
  • SW_SHOWMINIMIZED: 激活窗口并将其最小化显示。
  • SW_SHOWNORMAL: 激活并显示窗口。如果窗口是最小化或最大化的状态,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。

更新窗口
在调用 ShowWindow 函数之后, 我们紧接着调用 UpdateWindow 来刷新窗口,就好像我们买了新房子,需要装修一下。UpdateWindow 函数的原型声明如下:
BOOL UpdateWindow( HWND hWnd );

其参数 hWnd 指的是创建成功后的窗口的句柄。 UpdateWindow 函数通过发送一个WM_PAINT 消息来刷新窗口, UpdateWindow 将 WM_PAINT 消息直接发送给了窗口过程函数进行处理, 而没有放到我们前面所说的消息队列里, 请读者注意这一点。 关于WM_PAINT 消息的作用和窗口过程函数, 后面我们将会详细讲解。
到此,一个窗口就算创建完成了。

消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用 GetMessage()函数,该函数的原型声明如下:
MFC界面编程基础(01):Windows 编程模型_第5张图片

参数 lpMsg 指向一个消息( MSG) 结构体,GetMessage 从线程的消息队列中取出的消息信息将保存在该结构体对象中。
参数 hWnd 指定接收属于哪一个窗口的消息。通常我们将其设置为 NULL,用于接收属于调用线程的所有窗口的窗口消息。
参数 wMsgFilterMin 指定要获取的消息的最小值,通常设置为 0。
参数 wMsgFilterMax 指定要获取的消息的最大值。如果 wMsgFilterMin 和 wMsgFilter Max 都设置为 0, 则接收所有消息。

GetMessage 函数接收到除 WM_QUIT 外的消息均返回非零值。对于 WM_QUIT 消息,该函数返回零。如果出现了错误,该函数返回-1,例如,当参数 hWnd 是无效的窗口句柄或 lpMsg 是无效的指针时。

通常我们编写的消息循环代码如下:

MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
	TranslateMessage(&msg);
	DispatchMessage(&msg);
}

前面已经介绍了,GetMessage 函数只有在接收到 WM_QUIT 消息时,才返回 0。此时while 语句判断的条件为假,循环退出,程序才有可能结束运行。在没有接收到 WM_QUIT消息时,Windows 应用程序就通过这个 while 循环来保证程序始终处于运行状态。
TranslateMessage 函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用 GetMessage 函数时被取出。当我们敲击键盘上的某个字符键时, 系统将产生 WM_KEYDOWN 和 WM_KEYUP 消息。 这两个消息的附加参数( wParam 和 lParam) 包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的 ASCII 码,TranslateMessage 这个函数就可以将 WM_KEYDOWN 和 WM_KEYUP 消息的组合转换为一条 WM_CHAR 消息( 该消息的 wParam 附加参数包含了字符的 ASCII 码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。

DispatchMessage 函数分派一个消息到窗口过程,由窗口过程函数对消息进行处理。DispachMessage 实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理( 响应)。
Windows 应用程序的消息处理机制如下图所示:
MFC界面编程基础(01):Windows 编程模型_第6张图片

①操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
②应用程序在消息循环中调用 GetMessage 函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用 TranslateMessage 产生新的消息。
③应用程序调用 DispatchMessage,将消息回传给操作系统。消息是由 MSG 结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此, DispatchMessage 函数总能进行正确的传递。
④系统利用 WNDCLASS 结构体的 lpfnWndProc 成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理( 即“ 系统给应用程序发送了消息”)。

知识点补充:
进队列消息和不进队列消息
Windows 程序中的消息可以分为“ 进队消息” 和“ 不进队消息”。
不进队消息是指由Windows直接调用消息处理函数,把消息直接交给其处理。
进队消息是指Windows将消息放入到程序中的消息队列中,并通过程序中的消息循环,循环把消息取出,经过一定处理(如例子中经过translate),然后由函数DispathMessage函数将消息分发给消息处理函数处理。

进队消息基本上是用户的输入比如:

  • 击键的消息(WM_KEYDOWN、WM_KEYUP)
  • 键盘输入产生字符(WM_CHAR)
  • 鼠标移动(WM_MOUSEMOVE)
  • 鼠标左键(WM_LBUTTONDOWN)
  • 计时消息(WM_TIMER)
  • 刷新消息(WM_PAINT)
  • 退出消息(WM_QUIT)

一般情况下,不进队消息的产生是由于调用了其他Windows函数。如,当调用CreateWindow时,Windows将创建WM_CREATE消息、当调用ShowWindow时,将产生WM_SIZE和WM_SHOWWINDOW消息、当调用UpdateWindow时创建的WM_PAINT消息(注意,并不是某个类型是进队消息就永远是进队消息,如WM_PAINT有进队的,也有不进队的)、还有其他进队消息也有可能在不进队消息中出现,整个处理过程是复杂的,但由于Windows已经解决大部分的问题,因此我们可以认为我们获得的消息是有序的、同步的。

发送消息:SendMessage 和 PostMessage
SendMessage发送“不进队消息”,直接把消息发送给窗口,并调用该窗口的窗口过程函数进行处理。在窗口过程对消息处理完毕后,返回处理结果。
PostMessage发送“进队消息”。将消息放入与创建窗口的线程相关联的消息队列后立即返回。

窗口过程函数

在完成上述步骤后,剩下的工作就是编写一个窗口过程函数, 用于处理发送给窗口的消息。 一个 Windows 应用程序的主要代码部分就集中在窗口过程函数中。窗口过程函数的声明形式,如下所示:
MFC界面编程基础(01):Windows 编程模型_第7张图片

窗口过程函数的名字可以随便取, 如 WinSunProc, 但函数定义的形式必须和上述声明的形式相同。系统通过窗口过程函数的地址( 指针) 来调用窗口过程函数, 而不是名字
WindowProc 函数的 4 个参数分别对应消息的窗口句柄、消息代码、消息代码的两个附加参数。一个程序可以有多个窗口,窗口过程函数的第 1 个参数 hwnd 就标识了接收消息的特定窗口。在窗口过程函数内部使用 switch/case 语句来确定窗口过程接收的是什么消息,以及如何对这个消息进行处理。

switch(uMsg)
	{
		case WM_LBUTTONDOWN:
			break;
		case WM_DESTROY: 
			PostQuiteMessage(0);
			break;
		case WM_CLOSE:
			DestroyWindow(hWnd);
			break;
		……
		default:
			return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
  • DefWindowProc函数

DefWindowProc函数调用默认的窗口过程,对应用程序没有处理的其他消息提供默认处理。对于大多数的消息,应用程序都可以直接调用 DefWindowProc函数进行处理。在编写窗口过程时,应该将 DefWindowProc 函数的调用放到 default 语句中,并将该函数的返回值作为窗口过程函数的返回值

  • WM_CLOSE 消息

对 WM_CLOSE 消息的响应并不是必须的,如果应用程序没有对该消息进行响应, 系统将把这条消息传给 DefWindowProc 函数而 DefWindowProc 函数则调用 DestroyWindow 函数来响应这条 WM_CLOSE 消息。

  • WM_DESTROY消息

DestroyWindow 函数在销毁窗口后,会给窗口过程发送 WM_DESTROY消息,我们在该消息的响应代码中调用 PostQuitMessage 函数。PostQuitMessage函数向应用程序的消息队列中投递一条 WM_QUIT 消息并返回。我们在前边介绍过,GetMessage 函数只有在收到 WM_QUIT 消息时才返回 0,此时消息循环才结束,程序退出。要想让程序正常退出, 我们必须响应 WM_DESTROY 消息,并在消息响应代码中调用PostQuitMessage,向应用程序的消息队列中投递 WM_QUIT 消息。传递给 PostQuitMessage函数的参数值将作为 WM_QUIT 消息的 wParam 参数,这个值通常用做 WinMain 函数的返回值。

Windows编程模型

MFC界面编程基础(01):Windows 编程模型_第8张图片

Windows程序使用的事件驱动的编程模型,应用程序通过处理操作系统发送来的消息来响应事件。事件可能是一次击键,鼠标单击或是要求窗口更新的命令以及其他事情。Windows程序的进入点函数WinMain,但是大多数操作是在称为窗口过程的函数中进行的。窗口过程函数处理发送给窗口的消息。WinMain函数创建该窗口并进入消息循环,即获取消息或将其调度给窗口过程。消息被检索之前处于消息队列中等待。一个典型的应用程序的绝大部分操作是在响应它收到的消息,除了等待下一个消息到达以外,它几乎什么也不做。
窗口过程一般要调用其他函数来帮助处理接收到的消息。它可以调用应用程序自己的函数,也可以调用Windows程序提供的API函数。应用程序不能处理的消息被传递给了名为DefWindowProc的API函数,该函数对未被处理的消息提供默认响应。

Windows风格程序 – Hello MFC

MFC界面编程基础(01):Windows 编程模型_第9张图片
MFC界面编程基础(01):Windows 编程模型_第10张图片
MFC界面编程基础(01):Windows 编程模型_第11张图片
程序运行结果:
MFC界面编程基础(01):Windows 编程模型_第12张图片
WM_PAINT 消息
当窗口客户区的一部分或者全部变为“ 无效” 时, 系统会发送 WM_PAINT 消息,通知应用程序重新绘制窗口。当窗口刚创建的时候, 整个客户区都是无效的。因为这个时候程序还没有在窗口上绘制任何东西,当调用 UpdateWindow 函数时,会发送 WM_PAINT 消息给窗口过程,对窗口进行刷新。当窗口从无到有、改变尺寸、最小化后再恢复、被其他窗口遮盖后再显示时, 窗口的客户区都将变为无效, 时系统会给应用程序发送 WM_PAINT 消息,通知应用程序重新绘制。
BeginPaint、EndPaint 函数
BeginPaint 函数的第 1 个参数是窗口的句柄,第二个参数是 PAINTSTRUCT 结构体的指针,该结构体对象用于接收绘制的信息。在 调 用 BeginPaint 时,如果客户区的背景还没有被擦除, 那么 BeginPaint 会 发 送WM_ERASEBKGND 消息给窗口, 系统就会使用 WNDCLASS 结构体的 hbrBackground 成员指定的画刷来擦除背景。
在响应 WM_PAINT 消息的代码中, 要得到窗口的 DC, 必须调用 BeginPaint 函数。BeginPaint 函数也只能在 WM_PAINT 消息的响应代码中使用, 在其他地方, 只能使用GetDC 来得到 DC 的句柄。 另外, BeginPaint 函数得到的 DC, 必须用 EndPaint 函数去释放。
TextOut函数
调用 TextOut 函数在(300, 300) 的位置输出一个字符串“Hello,MFC!”。当发生重绘时,窗口中的文字和图形都会被擦除。在擦除背景后,TextOut 函数又一次执行,在窗口中再次绘制出 “Hello,MFC!”。 这个过程对用户来说是透明的,用户并不知道程序执行的过程,给用户的感觉就是你在响应 WM_PAINT 消息的代码中输出的文字或图形始终保持在窗口中。换句话说,如果我们想要让某个图形始终在窗口中显示, 就应该将图形的绘制操作放到响应 WM_PAINT 消息的代码中。那么系统为什么不直接保存窗口中的图形数据, 而要由应用程序不断地进行重绘呢?
这主要是因为在图形环境中涉及的数据量太大,为了节省内存的使用,提高效率,而采用了重绘的方式。

底层窗口实现完整代码

#include 

//窗口过程函数
LRESULT CALLBACK DealMessage(
	HWND hWnd, //窗口句柄,消息所属的窗口
	UINT uMsg,	//消息标志,具体消息名称,WM_xxx 消息名
	WPARAM wParam, //附加信息(键盘)
	LPARAM lParam //附加信息(鼠标)
	)
{
	switch (uMsg)
	{
	case WM_LBUTTONDOWN: //鼠标左键按下
		MessageBox(hWnd, TEXT("ABC"), TEXT("鼠标左键按下"), MB_OK);
		break;
	case WM_PAINT://绘图
	{
		 PAINTSTRUCT ps; //绘图结构体
		 HDC dc = BeginPaint(hWnd, &ps); //开始
		 TCHAR * p = L"aaaaaaa"; //转为宽字节
		 TextOutW(dc, 30, 30, p, wcslen(p));

		 EndPaint(hWnd, &ps);
		 break;
	}
	case WM_CLOSE://关闭窗口
		//所有xxxWindow为结尾的方法,不会进入消息队列中,而是直接执行
		DestroyWindow(hwnd);//DestroyWindow发送另一个消息 WM_DESTROY
		break;
	case WM_DESTROY:
		PostQuitMessage(2); //WM_QUIT, 给主函数 return msg.wParam,当获取消息时接收到WM_QUIT就回退出
		break;
	case WM_KEYDOWN://键盘
		MessageBox(hWnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
	default:
		//返回值用默认处理方式
		return DefWindowProc(hWnd, uMsg, wParam, lParam); 
	}


	return 0;
}


//WINAPI: 代表 _stdcall 参数的传递顺序,修饰函数,规定函数参数入栈从右往左,函数调用完毕,自动清理堆栈
//CALLBACK
int WINAPI WinMain(
	HINSTANCE hInstance, //应用程序实例
	HINSTANCE hPrevInstance, //上一个应用程序实例,不用
	LPSTR lpCmdLine, //命令行参数
	int nCmdShow //窗口显示的样式, 最大化,最小化
	)
{
	/*
	1、定义入口函数WinMain()
	2、创建一个窗口
	a)设计窗口类WNDCLASS(给成员变量赋值)
	b)注册窗口类
	c)创建窗口类
	d)显示和更新窗口
	3、消息循环
	4、窗口过程函数
	*/

	//a)设计窗口类WNDCLASS(给成员变量赋值)
	WNDCLASS wc;
	wc.cbClsExtra = 0; //类的附加信息
	wc.cbWndExtra = 0; //窗口附加信息
						//获取系统默认的白色画刷
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色,必须写

	//wc.hCursor = NULL;//使用默认
	wc.hCursor = LoadCursor(NULL, IDC_HELP); //加载系统默认光标,如果第一个参数为NULL,代表使用系统提供的光标
	//wc.hIcon = NULL; 
	wc.hIcon = LoadIcon(NULL, IDI_WARNING); //加载系统默认图标,如果第一个参数为NULL,代表使用系统提供的图标

	wc.hInstance = hInstance; //应用程序实例,传入WinMain中的形参即可
	wc.lpfnWndProc = DealMessage; //窗口过程函数名字,消息处理函数

	wc.lpszClassName = TEXT("abc"); //类的名字
	wc.lpszMenuName = NULL; //菜单名字
	wc.style = nCmdShow; //显示风格

	//b)注册窗口类, 告诉系统窗口过程函数的入口地址
	RegisterClass(&wc);

	//c)创建窗口类
	
	/*
	HWND CreateWindow	(  
	LPCTSTR lpClassName,  // registered class name (类名)
	  LPCTSTR lpWindowName, // window name (标题名) 
	  DWORD dwStyle,        // window style (风格)
	  int x,                // horizontal position of window (显示横坐标)
	  int y,                // vertical position of window (显示纵坐标)
	  int nWidth,           // window width  (宽)
	  int nHeight,          // window height (高)
	  HWND hWndParent,      // handle to parent or owner window (父窗口,默认NULL)
	  HMENU hMenu,          // menu handle or child identifier (菜单,默认NULL)
	  HINSTANCE hInstance,  // handle to application instance (实例句柄,前面为 hInstance)
	  LPVOID lpParam        // window-creation data (附加值,不加为NULL)
	  );
	*/
	
	HWND hWnd = CreateWindow(
	TEXT("abc"), 
	TEXT("hello, windwos"), 
	WS_OVERLAPPEDWINDOW,
	CW_USEDEFAULT, 
	CW_USEDEFAULT, 
	CW_USEDEFAULT, 
	CW_USEDEFAULT,
	NULL, 
	NULL, 
	hInstance, 
	NULL);

	//d)显示和更新窗口
	ShowWindow(hWnd, SW_SHOWNORMAL);
	UpdateWindow(hWnd);

	//消息循环
	/*
	typedef struct tagMSG {     // msg  
    HWND   hwnd;	//主窗口句柄      
    UINT   message;	//具体消息名称
    WPARAM wParam;	//附加消息 键盘消息
    LPARAM lParam;	//附加消息 鼠标消息(判断左右键)
    DWORD  time;	//消息产生时间
    POINT  pt;	//附加消息 鼠标消息(返回点击的位置的坐标XY)
		} MSG;			
	*/
	/*
	BOOL GetMessage(  
	LPMSG lpMsg,         // message information(消息)
  	HWND hWnd,           // handle to window(捕获窗口,填NULL代表捕获所有的窗口)
  	UINT wMsgFilterMin,  // first message(最小和最大过滤消息,一般填入0,代表捕获所有消息)  
  	UINT wMsgFilterMax   // last message()
  );	
	*/
	
	
	MSG msg;
	//如果接受到WM_QUIT, 返回0, 退出
	//如果出错,返回-1,不退出
	while (GetMessage(&msg, NULL, 0, 0))
	{
		//翻译工作(比如组合件Ctrl+C,需要先翻译)
		//虚拟按键转为标准字符WM_CHAR
		TranslateMessage(&msg);

		//给操作系统分发消息
		DispatchMessage(&msg);
	}

	return msg.wParam;

}
上一篇:MFC界面编程基础(00):基本概念 下一篇:MFC界面编程基础(02):MFC窗口的创建

你可能感兴趣的:(MFC界面编程基础(01):Windows 编程模型)