MFC教程 -- Windows界面开发

时间:2024-03-09 19:31:31

MFC教程 -- Windows界面开发

Windows消息机制 初步认识MFC

要想熟练掌握 Windows 应用程序的开发, 首先需要理解 Windows 平台下程序运行的内部机制。如果想要更好的学习掌握 MFC,必须要先了解Windows 程序的内部运行机制,为我们扫清学习路途中的第一个障碍,为进一步学习 MFC 程序打下基础。

1.1 基本概念解释

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

1.1.1 SDK和API

SDK: 软件开发工具包(Software Development Kit,一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。

API函数: Windows操作系统提供给应用程序编程的接口(Application Programming Interface)

Windows应用程序API函数是通过C语言实现的,所有主要的 Windows 函数都在 Windows.h 头文件中进行了声明。Windows 操作系统提供了 1000 多种 API 函数。

1.1.2 窗口和句柄

窗口是 Windows 应用程序中一个非常重要的元素,一个 Windows 应用程序至少要有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是 Windows 应用程序与用户进行交互的接口。利用窗口可以接收用户的输入、以及显示输出。一个应用程序窗口通常都包含标题栏、菜单栏、系统菜单、最小化框、最大化框、 可调边框,有的还有滚动条。如下图:

窗口可以分为客户区和非客户区, 如上图。 客户区是窗口的一部分, 应用程序通常在客户区中显示文字或者绘制图形。 标题栏、 菜单栏、 系统菜单、 最小化框和最大化框、 可调边框统称为窗口的非客户区, 它们由 Windows 系统来管理, 而应用程序则主要管理客户区的外观及操作。
窗口可以有一个父窗口, 有父窗口的窗口称为子窗口。除了上图所示类型的窗口外, 对话框和消息框也是一种窗口。 在对话框上通常还包含许多子窗口, 这些子窗口的形式有按钮、 单选按钮、 复选框、 组框、 文本编辑框等。

在 Windows 应用程序中, 窗口是通过窗口句柄( HWND) 来标识的我们要对某个窗口进行操作, 首先就要得到这个窗口的句柄。 句柄( HANDLE) 是 Windows 程序中一个重要的概念, 使用也非常频繁。 在 Windows 程序中, 有各种各样的资源( 窗口、 图标、光标,画刷等), 系统在创建这些资源时会为它们分配内存, 并返回标识这些资源的标识号, 即句柄。 在后面的内容中我们还会看到图标句柄( HICON)、 光标句柄( HCURSOR) 和画刷句柄( HBRUSH)。

1.1.3 消息与消息队列

Windows 程序设计是一种完全不同于传统的 DOS 方式的程序设计方法。它是一种事件驱动方式的程序设计模式,主要是基于消息的。例如,当用户在窗口中画图的时候,按下鼠标左键,此时,操作系统会感知到这一事件,于是将这个事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。在这个处理过程中,操作系统也会给应用程序“ 发送消息”。所谓“ 发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程

 

消息

在 Windows 程序中,消息是由 MSG 结构体来表示的。MSG 结构体的定义如下:

该结构体中各成员变量的含义如下:

第一个成员变量 hwnd 表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在 Windows 程序中,用 HWND类型的变量来标识窗口。

第二个成员变量 message 指定了消息的标识符。 在 Windows 中, 消息是由一个数值来表示的, 不同的消息对应不同的数值。 但是由于数值不便于记忆, 所以 Windows 将消息对应的数值定义为 WM_XXX 宏( WM 是 Window Message 的缩写) 的形式, XXX 对应某种消息的英文拼写的大写形式。 例如, 鼠标左键按下消息是 WM_LBUTTONDOWN, 键盘按下消息是 WM_KEYDOWN, 字符消息是 WM_CHAR , 等等。 在程序中我们通常都是以WM_XXX 宏的形式来使用消息的。

第三、 第四个成员变量 wParam 和 lParam,用于指定消息的附加信息。 例如, 当我们收到一个字符消息的时候,message 成员变量的值就是 WM_CHAR, 但用户到底输入的是什么字符,那么就由 wParam 和 lParam 来说明。wParam、lParam 表示的信息随消息的不同而不同。如果想知道这两个成员变量具体表示的信息,可以在 MSDN 中关于某个具体消息的说明文档查看到。WPARAM 和 LPARAM 这两种类型的定义,实际上就是 unsigned int和 long。

第五、第六个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。

消息队列

每一个 Windows 应用程序开始执行后, 系统都会为该程序创建一个消息队列, 这个消息队列用来存放该程序创建的窗口的消息。 例如, 当我们按下鼠标左键的时候, 将会产生WM_LBUTTONDOWN 消息, 系统会将这个消息放到窗口所属的应用程序的消息队列中,等待应用程序的处理。 Windows 将产生的消息依次放到消息队列中, 而应用程序则通过一个消息循环不断地从消息队列中取出消息, 并进行响应。 这种消息机制, 就是 Windows程序运行的机制。 关于消息队列和消息响应, 在后面我们还会详细讲述。

WinMain函数

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

1.2 Windows 编程模型

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

    1. WinMain函数的定义
    2. 创建一个窗口
    3. 进行消息循环
    4. 编写窗口过程函数

1.2.1 WinMain函数的定义

WinMain函数的原型声明如下:

知识点补充:

在我们以后的学习中会经常遇到以下宏定义:

#define CALLBACK    __stdcall

#define WINAPI      __stdcall

#define WINAPIV     __cdecl

#define APIENTRY    WINAPI

#define APIPRIVATE  __stdcall

#define PASCAL      __stdcall

__stdcall和__cdecl是两种函数名字修饰(注意是两个下划线),规定了函数参数的入栈方式。

相同点:

  1. __stdcall还是__cdecl函数参数都是从右向左入栈
  2. 并且由调用者完成入栈操作

不同点:

  1. __stdcall方式在函数返回前自动清空堆栈
  2. __cdecl则由调用者维护内存堆栈
  3. 由__cdecl约定的函数只能被C/C++调用。

Windows上不管是C还是C++,默认使用的都是__stdcall方式。

__cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

WinMain 函数接收 4 个参数,这些参数都是在系统调用 WinMain 函数时,传递给应用程序的。

第一个参数 hInstance 表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows 下运行时,它唯一标识运行中的实例( 注意,只有运行中的程序实例, 才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过 hInstance 参数传递给 WinMain 函数。

第二个参数 hPrevInstance 表示当前实例的前一个实例的句柄。通过查看 MSDN 我们可以知道,在 Win32 环境下,这个参数总是 NULL,即在 Win32 环境下,这个参数不再起作用。

第三个参数 lpCmdLine 是一个以空终止的字符串, 指定传递给应用程序的命令行参数。

第四个参数 nCmdShow 指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。

1.2.2 创建一个窗口

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

  1. 设计一个窗口类
  2. 注册窗口类
  3. 创建窗口
  4. 显示及更新窗口

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

据说微软开发MFC的时候为了和其它类库有所区别就在所有MFC的类库前加了一个C
结果后来被泛化了。在MFC中被封装的类中,各种类前面都加了一个C,谁能说说这个C具体代表了什么含义

如:CWinAppCDocument

Class,类的意思。

表示它是一个类,而不是别的变量……

设计一个窗口类

一个完整的窗口具有许多特征, 包括光标( 鼠标进入该窗口时的形状)、 图标、 背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前, 首先要对该型号的汽车进行设计, 在图纸上画出汽车的结构图, 设计各个零部件, 同时还要给该型号的汽车取一个响亮的名字, 例如“ 奥迪 A6”。

类似地, 在创建一个窗口前, 也必须对该类型的窗口进行设计, 指定窗口的特征。 当然, 在我们设计一个窗口时, 不像汽车的设计这么复杂, 因为 Windows 已经为我们定义好了一个窗口所应具有的基本属性, 我们只需要像考试时做填空题一样, 将需要我们填充的部分填写完整, 一种窗口就设计好了。 在 Windows 中, 要达到作填空题的效果, 只能通过结构体来完成, 窗口的特征就是由 WNDCLASS 结构体来定义的。 WNDCLASS 结构体的定义如下:

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

      1. CS_HREDRAW

当窗口水平方向上的宽度发生变化时, 将重新绘制整个窗口。 当窗口发生重绘时, 窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。

      1. CS_VREDRAW

当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。

      1. CS_NOCLOSE

禁用系统菜单的 Close 命令,这将导致窗口没有关闭按钮。

      1. CS_DBLCLKS

当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。

第二个成员变量 lpfnWndProc 是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。

针对 Windows 的消息处理机制, 窗口过程函数被调用的过程如下(了解即可):

  1. 在设计窗口类的时候,将窗口过程函数的地址赋值给 lpfnWndProc 成员变量。
  2. 调用 RegsiterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。
  3. 当应用程序接收到某一窗口的消息时,调用 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是一个以空终止的字符串,指定窗口类的名字。

核心代码如下:

注册窗口类

设计完窗口类( WNDCLASS) 后, 需要调用 RegisterClass 函数对其进行注册,注册成功后, 才可以创建该类型的窗口。 注册函数的原型声明如下:

ATOM RegisterClass(CONST WNDCLASS *lpWndClass);

该函数只有一个参数, 即上一步骤中所设计的窗口类对象的指针。

核心代码:

    RegisterClass(&wc);

创建窗口

设计好窗口类并且将其成功注册之后, 就可以用 CreateWindow 函数产生这种类型的窗口了。 CreateWindow 函数的原型声明如下:

参数 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。注意,在创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。

核心代码:   

显示及更新窗口

  1. 显示窗口

窗口创建之后,我们要让它显示出来,这就跟汽车生产出来后要推向市场一样。调用函数 ShowWindow 来显示窗口,该函数的原型声明如下所示:

BOOL ShowWindow( HWND hWnd, int nCmdShow );

ShowWindow 函数有两个参数, 第一个参数 hWnd 就是在上一步骤中成功创建窗口后返回的那个窗口句柄;第二个参数 nCmdShow 指定了窗口显示的状态,常用的有以下几种。

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

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

BOOL UpdateWindow( HWND hWnd );

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

到此,一个窗口就算创建完成了。

1.2.3 消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用 GetMessage()函数,该函数的原型声明如下:

参数 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 应用程序的消息处理机制如下图所示:

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

知识点补充:

进队列消息和不进队列消息

Windows 程序中的消息可以分为“ 进队消息” 和“ 不进队消息”。

  1. 不进队消息是指由Windows直接调用消息处理函数,把消息直接交给其处理。
  2. 进队消息是指Windows将消息放入到程序中的消息队列中,并通过程序中的消息循环,循环把消息取出,经过一定处理(如例子中经过translate),然后由函数DispathMessage函数将消息分发给消息处理函数处理。

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

  1. 击键的消息(WM_KEYDOWN、WM_KEYUP)
  2. 键盘输入产生字符(WM_CHAR)
  3. 鼠标移动(WM_MOUSEMOVE)
  4. 鼠标左键(WM_LBUTTONDOWN)
  5. 计时消息(WM_TIMER)
  6. 刷新消息(WM_PAINT)
  7. 退出消息(WM_QUIT)

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

发送消息:SendMessage 和 PostMessage

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

1.2.4 窗口过程函数

在完成上述步骤后,剩下的工作就是编写一个窗口过程函数, 用于处理发送给窗口的消息。 一个 Windows 应用程序的主要代码部分就集中在窗口过程函数中。窗口过程函数的声明形式,如下所示:

窗口过程函数的名字可以随便取, 如 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);

    }

  1. DefWindowProc函数

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

