MFC教程 -- Windows界面开发

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)是整个应用程序外框所包括的部分,即图中粗框以内的内容,而视类窗口只是主框架中空白的地方。

uploading.4e448015.gif

正在上传…重新上传取消

我们后序会对文档/视图做详细介绍。

1.7 Widnows字符集和TEXT(_T)宏

  1. 8位的ANSI字符集 - 多字节字符集

在Windows98以及以前的版本使用8位ANSI字符集,它类似于我们程序员熟悉的ASCII字符集。

字符串表示方法:

char sz[] = “ABCDEFG”;

char *psz = “ABCDEFG”;

  1. 16位的Unicode字符集 – 宽字符集

在WindowsNT和Windows2000后开始使用16位的Unicode字符集,它是ANSI字符集的一个超集。Unicode适用于国际市场销售的应用程序,因为它包含各种各样来自非U.S.字母表的字符,比如中文,日文,韩文,西欧语言等。

字符串表示方法:

wchar_t wsz[] = L”ABCDEFG”;

wchar_t *pwsz = L”ABCDEFG”;

在字符串前加字母L表示将ANSI字符集转换成Unicode字符集。

  1. MFC中的TEXT(_T)宏

MFC中的TEXT宏可以自动适应字符类型,如果定义了预处理器程序符号_UNICODE,那么编译器将使用Unicode字符,如果没用定义该预处理器程序符号,那么编译器将使用ANSI字符。

使用TEXT宏修饰字符串常量就足以使一个应用程序完全不关心其字符集吗?

回答是并不一定,我们还需要做以下操作:

    1. 将字符声明为TCHAR类型,而不是char或wchar_t类型。如果定义了_UNICODE符号TCHAR将变为wchar_t类型。如果没用定义_UNICODE符号,TCHAR将变为普通古老的char类型。
    2. 不要使用char* 或者wchar_t* 来声明TCHAR字符串的指针,而应当使用TCHAR*,或者使用更佳的LPTSTR(指向TCHAR字符串的指针)和LPCTSTR(指向const TCHAR字符串的指针)数据类型。
    3. 不要认为一个字符只有8位宽,可以借助sizeof(TCHAR)来划分缓冲区长度。

1.8 用向导生成一个MFC应用程序

在VS中选择 文件 -- 新建 -- 项目…

uploading.4e448015.gif

正在上传…重新上传取消

选择  MFC – MFC应用程序,接下来我们创建一个单文档MFC标准类型应用程序

uploading.4e448015.gif

正在上传…重新上传取消

一路按默认值next,到最后一个页面

uploading.4e448015.gif

正在上传…重新上传取消

MFC自动为我们生成了四个类,它们的继承关系如下:

uploading.4e448015.gif

正在上传…重新上传取消

接下来就可以开启我们的MFC之旅了。。。

1.9 MFC框架中一些重要的函数

InitInstance函数

应用程序类的一个虚函数,MFC应用程序的入口,该知识点前边有讲到。

OnCreate函数

OnCreate是一个消息响应函数,是响应WM_CREATE消息的一个函数,而WM_CREATE消息是由Create函数调用的。一个窗口创建(Create)之后,会向操作系统发送WM_CREATE消息,OnCreate()函数主要是用来响应此消息的。

在MFC里面用一种消息映射的机制来响应消息,也就是可以用函数来响应相应的消息。就拿CMainFrame类来说,当窗口创建后会产生WM_CREATE消息,我们可以在OnCreate函数里实现我们要在窗口里面增加的东西,例如按扭,状态栏,工具栏等。这些子窗口一般是定义成类中的一个成员变量,因为要保证生命周期。一般以m_开头来表示成员(member)

  1. OnCreate与Create的区别
    1. Create()负责注册并产生窗口,像动态创建控件中的Create()一样,窗口创建之后会向操作系统发送WM_CREATE消息。
    2. OnCreate()不产生窗口,只是在窗口显示前设置窗口的属性如风格、位置等。
    3. OnCreate()是消息WM_CREATE的消息响应函数。

PreCreateWindow函数

PreCreateWindow 允许应用程序访问通常由CDocTemplate 类内部管理的创建进程。框架在即将创建窗口前调用 PreCreateWindow通过修改传递给 PreCreateWindow 的结构体类型参数CREATESTRUCT,应用程序可以更改用于创建窗口的属性。在产生窗口之前让程序员有机会修改窗口的外观。

当框架调用CreateEx函数创建窗口时,会首先调用PreCreateWindow函数,然后在调用CreateWindowEx函数完成窗口的创建。PreCreateWindow的参数CREATESTRUCT结构体中的字段与CreateWindowEx函数参数是一致的,只是顺序相反而已。

  1. CREATESTRUCT结构体成员与CreateWindowEx参数对比

uploading.4e448015.gif

正在上传…重新上传取消

而且PreCreateWindow函数的参数是引用类型,只要修改了结构体的值,在调用CreateWindowEx时,其参数也会发生相应改变,从而创建出一个符合我们要求的窗口。

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

备注:以上代码位于wincore.cpp文件中。

OnDraw和OnPaint

OnPaint是WM_PAINT消息的消息处理函数,在OnPaint中调用OnDraw,一般来说,用户自己的绘图代码应放在OnDraw中。

  1. OnPaint()是CWnd的类成员,负责响应WM_PAINT消息。
  2. OnDraw()是CView的成员函数,没有响应消息的功能。

当视图变得无效时(包括大小的改变,移动,被遮盖等等),Windows发送WM_PAINT消息。该视图的OnPaint 处理函数通过创建CPaintDC类的DC对象来响应该消息并调用视图的OnDraw成员函数。OnPaint最后也要调用OnDraw,因此一般在OnDraw函数中进行绘制。 通常我们不必编写重写的 OnPaint 处理成员函数。

当在View类里添加了消息处理OnPaint()时,OnPaint()就会覆盖掉OnDraw()。

知识点

extension

美 [ɪkˈstenʃ(ə)n]

英 [ɪk'stenʃ(ə)n]

  • n.延伸;延长;延期;扩大
  • 网络扩展;分机;伸展

  1. MFC 中后缀名为 Ex 的函数都是扩展函数。
  2. Afx 前缀的函数代表应用程序框架( Application Framework) 函数。 应用程序框架实际上是一套辅助我们生成应用程序的框架模型。该模型把多个类进行了一个有机的集成,可以根据该模型提供的方案来设计我们自己的应用程序。在 MFC 中, 以Afx 为前缀的函数都是全局函数,可以在程序的任何地方调用它们。

CDC 图形设备环境(DC :Device Context)

一、基本概念

SDK: 软件开发工具包

JDK:java开发工具包

IDE:   集成开发环境,VS    qt

API:应用程序接口

WINAPI: windows平台下的系统调用, windows.h, 调用系统提供的特殊接口,得到系统提供的资源

窗口:父窗口和子窗口, 客户区和非客户区

句柄:结构体变量, 控件的标志,窗口句柄HWND, 图标句柄:HICO

消息队列

消息

窗口过程函数

main()

WinMain() //WINAPI 入口地址

二、winAPI窗口程序(#include <windows.h>)

1、定义入口函数WinMain()

2、创建一个窗口

    a)设计窗口类WNDCLASS(给成员变量赋值)

    b)注册窗口类

    c)创建窗口

    c)显示和更新窗口

3、消息循环

4、窗口过程函数

MSDN: winAPI, MFC

MFC参考文档(中文)

三、第一个MFC程序(纯代码)(#include <afxwin.h>)

1)应用程序类 CWinApp

2)框架类   CFrameWnd

class MyApp: public CWinApp

{

public:

    //MFC程序的入口地址

    virtual BOOL InitInstance();

};

class MyFrame: public CFrameWnd

{

public:

    MyFrame();

private:

};

1)有且只有一个全局的应用程序类对象

2)在程序入口函数实现功能 InitInstance()

    A) 给框架类MyFrame对象动态分配空间(自动调用它的构造函数)

        a) 框架类MyFrame对象构造函数函数里创建窗口 CWnd::Create

    B) 框架类对象显示窗口 CWnd::ShowWindow

    C) 框架类对象更新窗口 CWnd::UpdateWindow

    D) 保存框架类对象指针 CWinThread::m_pMainWnd

           

事件处理:

消息映射

1、所操作类中,声明消息映射宏

    DECLARE_MESSAGE_MAP()

2、对应的.cpp 定义宏

    BEGIN_MESSAGE_MAP(MyFrame, CFrameWnd) //派生类名, 基类名

        ON_WM_LBUTTONDOWN() //消息映射入口标志

    END_MESSAGE_MAP()

   

3、对应类中,消息处理函数的声明

4、对应.cpp 消息处理函数的定义

四、根据向导创建工程

1、文档视图结构

文档:它是一个类,这个类专门来存储加载(读写)数据

视图:它是一个类,这个类专门来显示和修改数据

框架类:一个容器,这个容器装了视图

2、几个比较重要的函数

a) 应用程序类CWinApp:InitInstance(), 程序的入口地址

b) 框架类CFrameWnd:

    PreCreateWindow() 创建窗口之前调用

    OnCreate() 创建窗口后,触发WM_CREATE, 它是WM_CREATE消息的处理函数

c)视图类CView:

    OnDraw(): 绘图

    WM_PAINT消息处理函数OnPaint() 内部调用 OnDraw()

    OnPaint()和 OnDraw()同时存在,只有OnPaint()有效

3、事件的添加和删除

    a) 框架和视图的区别

        添加事件步骤:选择所需类 -> 右击 -> 属性 -> 消息 -> WM_LBUTTONDOW -> ADD

       

        框架就相当于容器, 容器放视图

        视图相当于壁纸,我们点击窗口,只有视图响应,框架被视图挡住,无法响应

       

四、字符集

ANSI 多字节(单字节)

char p[] = "abcdet"; //一个字符一个字节

unicode 宽字节, 一个字符2个字节

TCHAR *p = L"abc"; //一个字符2个字节

wcslen(p);

MFC:

TCHAR: 自动适应字节(条件编译), 相当于char

TEXT()

_T()

五、拓展

afx_xxxx: 全局函数,不属于某个类特有的

xxxEx: xxxW, 拓展

MFC命名规范:

类名和函数名字:单词首字母大小

class MyClass

{

};

void SetName()

{

}

函数形参:第二单词开始,首字母大写

isFlag

isPressTest

成员变量:

m_xxxx

m_hWnd

2. 简单绘图和文本编程

2.1 简单绘图

2.1.1 关于鼠标事件

根据我们前面学过是知识,用MFC向导生成一个单文档视图程序。在我们的主框架类中添加WM_LBUTTONDOWN消息的响应函数,具体操作如下:

uploading.4e448015.gif

正在上传…重新上传取消

从类视图中找到CMainFrame(继承自CFrameWnd),选择此类然后从属性面板中找到消息按钮

uploading.4e448015.gif

正在上传…重新上传取消 ,在消息列表中找到WM_LBUTTONDOWN消息,添加,然后看我们的工程文件中都多了些什么???

  1. 第一处:在框架类头文件中添加了鼠标左键消息函数的函数声明

uploading.4e448015.gif

正在上传…重新上传取消

  1. 第二处:在框架类cpp文件中添加了消息映射宏

uploading.4e448015.gif

正在上传…重新上传取消

  1. 第三处:在框架列cpp文件中添加了处理鼠标左键消息的函数定义:

uploading.4e448015.gif

正在上传…重新上传取消

根据前边我们学过的知识要完成MFC中的消息映射需要我们手动将这三处依次做一个添加,但是使用MFC的类向导我们很容易就能完成一个消息的添加,之后再对应生成的消息函数中做相应的处理即可。

我们再此OnLButtonDown函数中添加一个MessageBox消息,鼠标左键按下弹出一个提示框,然后执行程序。我们会惊奇的发现程序并未如我们所愿弹出消息框,why?我的世界凌乱了。。。

我们先搁置疑问,按照上述操作在视类中捕获鼠标左键消息并处理,看结果会如何?

uploading.4e448015.gif

正在上传…重新上传取消

此时弹出了对话框,说明视类捕获道理鼠标左键消息。但是为什么框架类捕获不到呢?我们前边在将文档/视图结构的时候说过,框架窗口是视窗口的父窗口,那么视类窗口就应该始终覆盖在框架类窗口之上。就好比框架窗口是一面墙,视类窗口就是墙纸,它始终挡在这面墙前边。也就是说,所有操作,包括鼠标单击、鼠标移动等操作都只能有视类窗口捕获,这也是框架类窗口为什么收不到鼠标左键消息的原因。

2.1.2 绘制线条

我们可以利用MFC提供的CClientDC类来实现这一功能,这个类跟CPaintDC一样,也派生于CDC类。CClientDC是窗口客户区的设备描述环境,它应用在WM_PAINT消息之外的消息处理函数中。我们只需要定义一个CClientDC对象,然后就可以利用该对象提供的函数进行绘图操作了。CClientDC对象构造时接收一个参数,即:要进行图像绘制的窗口指针对象。

CClientDC提供的划线函数:

  1. 绘制线条的起始点:MoveTo(CPoint p) p – 起始点坐标
  2. 绘制一条到指定点的线:LineTo(CPoint p) p – 终点坐标

在程序的视图窗口用鼠标画一条线:

在视类中定义一CPoint类型成员变量m_oldPoint,添加鼠标左键按下和弹起的消息处理函数,记录鼠标左键按下时的坐标,在鼠标弹起时,通过两个坐标点将线画到视类窗口屏幕区域。

uploading.4e448015.gif

正在上传…重新上传取消

2.1.3 绘制彩色线条

我们实现的画线功能,绘制的都是黑色的线条。这是因为设备描述表中有一个默认的黑色画笔。如果想要绘制其他颜色的线条,首先需要创建一个特定颜色的画笔,然后将此画笔选入设备描述表中,接下俩绘制的线条的颜色就由这个新画笔决定了。

我们可以通过MFC提供的类CPen来创建画笔对象。

CPen(int nPenStyle,int nWidth,COLORREF crColor);

第一个参数:指定笔的线型(实线、点线、虚线等)

第二个参数:线的宽度

第三个参数:颜色,是一个RGB宏

COLORREF RGB(BYTE bRed,BYTE bGreen,BYTE bBlue)

当构建一个GDI(图形设备接口)对象后,该对象并不会马上生效,必须选入设备描述表,它才会在以后的操作中生效。可以使用SelectObject函数把GDI对象选入设备描述表中,并且该函数会返回指向先前备选对象的指针,这主要是为了完成当前绘制之后再次利用SelectObject把先前的GDI对象选入设备描述表,以便使其恢复到先前状态。

uploading.4e448015.gif

正在上传…重新上传取消

2.1.4 绘制连续线条

如果我们想让程序实现Windows画图板那样的画线功能,应如何实现?

为了绘制连续的线条,首先需要得到线条的起点,前面我们已经实现了。然后需要捕获鼠标移动过程中的每个点,这可以通过捕获鼠标移动消息(WM_MOUSEMOVE)来实现。在此消息相应函数中,在依次捕获的各个点之间绘制一条非常短的线段,从而就可以绘制出一条连续的线条。

思路:

  1. 需要一变量来标识鼠标左键是否按下这一状态
  2. 在鼠标移动消息响应函数中,如果鼠标是按下状态就开始绘图,弹起之后停止绘图。

在视类头文件中添加私有成员变量:

    BOOL m_bDraw;

在视类构造函数中将此变量初始化为FALSE

    m_bDraw = FALSE;

当鼠标左键按下去时,在视类的OnLButtonDown函数中将此变量初始化为真

    m_bDraw = TRUE;

当鼠标左键弹起,在视类的OnLButtonUp函数中将此变量初始化为假

    m_bDrae = FALSE;

在视类的OnMouseMove函数中完成线段的绘制

uploading.4e448015.gif

正在上传…重新上传取消

CDC类中封装的绘图函数还有很多,直接通过CDC类对象调用直接使用即可,可以通过CPen来调节样式,图形函数的参数可参考具体的API使用文档。

uploading.4e448015.gif

正在上传…重新上传取消

2.1.5 使用画刷绘图

在默认情况下由Rectangle、Ellipse以及其它CDC输出函数画出的封闭图形填充着白色像素点。通过创建GDI画刷并在画图之前将它选入设备描述表可以改变图形的填充颜色。MFC的CBrush类封装了GDI画刷。

画刷有三种基本类型:单色,带阴影线,带图案。单色画刷填充的是单色,阴影线画刷采用预先定义好的交叉线图案填充图形,这中图案有六种,图案画刷用位图来填充图形。

CBrush类为每种画刷提供了一个构造函数。

  1. 单色:CBrush( COLORREF crColor );

crColor 指定画刷的前景色(RGB方式)。

  1. 阴影:CBrush( int nIndex, COLORREF crColor );

nIndex 指定画刷阴影线采用的风格,取值如下:

    1. HS_BDIAGONAL 45度的向下影线(从左到右) 
    2. HS_CROSS 水平和垂直方向以网格线作出阴影 
    3. HS_DIAGCROSS 45度的网格线阴影 
    4. HS_FDIAGONAL 45度的向上阴影线(从左到右) 
    5. HS_HORIZONTAL 水平的阴影线 
    6. HS_VERTICAL 垂直的阴影线 

creColor指定的阴影线的颜色。

  1. 图案:CBrush( CBitmap* pBitmap );

pBitmap 指向CBitmap对象的指针,该对象指定了画刷要绘制的位图。 

说明:

类CBrush一共有四个覆盖的构造函数。不带参数的那个构造函数构造一个未初始化的CBrush对象,在使用该对象之前需要另外初始化。

如果使用了不带参数的那个构造函数,则必须用CreateSolidBrush、CreateHatchBrush、CreateBrushIndirect、CreatePatternBrush或CreateDIBPatternBrush来初始化返回的CBrush对象。如果使用了带参数的构造函数,则不再需要初始化CBrush对象。带参数的构造函数在出错时会产生一个异常,而不带参数的构造函数总是成功返回。

只带有一个参数COLORREF的构造函数用指定的颜色构造一个实线型的画刷。颜色是一个RGB值,可以用WINDOWS.H中的宏RGB构造出来。

带两个参数的构造函数构造一个阴影线型的画刷,参数:nIndex指定了阴影线模式的指数(index)。参数:crColor指定了画刷的颜色。

带有一个CBitmap型参数的构造函数构造一个模式化的画刷。参数指定一个位图。该位图应该是已经用CBitmap::CreateBitmap、CBitmap::CreateBitmapIndirect、CBitmap::LoadBitmap或CBitmap::CreateCompatiableBitmap建立或加载的位图。填充模式下的位图的最小尺寸为8像素×8像素。

Windows使用与设备无关的图形设备环境(DC :Device Context) 进行显示 MFC基础类库定义了设备环境对象类----CDC类。

2.2 文本编程

2.2.1 创建插入符

在我们使用文本编辑器的时候,在这些文本处理程序的编辑窗口中都有一条闪烁的竖线,将之称为插入符。插入符可以用于提示用户:你输入的文字信息将在这个插入符所在的位置显示出来。

在程序中想要创建插入符,可以利用CWnd类的CreateSolidCaret()函数,该函数原型:

void CreateSolidCaret(int nWidth,int nHeight);

函数的两个参数分别代表插入符的宽度和高度,如何让插入符适合于当前字体的大小呢?首先我们需要得到设备描述表中当前字体的信息,然后根据字体信息来调整插入符的大小。可以通过调用CDC类的GetTextMetrics成员函数得到设备描述表中当前字体的度量信息。函数原型如下:

BOOL GetTextMetrics(LPTEXTMETRIC lpMetrics)const

我们使用CreateSolidCaret函数创建插入符之后,该插入符初始状态是隐藏的,必须调用ShowCaret()函数来显示插入符。

在前边我们已经讲过,视类窗口始终位于框架窗口之上,对窗口客户区的鼠标和键盘操作实际上都是在视类窗口上进行的,因此应该在视类窗口上创建插入符。

插入符的创建应该在窗口创建之后进行,可以在WM_CREATE消息的响应函数OnCreate中添加创建插入符的代码。

uploading.4e448015.gif

正在上传…重新上传取消

根据上述代码有同学可能会有疑问,为什么创建插入符时,要将字体的平均宽度除以8,这是一个经验,此时可以达到最优。

2.2.2 字符输入

想要实现字符的输入功能,也就是当我们按下键盘上某个键之后,要把该字符输出到程序窗口上,这就需要捕获键盘按下(WM_CHAR)这一消息。我们需要利用TextOut函数在窗口输出字符串,TextOut函数原型:

BOOL TextOut(int x ,int y ,const CString& str);

参数:

    1. x 指定文本起点的X逻辑坐标。
    2. y 指定文本起点的Y逻辑坐标。
    3. str 包含字符的CString对象。

根据函数我们可以知道输出字符时我们需要提供字符的x,y坐标,但是这是有难度的,因为每个字符在屏幕所占的宽度都不一样,所以我们获取下一个输入点的坐标就不太容易实现。所以我们可以采用一种简单的方式,把每次输入的字符都保存到一个字符串中,当每次输入新的字符时,我们就在窗口当前插入符的位置重新把字符串输出一次。

需要注意的到的几个问题:

  1. 程序在当前插入的符的位置输出字符。也就是说程序运行时,如果鼠标左键单击窗口中的某个位置,那么插入符就移动到这个地方,随后输入的字符都应该在此位置往后输出。把插入符移动到鼠标左键的单击处,可以利用CWnd类的SetCaretPos函数来实现,函数声明如下:

static void PASCAL SetCaretPos( POINT point );

    参数: point 指定了插字符的新的x和y坐标(客户坐标)。 

  1. 用来存储输入字符的字符串取值变化问题。

当鼠标左键单击窗口中一个新的地方时,插入符就会移动到这个位置,那么以后输入的字符都应该从这个位置开始输出,以前输入的字符不应该再从此位置输出,依次需要把存储字符的字符串清空。

  1. 每次输入的字符串都应该在当前插入符的位置,也就是鼠标单击的位置开始显示。这样就需要把鼠标左键单击的坐标保存起来,以便在On_Char函数中使用。
  2. 在输出字符时,还需要考虑到回车字符的处理,按下回车后,插入符应切换到下一行,随后的输入也应该新的一行开始输出,前面介绍过GetTextMetrics函数,可以获得当前设备描述表中字体高度信息。
  3. 在输出字符时,还需要另外一个字符处理,退格键。按下退格键需要删除屏幕上位于插入符前面的那个字符。我们可以采取最简单的实现方式,先将文本颜色设置为背景色,在窗口中将字符串输出一次,然后将字符串中最后一个字符删掉,再把文本颜色设置为原来是颜色,将字符串再输出一次。屏幕上就看到了正确的删除效果。获取背景颜色可以使用CDC类的GetBKColor函数。而设置文本颜色我们可以使用CDC类提供的另一个成员函数SetTextColor函数,这个函数将会返回文本之前的颜色。如果想要实现从字符串中删除一个字符,可以使用CString类的Left函数。函数原型: CString Left(int nCount) const;返回一个CString对象,即返回指定字符串左边指定数目(nCount参数指定)的字符。

完成上述代码之后,执行程序,可以在窗口中插入字符了,但是插入符的位置没有改变,一般情况我们需要插入随着字符的输入而移动,我们知道可以利用SetCaretPos函数来设置插入符的位置,但是移动的位置如何确定呢?实际上对于同一行输入来说,插入符横向移动的距离就是输入字符的宽度,纵坐标是没有改变的。可以利用函数GetTextExtent得到字符串的宽度。

核心代码实现:

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

2.2.3 设置字体

MFC中提供了CFront类专门来设置字体。这个类派生于CGdiObject类,封装了一个Windows图形设备接口的字体。在编程时,在构造了一个CFont对象后,还必须利用该类提供的几个初始化函数之一对该对象进行初始化,然后才能使用这个对象。

CFont提供的几个初始化函数如下表:

CreateFontIndirect

初始化一个由LOGFONT结构给出其特征的CFont对象

CreateFont

初始化用指定特性定义的CFont对象

CreatePointFont

用指定高度(用0.1点)和字体初始化一个CFont对象

CreatePointFontIndirect

与CreateFontIndirect相似,但字体高度用0.1点定义而不用逻辑单位定义

这些初始化函数的主要作用就是讲CFront这个C++对象与字体资源关联起来。

在程序中与其他GDI对象一样,当创建了一个字体对象并初始化后,还必须将他选入设备描述表,之后这个新字体才能发挥作用。这个可以利用CDC类的SelectObject函数来实现,同样函数会返回先前的字体,我们可以保存这个字体,在使用完新字体之后,再把设备描述表中的字体恢复为先前的字体。

uploading.4e448015.gif

正在上传…重新上传取消

2.2.4 字体变色功能实现

我们平时在唱卡拉OK时,应该注意到歌曲字幕会随着曲调的播放有一个平滑的变色过程。如何在程序中实现这种变色效果呢?

如果我们先把字体输出到屏幕上,接着把文本的颜色设置为新的颜色,然后一个字符一个字符的输出显示该字符串,也可以达到一种变色效果,但不能达到平滑的变色效果。为了达到卡拉OK那种平滑的变色效果我们需要利用CDC类提供的另一个文字输出的函数DrawText来实现,该函数声明如下:

int DrawText(const CString& str, LPRECT lpRect, UNIT nFormat);

该函数实际上是把文字输出局限在一个矩形范围内。当初始输出文本是先把矩形的宽度设置为一个较小的值,然后不断加大矩形的宽度,这样就可以不断的增加显示文字的内容,从而实现文字平滑变色的效果。

文字变色是一个不断变化、自行进行的过程,这意味着我们需要不断的调用DrawText函数,同时增大包含文本的矩形宽度。要实现这个功能,我们需要用到定时器,通过定时器自动控制文字颜色的进程。

利用CWnd类的SetTimer成员函数可以设置定时器。函数原型如下:

UINT SetTimer(UINT nIDEvent, UINT nElapse,

void (CALLBACK EXPORT*lpfnTimer) (HWND, UINT, UINT, DWORD) );

返回值:

如果函数成功,则返回新定时器的标识符。应用程序可以将这个值传递给KillTimer成员函数以销毁定时器。如果成功,则返回非零值;否则返回0。

参数:

  1. nIDEvent 指定了不为零的定时器标识符。 
  2. nElapse 指定了定时值;以毫秒为单位。 
  3. lpfnTimer 指定了应用程序提供的TimerProc回调函数的地址,该函数被用于处理WM_TIMER消息。如果这个参数为NULL,则WM_TIMER消息被放入应用程序的消息队列并由CWnd对象来处理

具体步骤如下:

  1. 在视类的OnCreate函数中设置定时器。
  2. 在视类中对定时器消息进行处理,因此需要给视类添加WM_TIMER消息的响应函数
  3. 需要使DrawText的函数的第二个参数,即显示文字的矩形范围不断增加,所以需要设置一个变量,让它旳值不断增加,实现矩形宽度的不断增加。

核心代码实现:

uploading.4e448015.gif

正在上传…重新上传取消

3 菜单

菜单栏、工具栏和状态栏是组成Windows程序图形界面的三个主要元素。大多数Windows程序都提供了菜单,作为用于与应用程序之间交互的一种途径。本节主要介绍与菜单相关的编程知识。

3.1 菜单命令响应函数

创建一个MFC单文档工程并运行该程序,对应这个新创建的程序来说,MFC已经帮我们创建了一个菜单,并完成了一些菜单功能。,例如 【文件】 菜单下的 【打开】菜单命令,即可弹出打开文件对话框。

在VS中打开资源视图选型卡,可以看到Menu下有一个IDR_MAINFRAME的菜单资源,它就是我们应用程序界面看到的菜单。

uploading.4e448015.gif

正在上传…重新上传取消

VS为我们提供了一个所见即所得的资源编辑器,如果我们要为应用程序添加自己的菜单项,可以直接在这个菜单中添加,程序运行之后就能看到自己添加的菜单项。

uploading.4e448015.gif

正在上传…重新上传取消

当选中菜单项相应的就会在属性面板中显示出该菜单项的所用属性,也可以在次面板中对菜单项的属性做相应的修改。

uploading.4e448015.gif

正在上传…重新上传取消

在MFC中,把设置为Popup类型的菜单称为弹出菜单,一般默认顶层菜单为弹出式菜单,这种菜单不能响应命令,它的ID也是无法被编辑的,如果把Popup设置为False该菜单就不是弹出菜单,而成为一个菜单项,它的ID也能够被编辑了。MFC中都是采用大写字母来标识资源ID号的。

在菜单编辑器中给应用程序添加新的菜单项,然后为其添加消息响应函数。在新添加的菜单项上点鼠标右键,弹出菜单中选择 添加事件处理程序(A)…

uploading.4e448015.gif

正在上传…重新上传取消

弹出如下对话框:

uploading.4e448015.gif

正在上传…重新上传取消

我们打算在框架类中响应这个菜单命令,选择CMainFrame类,消息类型选择COMMAND,添加编辑,在框架类中会自动添加该菜单命令的响应函数。

uploading.4e448015.gif

正在上传…重新上传取消

运行程序,单击新添加的MyTest菜单项,会弹出一个消息框,说明OnMyTest函数被调用了。

3.2菜单命令的路由

下面我们研究一下程序中的各个类对菜单命令的响应顺序,是不是菜单命令只有框架类才能捕获呢?

按照上面讲到的方法依次为CMainFrame之外的其他类,视图类、文档类和应用程序类中添加菜单命令响应函数。

uploading.4e448015.gif

正在上传…重新上传取消

因为CMenuApp和CMenuDoc类都不是从CWnd类派生出来的,所有他们没有MessageBox成员函数,我们可以使用全局的MessageBox函数,或者使用应用程序框架的函数AfxMessageBox,这里我们使用的后者。

添加完成之后,执行程序,单击新添加的菜单项MyTest,弹出一个对话框

uploading.4e448015.gif

正在上传…重新上传取消

这就是说视类最先响应了这个菜单命令。关闭这个提示框在没有其他信息显示,说明其他几个菜单命令响应函数没有起作用。

下面,我们将视图类的OnMyTest响应函数删除,再次运行程序,我们会发现是文档类做出了响应。后面依次删除、运行。 实验发现:响应【MyTest】菜单项命令的顺序是:视图类、文档类、框架类,最后才是应用程序类。

菜单命令消息路由的具体过程:当点击某个菜单项时,最先接收到这个菜单命令消息的是框架类。框架类将把接收到的这个消息交给它的子窗口,即视图类,由视类首先进行处理。视类首先根据命令消息映射机制查找自身是否对此消息进行了响应,如果响应了,就调用相应响应函数对这个消息进行处理,消息路由过程结束;如果视类没有对此命令消息做出响应,就交由文档类,文档类同样查找自身是否对这个菜单命令进行了响应,如果响应了,就由文档类的命令消息响应函数进行处理,路由过程结束。如果文档类也未做出响应,就把这个命令消息交还给视类,后者又把该消息交还给框架类。框架类查看自己是否对这个命令消息进行了响应,如果它也没有做出响应,就把这个菜单命令消息交给应用程序类,由后者来进行处理。

uploading.4e448015.gif

正在上传…重新上传取消

Windows消息的分类:

  1. 标准消息

除WM_COMMAND之外,所有WM_开头的消息都是标准消息。从CWnd类派生的类,都可以接受这类消息。

  1. 命令消息

来自菜单,加速键或工具栏按钮的消息。这类消息都以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收这类消息。

  1. 通告消息

由控件产生的消息,例如按钮的单击,列表框的选择都会产生这类消息,目的是为了向其父窗口通知事件的发生。这类消息也是以WM_COMMAND形式呈现的。从CCmdTarget派生的类,都可以接收这类消息。

CWnd类派生于CCmdTarget类。也就是说,凡是从CWnd派生的类,他们既可以接收标准消息,也可以接收命令消息和通告消息。而对于那些从CCmdTarget派生的类则只能接收命令消息和通告消息,不能接收标准消息。

3.3 菜单的基本操作

3.3.1 标记菜单

我们想实现这样的功能:在【文件】子菜单中的【新建】菜单项上添加一个标记(√

)。因为程序的主菜单属于框架窗口,所以需要在框架类窗口创建完成之后再去访问菜单对象。可以在框架类的OnCreate函数的最后(但一定要在return语句之前)添加实现这个功能的代码。

首先要获得程序的菜单栏,也就是要在框架窗口中获得指向菜单栏的指针,这可以通过CWnd的成员函数:GetMenu来实现,函数声明如下:

CMenu* GetMenu() const;

该函数返回一个指向CMenu类对象的指针。CMenu类是一个MFC类,提供了一些与菜单操作有关的成员函数,其中就有获取一个菜单的子菜单功能的成员函数GetSubMenu函数。函数原型如下:

    CMenu* GetSubMenu(int nPos) const;

这个函数的参数nPos指定了子菜单的索引号。

为了设置一个标记菜单,需要使用CMenu类的CheckMenuItem这个函数,该函数的功能就是为菜单项添加一个标记,或者移除菜单项的标记。该函数声明如下:

    UINT CheckMenuItem( UINT nIDCheckItem, UINT nCheck );

uploading.4e448015.gif

正在上传…重新上传取消

3.3.2 默认菜单项

有些应用程序的子菜单下有一个菜单项是以粗体形式显示的,以这种形式显示的就是该子菜单的默认菜单项。 要实现的功能:将【文件】子菜单下的【新建】菜单项设置为该子菜单的默认菜单项。为了实现这种菜单项,可以利用CMenu类的SetDefaultItem成员函数来完成。这个函数的声明形式如下所示:

BOOL SetDefaultItem(UINT uItem,BOOL fByPos = FALSE);

默认菜单项的代码实现有两种方式:

  1. 利用位置索引的方式来实现,这时,SetDefaultItem函数的第二个参数应该设置为TRUE;
  2. 利用菜单项标识的方式来实现,这时,SetDefaultItem函数的第二个参数应该设置为FALSE。

uploading.4e448015.gif

正在上传…重新上传取消

注意:一个子菜单只能有一个默认菜单项。

3.3.3 分隔栏

要实现的功能:在子菜单中添加分隔栏。在新添加的子菜单的菜单项的属性页中进行修改,具体如下

选择菜单项,在菜单项的属性窗口中【杂项】的【Separator】设置为True,该菜单项会自动变成一个分隔符。

uploading.4e448015.gif

正在上传…重新上传取消

注意:分隔栏在子菜单中是占据索引位置的。

3.3.4 禁用菜单项

实现功能:屏蔽某一子菜单下某个菜单项的功能。这里我们将要禁用【文件】子菜单下的【打开】菜单项的功能。  利用CMenu类的成员函数:EnableMenuItem来完成。该函数的作用是设置菜单项的状态:能够使用、禁用或变灰显示。其声明形式如下所示:

UINT EnableMenuItem( UINT nIDEnableItem, UINT nEnable );

返回值:

返回以前的状态(MF_DISABLED, MF_ENABLED, 或MF_GRAYED),如果无效,则返回-1。

参数:

  1. nIDEnableItem 指定由nEnable决定的将要有效的菜单项。该参数既可以指定弹出菜单项,也可以指定标准菜单项。 
  2. nEnable 指定了将要进行的动作。它可以是MF_DISABLED, MF_ENABLED,或MF_GRAYED与 MF_BYCOMMAND或MF_BYPOSITION的组合。这些值通过位与操作进行组合。这些值有下列含义:
    1. MF_BYCOMMAND 指定参数给出已存在的菜单项的命令ID号。此为缺省值。 
    2. MF_BYPOSITION 指定参数给出已存在菜单项的位置。第一项所在的位置是0。 
    3. MF_DISABLED 使菜单项无效,以便它不能被选择,但不变灰。 
    4. MF_ENABLED使菜单项有效,以便它能够被选择,并可从变灰的状态中恢复出来。 
    5. MF_GRAYED 使菜单项无效,以便它不能被选择并同时变灰。

uploading.4e448015.gif

正在上传…重新上传取消

问题:运行后,选择【文件】子菜单下的【新建】菜单项,但是这时仍会出现“打开文件”对话框,这说明【新建】菜单项未被禁用。

原因是这样的:

默认情况下,所有菜单项的更新都是由MFC的命令更新机制完成的。如果我们想自己更改菜单项的状态,那就必须先把m_bAutoMenuEnable变量设置为FALSE,之后我们自己对菜单项的状态更新才能起作用。因此,我们就在程序的CMainFrame类构造函数中把m_bAutoMenuEnable这个变量初始化为FALSE,

uploading.4e448015.gif

正在上传…重新上传取消

3.3.5 移除和装载菜单

实现功能:移除一个菜单。我们利用Cwnd类提供的SetMenu成员函数来实现,该函数的声明形式如下所示:

BOOL SetMenu( CMenu* pMenu );

这个函数有一个CMenu类型指针的参数,它指向一个新菜单对象。如果这个参数值为NULL,则当前菜单就被移除了。

uploading.4e448015.gif

正在上传…重新上传取消

如果想要装载一个菜单资源并显示。我们可将上面程序中移除的菜单在这里再把它显示出来。我们利用CMenu类提供的LoadMenu成员函数来实现:

uploading.4e448015.gif

正在上传…重新上传取消

该程序先定义了一个菜单对象,Menu实例程序主菜单的资源标识是IDR_MAINFRAME,把这个资源加载到菜单对象中,最后调用SetMenu函数,把程序的菜单设置为刚刚加载的菜单对象,运行会发现程序的菜单又出现了。

问题:

这里定义的CMenu对象:menu是一个局部对象。虽然运行时未出现错误,但在窗口的操作过程中还是会出现一些错误,这都是因为CMenu对象menu是一个局部对象造成的。 

解决问题:

  1. 将CMenu对象定义为CMainFrame类的一个成员变量;
  2. 仍把这个菜单对象定义为局部对象,但在调用SetMenu函数把此对象设置为窗口的菜单之后,立即调用CMenu类的另一个成员函数Detach,以便把菜单句柄与这个菜单对象分离。

uploading.4e448015.gif

正在上传…重新上传取消

3.3.6 MFC菜单命令更新机制

如果要在程序中设置某个菜单项的状态,可以在菜单编辑器中为这个菜单项添加UPDATE_COMMAND_UI消息响应函数,然后在这个函数中进行状态的设置即可。 当程序框架捕获到了CN_UPDATE_COMMAND_UI消息后,最终还是交由该消息的响应函数来处理,我们会发现在这个函数有一个CCmdUI指针类型的参数,利用这个CCmdUI类,可以决定一个菜单项是否可以使用(Enable函数)、是否有标记(SetCheck函数),还可以改变菜单项的文本(SetText函数)。

uploading.4e448015.gif

正在上传…重新上传取消

实现功能:改变【文件】子菜单下的【保存】菜单项的状态。

uploading.4e448015.gif

正在上传…重新上传取消

我们可从工具栏上的保存按钮观察代码修改后的结果。

注意:如果要把工具栏上的一个工具按钮与菜单栏中的某个菜单项相关联,只要将它们的ID设置为同一个标识就可以了。

3.3.7 快捷菜单

我们平时在使用程序时,经常会用到单击鼠标右键显示快捷菜单(也称上下文菜单,右键菜单)这一功能。如果想要自己实现这个功能,需要通过以下步骤来完成:

  1. 为程序添加一个新的菜单资源。

可在【资源视图】选项卡上的Menu分支上单击鼠标右键,从弹出的菜单中选择【添加资源】菜单命令,

uploading.4e448015.gif

正在上传…重新上传取消

这时,在Menu分支下就多了一个名为IDR_MENU1的菜单资源,并同时在VS开发界面窗口的右边窗口中打开了这个菜单资源。接着就要为这个菜单资源添加菜单项了。因为在显示快捷菜单时顶级菜单是不出现的,所以可以给它设置任意的文本。

  1. 给视图类添加WM_RBUTTONDOWN消息响应函数。

我们可以使用TrackPopupMenu函数来显示一个快捷菜单。该函数声明形式如下:

BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd,

LPCRECT lpRect = NULL );

    参数:

    1. nFlags 指定屏幕位置标志或鼠标键标志。
      • 屏幕位置标志可以为下列值之一:
        1. TPM_CENTERALIGN 使弹出菜单在水平方向相对于x指定的坐标居中。 
        2. TPM_LEFTALIGN 放置弹出菜单,以便弹出菜单在由坐标值x指定的位置左对齐。 
        3. TPM_RIGHTALIGN 放置弹出菜单,以便弹出菜单在由坐标值x指定的位置右对齐。 
      • 鼠标键标志可以为下列值之一:
        1. TPM_LEFTBUTTON 导致弹出菜单追踪鼠标左键。 
        2. TPM_RIGHTBUTTON 导致弹出菜单追踪鼠标右键。 
    2. x 指定弹出菜单屏幕坐标的水平位置。根据参数nFlags的值,该菜单可以左对齐、右对齐或在该位置上居中。 
    3. y 指定弹出菜单屏幕坐标的垂直位置。 
    4. pWnd 标识拥有弹出菜单的窗口。该窗口接收来自菜单的所有WM_COMMAND消息。在Windows 3.1或以后版本中,窗口直到TrackPopupMenu返回才接收WM_COMMAND消息。在Windows 3.0中,窗口在TrackPopupMenu返回之前接收WM_COMMAND消息。 
    5. lpRect 指向RECT结构或包含矩形的屏幕坐标CRect对象,用户在该矩形内部单击鼠标也不会消除弹出菜单。若该参数为NULL ,那么用户在该矩形外部单击鼠标,弹出菜单将消失。对于Windows 3.0,该值必须为NULL。 

uploading.4e448015.gif

正在上传…重新上传取消

问题:

运行后发现这个快捷菜单显示的位置好像不太对,并不是在鼠标右键单击点处显示的。这是因为TrackPopupMenu函数中的x和y参数都是屏幕坐标,而鼠标单击点处的坐标是窗口客户区坐标,即以程序窗口左上角为坐标原点。

解决:

需要把客户区坐标转换为屏幕坐标。我们须在调用TrackPopupMenu函数之前调用ClientToScreen函数。函数原型如下:

    void ClientToScreen( LPPOINT lpPoint ) const;

void ClientToScreen( LPRECT lpRect ) const;

参数:

lpPoint 指向一个POINT结构或CPoint对象,其中包含了要转换的客户区坐标。 

lpRect 指向一个RECT结构或CRect对象,其中包含了要转换的客户区坐标。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 为程序添加快捷菜单上各菜单项命令的响应函数。

在菜单资源编辑器中,分别为CMainFrame类和CXXView类添加一个响应快捷菜单项的函数,运行后你会发现是CXXView类中添加的响应在处理这个消息。我们将CXXView类对快捷菜单项命令消息的响应函数删除,再次运行,可是发现CMainFrame类对快捷菜单命令消息的响应函数还是没有反应。这主要是因为在创建快捷菜单时,即调用TrackPopupMenu函数时,对这个快捷菜单的拥有者参数传递的是this值,也就是视类窗口拥有这个快捷菜单。因此,只有视类才能对快捷菜单项命令做出响应。如果想让CMainFrame类能对这个快捷菜单项进行响应的话,就应该在调用TrackPopupMenu函数时把快捷菜单的拥有者指定为CMainFrame类窗口,为此我们可以修改CXXView类OnRButtonDown函数中队TrackPopupMenu函数的调用,如下:

uploading.4e448015.gif

正在上传…重新上传取消

注意:

即便如此,框架类窗口能有机会获得对该快捷菜单中的菜单项的命令响应,但最先响应还应是CXXView类窗口,除非CXXView类窗口没有设置快捷菜单的函数,才能由框架类窗口做出响应。这可由菜单命令消息路由的过程来解释。

3.3.8 菜单项动态操作

添加菜单项

通过代码来动态地在已有菜单项目后面添加新的菜单项。

我们可以利用CMenu类提供的一个成员函数:AppendMenu来完成。该函数的作用是把一个新菜单项目添加到一个指定菜单项目的末尾。该函数具体声明形式如下:

BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0,

 LPCTSTR lpszNewItem = NULL );

参数:

    1. nFlags

指定了增加到菜单中的新菜单项状态的有关信息。它包括说明中列出的一个或多个值。下面列出的是nFlags可以设置的值:

MF_CHECKED 该值的行为如同使用MF_UNCHECKED来作为一个标记,用于替换项前的检测标记。若应用支持检测标记位图(请参阅SetMenuItemBitmaps成员函数),那么将显示“检测标记打开”位图。 

      1. MF_UNCHECKED 该值的行为如同使用MF_CHECKED来作为一个标记,用于删除项前的检测标记。若应用支持检测标记位图(请参阅SetMenuItemBitmaps成员函数),那么将显示“检测标记关闭”位图。 
      2. MF_DISABLED 使菜单项无效以便它不能被选择,但菜单项不变灰。 
      3. MF_ENABLED 使菜单项有效以便它能够被选择,并从灰色状态中恢复原样。 
      4. MF_GRAYED 使菜单项无效以便它不能被选择,同时使菜单项变灰。 
      5. MF_MENUBARBREAK 在静态菜单里的新行中或弹出菜单的新列中放置菜单项。新的弹出菜单列与老的菜单列将由垂直分割线分开。 
      6. MF_MENUBREAK 在静态菜单里的新行中或弹出菜单的新列中放置菜单项。列与列之间没有分割线。 
      7. MF_OWNERDRAW 指定菜单项为一个拥有者描绘的项。当菜单首次显示时,拥有该菜单的窗口将接收WM_MEASUREITEM消息,以获取菜单项的高度与宽度。WM_DRAWITEM消息将使属主窗口必须更新菜单项的可视界面。该选择项对于顶层菜单项无效。 
      8. MF_POPUP 指定菜单项有与之相关联的弹出菜单。参数ID指定了与项相关联的弹出菜单的句柄。它用于增加顶层弹出菜单项或用于增加弹出菜单项的下一级弹出菜单。 
      9. MF_SEPARATOR 绘制一条水平的分割线。它仅仅能用于弹出菜单项。该线不能变灰、无效或高亮度显示。其它的参数将被忽略。 
      10. MF_STRING 指定菜单项为一个字符串。 

下面列出的各组标志互相排斥,不能一起使用:

      1. MF_DISABLED, MF_ENABLED,和 MF_GRAYED 
      2. MF_STRING, MF_OWNERDRAW, MF_SEPARATOR和位图版本。 
      3. MF_MENUBARBREAK和MF_MENUBREAK 
      4. MF_CHECKED 和MF_UNCHECKED 
  1. nIDNewItem

指定了新菜单项的命令ID号,或如果nFlags被设置为MF_POPUP,该参数指定弹出菜单的菜单句柄(HMENU),否则就是添加新菜单项的命令ID。如果nFlags被设置为MF_SEPARATOR,那么参数NewItem将被忽略。

  1. lpszNewItem

lpszNewItem指定了新菜单项的内容。参数nFlags以下列方式解释lpszNewItem:

nFlags

lpszNewItem的解释

MF_OWNERDRAW

包含一个应用支持的32 位值,应用可以用于维护与菜单项关联的附加数据。当应用进行了WM_MEASUREITEM和WM_DRAWITEM,该32位值有效。这些信息存储在提供这些消息的结构的itemData成员中

MF_STRING

包含一个指向以空字符终止的字符串的指针。这是它的缺省说明

MF_SEPARATOR

参数lpszNewItem被忽略

插入菜单项

通过代码来动态地在已有菜单项目之间添加新的菜单项。

我们可以利用CMenu类的InsertMenu成员函数来实现,该函数的声明形式如下:

BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0,

LPCTSTR lpszNewItem = NULL );

参数:

  1. nPosition

指定将要插入的新菜单项前的菜单项。参数nFlags被用于以下列方式解释:

MF_BYCOMMAND

指定参数给出已存在菜单项的命令ID号。如果没有设置MF_BYCOMMAND或MF_BYPOSITION,那么此为缺省值

MF_BYPOSITION

指定参数给出已存在菜单项的位置。第一项位于位置0。如果nPosition为-1,那么新菜单项将添加到菜单尾

  1. nFlags

指定nPosition值如何被解释,并指定要增加到菜单中新菜单项的状态。对于能够被设置的标志,请参阅成员函数AppendMenu。如果需要指定多个值,需使用位与操作来组合MF_BYCOMMAND或MF_BYPOSITION标志。 

  1. nIDNewItem

指定新菜单项的命令ID号,或者,若nFlags被设置为MF_POPUP,则指定为弹出菜单的菜单句柄(HMENU)。若nFlags被设置为MF_SEPARATOR,那么参数nIDNewItem将被忽略。 

  1. lpszNewItem

指定新菜单项的文本。nFlags被用于以下列方式解释lpszNewItem:

MF_OWNERDRAW

该值含有应用用于包含菜单项附加数据应用提供的32位值。该32位值在由WM_MEASUREITEM和WM_DRAWITEM消息提供的itemData结构成员里对于应用有效。这些消息的发送是在菜单项最初显示或更改时发生

MF_STRING

包含指向以空格位终止符的字符串指针。它为缺省解释

MF_SEPARATOR

参数lpszNewltem将被忽略

删除菜单项。

CMenu提供一个DeleteMenu成员函数,函数声明如下:

BOOL DeleteMenu( UINT nPosition, UINT nFlags );

参数:

  1. nPosition

指定由nFlags决定的将要删除的菜单项。 

  1. nFlags

以下列方式解释nPosition:

MF_BYCOMMAND

指定参数给出已存在菜单项的命令ID号。如果没有设置MF_BYCOMMAND或MF_BYPOSITION,那么此为缺省值

MF_BYPOSITION

指定参数给出已存在菜单项的位置。第一项位于位置0

利用这个函数可以删除一个菜单项目,包括子菜单,以及子菜单下的菜单项,主要取决于调用这个函数的对象,如果该对象是程序的菜单栏对象,那么删除的就是指定的子菜单;如果该对象是一个子菜单对象,那么删除的就是该子菜单的下一个菜单项。

知识拓展

模拟动画图标

当我们的程序启动之后,让程序的图标不断的变化,给人一种动画效果。

  1. 加载图标资源
  1. 在程序的【资源视图】中导入图标资源:

uploading.4e448015.gif

正在上传…重新上传取消

  1. 在程序的CMainFrame类中,定义一个图标句柄数组成员变量,用来存放在资源视图中添加的图标的句柄:

private:

        HICON m_hIcons[4];

  1. 在CMainFrame类的OnCreate函数中利用LoadIcon加载添加的图标资源:

使用LoadIcon函数如果加载的是系统图标第一个参数应该设置为NULL,如果需要使用自定义图标,该函数的第一个参数应该设置为改程序当前的实例句柄。

HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);

获取当前程序实例句柄的方式:

    1. 使用AfxGetInstanceHandle()函数
    2. 使用全局实例对象theApp访问其数据成员m_hInstance

注意:在使用之前必须声明这个变量是在外部定义的

extern CMyApp theApp

    1. 使用全局函数AfxGetApp()获取当前应用程序指针,通过该指针来访问应用程序的成员变量m_hInstance

    m_hIcons[0] = LoadIcon(AfxGetInstanceHandle(),

 MAKEINTRESOURCE(IDI_ICON1));

    m_hIcons[1] = LoadIcon(AfxGetInstanceHandle(),

 MAKEINTRESOURCE(IDI_ICON2));

    m_hIcons[2] = LoadIcon(AfxGetInstanceHandle(),

MAKEINTRESOURCE(IDI_ICON3));

        m_hIcons[3] = LoadIcon(AfxGetInstanceHandle(),

 MAKEINTRESOURCE(IDI_ICON4));

MAKEINTRESOURCE宏:

将资源ID转换为相应的资源标识符字符串

  1. 定时器处理
  1. 在CMainFrame类的OnCreate函数中设置定时器:

SetTimer(1, 1000, NULL);

  1. 为CMainFrame类添加WM_TIMER消息相应函数OnTimer
  2. 在OnTimer函数中使用SetClassLong函数为应用程序设置自定义图标

函数原型如下:

DWORD SetClassLongHWND hWndint nlndexLONG dwNewLong

参数:

  1. hWnd:窗口句柄及间接给出的窗口所属的类。
  2. nIndex: 指定要设置的索引
    1. GCL_HBRBACKGROUND 设置新的背景画刷
    2. GCL_HCURSOR 设置新的光标
    3. GCL_HICON 设置新的图标
    4. GCL_STYLE 设置新的窗口样式

void CMainFrame::OnTimer(UINT_PTR nIDEvent)

{

        // TODO:  在此添加消息处理程序代码和/或调用默认值

        static int nIndex = 0;

       SetClassLong(m_hWnd, GCL_HICON, (LONG)m_hIcons[nIndex]);

       nIndex = ++nIndex % 4;

        CFrameWnd::OnTimer(nIDEvent);

}

LPSZ的全称:Long Pointer to Zero Terminated String | 中文意思:───长以零结尾的字符串的指针。

一、简单绘图

    a)画直线

    b)画笔CPen的使用

    c)画刷CBrush的使用

    d)写字,CFont的使用

   

二、文本编辑器

1)创建插入符CWnd::CreateSolidCaret()

    a)创建CWnd::CreateSolidCaret()

    b)显示CWnd::ShowCaret()

    c)插入符的高度是根据字体的高度来确定

        获取字体信息CDC::GetTextMetrics()

    d)设置插入符位置CWnd::SetCaretPos()

2)在字符消息处理函数中写字

    a)写字CDC::TextOutW()

    b)获取字符串的尺寸信息CDC::GetTextExtent()

    c)截取字符串(CString)左边指定长度的字符 str = str.Left(str.GetLength() - 1);

三、字体渐变

1)定时器的使用

    a)设置定时器:CWnd::SetTimer()

    b)关闭定时器:CWnd::KillTimer()

    c)定时器消息:WM_TIMER

2)视图类中的OnDraw()中,写字 CDC::TextOutW()

3)指定区域写字:CDC::DrawText()

4)让窗口失效,产生WM_PAINT,间接调用OnDraw()函数: CWnd::Invalidate

四、菜单的相关操作

1)菜单的命令响应函数

    a)弹式菜单,ID不可编辑,按下去,弹出一个菜单项

    b)非弹式菜单,ID可编辑

    c)菜单响应命令消息的路由(顺序)

    d)消息类型

        非标准消息 WM_COMMAND,命令消息、通告消息,CCmdTarget、CWnd子类子类能接收到非标准消息

        标准消息    WM_XXXX     CWnd子类才能接收到标准消息

       

        命令消息WM_COMMAND:菜单处理函数选中

        标准消息: 属性 -> 消息

        通告消息:点击按钮,处理函数

       

        CWnd可以接受任何消息

        CCmdTarget不能接受标准消息

       

2)菜单相关静态操作

在框架类中进行相应操作,在OnCreate()中实现

    A)获取菜单栏: CWnd::GetMenu

    B)获取菜单栏中的子菜单: CMenu::GetSubMenu

        a)标志菜单: CMenu::CheckMenuItem

            MF_BYPOSITION: 通过位置

            MF_BYCOMMAND:通过ID

        b)设置默认菜单: CMenu::SetDefaultItem

            注意:一个菜单项只能设置一个默认菜单

        c)禁用菜单: CMenu::EnableMenuItem

        注意:需要把 CFrameWnd::m_bAutoMenuEnable 成员变量设置为 FALSE

        d)分隔线

    C)移除菜单 CWnd::SetMenu

        SetMenu(NULL);

    D)装载菜单

        a)创建菜单 CMenu::LoadMenu

        b)设置菜单 CWnd::SetMenu

        d)从CMenu 对象中分离Windows菜单 CMenu::Detach

    E)菜单命令更新机制

    F)快捷菜单,弹出菜单(一定要新建菜单,不能使用框架类的主菜单)

        由于鼠标点击的区域是在视图区域,所以需要在视图类中处理

        a)处理鼠标右击事件WM_RBUTTONDOWN

        b)获取所需的子菜单

        c)弹出菜单项 CMenu::TrackPopupMenu

        d)客户区坐标转屏幕坐标:CWnd::ClientToScreen

3)菜单相关动态操作

    a)创建空菜单 CMenu::CreateMenu

    b)追加弹式菜单 CMenu::AppendMenu(MF_POPUP, )

        每个菜单都有一个菜单句柄:CMenu::m_hMenu

        重画菜单条:CWnd::DrawMenuBar

    c)追加普通菜单 CMenu::AppendMenu(MF_STRING, )

    d)插入菜单 CMenu::InsertMenu

    c)删除菜单 CMenu::DeleteMenu

   

五、动态图标

1)定时器:CWnd::SetTimer

    a)定时器信号:WM_TIMER

    b)设置定时器应该放在OnCreate()

2)加载自定义图标(WinAPI): LoadIcon()

    a)将资源ID转换为字符串 MAKEINTRESOURCE()

    b)获取应用程序实例:

        AfxGetInstanceHandle()

        AfxGetApp()->m_hInstance

3)设置图标(WinAPI)SetClassLong()

    窗口句柄:CWnd::m_hWnd

4 基于对话框的MFC应用程序

4.1  MFC编程

MFC 是 Visual C++ 的核心。虽然在 Windows 应用程序中可以直接调用 API 函数,但是一般不经常直接调用,而是从 MFC 类创建对象并调用属于这些对象的成员函数。MFC 是 Microsoft 公司提供的用来编写 Windows 应用程序的 C++ 类库,MFC 大约有 200 多个类,可以分成两种:一是 Cobject 类的派生类,它们以层次结构的形式组织起来,几乎每个子层次结构都与一个具体的 Windows 实体对应;二是非 Cobject 派生类,这些都是独立的类,如表示点的 CPoint 类,表示矩形的 CRect 类。

在 Visual C++ 中,可以创建以下 3 类典型的 Windows 应用程序,它们都是通过 MFC AppWizard(exe) (以下简称 AppWizard )向导创建的:

  1. 基于对话框的应用程序:这类程序适合于文档较少而交互操作较多的应用场合,如 Windows 自带的计算器程序。
  2. 单文档界面( SDI )应用程序:这类程序一次只能打开一个文档,如 Windows 自带的 Notepad 程序。
  3. 多文档界面( MDI )应用程序:这类程序可以同时打开多个文档并进行处理,处理的过程中很容易地进行切换,如 Microsoft Word 。

4.2  基于对话框的MFC应用程序

对话框是一种特殊类型的窗口,绝大多数Windows程序都通过对话框与用户进行交互。在Visual C++中,对话框既可以单独组成一个简单的应用程序,又可以成为文档/视图结构程序的资源。

对话框的种类

有两种对话框: 模态对话框(Modal)和非模态对话框

  1. 模态对话框

当其显示时,程序会暂停执行,直到关闭这个模态对话框之后,才能执行程序中的其他任务。我们平时遇到的对话框都是模态对话框。

    1. 模态对话框的创建

实现模态对话框的创建需要调用CDialog类的成员函数 DoModel(),该函数的功能就是创建并显示一个对话框。

CTestDlg dlg;

dlg.DoModel();  //显示模态对话框

  1. 非模态对话框

当非模态对话框显示时,运行转而执行程序中 的其他任务,而不用关闭这个对话框。典型的就是Windows记事本中提供的查找对话框,打开该对话框后仍可以与其他用户界面对象进行交互,一遍查找,一遍修改文章,这样就大大方便了使用。

    1. 非模态对话框的创建

如果要创建非模态对话框在需要使用CDialog的Create成员函数。

BOOL Create(LPCSTR lpszTemplateName, CWnd* pParentWnd = NULL);

BOOL Create(UNIT nIDTemplate, CWnd* pParentWnd = NULL);

      1. 返回值:

如果对话框创建和初始化成功,则返回非零值,否则为0。

      1. 参数:
        • lpszTemplateName 对话框模板的名称。 
        • nIDTemplate 对话框资源的ID。  
        • pParentWnd 指向含有对话框的父窗口对象的指针。如果为NULL,对话框对象的父窗口设置为应用的主窗口。 

利用Create函数创建非模态对话框时,还需要调用ShowWindow函数将这个对话框显示出来。

CTestDlg dlg;

dlg.Create(IDD_DIALOG1, this);

dlg.ShowWindow(SW_SHOW);

执行程序发现仍然没有弹出测试对话框,问题就出在创建的非模态对话框是一个局部对象,函数结束后,对话框的生命周期也就结束了。在创建非模态对话框时不能将其定义为局部对象。有两种解决方法:

  1. 把对话框对象定义为类的成员变量
  2. 将对话框对象定义为指针,在堆上分配内存

如果选择在堆上分配内存,关闭对话框时就需要释放堆内存,释放的方式有两种:

  1. 将指针定义为对话框所属的类的成员变量(比如视类),在指针所属的类的析构函数中释放
  2. 在对话框类中重载PostNcDestroy虚函数,释放this指针指向的内存。

void MyDialog::PostNcDestroy()

{

    // TODO:  在此添加专用代码和/或调用基类

    delete this;

    CDialogEx::PostNcDestroy();

}

4.2.1  创建基于对话框的 MFC 应用程序框架

程序的创建过程:

  1. 选择“文件 | 新建 | 项目”菜单;
  2. 在“新建项目”对话框中,选择“ MFC 应用程序 ”,输入工程名称,选择“确定”。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 选择“ 基于对话框”,即创建基于对话框的应用程序,选择“完成”。

uploading.4e448015.gif

正在上传…重新上传取消

4.2.2  对话框应用程序框架

用 AppWizard 创建基于对话框的应用程序框架(假定工程名为 Dialog )后,项目工作区上增加了一个“ 资源视图 ”选项卡。