中,并将该函数的返回值作为窗口过程函数的返回值。

  1. WM_CLOSE 消息

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

  1. WM_DESTROY消息

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

1.2.5 Windows编程模型

 

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

窗口过程一般要调用其他函数来帮助处理接收到的消息。它可以调用应用程序自己的函数,也可以调用Windows程序提供的API函数。应用程序不能处理的消息被传递给了名为DefWindowProc的API函数,该函数对未被处理的消息提供默认响应。

  1. Windows风格程序 – Hello MFC

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

    1. WM_PAINT 消息:

当窗口客户区的一部分或者全部变为“ 无效” 时, 系统会发送 WM_PAINT 消息,通知应用程序重新绘制窗口。当窗口刚创建的时候, 整个客户区都是无效的。因为这个时候程序还没有在窗口上绘制任何东西,当调用 UpdateWindow 函数时,会发送 WM_PAINT 消息给窗口过程,对窗口进行刷新。当窗口从无到有、改变尺寸、最小化后再恢复、被其他窗口遮盖后再显示时, 窗口的客户区都将变为无效, 时系统会给应用程序发送 WM_PAINT 消息,通知应用程序重新绘制。

    1. BeginPaint、EndPaint 函数

BeginPaint 函数的第 1 个参数是窗口的句柄,第二个参数是 PAINTSTRUCT 结构体的指针,该结构体对象用于接收绘制的信息。在 调 用 BeginPaint 时,如果客户区的背景还没有被擦除, 那么 BeginPaint 会 发 送WM_ERASEBKGND 消息给窗口, 系统就会使用 WNDCLASS 结构体的 hbrBackground 成员指定的画刷来擦除背景。