uploading.4e448015.gif

正在上传…重新上传取消  

  1. 资源视图

在 MFC中,与用户进行交互的对话框界面被认为是一种资源。展开“ Dialog ”,可以看到有一个 ID 为 IDD_ DIALOG _DIALOG(中间部分(DIALOG)与项目名称相同) 的资源,对应中间的对话框设计界面。不管在何时,只要双击对话框资源的 ID ,对话框设计界面就会显示在中间。

在MFC中对资源的操作通常都是通过一个与资源相关的类来完成的。

  1. 类视图

在类视图中,可以看到生成了3 个类: CAboutDlg 、 CDialogApp 和 CDialogDlg 。

uploading.4e448015.gif

正在上传…重新上传取消

    1. CAboutDlg:对应生成的版本信息对话框。
    2. CDialogApp:对话框类,从 Cdialog 继承过来的,在程序运行时看到的对话框就是它的一个具体对象。
    3. CDialogDlg:应用程序类,从 CWinApp 继承过来,封装了初始化、运行、终止该程序的代码。
      1. DoDataExchange函数,该函数主要完成对话框数据的交换和校验。
      2. OnInitDialog函数:相当于对对话框进行初始化处理

调用这个成员函数是对WM_INITDIALOG消息作出的反应。这条消息是在对话框即将显示之前,在Create,CreateIndirect或DoModal调用期间发出的。

  1. 对话框设计界面

对话框设计界面是进行对话框设计的地方,可以将各种控件放置在这里。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 控件工具栏

我们可以根据实际需求,在开发过程中选择不同的控件,实现不同的功能。

       

uploading.4e448015.gif

正在上传…重新上传取消

4.3  静态文本框、命令按钮和编辑框

静态文本框、命令按钮和编辑框是Windows应用程序中最基本的控件。静态文本框是CStatic类的对象,命令按钮是CButton类的对象,编辑框是CEdit类的对象。这三个类都是从CWnd类直接派生来的,具有CWnd类的全部功能。

4.3.1  静态文本框

静态文本框是最简单的控件。它主要用来显示文本信息,不能接受用户输入,一般不需要连接变量,也不需要处理消息。

静态文本框的重要属性有:

  1. ID :所有静态文本框的缺省 ID 都是 IDC_STATIC ,一般不需要重新设置。
  2. 标题:需要显示的文本信息是在这里设置的。
  3. 边框:可以对属性对话框中的Sunken,Static Edge属性进行设置。

4.3.2  命令按钮

命令按钮是最常见的、应用最广泛的一种控件。在程序执行期间,当单击某个命令按钮后就会执行相应的消息处理函数。

命令按钮的主要属性是标题属性,标题属性用来设置在命令按钮上显示的文本。

命令按钮一般不需要连接变量。

命令按钮处理的最多的消息是:BN_CLICKED 。

思考问题:如果从CButton类派生新类CMyButton,如何与按钮控件关联?

只需要给按钮控件添加一个CMyButton类型的变量,按钮控件就可以使用CMyButton类中实现的所有操作。

  1. 实现一个逃跑按钮功能,当鼠标移动到按钮上时,按钮自动移动到窗口其他位置上。
  1. 在对话框窗口上,放置一个按钮
  2. 创建一个CButton类的派生类,在该类中捕获鼠标移动事件。

void CMyButton::OnMouseMove(UINT nFlags, CPoint point)

{

    // TODO:  在此添加消息处理程序代码和/或调用默认值

    //获取按钮的大小

    CRect btnRect;

    this->GetWindowRect(&btnRect);

    //按钮的宽度

    int btnWidth = btnRect.Width();

    int btnHeight = btnRect.Height();

    //获取父窗口的大小

    CRect rect;

    GetParent()->GetWindowRect(&rect);

    int width = rect.Width();

    int height = rect.Height();

    //计算新坐标

    CPoint pt = point;

    //如果计算出的左边跟鼠标当前位置左边相等重新计算

    while (pt == point)

    {

        //产生随机X坐标

        pt.x = rand() % (width - btnWidth);

        //产生随机y坐标

        pt.y = rand() % (height - btnHeight);

    }

    //将按钮移动到新的位置

    MoveWindow(pt.x, pt.y, btnWidth, btnHeight);

    CButton::OnMouseMove(nFlags, point);

}

  1. 位图按钮实现

在对话框的OnInitDialog函数最后添加如下代码:

// 获取位图资源句柄

HBITMAP hBitmap = LoadBitmap(AfxGetInstanceHandle(),

 MAKEINTRESOURCE(IDB_BITMAP1));

//按钮添加位图资源

m_btn.SetBitmap(hBitmap);

//将按钮设置成与位图同样大小

    m_btn.MoveWindow(10, 10, 66, 77);

另外还需注意,在按钮上显示位图,需要将按钮的Bitmap属性设置为TRUE

4.3.3  编辑框

  1. 属性设置

编辑框的属性主要在 Styles 选项卡中设置。重要属性有:

    1. Multiline :定义该编辑框为多行文本框。
    2. Number :限定输入的字符只能是数字字符。
    3. Border :为控件创建边界。
    4. Read-only :编辑框成为只读的,禁止用户编辑。
  1. 成员函数

编辑框常用的成员函数见表。

成员函数

功能

应用实例

SetSel(n, m)

选定编辑框中从第n个字符到底m个字符的内容。SetSel(0, -1)的作用是选定所有的内容。

m_e.SetSel(0, -1)

Copy()

将编辑框中当前选定的内容复制到剪贴板

m_e.Copy()

Cut()

将编辑框中当前选定的内容剪切到剪贴板

m_e.Cut()

Clear()

删除编辑框中当前选定的内容

m_e.Clear()

Paste()

把剪贴板中的内容粘贴到编辑框中光标所在的位置

m_e.Paste()

GetLine(n, ch)

将多行编辑框中的第n行的内容复制到ch中。ch为一般的字符数组。

TCHAR ch[80];

m_e.GetLine(0, ch);

ReplaceSel(ch)

将ch中的内容替换编辑框中选定的内容

TCHAR ch[80] = “abcd”;

m_e.ReplaceSel(ch);

Undo()

撤销编辑框的最后一次操作

m_e.Undo();

此外 编辑框还可以使用 CWnd 类的成员函数。 CWnd 类的重要成员函数有:

  1. 获取编辑中的内容
  1. void GetWindowTextW ( CString&   ) const;

说明:将编辑框中的内容复制到 CString 类对象 rString 中。

示例:将编辑框 m_e 中的内容复制到 CString 类对象 ch 中。

CString ch;

m_e.GetWindowTextW (ch);

  1. int GetWindowTextW ( LPTSTR lpszStringBuf, int nMaxCount ) const;

说明:将编辑框中的内容复制到lpszStringBuf中,最多复制nMaxCount 个字符。lpszStringBuf 是字符数组或字符指针。

示例:将编辑框 m_e 中的内容复制到字符数组 ch 中。

TCHAR ch[80];

m_e.GetWindowTextW (ch, 80);  

  1. 设置编辑框中的内容

形式: void SetWindowText( LPCTSTR lpszString );

说明: 将 lpszString 中的内容替换编辑框中原有内容,lpszString 是字符数组或字符指针 。

示例: 设置编辑框中的内容为“ abcdefg ”

TCHAR ch[20] = "abcdefg";

m_e.SetWindowText(ch);

  1. 连接变量

编辑框在连接变量时,除了要指定变量名之外,还要确定变量类别。变量类别有两个可选择:

  1. Control,意味着该变量作控件使用,对应的变量类型只能是CEdit,可以使用该控件类的成员函数;
  2. Value,意味着该变量当作C/C++中普通的变量使用,对应的变量类型有CString、int、double等,可以使用这些数据类型的函数,但是不能使用控件的成员函数。

若一个编辑框连接了一个Value类别的变量,则该变量就表示这个编辑框,编辑框中显示的内容就是变量的值。但是,改变了编辑框的内容并不会自动更新对应的变量的值,同样,改变了变量的值也不会自动刷新编辑框的内容。若要保持一致,需要使用UpdateData()函数更新,如图所示。

  1. 若编辑框的内容改变了,则应使用语句UpdateData(TRUE) 获取对话框数据
  2. 若变量的值改变了,则应使用语句UpdateData(FALSE) 初始化对话框控件。

uploading.4e448015.gif

正在上传…重新上传取消

【 例 1 】 编写一个如图所示的应用程序。若单击“复制”按钮,则把上面的编辑框中的内容复制到下面的编辑框中;若单击“结束”按钮,则退出程序的运行。

                  

uploading.4e448015.gif

正在上传…重新上传取消

新建一个基于对话框的MFC应用程序

  1. 放置控件
    1. 删除原有的控件。
    2. 放置所需的控件:两个编辑框和两个命令按钮。

方法是:先单击控件工具栏上的控件图标选择所需的控件,然后在对话框设计界面上按住鼠标左键拖拉出所需要的大小后释放。

  1. 设置控件属性

选择编辑框控件,在属性面板中对该控件属性进行设置。上面编辑框的属性设置为:

    1. 【Multiline】设置为True,编辑框中可以输入多行文本。
    2. 【Vertical scroll】和【Auto Vscroll】 属性设置为True,编辑框将出现垂直滚动条。
    3. 【Want return】设置为True,控件接收回车键。

uploading.4e448015.gif

正在上传…重新上传取消

下面编辑框属性设置与上面编辑框基本一样,只是可以不设定【Want return】为True。从图中可以看到编辑框的 ID 为 IDC_EDIT1 ,这是该控件的标识。任何一个控件都有一个 ID ,某些函数需要通过 ID 对控件进行操作。

  1. 连接变量

为控件连接变量就是为控件起一个名称。每一个控件都是一个对象,调用 MFC 类库中的函数都是通过对象来实现的。为 IDC_EDIT1 连接变量 m_e1 的步骤为:

    1. 在 IDC_EDIT1 编辑框的右键菜单中选“添加变量”,弹出 “添加成员变量向导”对话框。

uploading.4e448015.gif

正在上传…重新上传取消

    1. 在“ 添加成员变量向导 ”中,给“ IDC_EDIT1 ”,添加成员变量。

uploading.4e448015.gif

正在上传…重新上传取消

用同样的方法再为 IDC_EDIT2 连接一个变量 m_e2 。

  1. 添加并且编写消息处理函数。

本例要求单击“复制”按钮后上面编辑框中的内容复制到下面的编辑框中。也就是说,在程序运行时,在“复制”按钮上发生单击事件后, Windows 向对话框发出了一个 BN_CLICKED 消息, CTESTDlg 类应有一个处理该消息的函数。添加和编写这个消息处理函数的过程是:

    1. 直接双击“复制”按钮,程序中会自动添加相对应的响应函数。或者在按钮上单击鼠标右键,在右键菜单中单击【类向导】

uploading.4e448015.gif

正在上传…重新上传取消

弹出类向导对话框

uploading.4e448015.gif

正在上传…重新上传取消

选择按钮对应的ID,要添加的消息类型,选择添加处理程序按钮,按钮对应的消息相应函数添加完毕。

复制按钮的消息处理函数如下:

uploading.4e448015.gif

正在上传…重新上传取消

用同样的方法为“结束”按钮添加 、编写如下的消息处理函数:

  

uploading.4e448015.gif

正在上传…重新上传取消

【例2】 输入一元二次方程 y=ax2 +bx+c 的系数 a 、 b 、 c ,计算并输出两个根 x1 、x2 ,如图所示。

             

uploading.4e448015.gif

正在上传…重新上传取消

对话框上有5个静态文本框、5个编辑框和2个命令按钮。编辑框按表连接变量,其余控件不需要连接变量。

                           编辑框链接的变量

控件

ID

变量

类型

数据类型

Edit Box

缺省

m_a

value

double

缺省

m_b

value

double

缺省

m_c

value

double

缺省

m_x1

value

double

缺省

m_x2

value

double

为了要计算 b2 -4ac 的平方根,需要使用 sqrt() 函数,因此在 TESTDlg.cpp 文件的开始添加了文件包含命令:

#include "math.h"  // "计算"按钮的函数 这条要放在最后

…………………………………………………………

void CTESTDlg::OnCalc()

{

   // TODO: Add your control notification handler code here

   UpdateData(TRUE);

    double a=m_a;

    double b=m_b;

    double c=m_c;

    m_x1=(-b+sqrt(b*b-4*a*c))/(2*a);

    m_x2=(-b-sqrt(b*b-4*a*c))/(2*a);

    UpdateData(FALSE);

}

// “结束”按钮的函数

…………………………………………………………

void CTESTDlg::OnOk()

{

   // TODO: Add your control notification handler code here

   CDialog::OnOK();           //OnOK();

}

…………………………………………………………

  1. 消息处理函数

在编辑框能处理的消息中是最重要的是 EN_CHANGE ,这是编辑框中的文本被修改后发出的消息。

【例】字符串转换程序

转换规则为: 大写字母转换成小写;

                  将小写字母转换成大写;

                  换行符和回车符不变;

                  其余字符转换为“*”

要求:每输入一个字符立即转换

uploading.4e448015.gif

正在上传…重新上传取消

  1. 第1个编辑框连接变量m_e1 (Control )

属性:  

  1. Multiline  多行,设置为True
  2. Vertical scroll 垂直滚动条,设置为True
  3. Auto VScroll(在多行控件中,当用户在最后一行按下ENTER键时自动向上滚动文本),设置为True
  4. Want return(使多行编辑器接收回车键,表示换行。如果不指定,按回车键会选择缺省的命令按钮,这往往会导致对话框的关闭),设置为True。
  1. 第2个编辑框连接变量m_e2 (Control )

属性:

  1. Multiline  多行,设置为True
  2. Vertical scroll 垂直滚动条,设置为True
  3. Auto VScroll(在多行控件中,当用户在最后一行按下ENTER键时自动向上滚动文本),设置为True
  4. Read-only  设置为True
  1. 对第1个编辑框添加EN_CHANGE 消息处理函数,首先在控件上选右键菜单

uploading.4e448015.gif

正在上传…重新上传取消

弹出事件处理程序向导对话框

uploading.4e448015.gif

正在上传…重新上传取消

相应的函数处理如下:

void CTESTDlg::OnChangeEdit1()

{

    // TODO: Add your control notification handler code here

    TCHAR s[80];

    m_e1.GetWindowTextW(s, 80);

    for (int i = 0; s[i] != '\0'; i++)

    {

        if (s[i] >= 'A' && s[i] <= 'Z')

        {

           //大写转换成小写

           s[i] = s[i] + 'a' - 'A';

        }

        else if (s[i] >= 'a' && s[i] <= 'z')

        {

           //小写转换成大写

           s[i] = s[i] + 'A' - 'a';

        }

        else if (s[i] == '\n' || s[i] == '\r')

        {

           //回车符合换行符不变

           s[i] = s[i];

        }

        else

        {

           //其余字符全部变成*号

           s[i] = '*';

        }

    }

    //将转换完成的字符串显示到m_e2中

    m_e2.SetWindowTextW(s);

}

4.4  组框、单选按钮和复选框

组框、单选按钮和复选框都是对话框的常见控件。组框与静态文本框一样是CStatic类的对象,单选按钮和复选框与命令按钮一样都是CButton类的对象。

4.4.1 组框

当对话框上控件较多时,可以使用组框将一组相关的控件框起来,达到标识一组控件的作用。组框不需要连接变量,也不需要处理消息。组框的重要属性有:

  1. ID :所有组框的缺省 ID 都是 IDC_STATIC ,不需要重新设置。
  2. 标题:该属性决定组框上的标题文本。

4.4.2 单选按钮

  1. 注意事项

同一组中的按钮必须一个接一个地放进对话框中,中间不能插入其他控件,并且一个组的第一个按扭的“Group”属性需要设置为True,表示一组控件的开始。

  1. 重要属性
    1. ID :每个命令按钮都有一个缺省 ID ,如 IDC_RADIO1 ,可以重新设置。
    2. 标题:该属性值就是单选按钮右边显示的文本标题。
    3. Group :一组中第一个按钮选中该属性,表示它是一组的开始。
  2. 消息

单选按钮能处理的消息是BN_CLICKED。

  1. 重要函数
  1. 设定单选按钮选中状态

初始时,可使用 Windows API 函数 CheckRadioButton() 设定一组单选按钮中选中的按钮。 形式:

void CheckRadioButton(int nIDFirstButton,

int nIDLastButton, int nIDCheckButton);

说明:

    1. nIDFirstButton 是一组中第一个单选按钮的 ID ;
    2. nIDLastButton 是一组中最后一个单选按钮的 ID ;
    3. nIDCheckButton 为初始时设置为选中的那个单选按钮的 ID 。
  1. 判定单选按钮是否被选定

函数原型如下:

UINT IsDlgButtonChecked( int nIDButton ) const;

说明:如果 ID 为 nIDButton 的按钮被选定,则该函数的返回值为 true ,否则为 false 。

【例】设计一个如图所示的程序。当单击“ 确定 ”后,用 MessageBox 函数显示一个如图所示的消息框。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 分组

例:将radio1、radio2分为1组,radio3、radio4、radio5分为另一组;

分组方法:

第一组:

    1. 设置  radio1 的属性:group设置为true
    2. 设置  radio2 的属性: group设为false

第二组:

        1. 设置  radio3 的属性:group设置为true
        2. 设置  radio4 的属性:group设为false
        3. 设置  radio5 的属性:group设为false

按CTRL+D,保证同一组内的radio的tab序号是连续的;

uploading.4e448015.gif

正在上传…重新上传取消

  1. 界面设计

首先创建一个对话框程序框架,然后放置各控件,进行属性设置,将编辑框连接到 CString 类型的 m_edit 变量,其余控件不需要连接变量。将“男”和“教授”对应的单选钮的Group属性勾上。

  1. 初始化

单选按钮的初始化工作在对话框的初始化函数实现。

BOOL CTESTDlg::OnInitDialog()

{

  CheckRadioButton(IDC_RADIO1, IDC_RADIO2, IDC_RADIO1); //

  CheckRadioButton(IDC_RADIO3, IDC_RADIO5, IDC_RADIO4);

    ┆

};

  1. 消息处理函数

void CTESTDlg::OnButton1()

{

 // TODO: Add your control notification handler code here

    //将控件中的数据更新到变量中

    UpdateData(TRUE);

    CString s;

    s += m_edit;

    if (IsDlgButtonChecked(IDC_RADIO1))

    {

        s += " 男 ";

    }

    else if (IsDlgButtonChecked(IDC_RADIO2))

    {

        s += " 女 ";

    }

    if (IsDlgButtonChecked(IDC_RADIO3))

    {

        s += " 教授 ";

    }

    else if (IsDlgButtonChecked(IDC_RADIO4))

    {

        s += " 副教授 ";

    }

    else

    {

        s += " 讲师 ";

    }

    MessageBox(s);  

}

      1. 复选框
  1. 重要属性
    1. ID :每个复选框都有一个缺省 ID ,如 IDC_CHECK1 ,可以重新设置。
    2. 标题:该属性值决定了复选框右边显示的文本标题。
  2. 连接变量

复选框通常被连接到Value类别BOOL类型的变量。

  1. 消息

复选框能处理 BN_CLICKED 消息。

【例】设计一个如图所示的程序。当单击“确定”后,在右边的编辑框中显示有关信息。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 界面设计

首先创建一个对话框程序框架,然后放置各控件,进行属性设计,各编辑框和复选框按表连接变量,其余控件不需要连接变量。

控件连接的变量

控件

标题

变量名

变量类别

变量类型

左编辑框

m_e1

Value

CString

右编辑框

m_e2

Value

CString

复选框

旅游

m_c1

Value

BOOL

运动

m_c2

Value

BOOL

音乐

m_c3

Value

BOOL

  1. 消息处理函数

void CTESTDlg::OnButton1()

{

// TODO: Add your control notification handler code here

UpdateData(TRUE);

CString s;

s = m_e1;

s += " 爱好 :\r\n";

if (m_c1) s += "  旅游\r\n";

if (m_c2) s += "  运动\r\n";

if (m_c3) s += "  音乐\r\n";

m_e2 = s;

UpdateData(FALSE);

}

对话框是 Windows 应用程序中最常用的一种与用户交互的方式。用户通过对话框输入数据,程序通过对话框显示执行的情况。

【例】 设计如图所示的对话框。若选定了“日期”或“时间”复选框,则在对应的只读编辑框中显示系统当前日期或时间。

   

uploading.4e448015.gif

正在上传…重新上传取消

分析: CTime 是 MFC 中的一个类,封装了日期和时间。它有一个静态成员 GetCurrentTime() ,返回系统当前的日期和时间。

实现:

  1. 界面设计

首先创建一个对话框程序框架,然后放置各控件,按表进行属性设置和连接变量。

控件链接的变量

控件

属性设置

变量名

变量类别

变量类型

复选框

标题为“时间“

m_k1

Value

BOOL

复选框

标题为“时间“

m_k2

Value

BOOL

编辑框

设置“只读”

m_e1

Value

CString

编辑框

设置“只读”

m_e2

Value

CString

  1. 消息处理函数

    …………………………………………………………

    void CTESTDlg::OnCheck1()

    {

      // TODO: Add your control notification handler code here

      UpdateData(TRUE);

      if(m_k1==TRUE)

      {

CTime time=Ctime::GetCurrentTime();

         // 构造“ YYYY.MM.DD ”形式的日期字符串

m_e1=time.Format("%Y.%m.%d");

      }

      else

         m_e1="";

     UpdateData(FALSE);

    }

    …………………………………………………………

    void CTESTDlg::OnCheck2()

    {

        // TODO: Add your control notification handler code here

        UpdateData(TRUE);

        if(m_k2==TRUE)

        {

CTime time=Ctime::GetCurrentTime();

                   // 构造“ HH:MM:SS ”形式的时间字符串

            m_e2=time.Format("%H:%M:%S");     

}

         else

            m_e2="";

     UpdateData(FALSE);

    }

    …………………………………………………………

4.5  列表框和组合框

列表框是CListBox类的对象,组合框是CComboBox的对象。

4.5.1  列表框

  1. 属性设置

列表框的重要属性有:

    1. Selection

该属性决定用户的选择方式,缺省值为 Single 。属性值有:

      1. Single ── 单项选择
      2. Multiple ── 多项选择,但是忽略 Ctrl 和 Alt 键。
      3. Extended ── 允许使用 Ctrl 和 Alt 进行多项选择
      4. None ── 禁止选择

uploading.4e448015.gif

正在上传…重新上传取消

    1. Sort

当该属性被设置为True后,列表框中的选项按字母顺序排列。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 常用成员函数
    1. 添加项目

格式: int AddString( LPCTSTR lpszItem );

例如:语句 m_l. AddString(" 陈蓉 ") ; 把“陈蓉”添加到了列表框 m_l 中。

    1. 删除项目

格式: int DeleteString( UINT nIndex );

说明: nIndex 表示被删除项目在列表框中的位置,对于第一个选项nIndex应为 0 。

例如:语句 m_l. DeleteString (4) ;删除列表框中的第 5 个项目。

    1. 获取当前被选定的项目的序号

格式: int GetCurSel( ) const;

例如:语句int I=m_l.GetCurSel(); 将使I 获得 m_l 列表框中当前选定的项目的序号。

    1. 获取列表框中指定的项目

格式 1 : int GetText( int nIndex, LPTSTR lpszBuffer ) const;

格式 2 : void GetText( int nIndex, CString& rString ) const;

说明 :将列表框中第 nIndex 个选项的文本送到 lpszBuffer 或 rString 中。

例如:假定有说明语句 char s1[20]; CString s2;则语句 m_l.GetText(4, s1); 和 m_l. GetText(4, s2); 把第 5 项内容分别送到 s1 和 s2 中。

【例】 编写一个能对列表框进行项目添加、修改和删除操作的应用程序,如图所示。“添加”按钮的功能是将文本框中的内容添加到列表框,“删除”按钮的功能是删除列表框中选定的选项。如果要修改列表框,则首先选定选项,然后单击“修改”按钮,所选的选项显示在文本框中,当在文本框中修改完之后再单击“修改确定”按钮更新列表框。

uploading.4e448015.gif

正在上传…重新上传取消

列表框应用示例

  1. 界面设计

首先创建一个对话框应用程序框架,然后放置按钮。列表框和命令按钮按表连接变量,其余控件不需要连接变量。

                       控件连接的变量

控件

变量名

变量类别

变量类型

列表框

m_l

Control

CListBox

编辑框

m_e

Value

CString

  1. 初始化

列表框的选项在对话框的初始化函数中用 AddString 函数添加。

BOOL CTESTDlg::OnInitDialog()

{

// TODO: Add extra initialization here

m_l.AddString(" 大学计算机基础 ");

m_l.AddString("C/C++ 程序设计 ");

m_l.AddString("VB 程序设计 ");

m_l.AddString(" 软件技术基础 ");

}

  1. 消息处理函数

…………………………………………………………

void CTESTDlg::OnButton1() // 选择“添加”后执行的函数

{

 // TODO: Add your control notification handler code here

 UpdateData(TRUE);

 m_l.AddString(m_e);

 m_e="";

 UpdateData(FALSE);

}

…………………………………………………………

void CTESTDlg::OnButton2() // 选择“删除”后执行的函数

{

 // TODO: Add your control notification handler code here

 int n=m_l.GetCurSel();

 m_l.DeleteString(n);

}

…………………………………………………………

void CTESTDlg::OnButton3() // 选择“修改”后执行的函数

{

 // 将选定的项目送编辑框供修改

 // TODO: Add your control notification handler code here

 CString s;

 int n=m_l.GetCurSel();

 m_l.GetText(n,s);

 m_e=s;

 UpdateData(FALSE);

}

…………………………………………………………

void CTESTDlg::OnButton4() // 选择“确定修改”后执行的函数

{

 // 将修改后的项目送回列表框中,替换原项目,实现修改。

 // TODO: Add your control notification handler code here

 UpdateData(TRUE);

 int n=m_l.GetCurSel();

 m_l.DeleteString(n);

 m_l.InsertString(n,m_e);

 m_e="";

 UpdateData(FALSE);

}

…………………………………………………………

4.5.2  组合框

  1. 属性设置
    1. 种类( Type )

该属性用于指定组合框的类型,共有三个选择: Simple 、 Dropdown 和 Droplist ,默认值为 Dropdown 。

                  

uploading.4e448015.gif

正在上传…重新上传取消

    1. 键入列表框项( Enter listbox items )

在列表框的属性窗口有一个“ Data ”属性,这是在设计阶段输入选项的地方,如图所示。输入每一项都需要用分号分隔

               

uploading.4e448015.gif

正在上传…重新上传取消

  1. 常用成员函数

编辑框和列表框的成员函数几乎都可以用于组合框,但是列表框的成员函数 GetText() 在组合框中的是 GetLBText() 。

    1. int GetLBText( int nIndex, LPTSTR lpszText ) const;
    2. void GetLBText( int nIndex, CString& rString ) const;

说明 :使用 GetLBText 函数可以将组合框中的第 nIndex 个项目的文本送入 lpszText 或 rString 中。

  1. 组合框发出的消息
    1. CBN_SELECTCHANGE :组合框的列表框中的选项改变时发送。
    2. CBN_EDITCHANGE :组合框的编辑框中文本改变时发出。

【例】设计一个如图所示的对话框。如果单击“确定”按钮,则用 MessageBox 显示如图所示的信息框。

       

uploading.4e448015.gif

正在上传…重新上传取消

  1. 首先创建一个对话框程序框架,然后放置各控件,进行属性设置,组合框的选项在设计阶段直接输入,按表连接变量,其余控件不需要连接变量。

控件连接的变量

控件

变量名

变量类别

变量类型

组合框

m_c

Value

CString

编辑框

m_e

Value

CString

  1. 消息处理函数

void CTESTDlg::OnButton1()

{

 // TODO: Add your control notification handler code here

 UpdateData(TRUE);

 CString s;

 s=" 品牌 :";

 s=s+m_c;

 s=s+"\n";

 s=s+" 数量 :";

 s=s+m_e;

 MessageBox(s);

}

4.6 滚动条

滚动条是 CScrollBar 类的对象,是 Windows 应用程序中一个很重要的控件,通常附在对话框上用来协助观察数据或确定位置,也可以用来作为数据输入的工具。滚动条有水平和垂直两种。

uploading.4e448015.gif

正在上传…重新上传取消

【例】 建立一个水平滚动条,最小值为 0 ,最大值为 100 ,单击滚动条两端箭头时滑块移动的增量值为 2 ,单击滚动条中的空白处(滑块与两端箭头之间的区域)时滑块移动的增量值为 10 。另有一个只读的编辑框,显示了滑块当前位置所代表的值。

假定工程名为 TEST 。

  1. 界面设计

首先创建一个对话框应用程序框架,然后放置水平滚动条、编辑框,按表连接变量,编辑框设置为只读。

控件连接的变量

控件

变量名

变量类别

变量类型

滚动条

m_s

Control

CScrollBar

编辑框

m_e

Value

int

  1. 初始化

滚动条的初始化就是设置最小值,最大值,以及初始时滑块所代表的值。滚动条初始化在对话框的初始化函数中完成,涉及两个函数:

    1. 设置最大值和最小值

函数原型:

void SetScrollRange(int nMinPos , int nMaxPos , BOOL bRedraw = TRUE);

参数说明:

      1. nMinPos 表示最小值
      2. nMinPos 表示最大值 。
      3. 当 bRedraw 为 TRUE 时重画滚动条。
    1. 设置滑块的位置

格式: int SetScrollPos( int nPos , BOOL bRedraw = TRUE );

参数说明 :

  1. nPos 表示滑块的位置。
  2. 当 bRedraw 为 TRUE 时重画滚动条。

滚动条初始化代码如下:

BOOL CTESTDlg::OnInitDialog()

{

  ┆

 // TODO: Add extra initialization here

 m_s.SetScrollRange(0,100);

 m_s.SetScrollPos(50);

 m_e=50; // 初始时,编辑框显示 50 。

 UpdateData(FALSE); // 更新编辑框显示的内容。

  ┆

};  

  1. 编写消息处理函数

当用户在滚动条上进行操作时,滚动条接收不到任何消息。但是对话框却能接收到 WM_HSCROLL (水平滚动条)或 WM_VSCROLL (垂直滚动条)消息,也就是说,程序对滚动条的操作只能在 OnHScroll() 或 OnVScroll() 中编写代码

滚动条处理 WM_Hscroll 消息的函数为:

void CTESTDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

   // TODO: Add your message handler code here and/or call default

    CDialog::OnHScroll(nSBCode, nPos, pScrollBar);

}

    在对话框的属性面板中查找WM_HSCROLL命令,并添加响应函数

               

uploading.4e448015.gif

正在上传…重新上传取消