在响应 WM_PAINT 消息的代码中, 要得到窗口的 DC, 必须调用 BeginPaint 函数。BeginPaint 函数也只能在 WM_PAINT 消息的响应代码中使用, 在其他地方, 只能使用GetDC 来得到 DC 的句柄。 另外, BeginPaint 函数得到的 DC, 必须用 EndPaint 函数去释放。

    1. TextOut函数   

调用 TextOut 函数在(300, 300) 的位置输出一个字符串“Hello,MFC!”。当发生重绘时,窗口中的文字和图形都会被擦除。在擦除背景后,TextOut 函数又一次执行,在窗口中再次绘制出 “Hello,MFC!”。 这个过程对用户来说是透明的,用户并不知道程序执行的过程,给用户的感觉就是你在响应 WM_PAINT 消息的代码中输出的文字或图形始终保持在窗口中。换句话说,如果我们想要让某个图形始终在窗口中显示, 就应该将图形的绘制操作放到响应 WM_PAINT 消息的代码中。那么系统为什么不直接保存窗口中的图形数据, 而要由应用程序不断地进行重绘呢?

这主要是因为在图形环境中涉及的数据量太大,为了节省内存的使用,提高效率,而采用了重绘的方式。

程序运行结果:

uploading.4e448015.gif

正在上传…重新上传取消

1.3 初步认识MFC

1.3.1 MFC是什么?

微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。

MFC把Windows SDK API函数包装成了几百个类,MFC给Windows操作系统提供了面向对象的接口,支持可重用性、自包含性以及其他OPP原则。MFC通过编写类来封装窗口、对话框以及其他对象,引入某些关键的虚函数(覆盖这些虚函数可以改变派生类的功能)来完成,并且MFC设计者使类库带来的总开销降到了最低。

1.3.2 编写第一个MFC应用程序

两个重要的MFC类:

    1. CWinApp     应用程序类
    2. CFrameWnd   窗口框架类
  1. CFrameWnd 框架窗口类

MFC的CWnd类以及其派生类为 窗口 或 应用程序创建的窗口 提供了面向对象的接口。CFrameWnd就是从CWnd派生出来的。CFrameWnd模仿框架窗口行为,我们可以把框架窗口作为顶层窗口看待,它是应用程序与外部世界的主要接口。

如果想要创建一个窗口,可以在此类中调用Create或CreateEX函数,并且在CWinApp::InitInstance中创建一个框架窗口对象,并通过ShowWindow函数将其显示出来。

CFrameWnd::Create原型如下:

uploading.4e448015.gif

正在上传…重新上传取消

Create接收的8个参数6个有默认值定义。我们只需要为函数的前两个参数指定值,剩下六个参数接收默认值。第一个参数lpszClassName指定了窗口基于WNDCLASS类的名称,为此将其设定为NULL将创建一个基于已注册的WNDCLASS类的默认框架窗口。lpszWindowName参数指定将在窗口的标题栏出现的文字。

  1. CWinApp应用程序类 