做如图选择,点击“<Add>”就把OnHScroll函数添加进去。

响应函数参数说明 :

① pScrollBar 指向用户正在进行操作的滚动条。

② nPos 表示滑块当前的位置。

③ nSBCode 指示用户正在进行的操作,其取值及其意义见表。

滚动条的通知消息

消息

用户操作

SB_THUMBTRACK

拖动滑块

SB_LINELEFT / SB_LINEUP

单击向左(上)的箭头

SB_LINERIGHT / SB_LINEDOWN

单击向右(下)的箭头

SB_PAGELEFT / SB_PAGEUP

单击向左(上)的箭头与滑块之间的区域

SB_PAGERIGHT / SB_PAGEDOWN

单击向右(下)的箭头与滑块之间的区域

 程序代码:

…………………………………………………………

void CTestDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{   // TODO: Add your message handler code here and/or call default

    if (pScrollBar==&m_s)

    {

int iNowPos;

      switch(nSBCode)

      {

case SB_THUMBTRACK: // 拖动滚动滑块时

             m_s.SetScrollPos(nPos);

             m_e=nPos;

              break;

       case SB_LINELEFT : // 单击滚动条向左的箭头

            iNowPos=m_s.GetScrollPos(); // 获取滑块当前位置所代表的值

            iNowPos=iNowPos-2;

            if(iNowPos<0)

               iNowPos=0;

            m_s.SetScrollPos(iNowPos);

            m_e=iNowPos;

            break;

        case SB_LINERIGHT : // 单击滚动条向右的箭头

            iNowPos=m_s.GetScrollPos(); // 获取滑块当前位置所代表的值

            iNowPos=iNowPos+2;

            if(iNowPos>100)

                iNowPos=100;

            m_s.SetScrollPos(iNowPos);

            m_e=iNowPos;

            break;

        case SB_PAGELEFT : // 单击滚动条左边的箭头与滑块之间的区域

            iNowPos=m_s.GetScrollPos(); // 获取滑块当前位置所代表的值

            iNowPos=iNowPos-10;

            if(iNowPos<0)

                iNowPos=0;

            m_s.SetScrollPos(iNowPos);

            m_e=iNowPos;

            break;

case SB_PAGERIGHT : // 单击滚动条右边的箭头与滑块之间的区域

            iNowPos=m_s.GetScrollPos(); // 获取滑块当前位置所代表的值

            iNowPos=iNowPos+10;

            if(iNowPos>100)

                iNowPos=100;

            m_s.SetScrollPos(iNowPos);

            m_e=iNowPos;

            break;

         }

    }

    UpdateData(FALSE);

    CDialog::OnHScroll(nSBCode, nPos, pScrollBar);

 }

…………………………………………………………

说明:

  1. 由于对话框上可能有多个滚动条,因此函数首先需要根据指针 pScrollBar 确定事件发生在哪一个滚动条上,方法是使用表达式 pScrollBar==&m_s。因为, pScrollBar 指向发生事件的滚动条,所以这个表达式为真时,意味着用户对滚动条 m_s 进行了操作。
  2. 用户对滚动条的操作有 5 种,因此函数中必须以这 5 种情况分别进行处理。

4.7 程序举例

【例】 设计如图所示的对话框。单击“确定”按钮后,在列表框中显示选择的信息。

uploading.4e448015.gif

正在上传…重新上传取消

  1. 界面设计

首先创建一个对话框程序框架,然后放置各控件,按图所示设置属性和连接变量。其中, m_e2 编辑框设置为只读,组合框和列表框取消排序属性。

  1. 初始化

组合框的选项在设计阶段输入,其余控件的初始化在对话框的初始化函数中完成。

BOOL CTESTDlg::OnInitDialog()

{

        ┆

    // TODO: Add extra initialization here

    m_scroll.SetScrollRange(0, 110);

    m_scroll.SetScrollPos(50);

    CheckRadioButton(IDC_RADIO1, IDC_RADIO2, IDC_RADIO1);

    m_check1 = TRUE;

    m_workAge = 50;

    UpdateData(FALSE);

    m_combo.SetCurSel(0);

        ┆

}

  1. 滚动条消息处理函数

请参照滚动条部分自己完成。为了不影响其它控件,应开始处添加命令:

    UpdateData(TRUE);

  1. 命令按钮的消息处理函数

…………………………………………………………

void CTESTDlg::OnButton1()

{

    // TODO: Add your control notification handler code here

    UpdateData(TRUE); // 用各控件中的值更新对应的连接变量

    m_list.ResetContent(); // 删除列表框中所有的内容

    m_list.AddString(_T("姓名: ") + m_name);

    if (IsDlgButtonChecked(IDC_RADIO1))

    {

        m_list.AddString(_T("性别: 男 "));

    }

    else

    {

        m_list.AddString(_T("性别: 女 "));

    }

    m_list.AddString(_T("爱好: "));

    if (m_check1) m_list.AddString(_T("    旅游 "));

    if (m_check2) m_list.AddString(_T("    运动 "));

    if (m_check3) m_list.AddString(_T("    音乐 "));

    CString s;

    // m_e2 是 int 类型,将它转换成字符串

    s.Format(_T("%s%d"), _T("工龄: "), m_workAge);       

    m_list.AddString(s);

    int nIndex = m_combo.GetCurSel();

    CString comboText;

    m_combo.GetLBText(nIndex, comboText);

    m_list.AddString(_T("职称: ") + comboText);    

}

…………………………………………………………

知识点补充:

屏蔽或接收键盘事件

在MFC对话框应用程序中,启动应用程序,默认状态下当我们按下键盘的回车键之后,程序就自动关闭了,如果不想让程序关闭,那么应该怎么办?如果按下ESC键之后想让程序关闭,那么又该怎么办?

处理方式:

屏蔽默认回车消息

当在对话框中按下回车键时,会选择对话框中默认的按钮消息响应函数来处理这一事件,而基类(CDialogEx)的IDOK按钮的默认响应函数(OnOk)的功能就是关闭对话框。因此,我们一但在对话框测试程序中按下回车键,这个对话框就关闭了。

为了屏蔽掉默认的回车键关闭对话框这一功能,应该在对话框子类中重写OK按钮的消息响应函数OnOk。

void CMy04_DlgEditDlg::OnOK()

{      

        //去掉此行, 子类中不能调用基类的OnOK函数

        //CDialogEx::OnOK();

}

彻底屏蔽回车键

1.  在类视图中选中对话框类

uploading.4e448015.gif

正在上传…重新上传取消

2.  重写该类的虚函数PreTranslateMessage

       

uploading.4e448015.gif

正在上传…重新上传取消

3.  在该函数中截获键盘消息的return按钮事件,屏蔽掉即可

BOOL CMy04_DlgEditDlg::PreTranslateMessage(MSG* pMsg)

{

    // TODO:  在此添加专用代码和/或调用基类

    if (pMsg->message == WM_KEYDOWN)

    {

       if (pMsg->wParam == VK_RETURN)

       {

           return TRUE;

       }

    }

    return CDialogEx::PreTranslateMessage(pMsg);

}

4.  要处理ESC键消息亦是如此,只需要捕捉键盘Esc消息:

BOOL CMy04_DlgEditDlg::PreTranslateMessage(MSG* pMsg)

{

    // TODO:  在此添加专用代码和/或调用基类

    if (pMsg->message == WM_KEYDOWN)

    {

        if (pMsg->wParam == VK_ESCAPE)

       {

           PostQuitMessage(0);

           return TRUE;

       }

    }

    return CDialogEx::PreTranslateMessage(pMsg);

}

在前几天学过的知识中讲过,程序在关闭时会调用PostQuitMessage(0) 函数,该函数执行完毕后会发送WM_QUIT消息,系统在接收到该消息之后会退出消息循环,应用程序随之退出。

一、对话框

1)模态对话框

    a) 资源视图 -> Dialog -> 右击 -> 插入 Dialog

    b) 创建对话框对象 CDialog

    c) 以模态方式运行 CDialog::DoModal

2)非模态对话框

    a)资源视图 -> Dialog -> 右击 -> 插入 Dialog

    b)创建对话框对象,需要在.h的地方声明为成员变量 CDialog

    c)创建对话框(在构造函数或OnCreate(),目的只创建一次) CDialog::Create

    d)显示窗口 CWnd::ShowWindow

3)自定义对话框类(重要)

    a)资源视图 -> Dialog -> 右击 -> 插入 Dialog

    b)点击对话框模板 -> 右击 -> 添加类

    c)多出来一个自定义的类, .h 类中有个枚举和对话框关联 enum { IDD = IDD_DIALOG2 };

   

二、基于对话框(控件)编程

1)基于对话框应用程序框架

    a)应用程序类:继承于 CWinApp

        InitInstance() : 程序的入口地址

    b)对话框类:继承于 CDialogEx

        OnInitDialog() :对话框的初始化工作

        DoDataExchange() : 控件和变量的关联和交换

三、常用控件

1)静态控件CStatic(Qt中的标签QLabel)  

    显示一些文字信息

    a) Caption: 修改显示的内容

    b) ID: XXX_STATIC,静态ID,不响应任何消息(事件)

2)按钮 CButton

    a) Caption: 修改显示的内容

    b) 处理消息 BN_CLICKED,用户点击按钮自动触发:

        1)属性 -> 控制事件 -> 选择所需事件

        2)双击按钮,自动生成消息处理函数

       

3)逃跑按钮(类似于Qt提升)

    a)自定义按钮类,继承于 CButton

    选择类视图最开始的文件夹 -> 右击 -> 添加类 -> MFC -> MFC类

        1)处理鼠标移动消息 WM_MOUSEMOVE

        2)获取父窗口指针 CWnd::GetParent

        3)获取父窗口客户区域的范围 CWnd::GetClientRect

        4)获取按钮的范围 CWnd::GetWindowRect

        5)产生随机坐标 rand()%w

        6)移动按钮的位置 CWnd::MoveWindow

    b)变量关联

    选中按钮 -> 右击 -> 添加变量 -> 变量类型: MyButton -> 变量:button

    最终,button和我们所选中的按钮关联成功,操作button,相当于操作ui上的按钮 (Qt的提升)

    c)为按钮设置位图

        1)按钮属性:Bitmap -> True

        2)在对话框类中 OnInitDialog() 做如下处理

            a)创建位图模板

            b)创建位图对象 CBitmap

            c)加载位图资源 CBitmap::LoadBitmap

            b)按钮设置位图 CButton::SetBitmap

            c)获取位图大小 CBitmap::GetBitmap

            c)重新设置按钮大小(图片和按钮大小一致) CWnd::MoveWindow

           

4)编辑框CEdit

    a)关联类别:Value, Control

        1)Value:标准普通数据类型 CString str;

            关联变量和控件数据的交互更新

            a)把编辑区的内容更新到str中 UpdateData(TRUE);

            b)把str的内容更新到编辑区中 UpdateData(FALSE);

       

        2)Control:控件类型

            控件类型的对象即为ui上的控件

           

    b)常用属性设置

        1)Number -> True 只能输入数字

        2)Password -> True 密码模式

        3)Want return -> True 接收回车键,自动换行,只有在多行模式下,才能换行

        4)Multiline -> True 多行模式

        5)Auto VScroll -> True

           Vertical Scroll -> True 当垂直方式字符太多,自动出现滚动条

        6)Read Only -> True 只读

    c)复制小案例

    关联 Control:控件类型,只能关联一次

        1)获取编辑区内容 CWnd::GetWindowText

        2)设置编辑区内容 CWnd::SetWindowText

        3)关闭对话框窗口

            CDialog::OnOK();

            CDialog::OnCancel();

5)单选框、复选框 (特殊的CButton, 没有单选框, 复选框类型 )

    a) 单选框

        1) 属性设置:顺序排放 Ctrl+D 查看

        2) 同组第一个按钮 Group 设置为 TRUE

        3) 初始化单选框 CWnd::CheckRadioButton

        4) 按钮是否按下 CWnd::IsDlgButtonChecked

       

    b) 复选框

        1) 常关联变量 BOOL  UpdateData(TRUE), UpdateData(FALSE);

        2) 设置按钮选择状态 CButton::SetCheck

        3) 获取按钮选择状态 CButton::GetCheck

6)列表框CListBox 

    a) 给列表框添加一个字符串 CListBox::AddString

    b) 选中列表列表框某一项,自动触发事件:LBN_SELCHANGE

        1)获取当前选中项 CListBox::GetCurSel

        2)获取指定位置的内容 CListBox::GetText

    c) 删除指定位置的字符串 CListBox::DeleteString

    d) 在指定位置插入字符串 CListBox::InsertString

7)组合框(下拉框)CComboBox

    a) 获取内容:CComboBox::GetLBText

        其它接口和 CListBox 的用法几乎一样

    b) 属性设置

        1) data: 设置内容,不同内容间同英文的分号“;”分隔

        2) type

       

8)滚动条 CScrollBar

    a) 设置给定滚动条的最小和最大位置:CScrollBar::SetScrollRange

    b) 获取一个滚动框的当前位置: CScrollBar::GetScrollPos

    c) 设置一个滚动框的当前位置:CScrollBar::SetScrollPos

    d) 处理滚动条的事件,不是在滚动条控件本身处理,是在滚动条所属的父窗口处理(对话框类)

        处理信号: WM_HSCROLL

    e)滚动条位置关系

        switch (nSBCode) //判断滚动条的哪一部分

        {

        case SB_THUMBPOSITION: //滑块位置

            break;

        case SB_LINELEFT: //向左的箭头

            break;

        case SB_LINERIGHT: //向左的箭头

            break;

        case SB_PAGELEFT: //箭头和滑块之间左边

            break;

        case SB_PAGERIGHT: //箭头和滑块之间右边

            break;

        default:

            break;

        }

       

9)微调(旋转)按钮 SpinControl 的使用

    a)属性设置

        Auto Buddy -> True

        Set buddy integer -> True

    b)微调(旋转)按钮的顺序比伙伴大1 (Ctrl + D 查看)

10)列表视图控件 CListCtrl

    a) 属性设置 view -> Report (报表方式)

    b) 常用接口

        1)设置列表风格 CListCtrl::SetExtendedStyle

            LVS_EX_FULLROWSELECT:选择整行

            LVS_EX_GRIDLINES:网格方式

            具体有哪些风格,可通过MSDN查看

        2)获取列表风格 CListCtrl::SetExtendedStyle

            具体有哪些风格,可通过MSDN查看   

        3)插入某列 CListCtrl::InsertColumn

        4)字符串格式化

            CString str;

            str.Format(_T("张三_%d"), i);

        5)插入新项后,才能设置子项内容

            a)插入新项(确定第几行) CListCtrl::InsertItem

            b)设置子项内容(设置第几列) CListCtrl::SetItemText 

11)树视图控件 CTreeCtrl

    a) 常用属性设置

        has buttons -> true

        has lines -> true

        lines at root -> true

    b) 写代码流程

        1) 加载自定义图标

            a) 获取应用程序对象指针 AfxGetApp()

            b) 加载自定义图标  CWinApp::LoadIcon

        2) 创建图像列表

            a) .h 文件类中定义图形列表(CImageList)对象

            b) 创建图像列表 CImageList::Create

            c) 图像列表追加图标 CImageList::Add

        4) 设置图形状态列表 CTreeCtrl::SetImageList

        5) 插入节点 CTreeCtrl::InsertItem

        6) 设置默认选中项 CTreeCtrl::SelectItem

       

12) 标签控件 CTabCtrl

    1)在ui工具箱拖放 Tab Control

    2)把 TabSheet.h和TabSheet.cpp 放在项目文件同级目录,并且添加到工程目录中

    3)给ui上 Tab Control 关联Control类型(CTabSheet)

    4)CTabSheet对象添加对话框

        a) 资源视图 -> Dialog -> 右击 -> 插入 Dialog

        b) 设置相应属性:

            Style -> Child (子窗口)

            Border -> None (无边框)

        c) 自定义类:点击对话框模板 -> 右击 -> 添加类

        d) 主对话框类中, 定义自定义类对象

        e) 主对话框类中 OnInitDialog() 做初始化工作

        f)CTabSheet添加对话框 CTabSheet::AddPage()

            tmp.AddPage(_T("系统管理"), &t1, tab1);

                tmp: 为CTabSheet对象

                t1:需要添加对话框对象

                tab1:对话框ID

        g)显示窗口:CTabSheet::Show()

5 文档/视图

MFC应用程序模型历经多年以有了相当大的发展。有一个时期,它只是个使用应用程序对象和主窗口对象的简单模型。在这个模型中,应用程序的数据作为成员变量保持在框架窗口类中,在框架窗口的客户区中,该数据被提交显示器。随着MFC2.0的问世,一种应用程序结构的新方式----MFC文档/视结构出现了。在文档/视图结构中,CFrameWnd繁重的任务被委派给几个不同类,实现了数据存储和显示的分离。

5.1文档/视图应用程序组成

一般情况下,采用文档/视结构的应用程序至少应由以下对象组成:

  1. 应用程序是一个CWinApp派生对象,它充当全部应用程序的容器。应用程序沿消息映射网络分配消息给它的所有子程序。
  2. 框架窗口是一CFrmeWnd派生对象。
  3. 文档是一个CDocument派生对象,它存储应用程序的数据,并把这些信息提供给应用程序的其余部分。
  4. 视窗是CView派生对象,它与其父框架窗口用户区对齐。视窗接受用户对应用程序的输入并显示相关联的文档数据。

通常,应用程序数据存在于简单模型中的框架窗口中。在文档/视方式中,该数据移入称为document的独立数据对象。当然,文档不一定是文字,文档是可以表现应用程序使用的数据集的抽象术语。而用户输入处理及图形输出功能从框架窗口转向视图。单独的视窗完全遮蔽框架窗口的客户区,这意味着即使程序员直接绘画至框架窗口的客户区,视图仍遮蔽绘画,在屏幕上不出现任何信息。所以输出必须通过视图。框架窗口仅仅是个视图容器。

CDocument类对文档的建立及归档提供支持并提供应用程序用于控制其数据的接口。MDI应用程序可以处理多个类型的文档,每个类型的文档拥有一个相关联的文档模板对象。文档对象驻留在场景后面,提供由视图对象显示的信息。文档至少有一个相关联的视图。视图只能与一个文档相关联。

在文档/视方式中,对象的建立是由文档模板来管理的,它是CDocTemplate派生对象,建立并维护框架窗口,文档及视。

总之,在文档/视模式中,文档和视图是分离的,即:文档用于保存数据,而视图是用来显示这些数据。文档模板维护它们之间的关西。这种文档/视结构在开发大型软件项目时特别有用。

5.2. 文档、视图、框架之间的关联

MFC SDI/MDI 中的核心就在于文档、视图、框架之间的关联,形成了一个有机的可运作的整体。 MFC 提供了默认的关联关系,但是在实际的项目开发中很多时候需要动态进行他们的之间的关联。

5.2.1 与文档/视结构有关的各种类之间的关系

CWinApp对象拥有并控制文档模板,后者产生文档、框架窗口及视窗。这种相互关系如图所示:

uploading.4e448015.gif

正在上传…重新上传取消

从用户的角度来看,“视”实际上是一个普通的窗口。像其他基于Widnows应用的窗口一样,人们可以改变它的尺寸,对它进行移动,也可以随时关闭它。若从程序员的角度来看,视实际上是一个从MFC类库中的CView类所派生出的类的对象。文档对象是用来保存数据的,而视对象是用来显示数据的,并且允许对数据进行编辑。SDI或MDI的文档类是由CDocument类派生出来的,它可以有一个或多个视类,而这些视类最终都是由CView类派生出来的。视对象只有一个与之相联系的文档对象,它所包含的CView::GetDocument函数允许应用在视中得到与之相联系的文档,据此,应用程序可以对文档类成员函数及公共数据成员进行访问。如果视对象接受到了一条消息,表示用户在编辑控制中输入了新的数据,此时,视就必须通知文档对象对其内部数据进行相应的更新。

如果文档数据发生了变化,则所有的视都必须被通知到,以便它们能够对所显示的数据进行相应的更新。CDocument::UpdateAllViews函数即可完成此功能。当该函数被调用时,派生视类的CView::OnUpdate函数被触发。通常OnUpdate函数要对文档进行访问,读取文档数据,然后再对视的数据成员或控制进行更新,以便反映出文档的变化。另外,还可以利用OnUpdate函数使视的部分客户区无效,以便触发CView::OnDraw函数,利用文档数据来重新对窗口进行绘制。Invalidate()函数也可以使当前窗口无效,导致窗口重绘。

MDI中的指针列表     

在MDI应用程序中,可以处理多个文档类型,即多个文档模板,每个模板又可以有多个文档,每个文档又可以多视显示。为管理方便,上一级往往保留了下一级的指针列表。如下图所示:

uploading.4e448015.gif

正在上传…重新上传取消

MDI中的存取关系

uploading.4e448015.gif

正在上传…重新上传取消

解释如下:

  1. 每个应用程序类(CWinApp的派生类)都保留并维护了一份所有文档模板的指针列表,这是一个链表结构。应用程序为所要支持的每个文档类型动态分配一个CMultiDocTemplate 对象,

CMultiDocTemplate(UINT nIDResource,

CRuntimeClass * pDocClass,

CRuntimeClass * pFrameClass,

CRuntimeClass * pViewClass );

并在应用程序类的CWinApp::InitInstance成员函数中将每个CMultiDocTemplate对象传递给CWinApp::AddDocTemplate。 该函数将一个文档模板加入到应用程序可用文档模板的列表中。函数原形为:

void AddDocTemplate(CdocTemplate * pTemplate);

应用程序可以用CWinApp::GetFirstDocTemplatePostion获得应用程序注册的第一个文档模板的位置,利用该值来调用CWinApp::GetNextDocTemplate函数,获得第一个CDocTemplate对象指针。函数原形如下:

POSITION GetFirstDocTemplate( ) const;

CDocTemplate *GetNextDocTemplate( POSITION & pos ) const;

第二个函数返回由pos 标识的文档模板。POSITION是MFC定义的一个用于迭代或对象指针检索的值。通过这两个函数,应用程序可以遍历整个文档模板列表。如果被检索的文档模板是模板列表中的最后一个,则pos参数被置为NULL。

使用 MFC 类向导生成 MFC SDI/MDI 程序,在 App 类的 InitInstance ()方法中有如下代码(假设 Project 名称均为 Test ):

SDI (单文档界面)中

CSingleDocTemplate* pDocTemplate;

pDocTemplate = new CSingleDocTemplate(

IDR_MAINFRAME,

RUNTIME_CLASS(CTestDoc),

RUNTIME_CLASS(CMainFrame),// main SDI frame window

RUNTIME_CLASS(CTestView)

);

AddDocTemplate(pDocTemplate);

MDI(多文档界面) 中

CMultiDocTemplate* pDocTemplate;

pDocTemplate = new CMultiDocTemplate(

IDR_TESTTYPE,

RUNTIME_CLASS(CTestDoc),

RUNTIME_CLASS(CChildFrame), // custom MDI child frame

RUNTIME_CLASS(CTestView)

);

AddDocTemplate(pDocTemplate);

这里通过 CDocTemplate (无论是 SDI 中的 CSingleDocTemplate 还是 MDI 中的 CMultiDocTemplate )的构造函数,将文当、视图和框架( SDI 中与主框架, MDI 中与自框架)关联在一起了,形成了一个整体。

  1. 一个文档模板可以有多个文档,每个文档模板都保留并维护了一个所有对应文档的指针列表。应用程序可以用CDocTemplate::GetFirstDocPosition函数获得与文档模板相关的文档集合中第一个文档的位置,并用POSITION值作为CDocTemplate::GetNextDoc的参数来重复遍历与模板相关的文档列表。函数原形为:

viaual  POSITION  GetFirstDocPosition( ) const = 0;

isual  CDocument *GetNextDoc(POSITION & rPos) const = 0;

如果列表为空,则rPos被置为NULL.

  1. 在文档中可以调用CDocument::GetDocTemplate获得指向该文档模板的指针。函数原形如下:

CDocTemplate * GetDocTemplate ( ) const;

如果该文档不属于文档模板管理,则返回值为NULL。

  1. 一个文档可以有多个视图。每一个文档都保留并维护一个所有相关视图的列表。CDocument::AddView将一个视图连接到文档上,将该视图加入到文档相联系的视图的列表中,并将视图的文档指针指向该文档。当有File/New、File/Open、Windows/New或Window/Split的命令而将一个新创建的视图的对象连接到文档上时, MFC会自动调用该函数,框架通过文档/视图的结构将文档和视图联系起来。当然,程序员也可以根据自己的需要调用该函数。

virtual POSITION GetFirstViewPosition( ) const;

virtual CViw * GetNextView( POSITION &rPosition) cosnt;

应用程序可以调用CDocument::GetFirstViewPosition返回与调用文档相联系的视图的列表中的第一个视图的位置,并调用CDocument::GetNextView返回指定位置的视图,并将rPositon的值置为列表中下一个视图的POSITION值。如果找到的视图为列表中的最后一个视图,则将rPosition置为NULL.

当在文档上新增一个视图或删除一个视图时,MFC会调用OnChangeViewList函数。如果被删除的视图是该文档的最后一个视图,则删除该文档。 

  1. 一个视图只能有一个文档。在视图中,调用CView::GetDocument可以获得一个指向视图的文档的指针。函数原形如下:

CDocument *GetDocument ( ) const;

如果该视图没有与任何文档相连接,则返回NULL.

  1. MDI框架窗口通过调用CFrameWnd::GetActiveDocument 可以获得与当前活动的视图相连的CDocument 指针。函数原形如下:

virtual CDocument * GetActiveDocument( );

  1. 通过调用CFrameWnd::GetActiveView 可以获得指向与CFrameWnd框架窗口连接的活动视图的指针,如果是被CMDIFrameWnd框架窗口调用,则返回NULL。MDI框架窗口可以首先调用MDIGetActive找到活动的MDI子窗口,然后找到该子窗口的活动视。函数原形如下:

virtual Cdocument * GetActiveDocument( );

  1. MDI框架窗口通过调用CFrameWnd::GetActiveFrame, 可以获得一个指向MDI框架窗口的活动多文档界面子窗口的指针。
  2. CMDIChildWnd调用GetMDIFrame获得MDI框架窗口(CMDIFrameWnd)。
  3. CWinApp 调用AfxGetMainWnd得到指向应用程序的活动主窗口的指针。

遍历整个文档模板、文档和视图

下面一段代码,就是利用CDocTemplate、CDocument和CView之间的存取关系,遍历整个文档模板、文档以及视图。

//获取应用程序的对象指针

CMyApp * pMyApp = (CMyApp *)AfxGetApp();

POSITION  p = pMyApp->GetFirstDocTemplatePosition();

while(p!= NULL)

{

       CDocTemplate * pDocTemplate = pMyApp->GetNextDocTemplate(p);

       POSITION p1 = pDocTemplate->GetFirstDocPosition();

       while(p1 != NULL)

{

           CDocument * pDocument = pDocTemplate->GetNextDoc(p1);

           POSITION p2 = pDocument->GetFirstViewPosition();

           while(p2 != NULL)

{

              CView * pView = pDocument->GetNextView(p2);

           }

       }

    }

5.2.2 MFC SDI/MDI 各个类之间的互访

在实际项目开发中用的最多就是各个类之间的互访问,这里将网络上和书籍中提到的做了一个总结,也是在实际开发中比较常用的。

访问对象

访问位置

访问实现

应用程序 App

任何位置

AfxGetApp();

② 在要使用应用程序 App 的文件中加入:

extern CApp theApp ,然后直接使用全局的 theApp 变量。

主框架窗口

任何位置

AfxGetMainWnd();

② AfxGetApp()->m_pMainWnd;

视图

框架类中

GetActiveView();// 当前的活动视图

文档类中

GetFirstViewPosition (); // 可以获取全部视图

GetNextView ();

文档

视类中

GetDocument()

文当模版类中

GetFirstDocPosition(); // 该文档模版对应全部文档

GetNextDoc();

框架类中

GetActiveDocument(); // 当前活动文当

子框架类(MDI中)

主框架类中

① MDIGetActive ();

② GetActiveFrame ();

视图类中

GetParentFrame();

文档模版

文档类中

GetDocTemplate();

应用程序 App 中

GetFirstDocTemplatePosition();

GetNextDocTemplate();

5.3文档序列化

5.3.1 序列化定义

MFC文档-视图结构中,序列化机制可以实现内存中对象储存和加载。

序列化机制分为序列化和反序列化:

  1. 序列化是把内存中的对象以二进制文件的形式存储在磁盘中
  2. 反序列化是把序列化后生成的文件恢复到内存。

5.3.2 CArchive类

CArchive没有基类。CArchive允许以一个永久二进制(通常为磁盘存储)的形式保存一个对象的复杂网络,它可以在对象被删除时,还能永久保存。可以从永久存储中装载对象,在内存中重新构造它们。使得数据永久保留的过程就叫作 “序列化”。

可以把一个归档对象(CArchive)看作一种二进制流。像输入/输出流一样,归档与文件有关并允许写缓冲区以及从硬盘读出或读入数据。输入/输出流处理一系列ASCII字符,但是归档文件以一种有效率、精练的格式处理二进制对象

5.3.3 CArchive对象处理基础类型数据

必须在创建一个CArchive对象之前,创建一个CFile对象。另外,必须确信归档文件的装入/存储与文件的打开模式是兼容的。每一个文件只限于一个活动归档文件。当构造一个CArchive对象时,要把它附加给表示一个打开文件的类CFile(或派生类)的对象上。还要指定归档文件将用于装载还是存储。

  1. 示例:

//将数据以二进制方式写入文件中

void CArchiveView::OnWritefile()

{

   // TODO:  在此添加命令处理程序代码

   //构造CFile文件对象

   CFile file(_T("demo.txt"),

CFile::modeCreate | CFile::modeWrite);

   //构造存档对象

   CArchive ar(&file, CArchive::store);

   int a = 12;

   char ch = 'p';

   double b = 12.345;

   CString str = _T("大江东去浪淘尽, 千古风流人物");

   //保存数据

   ar << a << ch << b << str;

}

//从文件中读取数据

void CArchiveView::OnReadfile()

{

   // TODO:  在此添加命令处理程序代码

   //构造CFile文件对象

   CFile file(_T("demo.txt"), CFile::modeRead);

   //构造存档对象

   CArchive ar(&file, CArchive::load);

   //定义变量,存放加载出来的数据

   int a;

   char ch;

   double b;

   CString str;

   //加载数据

   ar >> a >> ch >>b >> str;

   //格式化字符串

   CString strResult;

   strResult.Format(_T("%d\n%c\n%lf\n%s"), a, ch, b, str);

   MessageBox(strResult);

}

5.3.4 CArchive对象处理自定义对象类型数据