MFC应用程序的核心就是基于CWinApp类的应用程序对象。CWinApp提供了消息循环来检索消息并将消息调度给应用程序窗口。它还包括可被覆盖的、用来自定义应用程序行为的主要虚函数。一旦包含Afxwin.h,就可以将CWinApp以及其他MFC类引入应用程序中一个MFC程序可以有且仅有一个应用程序对象,此对象必须声明为在全局范围内有效,以便它在程序开始时即在内存中被实例化。

    1. InitInstance函数

CWinApp::InitInstance函数是一个虚函数,其默认操作仅有一条语句:

return TRUE;

InitInstance的目的是给应用程序提供一个自身初始化的机会,其返回值决定了框架接下来要执行的内容,如果返回FALSE将关闭应用程序,如果初始化正常返回TRUE以便允许程序继续进行。此函数是MFC应用程序的入口。

    1. m_pMainWnd 成员变量

在 CWinApp 中有一个名为 m_pMainWnd 的成员变量。 该变量是一个 CWnd 类型的指针,它保存了应用程序框架窗口对象的指针。也就是说,是指向 CFramWnd 对象(框架窗口类对象)的指针。

  1. 程序代码实现

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

1.4 消息映射

消息映射是一个将消息和成员函数相互关联的表。比如,框架窗口接收到一个窗口绘制消息,MFC将搜索该窗口的消息映射,如果存在一个处理WM_PAINT消息的处理程序,然后就调用OnPaint。MFC为执行消息映射在内部所做的工作隐藏在某些十分复杂的宏当中,但使用消息映射是相当简单的。下面是是将消息映射添加到一个类中所做的全部工作:

  1. 通过将DECLARE_MESSAGE_MAP添加到类声明中,声明消息映射。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 通过放置标识消息的宏来执行消息映射,相应的类将在对BEGIN_MESSAGE_MAP和END_MESSAGE_MAP的调用之间处理消息。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 添加成员函数来处理消息

uploading.4e448015.gif

正在上传…重新上传取消

1.5 窗口绘制

我们的程序如果想要随心所欲的在屏幕上进行绘制,必须响应来自windows的WM_PAINT消息,此消息通知它该更新窗口了。

WM_PAINT消息的发生可能有多重原因:由于移动了窗口;由于窗口原先被覆盖的部分显露了出来;或者由于窗口大小改变了等等。不论诱因是什么,都需要由应用程序来负责通过响应WM_PAINT消息绘制其窗口的客户区。由Windows来绘制非客户区,这样所有的应用程序将具有一致的外观。如果应用程序不为客户区执行其自身的绘制例程,窗口的内部将是一片空白。

在我们的上面的示例程序中,WM_PAINT消息是由CMainWindow::OnPaint来处理的,当一个WM_PAINT消息抵达时都将调用它。OnPaint的绘制是通过构造一个名为dc的CPaintDC对象开始的:

    CPaintDC dc(this);

MFC的CPaintDC类是从CDC类派生出来的。CDC类封装了Windows设备环境,以及包含了绘制屏幕、打印机和其他设备的几十个成员函数。CPaintDC只在WM_PAINT消息处理程序中使用,它是CDC的一个特殊的例子。如果想在屏幕上进行绘制,就必须在OnPaint程序内使用CPaintDC对象。

1.6 文档/视图结构体系

MFC应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象保存应用程序的数据,并依靠视图对象控制视图中显示的数据,把数据本身与它的显示分离开。数据的存储和加载由文档类来完成,数据的显示和修改则由视类来完成。 MFC在类CDocument和CView中为稳定视图提供了基础结构。CWinApp、CFrameWnd和其他类与CDocument和CView合作,把所有的片段连在了一起。

当然我们可以不使用文档/视图来编写MFC程序,但是想要从框架结构中获得最大的好处并利用MFC的某些高级特性就必须使用文档/视图体系结构。此处的文档是对程序数据的抽象表示,不能理解为其只对文字或电子表格程序有用。

CView类也派生与CWnd类,框架窗口是视图窗口的一个父窗口。主框架窗口(CFrameWnd)是整个应用程序外框所包括的部分,即图中粗框以内的内容,而视类窗口只是主框架中空白的地方。