CArchive对象不仅可以处理基础类型,而且还能处理为序列化而设计的特殊的类的对象。可以序列化的类有以下特点:

  1. 必须为CObject派生类。直接或者间接的继承CObject类。
  2. 必须重写CObject类的Serialize成员函数。
  3. 使用DECLARE_SERIAL和IMPLEMENT_SERIAL宏。
  4. 必须有一个默认构造函数

在Serialize成员函数中完成保存和加载的功能。

DECLARE_SERIAL( class_name ) 参数为当前类名。

IMPLEMENT_SERIAL( class_name, base_class_name, wSchema )

  1. 第一个参数为当前类名
  2. 第二个参数为父类类名
  3. 第三个参数为该类的特定整型标识,该标识将用来解序(重新实例化),最小为0。

示例:

//

DrawBase.h

//

class CDrawBase : public CObject //继承自CObject

{

public:

    DECLARE_SERIAL(CDrawBase)    //第一个宏的位置,参数为当前类名

    CDrawBase();               //必须有一个默认构造函数

    virtual void onDraw(CDC* pdc);

    virtual ~CDrawBase();

public:

    UINT m_PenStyle;

    int m_PenWidth;

    int m_BkMode;

    int m_BrushStyle;

    int m_issx;

    int m_isyy;

    COLORREF m_PenColor;

    COLORREF m_BackgroundColor;

    COLORREF m_BrushColor;

    CPoint m_ptBegin;

    CPoint m_ptEnd;

public:

    void Serialize(CArchive& ar);   //重写了Serialize成员函数

};

//

DrawBase.cpp

//

//第二个宏的位置,第一个参数为当前类名,第二个参数为父类类名,

//第三个参数为该类的特定整型标识,该标识将用来解序(重新实例化),

//最小为0

IMPLEMENT_SERIAL(CDrawBase, CObject, 1)

void CDrawBase::Serialize(CArchive& ar)

{

    CObject::Serialize(ar);

    if(ar.IsStoring())    //保存,加载为ar.IsLoading()

    {

        ar <<m_PenColor<<m_PenStyle<<m_PenWidth

<<m_BrushColor<<m_BrushStyle<<m_issx<<m_isyy;

    }

    else    //加载

    {

         ar >>m_PenColor>>m_PenStyle>>m_PenWidth>>

m_BrushColor>>m_BrushStyle>>m_issx>>m_isyy;

    }

}

5.3.5 总结

重载提取(>>)和插入(<<)是方便的归档编程接口。它支持主要类型和CObject派生类。

CArchive还支持使用MFC Windows套接字类CSocket和CSocketFile编程。IsBufferEmpty成员函数也支持这种使用。一些集合类也支持序列化,CObArray,Vector,CPtrArray。

在MFC文档-视图结构中,Doc类是被系统设定好支持序列化的类,在Doc类中重写Serialize成员函数。在函数中对你要保存的对象序列化。完成之后,点击菜单栏上的保存和打开就可以实现序列化了。

5.4. 文档/视图实例

【例5-4-1】文档视图结构应用程序例子(Ex_DocView)。

创建基于CFormView类的多文档应用程序

用MFC 应用程序向导创建一个默认的多文档应用程序Ex_DocView,但在向导的【生成的类】属性页将CEx_DocViewView的基类由默认的CView选择为CFormView类,如图1所示。

uploading.4e448015.gif

正在上传…重新上传取消

图1  设置视图的基类为CFormView类

添加应用程序所需的数据

  1. 第一步 在VS中,给新创建的工程添加一个新的MFC类,弹出新建类对话框。

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

                         图2 添加MFC类

在添加类向导对话框中,类名为CStudent,默认的类文件为Student.cpp,设置该类的父类为CObject,并单击【确定】按钮。如图3所示。

uploading.4e448015.gif

正在上传…重新上传取消

图3  添加CStudent类

  1. 第二步 打开项目工作区的文件视图,双击打开Student.h文件,如图4所示,编辑CStudent类定义的代码如下:

class CStudent : public CObject 

{

public:

CStudent();

       CStudent(long code, CString name, long age, double score);

       virtual ~CStudent();

       long m_nCode;

        CString m_sName;

        long m_lAge;

        double m_lScore;

};

uploading.4e448015.gif

正在上传…重新上传取消

图4  打开Student.h文件

  1. 第三步 同上步,编辑Student.cpp文件,修改类CStudent的构造函数实现代码如下:

CStudent::CStudent(long code,CString name,long age,double score)

{

  m_nCode=code;

  m_sName=name;

  m_lAge=age;

  m_lScore=score;

}

  1. 第四步 在Ex_DocViewDoc.h文件中,为类CEx_DocViewDoc添加数据成员:

class CEx_DocViewDoc : public CDocument

{

……

public:

    POSITION   curPos;

    CTypedPtrList <CObList, CStudent*> m_dataList;

……

}

说明:m_dataList定义为CStudent类指针的表CtypedPtrList变量,curPos定义为POSITION变量,用来指示当前指针。

  1. 第五步 在文件stdafx..h中加入添加如下代码:

#include<afxtempl.h>

因为在程序中使用了模板类CTypedPtrList<CObList, CStudent*>。

  1. 第六步 在Ex_DocViewDoc.h文件的头部添加如下代码:

#include "Student.h"

  1. 第七步 在文档关闭时,需要清除m_dataList占用的内存,利用属性窗口为CEx_DocViewDoc类加入虚函数DeleteContens(),并添加代码:

void CEx_DocViewDoc::DeleteContents()

{

while(!m_dataList.IsEmpty())

{   

delete  m_dataList.RemoveHead();

}

    CDocument::DeleteContents();

}

修改IDD_EX_DOCVIEW_FORM对话框资源,添加应用程序所需控件:

  1. 第一步 打开项目工作区的资源视图,双击打开IDD_EX_DOCVIEW_FORM对话框资源如图5所示,编辑IDD_EX_DOCVIEW_FORM对话框资源,如图6所示。

uploading.4e448015.gif

正在上传…重新上传取消  

图5  打开对话框资源         

uploading.4e448015.gif

正在上传…重新上传取消

图6  编辑后的对话框控件

  1. 第二步 设置图6各控件的属性如表1所示。

表1  添加的控件

控件

ID

标题

其他属性

编辑框(学号)

IDC_CODE

默认

编辑框(姓名)

IDC_NAME

默认

编辑框(年龄)

IDC_AGE

默认

编辑框(成绩)

IDC_SCORE

默认

按钮(添加)

IDC_ADD

添加

默认

按钮(下一个)

IDC_NEXT

下一个

默认

  1. 第三步 打开MFC类向导的成员变量页面,确定类名是CEx_DocViewView,如图7所示。选中所需的控件ID号,双击鼠标。依次为下列控件添加成员变量,如下表2所示。

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

图7  为CEx_DocViewView添加成员变量

表2  控件变量

ID

成员变量名

属性类型

变量类型

IDC_CODE

m_nCode

Value

UINT

IDC_NAME

m_sName

Value

CString

IDC_AGE

m_lAge

Value

int

IDC_SCORE

m_lScore

Value

double

为按钮编写消息响应函数

  1. 第一步 用MFC ClassWizard为按钮IDC_ADD添加BN_CLICKED的消息映射,如图8所示,单击【添加函数】按钮为CEx_DocViewView添加OnAdd( )成员函数,再单击【编辑代码】按钮为该含函数加入下列代码:

void CEx_DocViewView:: OnBnClickedAdd ()

{

    UpdateData();

  CEx_DocViewDoc *pDoc=GetDocument();

  ASSERT_VALID(pDoc);

  CStudent *pStudent;

  pStudent=new CStudent(m_nCode,m_sName,m_lAge,m_lScore);

    pDoc->m_dataList.AddTail(pStudent);

  pDoc->curPos=pDoc->m_dataList.GetHeadPosition();

}

uploading.4e448015.gif

正在上传…重新上传取消

图8  为按钮IDC_ADD编写消息响应函数

说明:

    1. UpdateData()函数迫使对话框编辑控件和相应变量之间传送数据,该函数原型为:

BOOL UpdateData(BOOL bSaveAndValidate::TRUE);

    1. 其中bSaveAndValidate为true时表示数据已经更新。

在CEx_DocViewView::OnAdd()函数中,取得指向文档的指针,操作文档对象的成员变量m_dataList,首先使用用户输入的变量值构造一个新CStudent对象,然后将其加入m_dataList表尾,最后将curPos指向表头。通过上面的操作,一个新CStudent对象就加到m_dataList表中。

  1. 第二步 同上步,用MFC ClassWizard为按钮IDC_NEXT添加BN_CLICKED的消息映射,并增加下列代码:

void CEx_DocViewView:: OnBnClickedNext ()

{

    CEx_DocViewDoc *pDoc =GetDocument();

    ASSERT_VALID(pDoc);

    if(pDoc->curPos!=NULL)  

    {

      CStudent *pStudent=(CStudent *)

pDoc->m_dataList.GetAt(pDoc->curPos); 

       m_nCode=pStudent->m_nCode;

       m_sName=pStudent->m_sName;

       m_lAge=pStudent->m_lAge;

       m_lScore=pStudent->m_lScore;   

       UpdateData(false);

       pDoc->m_dataList.GetNext(pDoc->curPos);

           if(pDoc->curPos==NULL)  

        pDoc->curPos=pDoc->m_dataList.GetHeadPosition();

    }

    else

       MessageBox("当前列表中没有数据!");

}

说明:CEx_DocViewView::OnNext函数用来循环遍历m_dataList表,首先得到文档指针,然后判断,如果curPos为空,就说明没有数据,因为在OnAdd函数中将其设在表头。如果表不空,就进行循环遍历。

【例5-4-2】文档视图结构应用程序例子(Editor)。

创建单文档应用程序Editor

  1. 第一步 用MFC AppWizard(exe)创建一个默认的单文档应用程序Editor,但在向导的【文档模板属性】页,设置文档视图结构的一些属性,如图9所示。

uploading.4e448015.gif

正在上传…重新上传取消

图9  Advanced Option对话框

该对话框中包含以下几项:

  1. 文件扩展名:指定应用程序创建的文档所用的文件名后缀。输入后缀名.txt,表明Editor使用文本文件的后缀名.TXT;
  2. 文件类型ID:用于在Windows的注册数据库中标识应用程序的文档类型;
  3. 主框架标题:主框架窗口标题,默认情况下与项目名相一致;
  4. 文档类型名称:指定与一个从CDocument派生的文档类相关的文档类型名;
  5. 筛选器名称:用作“打开文件”、“保存文件”对话框中的过滤器。Visual Studio会自动根据输入的后缀名生成一个过滤器:Editor Files(*.txt)。这样,当在Open File对话框中选择Editor Files(*.txt)时,只有以.txt为后缀名的文件名显示在文件名列表中;
  6. 文件的新短名称:用于指定在new对话框中使用的文档名。当应用程序支持多种文档类型时,选择【Fi1e】à【New】命令会弹出一个对话框,列出应用程序所支持的所有文档类型,供用户选择。选择一种文档类型后,自动创建相应类型的文档。
  7. 文件类型长名称:用于指定当应用程序作为OLE Automation服务器时使用的文档类型名,使用默认值。

添加应用程序所需的数据

  1. 第一步 打开项目工作区的文件视图,双击打开EditorDoc.h文件,如图10所示,并在该文件中,定义文档的数据成员,加入以下代码:

class CEditorDoc : public CDocument

{

……

public:

  CStringList lines;  // 链表CStringList来保存文本编辑器的数据

  int nLineNum;    // 用于指示当前编辑行行号

……

   }

uploading.4e448015.gif

正在上传…重新上传取消

图10  打开EditorDoc.h文件

  1. 第二步 同上步,打开在EditorDoc.cpp文件,在CEditorDoc::OnNewDocument()成员函数加入初始化数据成员的代码:

BOOL EditorDoc::OnNewDocument()

{

  if (!CDocument::OnNewDocument())   return   FALSE;

  nLineNum=0;

  POSITION pos;             // pos指向链表当前元素。

  pos=lines.GetHeadPosition();   // 返回链表头指针

  while(pos!=NULL)

{

      ((CString)lines.GetNext(pos)).Empty();

}

  lines.RemoveAll();           // 清除链表中的所有元素

return TRUE;

}

说明:语句((CString)lines.GetNext(pos)).Empty()的作用是:以当前pos为参数,返回下一个元素指针,同时修改pos,使它指向下一个元素。使用强制类型转换将GetNext()函数返回的元素指针转化为CString类型,然后调用Cstring::Empty()方法清除该行中的所有字符。通过一个while循环,清除所有文本行的数据。

一般地,类的数据成员的初始化都是在构造函数中完成的,但由于文档对象创建后,需要反复刷新而不是反复创建,因此文档类的数据成员初始化工作放在OnNewDocument成员函数中完成而不在构造函数中做这件事情。

  1. 第三步 用MFC ClassWizard为CEditorDoc类添加虚函数DeleteContents(),如图11所示。同时,增加下述代码:

void CEditorDoc::DeleteContents()

{

    nLineNum=0;

    POSITION pos;

    pos=lines.GetHeadPosition();

    while(pos!=NULL)

{

       ((CString)lines.GetNext(pos)).Empty();

}

lines.RemoveAll();

    CDocument::DeleteContents();

}

uploading.4e448015.gif

正在上传…重新上传取消

图11  为CEditorDoc类添加虚函数

说明:在使用【File】à【Open】命令打开一个文档或关闭应用程序时,都需要清理文档对象中的数据。文档的清理是在文档的CDocument::DeleteContents()虚函数中完成的。DeleteContents()成员函数需要反复调用,它的功能是删除文档的数据,并确信一个文档在使用前为空。有读者可能想到在析构函数中清理文档数据,但析构函数只在文档对象结束时调用,用于清除那些在对象生存期都将存在的数据项,显然析构函数不能满足重复调用的要求。

处理键盘输入

  1. 第一步 在文档类的头文件EditorDoc.h中,定义视图类的数据成员,加入以下代码:

class CEditorView : public CView

{

……

  int lHeight; 

int cWidth;

……

}

  1. 第二步 用MFC ClassWizard为CEditorView类添加虚函数OnInitialUpdate(),并增加下列代码:

void CEditorView::OnInitialUpdate()

{

    CDC *pDC=GetDC(); // 取得当前窗口的设备场境指针并存放在pDC中

    TEXTMETRIC tm;

    pDC->GetTextMetrics(&tm);

    lHeight=tm.tmHeight+tm.tmExternalLeading;

    cWidth=tm.tmAveCharWidth;

    CView::OnInitialUpdate();

}

说明:视图类一般在CView::OnInitialUpdate()成员函数来初始化视图类的数据成员。因为这时,视图窗口已经创建,马上开始更新,那么可能影响视图显示的数据一定要在这时初始化。

在以下情况下,应用程序将自动执行视图类的OnInitialUpdate()来初始化视图类数据成员:

    1. 调用CDocument::OnNewDocument时;
    2. 调用CDocument::OnOpenDocument时需要清除视图原有的显示内容。

TEXTMETRIC是一个数据结构,它包含字体的宽度、高度、字的前后空白等字段。调用CDC::GetTextMetrics()获取字体的TEXTMETRIC,从而取得字体的宽度和高度等信息。

  1. 第三步 用MFC ClassWizard为类CEditorView添加WM_CHAR的消息处理函数OnChar(),如图12所示。打开CEditorView::OnChar()函数进行编辑。修改后的OnChar函数如下:

void CEditorView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

  CEditorDoc* pDoc=GetDocument();

  CClientDC dc(this);

  CString line("");    // 存放编辑器当前行字符串

  POSITION pos=NULL;   // 字符串链表位置指示

  if(nChar=='\r')      // 若是回车,则增加一行

  {

     pDoc->nLineNum++;

  }

  else

  {

     // 按行号返回字符串链表中位置值

     pos=pDoc->lines.FindIndex(pDoc->nLineNum);

     if(!pos)

     {

        // 没有找到该行号对应的行,因此它是一个空行,

// 把它加到字符串链表中

         line+=(char)nChar;

         pDoc->lines.AddTail(CString(line));

     }

     else

{

        // 当前文本行还没有换行结束,因此将文本加入到行末

         line=pDoc->lines.GetAt(pos);

         line+=(char)nChar;

         pDoc->lines.SetAt(pos,line);

      }

      TEXTMETRIC tm;

      dc.GetTextMetrics(&tm);

      dc.TextOut(0, (int)pDoc->nLineNum*tm.tmHeight,

line,line.GetLength());

  }

  CView::OnChar(nChar, nRepCnt, nFlags);

}

uploading.4e448015.gif

正在上传…重新上传取消

图12  为CEditorView类添加消息处理函数

说明:编辑器要不断接收用户的键盘输入,就必须处理键盘消息。每按下一个字符,窗口就会接收到一个消息WM_CHAR。WM_CHAR消息是在视图类中处理的,对该消息的处理过程大致包括:读取用户输入的字符,如果输入是一个回车,则将总行数nLineNum加1,否则将输入字符加到当前行行末。最后调用TextOut函数输出当前编辑中的文本行。

  1. 第四步 修改CEditorView类的OnDraw()函数,编辑后该函数的代码如下:

void CEditorView::OnDraw(CDC* pDC)

{

  CEditorDoc* pDoc = GetDocument();

  ASSERT_VALID(pDoc);

  TEXTMETRIC tm;

  pDC->GetTextMetrics(&tm);

  lHeight=tm.tmHeight+tm.tmExternalLeading;

  cWidth=tm.tmAveCharWidth;

  int y=0;

  POSITION pos;

  CString line;

  if(!(pos=pDoc->lines.GetHeadPosition()))

  {

     return;

  }

  while(pos!=NULL)

  {

     line=pDoc->lines.GetNext(pos);

     pDC->TextOut(0, y, line, line.GetLength());

     y+=lHeight;

  }

}

在OnDraw()函数中,首先调用GetDocument()函数,取得指向当前视图所对应的文档的指针。通过这个指针,来访问文档中的数据。以后在视图中修改文档中的数据,也是通过GetDocument()来取得文档指针,再通过该文档指针修改文档中的数据。

  1. 第五步 编译运行,结果如图13所示。

uploading.4e448015.gif

正在上传…重新上传取消

图13  Editor的运行结果

【例6-4-3】一个简单的文档序列化示例(Ex_SDIArchive)。

  1. 第一步 用MFC应用程序向导创建一个默认的单文档应用程序Ex_SDIArchive。
  2. 第二步 打开StringTable资源,将文档模板字符串资源IDR_MAINFRAME,该字符串资源实际上是由 \n字符分隔的7个字串,前两个 \n之间没有任何内容,所以文档才没有标题,我们可以在它们之间添加一个标题。如图16所示。

uploading.4e448015.gif

正在上传…重新上传取消

图16  文档模板字符串的设置

  1. 第三步 为CEx_SDIArchiveDoc类添加下列成员变量:

class CEx_SDIArchiveDoc : public CDocument

{

……

public: 

CPoint  points[100];   

int   m_index;  // 表示数组中点的数目;

……

}

  1. 第四步 在CExSDIArchiveDoc::OnNewDocument函数中添加下列代码:m_index=0;

BOOL CEx_SDIArchiveDoc::OnNewDocument()

{

……

m_index=0; 

return TRUE;

}

  1. 第五步 用MFC ClassWizard为类CEx_SDIArchiveView添加WM_LBUTTONDOWN的消息处理函数OnLButtonDown(),如图17所示。然后打开该函数进行编辑。修改后的OnLButtonDown函数如下:

void CEx_SDIArchiveView::OnLButtonDown(UINT nFlags, CPoint point)

{

CEx_SDIArchiveDoc* pDoc=GetDocument();

  if(pDoc->m_index==100)      return;      

// 接受鼠标输入,将其添加到文档类

pDoc->points[pDoc->m_index]=point;

pDoc->m_index++;

pDoc->SetModifiedFlag();    // 设置文档修改标志

    Invalidate();               // 更新客户区域

  CView::OnLButtonDown(nFlags, point);

}

uploading.4e448015.gif

正在上传…重新上传取消

图17  为视图类添加WM_LBUTTONDOWN的消息处理函数

  1. 第六步 修改CEx_SDIArchiveView CEditorView类的OnDraw()函数,编辑后该函数的代码如下:

void CEx_SDIArchiveView::OnDraw(CDC* pDC)

{

……

  int index;

  index=pDoc->m_index;

     for (int i=1;i<=index;i++)

{

    pDC->Ellipse(pDoc->points[i].x-2,pDoc->points[i].y-2,

                  pDoc->points[i].x+2,pDoc->points[i].y+2);

}

}

  1. 第七步 用MFC ClassWizard为类CEx_SDIArchiveDoc添加Serialize虚函数,实现文档类中成员添加下列代码:

void CEx_SDIArchiveDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

  {

      ar<<m_index;

     for (int i=0;i<=m_index;i++)

{

         ar<<points[i].x<<points[i].y;

}

  }

  else

  {

     ar>>m_index;

     for (int i=0;i<=m_index;i++)

{

         ar>>points[i].x>>points[i].y;

}

  }  

}

  1. 第八步 编译运行并测试。结果如图18所示。

uploading.4e448015.gif

正在上传…重新上传取消

程序运行后,选择【文件】à【另存为】命令,指定一个文档名ab,然后选择【文件】à【新建】命令,再打开文档,结果就会弹出对话框,显示该文档的内容。

uploading.4e448015.gif

正在上传…重新上传取消

图18  Ex_SDIArchive的运行结果

【例6-4-4】一个简单的CArchive类用法的示例(Ex_Archive)。

  1. 第一步 用MFC 应用程序向导创建一个默认的基于对话框的应用程序Ex_Archive。
  2. 第二步 为新创建的工程添加一个新的.h文件, Person.h,单击【添加】按钮,如图19所示。在Person.h文件中输入下面的代码:

class CPerson : public CObject

{

  DECLARE_SERIAL(CPerson)

public:

  virtual void Serialize(CArchive& ar);

  CPerson();   

  virtual ~CPerson();

  UINT Load(void);

  CString m_szName ;

  UINT m_nAge;

};

uploading.4e448015.gif

正在上传…重新上传取消

图19  新建头文件

  1. 第三步 同上步,在项目中添加源文件Person.cp,在Person.cpp文件中输入下面的代码:

#include "stdafx.h"

#include "Person.h"

IMPLEMENT_SERIAL(CPerson, CObject, 1)

CPerson::CPerson()

{    }

CPerson::~CPerson()

{    }

void CPerson::Serialize(CArchive& ar)

{

  if (ar.IsStoring())        

{

ar<<m_szName<<m_nAge;

}

  else

{

ar>>m_szName>>m_nAge;

}

}

UINT CPerson::Load()

{

  CFile f;

  char buf[512];

  if( !f.Open( "person.dat", CFile::shareDenyWrite|CFile::modeRead) )       return 0;

  else

 {

     CArchive ar( &f, CArchive::load, 512, buf );

     Serialize(ar);      

return 1;

  }

}

说明:通过2、3两步建立了CPerson类,并且改类为可序列化的类,CPerson类的成员函数Load( )首先生成指向"person.dat"文件的CFile对象,然后构造读文件的CArchive对象ar,并从文件中读数据完成CPerson对象的初始化。

  1. 第四步 编辑IDD_EX_ARCHIVE_DIALOG对话框资源,如图20所示。

uploading.4e448015.gif

正在上传…重新上传取消

图20  对话框资源

设置图20控件的属性如表3示:

表3  添加的控件

控件

ID

标题

其他属性

编辑框(Name)

编辑框(Age)

按钮(保存)

IDC_EDIT_NAME

IDC_EDIT_AGE

IDC_SAVE

保存

默认

默认

默认

  1. 第五步 打开MFC 类向导的成员变量页面,确定类名是CEx_ArchiveDlg,选中所需的控件ID号,双击鼠标。依次为下列控件添加成员变量,如下表4所示。

表4  控件变量

ID

成员变量名

属性类型

变量类型

IDC_EDIT_AGE

m_nAge

Value

UINT

IDC_EDIT_NAME

m_szName

Value

CString

  1. 第六步 为类CEx_ArchiveDlg添加一个CPerson类的对象m_person成员。并在Ex_ArchiveDlg.h文件的头部添加以下语句:

#include "person.h"

  1. 第七步 修改CEx_ArchiveDlg的OnInitDialog()函数在该函数中添加从文件读取数据并初始化m_person的代码:

BOOL CEx_ArchiveDlg::OnInitDialog()

{

……

if(m_person.Load())

{

       this->m_szName=this->m_person.m_szName;

       this->m_nAge=this->m_person.m_nAge;

       this->UpdateData(FALSE);

    }

    else

{

       AfxMessageBox("load person data fails");

}

    return TRUE;

}

  1. 第八步 用MFC ClassWizard为按钮IDC_SAVE添加BN_CLICKED的消息映射,并增加下列代码:

void CEx_ArchiveDlg::OnSave()

{

   CFile f;

    char buf[512];

    if( !f.Open( "person.dat",CFile::modeCreate | CFile::modeWrite) )

{

         exit( 1 );

    }

    CArchive ar( &f, CArchive::store, 512, buf );

    UpdateData();

    m_person.m_nAge=this->m_nAge;

    m_person.m_szName=this->m_szName;

    m_person.Serialize(ar);

}

  1. 第九步 编译运行并测试。

6 数据库编程

6.1 MFC ODBC连接数据库

ODBC是微软公司支持开放数据库服务体系的重要组成部分,它定义了一组规范,提供了一组对数据库访问的标准API,这些API是建立在标准化版本SQL(Structed  Query  Language,结构化查询语言)基础上的。ODBC位于应用程序和具体的DBMS之间,目的是能够使应用程序端不依赖于任何DBMS,与不同数据库的操作由对应的DBMS的ODBC驱动程序完成。

6.1.1 ODBC的构成

ODBC的结构如图所示。

uploading.4e448015.gif

正在上传…重新上传取消

使用ODBC的层次图

ODBC层由三个部件构成:

  1. ODBC管理器

ODBC管理器的主要任务是管理安装ODBC驱动程序,管理数据源。应用程序要访问数据库,首先必须在ODBC管理器中创建一个数据源。ODBC管理器根据数据源提供的数据库存储位置,类型及ODBC驱动程序信息,建立起ODBC与一个特定数据库之间的联系,接下来,程序中只需提供数据源名,ODBC就能连接相关的数据库。ODBC管理器位于系统控件面板中。

  1. 驱动程序管理器

驱动器管理器位于ODBC32.DLL,是ODBC中最重要的部件,应用程序通过ODBC API执行数据库操作。其实ODBC API不能直接操作数据库,需要通过驱动管理器调用特定的数据库的驱动程序,驱动程序在执行完相应操作后,再将结果通过驱动程序管理器返回。驱动器管理器支持一个应用程序同时访问多个DBMS中的数据。

  1. ODBC驱动程序

ODBC驱动程序以DLL文件形式出现,提供ODBC与数据库之间的接口。

6.1.2 MFC ODBC类

进行ODBC编程,有三个非常重要的元素:环境(Enviroment),连接(Connection)和语句(Statement),它们都是通过句柄来访问的。在MFC的类库中,CDatabase类封装了ODBC编程的连接句柄,CRecordset类封装了对ODBC编程的语句句柄,而环境句柄被保存在一个全局变量中,可以调用一个全局函数AfxGetHENV来获得当前被MFC使用的环境句柄。

此外CRecordView类负责记录集的用户界面,CFieldExchange负责CRedordset类与数据源的数据交换。

使用AppWizard生成应用程序框架过程中,只要选择了相应的数据库支持选项,你就能够很方便地获得一个数据库应用程序的框架.

  1. CDatabase

CDatabase类的主要功能是建立与ODBC数据源的连接,连接句柄放在其数据成员m_hdbc中,并提供一个成员函数GetConnect()用于获取连接字符串。要建立与数据源的连接,首先创建一个CDatabase对象,再调用CDatabase类的Open()函数创建连接。Open()函数的原型定义如下:

virtul BOOL Open(LPCTSTR lpszDSN,

BOOL bExclusive=FALSE,

BOOL bReadOnly=FALSE,

LPCTSTR lpszConnect=”ODBC;”,

BOOL bUseCursorLib=TRUE);

其中:

lpszDSN指定数据源名,若lpszDSN的值为NULL时,在程序执行时会弹出数据源对话框,供用户选择一个数据源。

lpszConnect指定一个连接字符串,连接字符串中通常包括数据源名、用户ID、口令等信息,与特定的DBMS相关。

例如:

CDatabase db;

db.Open(NULL,FALSE,FALSE,“ODBC;DSN=HotelInfo;UID=SYSTEM;PWD=123456”);

从断开与一个数据源的连接,可以调用CDatabase类的成员函数Close()。

  1. CRecordset

CRecordset类对象表示从数据源中抽取出来的一组记录集。CRecordset类封装了大量操作数据库的函数,支持查询,存取,更新数据库操作。

记录集主要分为两种类型:

  1. 快照(Snapshot)记录集

快照记录集相当于数据库的一张静态视图,一旦从数据库抽取出来,当别的用户更新记录的操作时不会改变记录集,只有调用Requry()函数重新查询数据,才能反映数据的变化。自身用户的添加记录操作重要调用Requry()函数重新查询数据,但快照集能反应自身用户的删除和修改操作。

  1. 动态(Dynaset)记录集

动态(Dynaset)记录集与快照记录集恰恰相反,是数据库的动态视图。当别的用户更新记录时,动态记录集能即时反映所作的修改。在一些实时系统中必须采用动态记录集,如火车标联网购票系统。但别的用户添加记录,也需要调用Requry()函数重新查询数据后才能反映出来。

CRecordset有六个重要的数据成员如下表所示.。

CRecordset 类的数据成员

数据成员

类型

说明

m_strFilter

CString

筛选条件字符串

m_strSort

CString

排序关键字字符串

m_pDatabase

CDatabase类指针

指向CDatabasec对象的指针

m_hstmt

HSTMT

ODBC语句句柄

m_nField

UINT

记录集中字段数据成员总数

m_nParams

UINT

记录集中参数数据成员总数

CRecordset的主要成员函数如下表所示:

CRecordset类的成员函数

成员函数

类  型

Move

当前记录指针移动若干个位置

MoveFirst

当前记录指针移动到记录集第一条记录

MoveLast

当前记录指针移动到记录集最后一条记录

MoveNext

当前记录指针移动到记录集下一条记录

MovePrev

当前记录指针移动到记录集前一条记录

SetAbsolutePosition

当前记录指针移动到记录集特定一条记录

AddNew

添加一条新记录

Delete

删除一条记录

Edit

编辑一条记录

Update

更新记录

CancelUpdate

取消一条记录的更新操作

Requry

重新查询数据源

GetDefaultConnect

获得默认连接字符串

GetDefaultSQL

获得默认SQL语句

DoFieldExchange

记录集中字段数据成员与数据源中交换数据

GetRecordCount

 获得记录集记录个数

IsEOF

判断当前记录指针是否在最后一个记录之后

IsBOF

判断当前记录指针是否在第一个记录之前

CanUpdate

判断记录集是否允许更新

  1. CRecordView

CRecordView类是CFormView的派生类,支持以控件视图来显示当前记录,并提供移动记录的默认菜单和工具栏,用户可以通过记录视图方便地浏览、修改、删除和添加记录。记录视图与对话框一样使用DDX数据交换机制在视图中的控件的记录集成员之间交换数据,只需使用ClassWizard将控件与记录集的字段数据成员一一绑定。

CRecordView的主要函数如下表所示:

CRecordView类的主要成员函数

成员函数

类型

OnGetRecordset

获得指向记录集的指针

OnMove

当前记录指针移动时,OnMove()函数更新对当前记录所作的修改,这是将更新记录保存的方式。

IsOnFirstRecord

判断当前记录是否为记录集的第一条记录

IsOnLastRecord

判断当前记录是否为记录集的最后一条记录

  1. CFieldExchange

CFieldExchange类支持记录字段数据的自动交换,实现记录集中字段数据成员与相应的数据源中字段之间的数据交换,类似于对话框数据自动交换机制。

6.2数据库应用程序的实现

6.2.1 创建并注册数据源

在创建数据库应用程序之前,先要准备好数据源。下面我们假设数据库应用程序要连接的数据库hotel.accdb存放在C盘根目录下,该数据库下有一张TblCustomer的表,如下图所示:

uploading.4e448015.gif

正在上传…重新上传取消

数据表“tblCustomer”

在Windows操作系统的控制面板中,可以找到数据源ODBC管理器的图标,如下图所示为Windows7中的ODBC的图标,它的位置在控制面板中的管理工具文件夹。由于所要连接的数据库是由Microsoft ACCESS创建,要求ODBC管理器中安装有Microsoft ACCESS的ODBC驱动程序。一般,只需安装了Microsoft ACCESS软件,相应的ODBC驱动程序就已经默认安装了。

uploading.4e448015.gif

正在上传…重新上传取消

ODBC图标

鼠标双击ODBC图标,弹出“ODBC数据源管理器”对话框,如下图:

uploading.4e448015.gif

正在上传…重新上传取消

ODBC数据源管理器

在用户DSN、系统DSN、文件DSN标签页中都可以创建一个数据源,但所创建的数据源的应用范围是不同的:

  1. 用户DSN: 用户数据源只对当前用户可见,而且只能用于当前机器上。
  2. 系统DSN:系统数据源对当前机器上的所有用户可见。
  3. 文件DSN:文件数据源可以由安装了相同驱动程序的用户共享。

可以根据所创建的数据源的不同的应用场合选择在不同的标签页下创建数据源,在本例中选择文件DSN。在标签页中的列表中显示的是在本机已创建的系统数据源的列表。

单击“添加”按钮,新建一个数据源,弹出“创建新数据源”对话框。如图所示,在ODBC驱动程序列表中选择“Microsoft Access Driver(*.mdb, *.accdb)”。

uploading.4e448015.gif

正在上传…重新上传取消

选择ODBC驱动程序类别

单击“下一步”按钮,

uploading.4e448015.gif

正在上传…重新上传取消

给保存的文件数据源起一个名字,单击“下一步”按钮,

        

uploading.4e448015.gif

正在上传…重新上传取消

单击“完成”按钮,弹出“ODBC Microsoft Access 安装”对话框,如图所示。单击“选择”按钮,

uploading.4e448015.gif

正在上传…重新上传取消

设置Microsoft Access数据源

弹出“选择数据库”对话框,选择数据库文件C:\hotel.accdb,连续单击“确定”按钮回到前一对话框。

uploading.4e448015.gif

正在上传…重新上传取消

选择数据库

uploading.4e448015.gif

正在上传…重新上传取消

最后在文件DSN标签中可以看到创建的数据源hotel.dsn出现在数据源列表中,如图

uploading.4e448015.gif

正在上传…重新上传取消

创建好的系统数据源 

6.2.2创建数据库应用框架

〖例2-1〗使用MFC类向导可以方便地得到一个数据库应用程序的框架,创建一个MFC单文档EXE应用程序Exam2_1,在向导的[数据库支持]选项也中,选择单选项“不提供文件支持的数据库视图”,客户端类型选择 ODBC 如下图所示。

uploading.4e448015.gif

正在上传…重新上传取消

图9-9 设置数据库支持

单击“数据源…”按钮,弹出“选择数据源”对话框,

uploading.4e448015.gif

正在上传…重新上传取消

单击“新建”按钮,弹出跟上一节介绍的使用ODBC数据源管理器创建文件DSN一样的对话框,按照上面介绍的步骤新创建一个文件数据源,

uploading.4e448015.gif

正在上传…重新上传取消

创建完毕后文件数据源列表中会显示出新添加的数据域 hotel.dsn,单击“确定”按钮,再次弹出“ODBC Microsoft Access 安装”对话框,单击“确定”按钮

uploading.4e448015.gif

正在上传…重新上传取消

弹出“选择数据源对象”对话框,选择相应的数据库表,单击“确定”按钮,弹出“选择数据对象”对话框,列表框中列出了数据库中所包含的表和查询,选择应用程序所操作的表TblCustomer

uploading.4e448015.gif

正在上传…重新上传取消

选择数据库表

单击“确定”按钮,结束数据源的设置工作。

单击MFC应用程序向导对话框中的“完成”按钮,完成数据库应用程序框架的创建,编译运行这个程序,运行结果如下图所示。应用程序包含了数据库记录基本操作菜单和工具按钮,视图是一个对话框,可以添加控件。

uploading.4e448015.gif

正在上传…重新上传取消

数据库应用程序框架运行效果

选择工作区的ClassView,展开类树,进一步观察AppWizard自动添加的与数据库支持有关的内容。

增加了一个CExam2_1Set类,该类代表从数据库中选择的一组记录集。程序可以选择一个表作为一个记录集,本例选择了表tblCustomer中的记录构建记录集,也可以选择一个查询的结果集作为一个记录集。

如程序清单2-1所示。CExam2_1Set构造函数用于创建一个记录集对象,并把一个CDatabase对象的指针作为参数传递给构造函数,以便获得已由CDatabase对象建立起来的与数据源的连接。

CExam2_1Set类的成员函数GetDefaultConnect()用于获得定义了数据源类型和数据源名的连接字符串。GetDefaulltSQL()函数中定义了定义SQL语句的字符串,本例的SQL语句定义了查询一张表的完整记录。

CExam2_1Set类中定义了与数据源表的字段相对应的数据成员,成员函数DoFieldExchange()完成记录集上的字段数据成员与数据源上当前记录对应列之间数据的自动交换。

程序清单2-1:CExam2_1Set类

//CExam2_1Set.h

class CExam2_1Set : public CRecordset

{

public:

    CExam2_1Set(CDatabase* pDatabase = NULL);

    DECLARE_DYNAMIC(CExam2_1Set)

    int m_CustomerID;

    CStringW   m_LastName;

    CStringW   m_FirstName;

    CStringW   m_HomeCountry;

    CStringW   m_HomeState;

    CStringW   m_PhoneNumber;

    CStringW   m_Comments;

// Overrides

    public:

    virtual CString GetDefaultConnect();//Default connection string

    virtual CString GetDefaultSQL();   // default SQL for Recordset

    virtual void DoFieldExchange(CFieldExchange* pFX);// RFX support

// Implementation

#ifdef _DEBUG

    virtual void AssertValid() const;

    virtual void Dump(CDumpContext& dc) const;

#endif

};

//CExam2_1Set.cpp

CExam2_1Set::CExam2_1Set(CDatabase* pdb)

    : CRecordset(pdb)

{

   m_CustomerID = 0;

   m_LastName = L"";

   m_FirstName = L"";

   m_HomeCountry = L"";

   m_HomeState = L"";

   m_PhoneNumber = L"";

   m_Comments = L"";

   m_nFields = 7;

   m_nDefaultType = dynaset;

}

CString CExam2_1Set::GetDefaultConnect()

{

       return _T("DBQ=C:\\USERS\\KEVIN\\Documents\\hotel.mdb;

DefaultDir=C:\\USERS\\KEVIN\\Documents;

Driver={Driver do Microsoft Access (*.mdb)};

DriverId=25;FIL=MS Access;

FILEDSN=C:\\Users\\Kevin\\Documents\\hotel.dsn;

MaxBufferSize=2048;

MaxScanRows=8;PageTimeout=5;SafeTransactions=0;

Threads=3;UID=admin;UserCommitSync=Yes;");

}

CString CExam2_1Set::GetDefaultSQL()

{

    return _T("[tblCustomer]");

}

void CExam2_1Set::DoFieldExchange(CFieldExchange* pFX)

{

    pFX->SetFieldType(CFieldExchange::outputColumn);

// RFX_Text() 和 RFX_Int() 这类宏依赖的是

// 成员变量的类型,而不是数据库字段的类型。

// ODBC 尝试自动将列值转换为所请求的类型

    RFX_Int(pFX, _T("[CustomerID]"), m_CustomerID);

    RFX_Text(pFX, _T("[LastName]"), m_LastName);

    RFX_Text(pFX, _T("[FirstName]"), m_FirstName);

    RFX_Text(pFX, _T("[HomeCountry]"), m_HomeCountry);

    RFX_Text(pFX, _T("[HomeState]"), m_HomeState);

    RFX_Text(pFX, _T("[PhoneNumber]"), m_PhoneNumber);

    RFX_Text(pFX, _T("[Comments]"), m_Comments);

}

视图类CExam2_1View 是CRecordView的派生类,CRecordView是记录视图,支持在控件中显示数据库记录。默认提供了移动记录功能的实现(第一个,上一个,下一个,最后一个)。定义了一个指向记录集的指针m_pSet。

CRecordView类是CFormView类的派生类,CFormView类的视图对应一个对话框资源,所以CRecordView类从基类中继承的成员函数中最重要的是DoDataExchange()函数和UpdateData()函数,DoDataExchange()函数实现记录集的字段与视图中控件之间的自动数据交换,UpdateData()函数实现记录集的字段与视图中控件之间实时交换。

6.2.3 设计记录操作界面

〖例2-2〗打开资源管理器的 Dialog文件夹,选择IDD_EXAM2_1_FORM,在对话框中按下图添加静态控件和编辑框控件,设置ID编辑框的属性为只读。

uploading.4e448015.gif

正在上传…重新上传取消

记录操作界面

并按下表所示,修改编辑框控件的ID属性。

记录操作界面控件属性

控件ID

控件类型

标题

静态文本控件

ID

IDC_CustomerID

编辑框控件

静态文本控件

IDC_LastName

编辑框控件

静态文本控件

IDC_FirstName

编辑框控件

静态文本控件

国家

IDC_HomeCountry

编辑框控件

静态文本控件

电话

IDC_PhoneCall

编辑框控件

静态文本控件

备注

IDC_Comments

编辑框控件

接下来,要将编辑框控件与一个记录集字段数据成员绑定,打开CExam_1View.cpp文件在DoDataExchange函数中添加控件“连接”到数据库字段的代码:

void CExam2_1View::DoDataExchange(CDataExchange* pDX)

{

    CRecordView::DoDataExchange(pDX);

    // 可以在此处插入 DDX_Field* 函数以将控件“连接”到数据库字段,例如

    // DDX_FieldText(pDX, IDC_MYEDITBOX, m_pSet->m_szColumn1, m_pSet);

    // DDX_FieldCheck(pDX, IDC_MYCHECKBOX, m_pSet->m_bColumn2, m_pSet);

    // 有关详细信息,请参阅 MSDN 和 ODBC 示例

    DDX_FieldText(pDX, IDC_CUSTOMERID, m_pSet->m_CustomerID, m_pSet);

   DDX_FieldText(pDX, IDC_FIRSTNAME, m_pSet->m_FirstName, m_pSet);

   DDX_FieldText(pDX, IDC_LASTNAME, m_pSet->m_LastName, m_pSet);

   DDX_FieldText(pDX, IDC_HOMECOUNTRY, m_pSet->m_HomeCountry, m_pSet);

   DDX_FieldText(pDX, IDC_PHONECALL, m_pSet->m_PhoneNumber, m_pSet);

   DDX_FieldText(pDX, IDC_COMMENTS, m_pSet->m_Comments, m_pSet);

}

重新运行程序,运行结果如图所示。使用移动记录的四个工具按钮,前后浏览每一条记录,当移动到第一条记录时,“第一条”和“上一条”按钮变灰,当移动到最后一条记录时,“最后一条”和“下一条”按钮变灰。

在浏览记录的过程中,你可以修改各个编辑框中的内容,紧接着作一次移动记录操作,所作的修改就能被保存到数据库中。

uploading.4e448015.gif

正在上传…重新上传取消

增加了浏览功能后的应用程序

6.2.4 更新记录

更新记录操作包括修改,添加和删除记录,CRecordSet类提供了AddNew()、Delete()、Edit()、Update()、CancelUpdate()、Requery()等成员函数用于更新记录。

AddNew()函数用于添加一个新的空记录,所有字段数据成员的值都为NULL。Delete()函数用于删除当前记录,Edit()函数用于修改当前记录各字段数据成员的值。Update()函数用于AddNew和Edit操作后的数据的最后保存,CancelUpdate()函数用于取消任何由AddNew和Edit操作产生的待处理的更新。Requery()函数用于重新执行对记录集的查询,当记录集类型是快照型时,快照不反映用户添加的记录,这时需要调用该函数重新查询更新后的记录集。

下面在Exam2_1中增加添加新记录和删除记录的功能。

〖例2-3〗在“record”记录下添加三个菜单项如下图图9-17所示。一个菜单项是分割线,另外两个菜单项分别是“增加记录”和“删除记录”。菜单ID设置为ID_RECORD_ADD和ID_RECORD_DELETE。

uploading.4e448015.gif

正在上传…重新上传取消

增加菜单项

在菜单项上使用右键菜单在视图类中为菜单项ID_RECORD_ADD和ID_RECORD_DELETE映射COMMAND消息处理函数, 得到成员函数OnRecordAdd()和OnRecordDelete()。

uploading.4e448015.gif

正在上传…重新上传取消

uploading.4e448015.gif

正在上传…重新上传取消

添加CExam2_1View的BOOL类数据成员m_addflg,用以记录是否进入添加模式,当m_addflg的值为true时,进入添加模式。在CExam2_1View的构造函数中初始化m_addflg的值为false。

为成员函数OnRecordAdd()添加代码,增加一条空记录,并清除ID编辑框的只读属性。实现代码如程序清单2-2所示。

程序清单2-2:Add Record  菜单消息处理函数

void CExam2_1View::OnRecordAdd()

{

    // TODO: Add your command handler code here

    m_pSet->AddNew();  //进入添加模式

    m_addflg=true;    //设置添加模式标志

   CEdit* m_pCtrl=(CEdit*)GetDlgItem(IDC_CustomerID);

   m_pCtrl->SetReadOnly(false); //清除ID编辑框的只读属性

   UpdateData(false);   //用新记录的字段数据成员值更新控件显示

}

使用ClassWizard添加CExam2_1View类的虚函数OnMove()函数,并在OnMove()函数中添加代码,通过移动记录将添加的新记录保存到表中。实现代码如程序清单2-3所示

程序清单2-3:OnMove()函数

BOOL CExam2_1View::OnMove(UINT nIDMoveCommand)

{

    // TODO: Add your specialized code here and/or call the base class

   //添加模式处理

   if (m_addflag)

   {

      m_addflag = false;

      //使用控件值更新记录集字段数据成员

      UpdateData(true);

      //将记录集更新保存到表中

      if (m_pSet->CanUpdate())

      {

          m_pSet->Update();

      }

      //重新查询记录集

      m_pSet->Requery();

      //以更新后的记录集数据成员更新控件显示

      UpdateData(false);

      CEdit* m_pCtrl = (CEdit*)GetDlgItem(IDC_CUSTOMERID);

      //设置ID编辑框为只读

      m_pCtrl->SetReadOnly(true);

      return true;

   }

   else

   {

      return CRecordView::OnMove(nIDMoveCommand);

   }

}

为成员函数OnRecordDelete()添加代码,删除当前记录,实现代码如程序清单2-4所示。

程序清单2-4:Delete Record菜单处理函数

void CExam2_1View::OnRecordDelete()

{

    // TODO: Add your command handler code here

    m_pSet->Delete();//删除当前记录

   m_pSet->MoveNext();//移到下一记录

   if (m_pSet->IsEOF())//删除记录为最后一条记录处理

   {

      m_pSet->MoveLast();

   }

   if (m_pSet->IsBOF())//删空记录集处理

   {

      m_pSet->SetFieldNull(NULL);

   }

   UpdateData(false);//更新控件显示

}

6.2.5 排序和筛选

CRecordView类有两个重要的数据成员m_strFilter和m_strSort,m_strFilter是用于表示筛选记录的条件字符串,m_strSort是用于表示排序的关键字的字符串。只要对这两个数据成员赋值,只能实现排序和筛选。

〖例2-3〗首先在应用程序Exam2_1中建立两类排序,每一类是按ID号排序,另一类是按HomeCountry排序。

在“查看”菜单下添加三个菜单项:一条分隔线、“按ID排序”和“按国家排序”,菜单项ID设置为ID_VIEW_SORT_ID和ID_VIEW_SORT_COUNTRY。使用ClassWizard为两个菜单项在视图类中映射COMMAND消息处理函数得到,添加代码如程序清单2-5所示,实现排序。

程序清单2-5:实现排序的函数

void CExam2_1View::OnViewSortId()

{

    // TODO: Add your command handler code here

    m_pSet->m_strSort="CustomerID";// 定义排序关键字按ID排序

   m_pSet->Requery() ;// 重新查询

   UpdateData(false);  //更新控件显示

}

void CExam2_1View::OnViewSortCountry()

{

    // TODO: Add your command handler code here

    m_pSet->m_strSort="HomeCountry";

   m_pSet->Requery() ;

   UpdateData(false);

}

接着添加筛选条件,在一个对话框中输入一个国别,则只显示属于该国别的顾客记录。步骤如下:

  1. 创建一个对话框,并添加控件,如下图所示。设置编辑框的ID为IDC_FILTER.。为该对话对话框添加一个新的对话框类CFilterDlg并使用ClassWizard为编辑框IDC_FILTER在对话框类CFilterDlg中添加一个Ctring类型的成员变量m_Filter。

uploading.4e448015.gif

正在上传…重新上传取消

筛选对话框

  1. 在“查看”菜单下添加一个新的菜单项“筛选”,菜单项ID设为ID_VIEW_FLITER,使用ClassWizard在CExam2_1View类中为菜单项ID_VIEW_FILTER映射菜单处理函数,得到函数OnViewFilter()。
  2. 在CExam2_1View类的实现文件中使用include命令包含“FilterDlg.h”文件,并在函数OnViewFilter()中添加代码,调用筛选对话框,并按对话框返回的字符串设置数据成员m_strFilter的值,实现筛选。若对话框返回空串,显示整个记录集。

程序清单2-6:实现筛选的函数

void CExam2_1View::OnViewFilter()

{

    // TODO: Add your command handler code here

CFilterDlg dlg;

CString str;

   if (dlg.DoModal() == IDOK)//调用筛选对话框,按OK按钮返回

   {

      if (dlg.m_Filter.IsEmpty())//编辑框为空,显示整个记录集

      {

         str = "";

      }

      else

      {

//定义筛选字符串

         str = "HomeCountry='" + dlg.m_Filter + "'";

      }

      m_pSet->m_strFilter = str;

      m_pSet->Requery();  //重新查询记录集

      UpdateData(false);  //更新控件显示

   }

   }

6.3 直接访问MySql API 连接 mysql

  1. 第一步: 安装MySql
  2. 第二步: 在工程文件中添加mysql 头文件
    1. #include mysql.h
  3. 第三步: 在VS属性中做如下配置:
    1. 附加包含头文件mysql.h的目录,即安装目录下的include目录。

例如:C:\Program Files\MySQL\MySQL Server 5.6\include

    1. 附加依赖项,名称为libmysql.lib

#pragma comment( lib, "libmysql.lib");

此句话和在附加依赖项中增加libmysql.lib 的功能一样

  1. 第四步: 将libmysql.lib 和 libmysql.dll拷贝到工程目录下

这两个文件可以从mysql安装目录下的lib目录中拷贝

  • 29
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Visual C++MFC入门教程 目录 +-- 第一章 VC入门 |------ 1.1 如何学好VC |------ 1.2 理解Windows消息机制 |------ 1.3 利用Visual C++/MFC开发Windows程序的优势 |------ 1.4 利用MFC进行开发的通用方法介绍 |------ 1.5 MFC中常用类,宏,函数介绍 +-- 第二章 图形输出 |------ 2.1 和GUI有关的各种对象 |------ 2.2 在窗口中输出文字 |------ 2.3 使用点,刷子,笔进行绘图 |------ 2.4 在窗口中绘制设备相关位图,图标,设备无关位图 |------ 2.5 使用各种映射方式 |------ 2.6 多边形和剪贴区域 +-- 第三章 文档视结构 |------ 3.1 文档 视图 框架窗口间的关系和消息传送规律 |------ 3.2 接收用户输入 |------ 3.3 使用菜单 |------ 3.4 文档,视,框架之间相互作用 |------ 3.5 利用序列化进行文件读写 |------ 3.6 MFC中所提供的各种视类介绍 +-- 第四章 窗口控件 |------ 4.1 Button |------ 4.2 Static Box |------ 4.3 Edit Box |------ 4.4 Scroll Bar |------ 4.5 List Box/Check List Box |------ 4.6 Combo Box/Combo Box Ex |------ 4.7 Tree Ctrl |------ 4.8 List Ctrl |------ 4.9 Tab Ctrl |------ 4.A Tool Bar |------ 4.B Status Bar |------ 4.C Dialog Bar |------ 4.D 利用AppWizard创建并使用ToolBar StatusBar Dialog Bar |------ 4.E General Window |------ 4.F 关于WM_NOTIFY的使用方法 +-- 第五章 对话框 |------ 5.1 使用资源编辑器编辑对话框 |------ 5.2 创建有模式对话框 |------ 5.3 创建无模式对话框 |------ 5.4 在对话框中进行消息映射 |------ 5.5 在对话框中进行数据交换和数据检查 |------ 5.6 使用属性对话框 |------ 5.7 使用通用对话框 |------ 5.8 建立以对话框为基础的应用 |------ 5.9 使用对话框作为子窗口 +-- 第六章 网络通信开发 |------ 6.1 WinSock介绍 |------ 6.2 利用WinSock进行无连接的通信 +------ 6.3 利用WinSock建立有连接的通信   第一章 VC入门 1.1 如何学好VC 这个问题很多朋友都问过我,当然流汗是必须的,但同时如果按照某种思路进行有计划的学习就会起到更好的效果。万事开头难,为了帮助朋友们更快的掌握VC开发,下面我将自己的一点体会讲一下: 1、需要有好的C/C++基础。正所谓“磨刀不误砍柴工”,最开始接触VC时不要急于开始Windows程序开发,而是应该进行一些字符界面程序的编写。这样做的目的主要是增加对语言的熟悉程度,同时也训练自己的思维和熟悉一些在编程中常犯的错误。更重要的是理解并能运用C++的各种特性,这些在以后的开发中都会有很大的帮助,特别是利用MFC进行开发的朋友对C++一定要能熟练运用。 2、理解Windows的消息机制,窗口句柄和其他GUI句柄的含义和用途。了解和MFC各个类功能相近的API函数。 3、一定要理解MFC中消息映射的作用。 4、训练自己在编写代码时不使用参考书而是使用Help Online。 5、记住一些常用的消息名称和参数的意义。 6、学会看别人的代码。 7、多看书,少买书,买书前一定要慎重。 8、闲下来的时候就看参考书。 9、多来我的主页。^O^ 后面几条是我个人的一点意见,你可以根据需要和自身的情况选用适用于自己的方法。 此外我将一些我在选择参考书时的原则: 对于初学者:应该选择一些内容比较全面的书籍,并且书籍中的内容应该以合理的方式安排,在使用该书时可以达到循序渐进的效果,书中的代码要有详细的讲解。尽量买翻译的书,因为这些书一般都比较易懂,而且语言比较轻松。买书前一定要慎重如果买到不好用的书可能会对自己的学习积极性产生击。 对于已经掌握了VC的朋友:这种程度的开发者应该加深自己对系统原理,技术要点的认识。需要选择一些对原理讲解的比较透彻的书籍,这样一来才会对新技术有更多的了解,最好书中对技术的应用有一定的阐述。尽量选择示范代码必较精简的书,可以节约银子。 此外最好涉猎一些辅助性的书籍。 1.2 理解Windows消息机制 Windows系统是一个消息驱动的OS,什么是消息呢?我很难说得清楚,也很难下一个定义(谁在嘘我),我下面从不同的几个方面讲解一下,希望大家看了后有一点了解。 1、消息的组成:一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单转中之后会有WM_COMMAND消息发送,WPARAM的高字中(HIWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。 2、谁将收到消息:一个消息必须由一个窗口接收。在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口中进行图形输出就必须对WM_PAINT进行处理。 3、未处理的消息到那里去了:M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。 4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。 5、示例:下面有一段伪代码演示如何在窗口过程中处理消息 LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM) { switch(uMessageType) { //使用SWITCH语句将各种消息分开 case(WM_PAINT): doYourWindow(...);//在窗口需要重新绘制时进行输出 break; case(WM_LBUTTONDOWN): doYourWork(...);//在鼠标左键被按下时进行处理 break; default: callDefaultWndProc(...);//对于其它情况就让系统自己处理 break; } } 接下来谈谈什么是消息机制:系统将会维护一个或多个消息队列,所有产生的消息都回被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。下面的伪代码演示了消息循环的用法: while(1) { id=getMessage(...); if(id == quit) break; translateMessage(...); } 当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。 下图为消息投递模式 在16位的系统中系统中只有一个消息队列,所以系统必须等待当前任务处理消息后才可以发送下一消息到相应程序,如果一个程序陷如死循环或是耗时操作时系统就会得不到控制权。这种多任务系统也就称为协同式的多任务系统。Windows3.X就是这种系统。而32位的系统中每一运行的程序都会有一个消息队列,所以系统可以在多个消息队列中转换而不必等待当前程序完成消息处理就可以得到控制权。这种多任务系统就称为抢先式的多任务系统。Windows95/NT就是这种系统。 1.3 利用Visual C++/MFC开发Windows程序的优势 MFC借助C++的优势为Windows开发开辟了一片新天地,同时也借助ApplicationWizzard使开发者摆脱离了那些每次都必写基本代码,借助ClassWizard和消息映射使开发者摆脱了定义消息处理时那种混乱和冗长的代码段。更令人兴奋的是利用C++的封装功能使开发者摆脱Windows中各种句柄的困扰,只需要面对C++中的对象,这样一来使开发更接近开发语言而远离系统。(但我个人认为了解系统原理对开发很有帮助) 正因为MFC是建立在C++的基础上,所以我强调C/C++语言基础对开发的重要性。利用C++的封装性开发者可以更容易理解和操作各种窗口对象;利用C++的派生性开发者可以减少开发自定义窗口的时间和创造出可重用的代码;利用虚拟性可以在必要时更好的控制窗口的活动。而且C++本身所具备的超越C语言的特性都可以使开发者编写出更易用,更灵活的代码。 在MFC中对消息的处理利用了消息映射的方法,该方法的基础是宏定义实现,通过宏定义将消息分派到不同的成员函数进行处理。下面简单讲述一下这种方法的实现方法: 代码如下 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //{{AFX_MSG_MAP(CMainFrame) ON_WM_CREATE() //}}AFX_MSG_MAP ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) END_MESSAGE_MAP() 经过编译后,代码被替换为如下形式(这只是作讲解,实际情况比这复杂得多): //BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) CMainFrame::newWndProc(...) { switch(...) { //{{AFX_MSG_MAP(CMainFrame) // ON_WM_CREATE() case(WM_CREATE): OnCreate(...); break; //}}AFX_MSG_MAP // ON_COMMAND(ID_FONT_DROPDOWN, DoNothing) case(WM_COMMAND): if(HIWORD(wP)==ID_FONT_DROPDOWN) { DoNothing(...); } break; //END_MESSAGE_MAP() } } newWndProc就是窗口过程只要是该类的实例生成的窗口都使用该窗口过程。 所以了解了Windows的消息机制在加上对消息映射的理解就很容易了解MFC开发的基本思路了。 1.4 利用MFC进行开发的通用方法介绍 以下是我在最初学习VC时所常用的开发思路和方法,希望能对初学VC的朋友有所帮助和启发。 1、开发需要读写文件的应用程序并且有简单的输入和输出可以利用单文档视结构。 2、开发注重交互的简单应用程序可以使用对话框为基础的窗口,如果文件读写简单这可利用CFile进行。 3、开发注重交互并且文件读写复杂的的简单应用程序可以利用以CFormView为基础视的单文档视结构。 4、利用对话框得到用户输入的数据,在等级提高后可使用就地输入。 5、在对多文档要求不强烈时尽量避免多文档视结构,可以利用分隔条产生单文档多视结构。 6、在要求在多个文档间传递数据时使用多文档视结构。 7、学会利用子窗口,并在自定义的子窗口包含多个控件达到封装功能的目的。 8、尽量避免使用多文档多视结构。 9、不要使用多重继承并尽量减少一个类中封装过多的功能。 1.5 MFC中常用类,宏,函数介绍 常用类 CRect:用来表示矩形的类,拥有四个成员变量:top left bottom right。分别表是左上角和右下角的坐标。可以通过以下的方法构造: CRect( int l, int t, int r, int b ); 指明四个坐标 CRect( const RECT& srcRect ); 由RECT结构构造 CRect( LPCRECT lpSrcRect ); 由RECT结构构造 CRect( POINT point, SIZE size ); 有左上角坐标和尺寸构造 CRect( POINT topLeft, POINT bottomRight ); 有两点坐标构造 下面介绍几个成员函数: int Width( ) const; 得到宽度 int Height( ) const; 得到高度 CSize Size( ) const; 得到尺寸 CPoint& TopLeft( ); 得到左上角坐标 CPoint& BottomRight( ); 得到右下角坐标 CPoint CenterPoint( ) const; 得当中心坐标 此外矩形可以和点(CPoint)相加进行位移,和另一个矩形相加得到“并”操作后的矩形。 CPoint:用来表示一个点的坐标,有两个成员变量:x y。 可以和另一个点相加。 CString:用来表示可变长度的字符串。使用CString可不指明内存大小,CString会根据需要自行分配。下面介绍几个成员函数: GetLength 得到字符串长度 GetAt 得到指定位置处的字符 operator + 相当于strcat void Format( LPCTSTR lpszFormat, ... ); 相当于sprintf Find 查找指定字符,字符串 Compare 比较 CompareNoCase 不区分大小写比较 MakeUpper 改为小写 MakeLower 改为大写 CStringArray:用来表示可变长度的字符串数组。数组中每一个元素为CString对象的实例。下面介绍几个成员函数: Add 增加CString RemoveAt 删除指定位置CString对象 RemoveAll 删除数组中所有CString对象 GetAt 得到指定位置的CString对象 SetAt 修改指定位置的CString对象 InsertAt 在某一位置插入CString对象 常用宏 RGB TRACE ASSERT VERIFY 常用函数 CWindApp* AfxGetApp(); HINSTANCE AfxGetInstanceHandle( ); HINSTANCE AfxGetResourceHandle( ); int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于弹出一个消息框 第二章 图形输出 2.1 和GUI有关的各种对象 在Windows中有各种GUI对象(不要和C++对象混淆),当你在进行绘图就需要利用这些对象。而各种对象都拥有各种属性,下面分别讲述各种GUI对象和拥有的属性。 字体对象CFont用于输出文字时选用不同风格和大小的字体。可选择的风格包括:是否为斜体,是否为粗体,字体名称,是否有下划线等。颜色和背景色不属于字体的属性。关于如何创建和使用字体在2.2 在窗口中输出文字中会详细讲解。 刷子CBrush对象决定填充区域时所采用的颜色或模板。对于一个固定色的刷子来讲它的属性为颜色,是否采用网格和网格的类型如水平的,垂直的,交叉的等。你也可以利用8*8的位图来创建一个自定义模板的刷子,在使用这种刷子填充时系统会利用位图逐步填充区域。关于如何创建和使用刷子在2.3 使用刷子,笔进行绘图中会详细讲解。 画笔CPen对象在画点和画线时有用。它的属性包括颜色,宽度,线的风格,如虚线,实线,点划线等。关于如何创建和使用画笔在2.3 使用刷子,笔进行绘图中会详细讲解。 位图CBitmap对象可以包含一幅图像,可以保存在资源中。关于如何使用位图在2.4 在窗口中绘制设备相关位图,图标,设备无关位图中会详细讲解。 还有一种特殊的GUI对象是多边形,利用多边形可以很好的限制作图区域或是改变窗口外型。关于如何创建和使用多边形在2.6 多边形和剪贴区域中会详细讲解。 在Windows中使用GUI对象必须遵守一定的规则。首先需要创建一个合法的对象,不同的对象创建方法不同。然后需要将该GUI对象选入DC中,同时保存DC中原来的GUI对象。如果选入一个非法的对象将会引起异常。在使用完后应该恢复原来的对象,这一点特别重要,如果保存一个临时对象在DC中,而在临时对象被销毁后可能引起异常。有一点必须注意,每一个对象在重新创建前必须销毁,下面的代码演示了这一种安全的使用方法: OnDraw(CDC* pDC) { CPen pen1,pen2; pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//创建对象 pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//创建对象 CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//选择对象进DC drawWithPen1... (CPen*)pDC->SelectObject(&pen2);//选择对象进DC drawWithPen2... pen1.DeleteObject();//再次创建前先销毁 pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次创建对象 (CPen*)pDC->SelectObject(&pen1);//选择对象进DC drawWithPen1... pDC->SelectObject(pOldPen);//恢复 } 此外系统中还拥有一些库存GUI对象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )选入这些对象,它们包括一些固定颜色的刷子,画笔和一些基本字体。 • BLACK_BRUSH Black brush. • DKGRAY_BRUSH Dark gray brush. • GRAY_BRUSH Gray brush. • HOLLOW_BRUSH Hollow brush. • LTGRAY_BRUSH Light gray brush. • NULL_BRUSH Null brush. • WHITE_BRUSH White brush. • BLACK_PEN Black pen. • NULL_PEN Null pen. • WHITE_PEN White pen. • ANSI_FIXED_FONT ANSI fixed system font. • ANSI_VAR_FONT ANSI variable system font. • DEVICE_DEFAULT_FONT Device-dependent font. • OEM_FIXED_FONT OEM-dependent fixed font. • SYSTEM_FONT The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font. • SYSTEM_FIXED_FONT The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows. • DEFAULT_PALETTE Default color palette. This palette consists of the 20 static colors in the system palette. 这些对象留在DC中是安全的,所以你可以利用选入库存对象来作为恢复DC中GUI对象。 大家可能都注意到了绘图时都需要一个DC对象,DC(Device Context设备环境)对象是一个抽象的作图环境,可能是对应屏幕,也可能是对应打印机或其它。这个环境是设备无关的,所以你在对不同的设备输出时只需要使用不同的设备环境就行了,而作图方式可以完全不变。这也就是Windows耀眼的一点设备无关性。如同你将对一幅画使用照相机或复印机将会产生不同的输出,而不需要对画进行任何调整。DC的使用会穿插在本章中进行介绍。 2.2 在窗口中输出文字 在这里我假定读者已经利用ApplicationWizard生成了一个SDI界面的程序代码。接下来的你只需要在CView派生类的OnDraw成员函数中加入绘图代码就可以了。在这里我需要解释一下OnDraw函数的作用,OnDraw函数会在窗口需要重绘时自动被调用,传入的参数CDC* pDC对应的就是DC环境。使用OnDraw的优点就在于在你使用打印功能的时候传入OnDraw的DC环境将会是打印机绘图环境,使用打印预览时传入的是一个称为CPreviewDC的绘图环境,所以你只需要一份代码就可以完成窗口/打印预览/打印机绘图三重功能。利用Windows的设备无关性和M$为打印预览所编写的上千行代码你可以很容易的完成一个具有所见即所得的软件。 输出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )两个函数,对TextOut来讲只能输出单行的文字,而DrawText可以指定在一个矩形中输出单行或多行文字,并且可以规定对齐方式和使用何种风格。nFormat可以是多种以下标记的组合(利用位或操作)以达到选择输出风格的目的。 • DT_BOTTOM底部对齐 Specifies bottom-justified text. This value must be combined with DT_SINGLELINE. • DT_CALCRECT计算指定文字时所需要矩形尺寸 Determines the width and height of the rectangle. If there are multiple lines of text, DrawText will use the width of the rectangle pointed to by lpRect and extend the base of the rectangle to bound the last line of text. If there is only one line of text, DrawText will modify the right side of the rectangle so that it bounds the last character in the line. In either case, DrawText returns the height of the formatted text, but does not draw the text. • DT_CENTER中部对齐 Centers text horizontally. • DT_END_ELLIPSIS or DT_PATH_ELLIPSIS Replaces part of the given string with ellipses, if necessary, so that the result fits in the specified rectangle. The given string is not modified unless the DT_MODIFYSTRING flag is specified. You can specify DT_END_ELLIPSIS to replace characters at the end of the string, or DT_PATH_ELLIPSIS to replace characters in the middle of the string. If the string contains backslash (\) characters, DT_PATH_ELLIPSIS preserves as much as possible of the text after the last backslash. • DT_EXPANDTABS Expands tab characters. The default number of characters per tab is eight. • DT_EXTERNALLEADING Includes the font抯 external leading in the line height. Normally, external leading is not included in the height of a line of text. • DT_LEFT左对齐 Aligns text flush-left. • DT_MODIFYSTRING Modifies the given string to match the displayed text. This flag has no effect unless the DT_END_ELLIPSIS or DT_PATH_ELLIPSIS flag is specified. Note Some uFormat flag combinations can cause the passed string to be modified. Using DT_MODIFYSTRING with either DT_END_ELLIPSIS or DT_PATH_ELLIPSIS may cause the string to be modified, causing an assertion in the CString override. • DT_NOCLIP Draws without clipping. DrawText is somewhat faster when DT_NOCLIP is used. • DT_NOPREFIX禁止使用&前缀 Turns off processing of prefix characters. Normally, DrawText interprets the ampersand (&) mnemonic-prefix character as a directive to underscore the character that follows, and the two-ampersand (&&) mnemonic-prefix characters as a directive to print a single ampersand. By specifying DT_NOPREFIX, this processing is turned off. • DT_PATH_ELLIPSIS • DT_RIGHT右对齐 Aligns text flush-right. • DT_SINGLELINE单行输出 Specifies single line only. Carriage returns and linefeeds do not break the line. • DT_TABSTOP设置TAB字符所占宽度 Sets tab stops. The high-order byte of nFormat is the number of characters for each tab. The default number of characters per tab is eight. • DT_TOP定部对齐 Specifies top-justified text (single line only). • DT_VCENTER中部对齐 Specifies vertically centered text (single line only). • DT_WORDBREAK每行只在单词间被折行 Specifies word-breaking. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by lpRect. A carriage return杔inefeed sequence will also break the line. 在输出文字时如果希望改变文字的颜色,你可以利用CDC::SetTextColor( COLORREF crColor )进行设置,如果你希望改变背景色就利用CDC::SetBkColor( COLORREF crColor ),很多时候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )设置,可接受的参数有 • OPAQUE Background is filled with the current background color before the text, hatched brush, or pen is drawn. This is the default background mode. • TRANSPARENT Background is not changed before drawing. 接下来讲讲如何创建字体,你可以创建的字体有两种:库存字体CDC::CreateStockObject( int nIndex )和自定义字体。 在创建非库存字体时需要填充一个LOGFONT结构并使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont ),或使用CFont::CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename )其中的参数和LOGFONT中的分量有一定的对应关系。下面分别讲解参数的意义: nHeight 字体高度(逻辑单位)等于零为缺省高度,否则取绝对值并和可用的字体高度进行匹配。 nWidth 宽度(逻辑单位)如果为零则使用可用的横纵比进行匹配。 nEscapement 出口矢量与X轴间的角度 nOrientation 字体基线与X轴间的角度 nWeight 字体粗细,可取以下值 Constant Value FW_DONTCARE 0 FW_THIN 100 FW_EXTRALIGHT 200 FW_ULTRALIGHT 200 FW_LIGHT 300 FW_NORMAL 400 FW_REGULAR 400 FW_MEDIUM 500 FW_SEMIBOLD 600 FW_DEMIBOLD 600 FW_BOLD 700 FW_EXTRABOLD 800 FW_ULTRABOLD 800 FW_BLACK 900 FW_HEAVY 900 bItalic 是否为斜体 bUnderline 是否有下划线 cStrikeOut 是否带删除线 nCharSet 指定字符集合,可取以下值 Constant Value ANSI_CHARSET 0 DEFAULT_CHARSET 1 SYMBOL_CHARSET 2 SHIFTJIS_CHARSET 128 OEM_CHARSET 255 nOutPrecision 输出精度 OUT_CHARACTER_PRECIS OUT_STRING_PRECIS OUT_DEFAULT_PRECIS OUT_STROKE_PRECIS OUT_DEVICE_PRECIS OUT_TT_PRECIS OUT_RASTER_PRECIS nClipPrecision 剪辑精度,可取以下值 CLIP_CHARACTER_PRECIS CLIP_MASK CLIP_DEFAULT_PRECIS CLIP_STROKE_PRECIS CLIP_ENCAPSULATE CLIP_TT_ALWAYS CLIP_LH_ANGLES nQuality 输出质量,可取以下值 • DEFAULT_QUALITY Appearance of the font does not matter. • DRAFT_QUALITY Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled. Bold, italic, underline, and strikeout fonts are synthesized if necessary. • PROOF_QUALITY Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Bold, italic, underline, and strikeout fonts are synthesized if necessary. nPitchAndFamily 字体间的间距 lpszFacename 指定字体名称,为了得到系统所拥有的字体可以利用EmunFontFamiliesEx。 此外可以利用CFontDialog来得到用户选择的字体的LOGFONT数据。 最后我讲一下文本坐标的计算,利用CDC::GetTextExtent( const CString& str )可以得到字符串的在输出时所占用的宽度和高度,这样就可以在手工输出多行文字时使用正确的行距。另外如果需要更精确的对字体高度和宽度进行计算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 该函数将会填充TEXTMETRIC结构,该结构中的分量可以非常精确的描述字体的各种属性。 2.3 使用点,刷子,笔进行绘图 在Windows中画点的方法很简单,只需要调用COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定点画上指定颜色,同时返回原来的颜色。COLORREF CDC::GetPixel( int x, int y)可以得到指定点的颜色。在Windows中应该少使用画点的函数,因为这样做的执行效率比较低。 刷子和画笔在Windows作图中是使用最多的GUI对象,本节在讲解刷子和画笔使用方法的同时也讲述一写基本作图函数。 在画点或画线时系统使用当前DC中的画笔,所以在创建画笔后必须将其选入DC才会在绘图时产生效果。画笔可以通过CPen对象来产生,通过调用CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )来创建。其中nPenStyle指名画笔的风格,可取如下值: • PS_SOLID 实线 Creates a solid pen. • PS_DASH 虚线,宽度必须为一 Creates a dashed pen. Valid only when the pen width is 1 or less, in device units. • PS_DOT 点线,宽度必须为一 Creates a dotted pen. Valid only when the pen width is 1 or less, in device units. • PS_DASHDOT 点划线,宽度必须为一 Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units. • PS_DASHDOTDOT 双点划线,宽度必须为一 Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units. • PS_NULL 空线,使用时什么也不会产生 Creates a null pen. • PS_ENDCAP_ROUND 结束处为圆形 End caps are round. • PS_ENDCAP_SQUARE 结束处为方形 End caps are square. nWidth和crColor为线的宽度和颜色。 刷子是在画封闭曲线时用来填充的颜色,例如当你画圆形或方形时系统会用当前的刷子对内部进行填充。刷子可利用CBrush对象产生。通过以下几种函数创建刷子: • BOOL CreateSolidBrush( COLORREF crColor ); 创建一种固定颜色的刷子 • BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 创建指定颜色和网格的刷子,nIndex可取以下值: • HS_BDIAGONAL Downward hatch (left to right) at 45 degrees • HS_CROSS Horizontal and vertical crosshatch • HS_DIAGCROSS Crosshatch at 45 degrees • HS_FDIAGONAL Upward hatch (left to right) at 45 degrees • HS_HORIZONTAL Horizontal hatch • HS_VERTICAL Vertical hatch • BOOL CreatePatternBrush( CBitmap* pBitmap ); 创建以8*8位图为模板的刷子 在选择了画笔和刷子后就可以利用Windows的作图函数进行作图了,基本的画线函数有以下几种 • CDC::MoveTo( int x, int y ); 改变当前点的位置 • CDC::LineTo( int x, int y ); 画一条由当前点到参数指定点的线 • CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 画弧线 • CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 将多条线依次序连接 基本的作图函数有以下几种: • CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形 • CDC::RoundRect( LPCRECT lpRect, POINT point ); 圆角矩形 • CDC::Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D边框 • CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形 • CDC::Ellipse( LPCRECT lpRect ); 椭圆形 • CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); • CDC::Polygon( LPPOINT lpPoints, int nCount ); 多边形 对于矩形,圆形或类似的封闭曲线,系统会使用画笔绘制边缘,使用刷子填充内部。如果你不希望填充或是画出边缘,你可以选入空刷子(NULL_PEN)或是(NULL_BRUSH)空笔。 下面的代码创建一条两象素宽的实线并选入DC。并进行简单的作图: { ... CPen pen; pen.CreatePen(PS_SOLID,2,RGB(128,128,128)); CPen* pOldPen=(CPen*)dc.SelectObject(&pen); dc.SelectStockObject(NULL_BRUSH);//选入空刷子 dc.Rectangle(CRect(0,0,20,20));//画矩形 ... } 2.4 在窗口中绘制设备相关位图,图标,设备无关位图 在Windows中可以将预先准备好的图像复制到显示区域中,这种内存拷贝执行起来是非常快的。在Windows中提供了两种使用图形拷贝的方法:通过设备相关位图(DDB)和设备无关位图(DIB)。 DDB可以用MFC中的CBitmap来表示,而DDB一般是存储在资源文件中,在加载时只需要通过资源ID号就可以将图形装入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以装入指定DDB,但是在绘制时必须借助另一个和当前绘图DC兼容的内存DC来进行。通过CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )绘制图形,同时指定光栅操作的类型。BitBlt可以将源DC中位图复制到目的DC中,其中前四个参数为目的区域的坐标,接下来是源DC指针,然后是源DC中的起始坐标,由于BitBlt为等比例复制,所以不需要再次指定长宽,(StretchBlt可以进行缩放)最后一个参数为光栅操作的类型,可取以下值: • BLACKNESS 输出区域为黑色 Turns all output black. • DSTINVERT 反色输出区域 Inverts the destination bitmap. • MERGECOPY 在源和目的间使用AND操作 Combines the pattern and the source bitmap using the Boolean AND operator. • MERGEPAINT 在反色后的目的和源间使用OR操作 Combines the inverted source bitmap with the destination bitmap using the Boolean OR operator. • NOTSRCCOPY 将反色后的源拷贝到目的区 Copies the inverted source bitmap to the destination. • PATINVERT 源和目的间进行XOR操作 Combines the destination bitmap with the pattern using the Boolean XOR operator. • SRCAND 源和目的间进行AND操作 Combines pixels of the destination and source bitmaps using the Boolean AND operator. • SRCCOPY 复制源到目的区 Copies the source bitmap to the destination bitmap. • SRCINVERT 源和目的间进行XOR操作 Combines pixels of the destination and source bitmaps using the Boolean XOR operator. • SRCPAINT 源和目的间进行OR操作 Combines pixels of the destination and source bitmaps using the Boolean OR operator. • WHITENESS 输出区域为白色 Turns all output white. 下面用代码演示这种方法: CYourView::OnDraw(CDC* pDC) { CDC memDC;//定义一个兼容DC memDC.CreateCompatibleDC(pDC);//创建DC CBitmap bmpDraw; bmpDraw.LoadBitmap(ID_BMP) ;//装入DDB CBitmap* pbmpOld=memDC.SelectObject(&bmpDraw) ; //保存原有DDB,并选入新DDB入DC pDC->BitBlt(0,0,20,20,&memDC,0,0,SRCCOPY) ; //将源DC中(0,0,20,20)复制到目的DC(0,0,20,20) pDC->BitBlt(20,20,40,40,&memDC,0,0,SRCAND); //将源DC中(0,0,20,20)和目的DC(20,20,40,40)中区域进行AND操作 memDC.SelectObject(pbmpOld) ;//选入原DDB } (图标并不是一个GDI对象,所以不需要选入DC)在MFC中没有一个专门的图标类,因为图标的操作比较简单,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 装入后就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )绘制。由于在图标中可以指定透明区域,所以在某些需要使用非规则图形而且面积不大的时候使用图标会比较简单。下面给出简单的代码: OnDraw(CDC* pDC) { HICON hIcon1=AfxGetApp()->LoadIcon(IDI_I1); HICON hIcon2=AfxGetApp()->LoadIcon(IDI_I2); pDC->DrawIcon(0,0,hIcon1); pDC->DrawIcon(0,40,hIcon2); DestroyIcon(hIcon1); DestroyIcon(hIcon2); } 同样在MFC也没有提供一个DIB的类,所以在使用DIB位图时我们需要自己读取位图文件中的头信息,并读入数据,并利用API函数StretchDIBits绘制。位图文件以BITMAPFILEHEADER结构开始,然后是BITMAPINFOHEADER结构和调色版信息和数据,其实位图格式是图形格式中最简单的一种,而且也是Windows可以理解的一种。我不详细讲解DIB位图的结构,提供一个CDib类供大家使用,这个类包含了基本的功能如:Load,Save,Draw。DownLoad CDib 4K 2.5 使用各种映射方式 所谓的映射方式简单点讲就是坐标的安排方式,系统默认的映射方式为MM_TEXT即X坐标向右增加,Y坐标向下增加,(0,0)在屏幕左上方,DC中的每一点就是屏幕上的一个象素。也许你会认为这种方式下是最好理解的,但是一个点和象素对应的关系在屏幕上看来是正常的,但到了打印机上就会很不正常。因为我们作图是以点为单位并且打印机的分辨率远远比显示器高(800DPI 800点每英寸)所以在打印机上图形看起来就会很小。这样就需要为打印另做一套代码而加大了工作量。如果每个点对应0.1毫米那么在屏幕上的图形就会和打印出来的图形一样大小。 通过int CDC::SetMapMode( int nMapMode )可以指定映射方式,可用的有以下几种: • MM_HIENGLISH 每点对应0.001英寸 Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up. • MM_HIMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up. • MM_LOENGLISH 每点对应0.01英寸 Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up. • MM_LOMETRIC 每点对应0.001毫米 Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up. • MM_TEXT 象素对应 Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down. 以上几种映射默认的原点在屏幕左上方。除MM_TEXT外都为X坐标向右增加,Y坐标向上增加,和自然坐标是一致的。所以在作图是要注意什么时候应该使用负坐标。而且以上的映射都是X-Y等比例的,即相同的长度在X,Y轴上显示的长度都是相同的。 DownLoad Sample 另外的一种映射方式为MM_ANISOTROPIC,这种方式可以规定不同的长宽比例。在设置这中映射方式后必须调用CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )来设定长宽比例。系统会根据两次设定的长宽的比值来确定长宽比例。下面给出一段代码比较映射前后的长宽比例: OnDraw(CDC* pDC) { CRect rcC1(200,0,400,200); pDC->FillSolidRect(rcC1,RGB(0,0,255)); pDC->SetMapMode(MM_ANISOTROPIC ); CSize sizeO; sizeO=pDC->SetWindowExt(5,5); TRACE("winExt %d %d\n",sizeO.cx,sizeO.cy); sizeO=pDC->SetViewportExt(5,10); TRACE("ViewExt %d %d\n",sizeO.cx,sizeO.cy); CRect rcC(0,0,200,200); pDC->FillSolidRect(rcC,RGB(0,128,0)); } 上面代码在映射后画出的图形将是一个长方形。 DownLoad Sample 最后讲讲视原点(viewport origin),你可以通过调用CPoint CDC::SetViewportOrg( POINT point )重新设置原点的位置,这就相对于对坐标进行了位移。例如你将原点设置在(20,20)那么原来的(0,0)就变成了(-20,-20)。 2.6 多边形和剪贴区域 多边形也是一个GDI对象,同样遵守其他GDI对象的规则,只是通常都不将其选入DC中。在MFC中多边形有CRgn表示。多边形用来表示一个不同与矩形的区域,和矩形具有相似的操作。如:检测某点是否在内部,并操作等。此外还得到一个包含此多边形的最小矩形。下面介绍一下多边形类的成员函数: • CreateRectRgn 由矩形创建一个多边形 • CreateEllipticRgn 由椭圆创建一个多边形 • CreatePolygonRgn 创建一个有多个点围成的多边形 • PtInRegion 某点是否在内部 • CombineRgn 两个多边形相并 • EqualRgn 两个多边形是否相等 在本节中讲演多边形的意义在于重新在窗口中作图时提高效率。因为引发窗口重绘的原因是某个区域失效,而失效的区域用多边形来表示。假设窗口大小为500*400当上方的另一个窗口从(0,0,10,10)移动到(20,20,30,30)这时(0,0,10,10)区域就失效了,而你只需要重绘这部分区域而不是所有区域,这样你程序的执行效率就会提高。 通过调用API函数int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效区域,但是一般用不着那么精确而只需得到包含该区域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成这一功能。 第三章 文档视结构 3.1 文档 视图 框架窗口间的关系和消息传送规律 在MFC中M$引入了文档-视结构的概念,文档相当于数据容器,视相当于查看数据的窗口或是和数据发生交互的窗口。(这一结构在MFC中的OLE,ODBC开发时又得到更多的拓展)因此一个完整的应用一般由四个类组成:CWinApp应用类,CFrameWnd窗口框架类,CDocument文档类,CView视类。(VC6中支持创建不带文档-视的应用) 在程序运行时CWinApp将创建一个CFrameWnd框架窗口实例,而框架窗口将创建文档模板,然后有文档模板创建文档实例和视实例,并将两者关联。一般来讲我们只需对文档和视进行操作,框架的各种行为已经被MFC安排好了而不需人为干预,这也是M$设计文档-视结构的本意,让我们将注意力放在完成任务上而从界面编写中解放出来。 在应用中一个视对应一个文档,但一个文档可以包含多个视。一个应用中只用一个框架窗口,对多文档界面来讲可能有多个MDI子窗口。每一个视都是一个子窗口,在单文档界面中父窗口即是框架窗口,在多文档界面中父窗口为MDI子窗口。一个多文档应用中可以包含多个文档模板,一个模板定义了一个文档和一个或多个视之间的对应关系。同一个文档可以属于多个模板,但一个模板中只允许定义一个文档。同样一个视也可以属于多个文档模板。(不知道我说清楚没有) 接下来看看如何在程序中得到各种对象的指针: • 全局函数AfxGetApp可以得到CWinApp应用类指针 • AfxGetApp()->m_pMainWnd为框架窗口指针 • 在框架窗口中:CFrameWnd::GetActiveDocument得到当前活动文档指针 • 在框架窗口中:CFrameWnd::GetActiveView得到当前活动视指针 • 在视中:CView::GetDocument得到对应的文档指针 • 在文档中:CDocument::GetFirstViewPosition,CDocument::GetNextView用来遍历所有和文档关联的视。 • 在文档中:CDocument::GetDocTemplate得到文档模板指针 • 在多文档界面中:CMDIFrameWnd::MDIGetActive得到当前活动的MDI子窗口 一般来讲用户输入消息(如菜单选择,鼠标,键盘等)会先发往视,如果视未处理则会发往框架窗口。所以定义消息映射时定义在视中就可以了,如果一个应用同时拥有多个视而当前活动视没有对消息进行处理则消息会发往框架窗口。 3.2 接收用户输入 在视中接收鼠标输入: 鼠标消息是我们常需要处理的消息,消息分为:鼠标移动,按钮按下/松开,双击。利用ClassWizard可以轻松的添加这几种消息映射,下面分别讲解每种消息的处理。 WM_MOUSEMOVE对应的函数为OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了当前一些按键的消息,你可以通过“位与”操作进行检测。 • MK_CONTROL Ctrl键是否被按下 Set if the CTRL key is down. • MK_LBUTTON 鼠标左键是否被按下 Set if the left mouse button is down. • MK_MBUTTON 鼠标中间键是否被按下 Set if the middle mouse button is down. • MK_RBUTTON 鼠标右键是否被按下 Set if the right mouse button is down. • MK_SHIFT Shift键是否被按下 Set if the SHIFT key is down. point表示当前鼠标的设备坐标,坐标原点对应视左上角。 WM_LBUTTONDOWN/WM_RBUTTONDOWN(鼠标左/右键按下)对应的函数为OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 WM_LBUTTONUP/WM_RBUTTONUP(鼠标左/右键松开)对应的函数为OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(鼠标左/右键双击)对应的函数为OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )参数意义和OnMouseMove相同。 下面我用一段伪代码来讲解一下这些消息的用法: 代码的作用是用鼠标拉出一个矩形 global BOOL fDowned;//是否在拉动 global CPoint ptDown;//按下位置 global CPoint ptUp;//松开位置 OnLButtonDown(UINT nFlags, CPoint point) { fDowned=TRUE; ptUp=ptDown=point; DrawRect(); ... } OnMouseMove(UINT nFlags, CPoint point) { if(fDowned) { DrawRect();//恢复上次所画的矩形 ptUp=point; DrawRect();//画新矩形 } } OnLButtonUp(UINT nFlags, CPoint point) { if(fDowned) { DrawRect();//恢复上次所画的矩形 ptUp=point; DrawRect();//画新矩形 fDowned=FALSE; } } DrawRect() {//以反色屏幕的方法画出ptDown,ptUp标记的矩形 CClientDC dc(this); MakeRect(ptDown,ptUp); SetROP(NOT); Rect(); } 坐标间转换:在以上的函数中point参数对应的都是窗口的设备坐标,我们应该将设备坐标和逻辑坐标相区别,在图32_g1由于窗口使用了滚动条,所以传入的设备坐标是对应于当前窗口左上角的坐标,没有考虑是否滚动,而逻辑坐标必须考虑滚动后对应的坐标,所以我以黄线虚拟的表达一个逻辑坐标的区域。可以看得出同一点在滚动后的坐标值是不同的,这一规则同样适用于改变了映射方式的窗口,假设你将映射方式设置为每点为0.01毫米,那么设备坐标所对应的逻辑坐标也需要重新计算。进行这种转换需要写一段代码,所幸的是系统提供了进行转换的功能DC的DPtoLP,LPtoDP,下面给出代码完成由设备坐标到逻辑坐标的转换。 图32_g1 CPoint CYourView::FromDP(CPoint point) { CClientDC dc(this); CPoint ptRet=point; dc.PrepareDC();//必须先准备DC,这在使用滚动时让DC重新计算坐标 //如果你作图设置了不同的映射方式,则在下面需要设置 dc.SetMapMode(...) // dc.DPtoLP(&ptRet);//DP->LP进行转换 return ptRet; } 在图32_g1中以蓝线标记的是屏幕区域,红线标记的客户区域。利用ScreenToClient,ClientToScreen可以将坐标在这两个区域间转换。 在视中接收键盘输入: 键盘消息有三个:键盘被按下/松开,输入字符。其中输入字符相当于直接得到用户输入的字符这在不需要处理按键细节时使用,而键盘被按下/松开在按键状态改变时发送。 WM_CHAR对应的函数为OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其中nChar为被按下的字符,nRepCnt表明在长时间为松开时相当于的按键次数,nFlags中的不同位代表不同的含义,在这里一般不使用。 WM_KEYDOWN/WM_KEYUP所对应的函数为OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags )nChar代表按键的虚拟码值,如VK_ALT为ALT键,VK_CONTROL为Ctrl键。nFlags各位的含义如下: Value Description 0? Scan code (OEM-dependent value). 8 Extended key, such as a function key or a key on the numeric keypad (1 if it is an extended key). 9?0 Not used. 11?2 Used internally by Windows. 13 Context code (1 if the ALT key is held down while the key is pressed; otherwise 0). 14 Previous key state (1 if the key is down before the call, 0 if the key is up). 15 Transition state (1 if the key is being released, 0 if the key is being pressed). 3.3 使用菜单 利用菜单接受用户命令是一中很简单的交互方法,同时也是一种很有效的方法。通常菜单作为一中资源存储在文件中,因此我们可以在设计时就利用资源编辑器设计好一个菜单。关于使用VC设计菜单我就不再多讲了,但你在编写菜单时应该尽量在属性对话框的底部提示(Prompt)处输入文字,这虽然不是必要的,但MFC在有状态栏和工具条的情况下会使用该文字,文字的格式为“状态栏出说明\n工具条提示”。 图33_g1 我们要面临的任务是如何知道用户何时选择了菜单,他选的是什么菜单项。当用户选择了一个有效的菜单项时系统会向应用发送一个WM_COMMAND消息,在消息的参数中表明来源。在MFC中我们只需要进行一次映射,将某一菜单ID映射到一处理函数,图33_g2。在这里我们在CView的派生类中处理菜单消息,同时我对同一ID设置两个消息映射,接下来将这两种映射的作用。 图33_g2 ON_COMMAND 映射的作用为在用户选择该菜单时调用指定的处理函数。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)会使菜单被选择时调用OnCommand1成员函数。 ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜单被显示时通过调用指定的函数来进行确定其状态。在这个处理函数中你可以设置菜单的允许/禁止状态,其显示字符串是什么,是否在前面打钩。函数的参数为CCmdUI* pCmdUI,CCmdUI是MFC专门为更新命令提供的一个类,你可以调用 • Enable 设置允许/禁止状态 • SetCheck 设置是否在前面打钩 • SetText 设置文字 下面我讲解一个例子:我在CView派生类中有一个变量m_fSelected,并且在视中处理两个菜单的消息,当IDM_COMMAND1被选时,对m_fSelected进行逻辑非操作,当IDM_COMMAND2被选中时出一提示;同时IDM_COMMAND1根据m_fSelected决定菜单显示的文字和是否在前面打上检查符号,IDM_COMMAND2根据m_fSelected的值决定菜单的允许/禁止状态。下面是代码和说明:下载示例代码 17K void CMenuDView::OnCommand1() { m_fSelected=!m_fSelected; TRACE("command1 selected\n"); } void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI) { pCmdUI->SetCheck(m_fSelected);//决定检查状态 pCmdUI->SetText(m_fSelected?"当前被选中":"当前未被选中");//决定所显示的文字 } void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI) {//决定是否为允许 pCmdUI->Enable(m_fSelected); } void CMenuDView::OnCommand2() {//选中时给出提示 AfxMessageBox("你选了command2"); } 接下来再讲一些通过代码操纵菜单的方法,在MFC中有一个类CMenu用来处理和菜单有关的功能。在生成一个CMenu对象时你需要从资源中装如菜单,通过调用BOOL CMenu::LoadMenu( UINT nIDResource )进行装入,然后你就可以对菜单进行动态的修改,所涉及到的函数有: • CMenu* GetSubMenu( int nPos ) 一位置得到子菜单的指针,因为一个CMenu对象只能表示一个弹出菜单,如果菜单中的某一项也为弹出菜单,就需要通过该函数获取指针。 • BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一项,nFlag为MF_SEPARATOR表示增加一个分隔条,这样其他两个参数将会被忽略;为MF_STRING表示添加一个菜单项uIDNewItem为该菜单的ID命令值;为MF_POPUP表示添加一个弹出菜单项,这时uIDNewItem为另一菜单的句柄HMENU。lpszNewItem为菜单文字说明。 • BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜单,位置由变量nPosition指明。如果nFlags包含MF_BYPOSITION则表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID为nPosition的菜单处。 • BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜单,如果nFlags包含MF_BYPOSITION则表明修改nPosition位置的菜单,如果包含MF_BYCOMMAND表示修改命令ID为nPosition处的菜单。 • BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于删除某一位置的菜单。如果nFlags包含MF_BYPOSITION则表明删除nPosition位置的菜单,如果包含MF_BYCOMMAND表示删除命令ID为nPosition处的菜单。 • BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位图菜单,但这样的菜单在选中时只是反色显示,并不美观。 视图中是没有菜单的,在框架窗口中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到应用的菜单指针。 最后我讲一下如何在程序中弹出一个菜单,你必须先装入一个菜单资源,你必需得到一个弹出菜单的指针然后调用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )弹出菜单,你需要指定(x,y)为菜单弹出的位置,pWnd为接收命令消息的窗口指针。下面有一段代码说明方法,下载示例代码 17K。当然为了处理消息你应该在pWnd指明的窗口中对菜单命令消息进行映射。 CMenu menu; menu.LoadMenu(IDR_POPUP); CMenu* pM=menu.GetSubMenu(0); CPoint pt; GetCursorPos(&pt); pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this); 另一种做法是通过CMenu::CreatePopupMenu()建立一个弹出菜单,然后使用TrackPopupMenu弹出菜单。使用CreatePopupMenu创建的菜单也可以将其作为一个弹出项添加另一个菜单中。下面的伪代码演示了如何创建一个弹出菜单并进行修改后弹出: CMenu menu1,menu2; menu1.CreatePopupMenu menu1.InsertMenu(1) menu1.InsertMenu(2) menu1.InsertMenu(3) menu2.CreatePopupMenu menu2.AppendMenu(MF_POPUP,1,menu1.Detach()) 将弹出菜单加入 or InsertMenu... menu2.InsertMenu("string desc"); menu.TrackPopupMenu(...) 3.4 文档,视,框架之间相互作用 一般来说用户的输入/输出基本都是通过视进行,但一些例外的情况下可能需要和框架直接发生作用,而在多视的情况下如何在视之间传递数据。 在使用菜单时大家会发现当一个菜单没有进行映射处理时为禁止状态,在多视的情况下菜单的状态和处理映射是和当前活动视相联系的,这样MFC可以保证视能正确的接收到各种消息,但有时候也会产生不便。有一个解决办法就是在框架中对消息进行处理,这样也可以保证当前文档可以通过框架得到当前消息。 在用户进行输入后如何使视的状态得到更新?这个问题在一个文档对应一个视图时是不存在的,但是现在有一个文档对应了两个视图,当在一个视上进行了输入时如何保证另一个视也得到通知呢?MFC的做法是利用文档来处理的,因为文档管理着当前和它联系的视,由它来通知各个视是最合适的。让我们同时看两个函数: • void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint ) • void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL ) 当文档的UpdateAllViews被调用时和此文档相关的所有视的OnUpdate都会被调用,而参数lHint和pHint都会被传递。这样一来发生改变视就可以通知其他的兄弟了。那么还有一个问题:如何在OnUpdate中知道是那个视图发生了改变呢,这就可以利用pHint参数,只要调用者将this指针赋值给参数就可以了,当然完全可以利用该参数传递更复杂的结构。 视的初始化,当一个文档被打开或是新建一个文档时视图的CView::OnInitialUpdate()会被调用,你可以通过重载该函数对视进行初始化,并在结束前调用父类的OnInitialUpdate,因为这样可以保证OnUpdate会被调用。 文档中内容的清除,当文档被关闭时(比如退出或是新建前上一个文档清除)void CDocument::DeleteContents ()会被调用,你可以通过重载该函数来进行清理工作。 在单文档结构中上面两点尤其重要,因为软件运行文档对象和视对象只会被产生并删除一次。所以应该将上面两点和C++对象构造和构析分清楚。 最后将一下文档模板(DocTemplate)的作用,文档模板分为两类单文档模板和多文档模板,分别由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在于记录文档,视,框架之间的对应关系。还有一点就是模板可以记录应用程序可以打开的文件的类型,当打开文件时会根据文档模板中的信息选择正确的文档和视。模板是一个比较抽想的概念,一般来说是不需要我们直接进行操作的。 当使用者通过视修改了数据时,应该调用GetDocument()->SetModifiedFlag(TRUE)通知文档数据已经被更新,这样在关闭文档时会自动询问用户是否保存数据。 好象这一节讲的有些乱,大家看后有什么想法和问题请在VCHelp论坛上留言,我会尽快回复并且会对本节内容重新整理和修改。 3.5 利用序列化进行文件读写 在很多应用中我们需要对数据进行保存,或是从介质上读取数据,这就涉及到文件的操作。我们可以利用各种文件存取方法完成这些工作,但MFC中也提供了一种读写文件的简单方法——“序列化”。序列化机制通过更高层次的接口功能向开发者提供了更利于使用和透明于字节流的文件操纵方法,举一个例来讲你可以将一个字串写入文件而不需要理会具体长度,读出时也是一样。你甚至可以对字符串数组进行操作。在MFC提供的可自动分配内存的类的支持下你可以更轻松的读/写数据。你也可以根据需要编写你自己的具有序列化功能的类。 序列化在最低的层次上应该被需要序列化的类支持,也就是说如果你需要对一个类进行序列化,那么这个类必须支持序列化。当通过序列化进行文件读写时你只需要该类的序列化函数就可以了。 怎样使类具有序列化功能呢?你需要以下的工作: • 该类从CObject派生。 • 在类声明中包括DECLARE_SERIAL宏定义。 • 提供一个缺省的构造函数。 • 在类中实现Serialze函数 • 使用IMPLEMENT_SERIAL指明类名和版本号 下面的代码建立了一个简单身份证记录的类,同时也能够支持序列化。 in H struct strPID { char szName[10]; char szID[16]; struct strPID* pNext; }; class CAllPID : public CObject { public: DECLARE_SERIAL(CAllPID) CAllPID(); ~CAllPID(); public:// 序列化相关 struct strPID* pHead; //其他的成员函数 void Serialize(CArchive& ar); }; in CPP IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用于读数据时的检测 void CAllPID::Serialize(CArchive& ar) { int iTotal; if(ar.IsStoring()) {//保存数据 iTotal=GetTotalID();//得到链表中的记录数量 arr<26;i++) ar<>iTotal; for(int i=0;i26;j++) ar>>*(((BYTE*)pID)+j);//读一个strPID中所有的数据 //修改链表 } } } 当然上面的代码很不完整,但已经可以说明问题。这样CAllPID就是一个可以支持序列化的类,并且可以根据记录的数量动态分配内存。在序列化中我们使用了CArchive类,该类用于在序列化时提供读写支持,它重载了<>运算符号,并且提供Read和Write函数对数据进行读写。 下面看看如何在文档中使用序列化功能,你只需要修改文档类的Serialize(CArchive& ar)函数,并调用各个进行序列化的类的Serial进行数据读写就可以了。当然你也可以在文档类的内部进行数据读写,下面的代码利用序列化功能读写数据: class CYourDoc : public CDocument { void Serialize(CArchive& ar); CString m_szDesc; CAllPID m_allPID; ...... } void CYourDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) {//由于CString对CArchive定义了<>操作符号,所以可以直接利用>>和<< ar<>m_szDesc; } m_allPID.Serialize(ar);//调用数据类的序列化函数 3.6 MFC中所提供的各种视类介绍 MFC中提供了丰富的视类供开发者使用,下面对各个类进行介绍: CView类是最基本的视类只支持最基本的操作。 CScrollView类提供了滚动的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )设置滚动尺寸,和坐标映射模式。但是在绘图和接收用户输入时需要对坐标进行转换。请参见3.2 接收用户输入。 CFormView类提供用户在资源文件中定义界面的能力,并可以将子窗口和变量进行绑定。通过UpdateData函数让数据在变量和子窗口间交换。 CTreeView类利用TreeCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。 CListView类利用ListCtrl界面作为视界面,通过调用CTreeCtrl& CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。 CEditView类利用Edit接收用户输入,它具有输入框的一切功能。通过调用CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以设置打印字体。 CRichEditView类作为Rich Text Edit(富文本输入)的视类,提供了可以按照格式显示文本的能力,在使用时需要CRichEditDoc的支持。 第四章 窗口控件 4.1 Button 按钮窗口(控件)在MFC中使用CButton表示,CButton包含了三种样式的按钮,Push Button,Check Box,Radio Box。所以在利用CButton对象生成按钮窗口时需要指明按钮的风格。 创建按钮:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按钮上显示的文字,dwStyle为按钮风格,除了Windows风格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)还有按钮专用的一些风格。 • BS_AUTOCHECKBOX 检查框,按钮的状态会自动改变 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box. • BS_AUTORADIOBUTTON 圆形选择按钮,按钮的状态会自动改变 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group. • BS_AUTO3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a three-state check box, except that the box changes its state when the user selects it. • BS_CHECKBOX 检查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). • BS_DEFPUSHBUTTON 默认普通按钮 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option). • BS_LEFTTEXT 左对齐文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box. • BS_OWNERDRAW 自绘按钮 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class. • BS_PUSHBUTTON 普通按钮 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button. • BS_RADIOBUTTON 圆形选择按钮 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices. • BS_3STATE 允许按钮有三种状态即:选中,未选中,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled. rect为窗口所占据的矩形区域,pParentWnd为父窗口指针,nID为该窗口的ID值。 获取/改变按钮状态:对于检查按钮和圆形按钮可能有两种状态,选中和未选中,如果设置了BS_3STATE或BS_AUTO3STATE风格就可能出现第三种状态:未定,这时按钮显示灰色。通过调用int CButton::GetCheck( ) 得到当前是否被选中,返回0:未选中,1:选中,2:未定。调用void CButton::SetCheck( int nCheck );设置当前选中状态。 处理按钮消息:要处理按钮消息需要在父窗口中进行消息映射,映射宏为ON_BN_CLICKED( id, memberFxn )id为按钮的ID值,就是创建时指定的nID值。处理函数原型为afx_msg void memberFxn( ); 4.2 Static Box 静态文本控件的功能比较简单,可作为显示字符串,图标,位图用。创建一个窗口可以使用成员函数: BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对静态控件指明专门的风格。 • SS_CENTER,SS_LEFT,SS_RIGHT 指明字符显示的对齐方式。 • SS_GRAYRECT 显示一个灰色的矩形 • SS_NOPREFIX 如果指明该风格,对于字符&将直接显示,否则&将作为转义符,&将不显示而在其后的字符将有下划线,如果需要直接显示&必须使用&&表示。 • SS_BITMAP 显示位图 • SS_ICON 显示图标 • SS_CENTERIMAGE 图象居中显示 控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。 控制显示的图标利用成员函数SetIcon/GetIcon用于设置/得到当前显示的图标。 控制显示的位图利用成员函数SetBitmap/GetBitmap用于设置/得到当前显示的位图。下面一段代码演示如何创建一个显示位图的静态窗口并设置位图 CStatic* pstaDis=new CStatic; pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE, CRect(0,0,40,40),pWnd,1); CBitmap bmpLoad; bmpLoad.LoadBitmap(IDB_TEST); pstaDis->SetBitmap(bmpLoad.Detach()); 4.3 Edit Box Edit窗口是用来接收用户输入最常用的一个控件。创建一个输入窗口可以使用成员函数: BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff ); 其中dwStyle将指明该窗口的风格,除了子窗口常用的风格WS_CHILD,WS_VISIBLE外,你可以针对输入控件指明专门的风格。 • ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明输入文字超出显示范围时自动滚动。 • ES_CENTER,ES_LEFT,ES_RIGHT 指定对齐方式 • ES_MULTILINE 是否允许多行输入 • ES_PASSWORD 是否为密码输入框,如果指明该风格则输入的文字显示为* • ES_READONLY 是否为只读 • ES_UPPERCASE,ES_LOWERCASE 显示大写/小写字符 控制显示的文本利用成员函数SetWindowText/GetWindowText用于设置/得到当前显示的文本。 通过GetLimitText/SetLimitText可以得到/设置在输入框中输入的字符数量。 由于在输入时用户可能选择某一段文本,所以通过void CEdit::GetSel( int& nStartChar, in
说明:本书稿为张孝祥、袁龙友两位老师在2000 年依据张孝祥的vc 讲课记录整理,由于时间关系,仅仅是写成了草稿,欢迎大家使用! 第1 章 掌握C 1.1 类的定义与应用 1.1.1 结构的定义 1.1.2 类的定义 1.1.2.1 类与结构 1.1.2.2 类的使用 (例子代码EX01-00) 1.2 函数的重载 1.2.1 重载参数个数不同的函数 (例子代码EX01-01) 1.2.2 重载参数数据类型不同的函数 (例子代码EX01-02) 1.3 构造函数与析构函数 1.3.1 构造函数 1.3.2 析构函数 (图1,没有)(图2,没有) 1.4 this 指针的引用 1.5 类的继承与protected 访问修饰符 1.5.1 单一继承 (例子代码EX01-03) (图x,没有) 1.5.2 多重继承 1.6 虚函数与多态性 1.7 类的书写规范 1.8 小结 第2 章 Windows 程序内部运行原理 2.1 Windows 应用程序,操作系统,计算机硬件之间的相互关系 2.1.1 关于API (图1,没有) 2.1.2 关于消息及消息队列 (图1、图2 没有) 2.2 什么是句柄 2.3 谈谈WinMain 函数 (例子代码EX02-00) 2.3.1 WinMain 函数的定义及功能 2.3.2 窗口及其生成 2.3.2.1 如何设计一个窗口类——步骤1 2.3.2.2 注册设计好的窗口类——步骤2 2.3.2.3 创建窗口——步骤3 2.3.2.4 显示创建的窗口——步骤4 2.3.3 消息循环 2.3.4 完成回调函数 2.4 程序编写操作步骤与实验 2.5 小结 第3 章 VC 集成开发环境介绍 3.1 Visual C 开发环境 3.1.1 工作区窗格 3.1.2 输出窗格 3.1.3 编辑区 3.1.4 菜单栏、工具栏、状态栏 3.2 系统菜单功能介绍 3.2.1 File 菜单 3.2.2 Edit 菜单 3.2.3 View 菜单 3.2.4 Insert 菜单 3.2.5 Project 菜单 3.2.6 Build 菜单 3.2.6 Tools 菜单 3.2.7 Window 菜单 3.2.8 Help 菜单 3.3 Visual C 重要工具介绍 3.3.1 C/C 编译器 3.3.2 资源编辑器 3.3.3 资源编译器 3.3.4 链接器和调试器 3.3.5 AppWizard 和ClassWizard 3.3.6 资源浏览器 3.3.7 Microsoft 活动模板库、仓库 3.4 小结 第4 章 MFC 应用程序框架剖析 4.1 什么是MFC 以及MFC 的特点 (例子代码EX04-00) 4.2 MFC 应用程序框架 (例子代码EX04-01) 4.3 应用程序框架说明 4.4 文档-视图体系结构 4.4.1 文档-视图相互作用的函数 4.4.2 单文档-视图应用程序结构 4.4.2 多文档-视图应用程序结构 4.5 MFC 消息映射机制 4.5.1 消息的种类 4.5.2 应用程序的Run 函数 4.5.3 消息映射表 4.5.4 如何添加消息映射 4.6 ClssWizard 的使用 4.6.1 ClssWizard 概貌 4.6.2 如何添加消息处理函数 4.6.3 如何添加成员变量 4.6.4 如何添加一个新类 第5 章 图形与文本 5.1 理解图形设备接口 5.2 设备描述表 5.2.1 什么是设备描述表 5.2.2 MFC 中的设备描述表类 5.2.3 获取设备描述表 5.3 Windows 的GDI 对象 5.4 GDI 对象的创建 5.4.1 自定义画刷(CBrush) 5.4.2 自定义画笔(CPen) 5.4.3 自定义字体(CFont) 5.5 GDI 对象的使用及示例 5.5.1 画笔的使用 5.5.1.1 在MFC 程序中画线 5.5.1.2 在Windows Application 程序中画线 5.5.1.3 实现橡皮筋功能 5.5.2 画刷的使用 5.5.2.1 带颜色的画刷的使用 5.5.2.2 带位图的画刷的使用 5.5.3 字体的使用 5.5.3.1 一个简单的文字处理程序 5.5.3.2 模拟卡拉OK 程序 5.5.3.3 剪切区和路径层 第六章 菜单、工具栏和状态栏 6.1 菜单 6.1.1 菜单介绍 6.1.2 创建一个菜单 6.1.2.1 添加一个菜单资源 6.1.2.2 利用菜单编辑器编辑菜单资源 6.1.2.3 将菜单加入到应用程序中 6.1.2.4 给菜单项添加COMMAND 消息处理 6.1.2.5 给菜单项添加UPDATE_COMMAND_UI 消息处理 6.1.2.6 一个简单的绘图程序 6.1.3 在应用程序中控制菜单 6.1.3.1 在应用程序中取得菜单 6.1.3.2 在应用程序中修改菜单的状态 6.1.3.3 在应用程序中添加、删除、插入菜单或菜单项 6.1.3.4 一个简易的电话本程序 6.1.4 创建快捷方式菜单 6.2 工具栏 6.2.1 工具栏介绍 6.2.1.1 熟悉CToolBar 类 6.2.1.2 AppWizard 是如何创建工具栏 6.2.1.3 利用工具栏编辑器设计工具栏按钮 6.2.2 新建一个工具栏 6.2.3 显示/隐藏工具栏 6.3 状态栏 6.3.1 状态栏介绍 6.3.1.1 熟悉CStatusBar 类 6.3.1.2 AppWizard 是如何创建状态栏 6.3.2 修改状态栏 6.3.2.1 指示器数组 6.3.2.2 修改状态栏窗格 6.3.3 在状态栏上显示鼠标坐标、时钟和进度条 6.3.3.1 在状态栏上显示鼠标坐标 6.3.3.2 在状态栏上显示时钟 6.3.3.3 在状态栏上显示进度条 第七章 对话框和控件 7.1 对话框及控件的介绍 7.1.1 常用控件介绍 7.1.2 对话框介绍 7.1.2.1 对话框的组成 7.1.2.2 对话框的种类 7.1.2.3 使用对话框编辑器设计对话框 7.1.3 创建一个对话框 7.1.3.2 创建非模态对话框 7.1.3.3 对话框的数据交换机制 7.1.3.4 创建模态对话框 7.1.4 模态对话框和非模态对话框的区别 7.1.5 按钮逃跑小程序 7.2 属性页和向导对话框 7.2.1 创建属性页对话框 7.2.1 创建向导对话框 7.3 公用对话框 7.3.1 增加设置对话框来完善绘图程序 7.3.2 颜色对话框的使用 7.3.3 字体对话框的使用 7.3.4 控制控件颜色做漂亮界面 第8 章 文档序列化 8.1 序列化 8.1.1 CArchive 类和Serialize 函数 8.1.2 使自己的类支持序列化 8.1.3 实例:保存和显示图形 8.2 CFile 类 8.2.1 CFile 类的构造函数 8.2.2 打开文件 8.2.3 读写数据 8.2.4 关闭文件 8.3 文件I/O 处理 8.3.1 利用MFC 类来实现 8.3.2 利用C 函数来实现 8.3.3 利用C 函数来实现 8.3.4 利用API 函数来实现 8.3.5 创建保存、打开对话框 8.4 注册表操作 8.4.1 什么是注册表 8.4.2 注册表结构 8.4.3 修改注册表 第9 章 修改框架程序的显示效果 9.1 修改Windows 应用程序外观样式 9.1.1 在框架类中修改程序外观 9.1.2 在视图类中修改程序外观 9.2 制作动画图标 9.3 将图片作为窗口显示的背景 第10 章 网络编程 10.1 计算机网络的基本概念 10.1.1 计算机网络的分类 10.1.2 网络拓扑结构 10.2 网络体系结构和网络协议 10.2.1 ISO/OSI 参考模型 10.2.2 TCP/IP 参考模型 10.2.3 TCP/IP 协议 10.2.3.1 协议概述 10.2.3.2 TCP/IP 协议层次结构及各种协议介绍 10.2.3.3 IP 地址 10.2.3.4 端口号 10.2.4 专业术语解释 10.3 Socket 套接字 10.3.1 Socket 介绍 10.3.2 关于协议族和地址族 10.3.3 使用Socket 10.3.3.1 创建Socket 10.3.3.2 指定本地地址及端口号 10.3.3.3 建立连接 10.3.3.4 监听连接 10.3.3.5 发送数据 10.3.3.6 接收数据 10.3.3.7 关闭套接字 10.3.4 套接字一般调用过程 10.4 WinSock 编程机制 10.4.1 认识Windows Socket 10.4.2 Windows Sockets 库函数介绍 10.4.2.1 Berkeley socket 函数 10.4.2.2 数据库函数 10.4.2.3 Windows 专有扩展函数 10.5 WinSock 编程实例 10.5.1 实例一:面向连接服务的socket 调用 10.5.2 实例二:面向非连接服务的socket 调用 10.5.3 实例三:基于字符界面的聊天程序 第11 章 线程间的同步 11.1 进程和线程的概念 11.2 Win32 的线程 11.2.1 线程的创建 11.2.2 线程的终止 11.2.3 实例:通过创建多线程来编写网络聊天程序 11.3 MFC 的线程处理 11.3.1 创建工作者线程 11.3.2 创建用户界面线程 11.4 线程同步 11.4.1 为什么要同步 11.4.2 等待函数 11.4.3 同步对象 11.4.3.1 关键代码段 11.4.3.2 互斥对象 11.4.3.3 信标对象 11.4.3.4 事件对象 11.4.4 如何选择同步对象 第12 章 进程间的通讯 12.1 进程控制 12.1.1 进程的创建 12.1.2 进程的终止 12.2 进程间通讯 12.2.1 剪贴板通讯方式 12.2.2 邮槽通讯方式 12.2.3 管道通讯方式 12.2.3.1 命名管道通讯 12.2.3.2 匿名管道通讯 第14 章 ActiveX 技术 14.1 ActiveX 简介 14.2 ActiveX 控件和普通Windows 控件 14.2.1 ActiveX 控件和普通Windows 控件的相同点 14.2.2 ActiveX 控件和普通Windows 控件的相同点 14.3 ActiveX 控件的属性、方法和事件 14.3.1 ActiveX 控件的属性 14.3.2 ActiveX 控件的方法 14.3.3 ActiveX 控件的事件 14.4 创建ActiveX 控件
C 基础。正所谓“磨刀不误砍柴工”,最开始接触VC时不要急于开始Windows程序开发,而是应该进行一些字符界面程序的编写。这样做的目的主要是增加对语言的熟悉程度,同时也训练自己的思维和熟悉一些在编程中常犯的错误。更重要的是理解并能运用C 的各种特性,这些在以后的开发中都会有很大的帮助,特别是利用MFC进行开发的朋友对C 一定要能熟练运用。 2、理解Windows的消息机制,窗口句柄和其他GUI句柄的含义和用途。了解和MFC各个类功能相近的API函数。 3、一定要理解MFC中消息映射的作用。 4、训练自己在编写代码时不使用参考书而是使用Help Online。 5、记住一些常用的消息名称和参数的意义。 6、学会看别人的代码。 7、多看书,少买书,买书前一定要慎重。 8、闲下来的时候就看参考书。 9、多来我的主页。^O^ 后面几条是我个人的一点意见,你可以根据需要和自身的情况选用适用于自己的方法。 此外我将一些我在选择参考书时的原则: 对于初学者:应该选择一些内容比较全面的书籍,并且书籍中的内容应该以合理的方式安排,在使用该书时可以达到循序渐进的效果,书中的代码要有详细的讲解。尽量买翻译的书,因为这些书一般都比较易懂,而且语言比较轻松。买书前一定要慎重如果买到不好用的书可能会对自己的学习积极性产生打击。 对于已经掌握了VC的朋友:这种程度的开发者应该加深自己对系统原理,技术要点的认识。需要选择一些对原理讲解的比较透彻的书籍,这样一来才会对新技术有更多的了解,最好书中
### 回答1: VS2010 MFC(Microsoft Foundation Class)是一个应用程序框架,它在Windows操作系统上开发C++桌面应用程序。它提供了许多重要的类,如窗口控制,对话框,按钮,菜单,文本框等,方便程序员开发应用程序。此外,MFC还提供了用于文件I / O,数据库操作,打印和更多功能的许多类。 对于那些想要学习VS2010 MFC的人来说,有许多教程和资源可以帮助他们。首先,官方文档提供了完整的文档和示例代码,帮助开发者了解MFC以及MFC应用程序的特定方面,例如如何添加控件,如何处理事件等等。 此外,网络上有很多MFC教程,包括视频教程和博客文章。这些教程适合初学者和中级开发人员,帮助他们了解MFC的工作原理和如何编写应用程序。许多网站还提供了MFC资源的下载,例如MFC库,示例代码和其他工具。 最后,MFC社区也是一个了解和学习MFC的有用资源。这个社区由专业开发人员组成,他们会分享自己的经验和知识,并尝试回答其他开发人员的问题。 总之,学习VS2010 MFC需要花费时间和精力,但是对于那些想要开发Windows应用程序的人来说,它是一个非常有用的框架。有了适当的教程和资源,开发人员可以很好地掌握MFC并开始创建自己的应用程序。 ### 回答2: VS2010是微软公司推出的一款集成开发环境,其中包括了MFC教程。所谓的MFC是Microsoft Foundation Classes的缩写,是一组C++类,用来支持Windows GUI编程。 在使用VS2010进行MFC编程时,需要按照以下步骤进行: 1. 创建MFC应用程序:在VS2010中,选择“File”菜单,然后选择“New Project”,再选择“MFC Application”。在“Application Type”中可以选择多种类型,比如对话框应用程序、MDI应用程序等。 2. 设计用户界面:在设计用户界面时,可以采用拖放的方式添加控件,例如按钮、文本框、列表框等,然后修改属性来实现想要的功能。 3. 写代码:在用户界面设计完成后,需要编写具体的业务逻辑代码,例如在按钮单击事件中添加对文本框的处理等。VS2010使用C++语言进行编写,可以充分利用MFC提供的类库和函数来完成编程任务。 4. 调试和部署:在编程完成后,需要进行调试、测试和部署。VS2010中提供了丰富的调试工具,在测试和调试过程中能够及时发现和修复错误。 总的来说,VS2010MFC教程是一个非常丰富的知识库,能够帮助开发人员快速学习和掌握MFC编程技术,从而快速开发出高质量的Windows应用程序。 ### 回答3: VS2010MFC(Microsoft Foundation Classes)是微软公司开发的一套面向对象的类库,用于开发Windows平台下的C++应用程序,集成于Visual Studio 2010开发环境中。VS2010MFC教程是学习和掌握MFC编程的必备材料之一。 VS2010MFC教程主要包括MFC框架、消息和事件处理、控件编程、对话框和窗口等核心内容。学习MFC框架是理解MFC编程的基础,包括应用框架、文档框架、视图框架等,这是实现MFC应用程序结构的必要知识。消息和事件处理是MFC编程中的重要知识点,包括如何响应用户操作、如何处理消息和事件等。控件编程是MFC编程中的重点,掌握它可以实现多种组件的创建和交互。对话框和窗口是MFC编程中的基本元素,通过创建对话框和窗口来实现用户界面的展示和交互。 学习VS2010MFC教程的过程中,需要有一定的C++编程基础,了解面向对象编程的概念和实践经验。同时,需要具备探究和实践的能力,通过不断实践和调试来掌握MFC编程的技能。 总之,通过学习VS2010MFC教程,可以掌握Windows平台下C++应用程序的开发技能,搭建强大的程序框架和用户界面,实现更加丰富和复杂的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值