VC-MFC编程基础

更新时间:2024-05-09 02:18:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第一章 VC++的Windows编程入门

《C++程序设计》教材和该课程中的例程都是基于MS-DOS的控制台应用程序。这种程序是在文字用户界面下以命令行方式运行的,其特点是开销小,运行速度快。对于开发特定用途的应用程序以及进行基本程序设计训练,可以说控制台应用程序是合适的选择。

随着Windows操作系统的出现和普及,Windows(窗口式)风格的图形用户界面被人们普遍接受和喜爱,它较之传统的文字用户界面对用户更为友好。Visual C++就提供了编写Windows风格的应用程序的全面支持。

本课程简要介绍Windows应用程序的形式和在VC++集成开发环境下使用MFC编写Windows应用程序的基本方法,以使同学们对面向对象的可视化(Visual)程序设计建立一个初步的概念并掌握一些基本方法,为深入学习和使用VC++和其他可视化编程工具打下基础。 §1 Windows编程概述 1.1 Windows编程的基本特点 Windows编程有三个基本特点:

●可视化的图形用户界面设计 ●使用面向对象方法编程 ●采用事件驱动的程序运行方式 1.可视化的图形用户界面设计

我们已经相当熟悉以下小程序: #include using namespace std; int main() { double r;

cout<<”请输入圆半径:”<>r;

cout<<”圆周长=”<<2*3.1416*r <

这是一个基于控制台的文字用户界面应用程序。运行这种程序之后,屏幕出现MS-DOS窗口,并且按照程序代码的流程执行,用户输入数据后即显示执行结果。

进行Windows编程,同样可以写出相同功能的程序,程序运行出现如图1所示的窗口。这个窗口称为对话框,可作为人-机交互的接口,它比MS-DOS的文字用户界面更为友好且方便。这种界面称为图形用户界面(GUI, Graphical User Interface)。

在非可视化环境下,图形用户界面的设计都需通过编写程序代码来实现,且在设计过程中是看不到界面的实际显示效果的。

Visual C++支持可视化的图形用户界面设计。Windows应用程序的每个图形界面元素(如对话框、菜单、命令按钮、文本框等)都是可视的(Visual),即设计时在屏幕上是可见的,且所见即所得,编程者可根据具体用户界面设计的要求,直接使用VC++系统提供的标准工具在屏幕上“画”出各种Windows风格的图形界面元素,而不必为这些界面元素的构建设计大量代码,VC++会自动生成这些界面元素的设计代码,开发人员只需为每个图形界面元素设置特定的属性值,代码编写只针对界面元素所要实现的具体功能。

2. 面向对象编程方式

由于Windows应用程序的复杂性,从图形用户界面到整个应用程序,都是采用面向对象的方法组织的。Windows中的对象可以是所有的规范部件,如窗口、菜单、工具栏、按钮、文本框、以及程序模块等。

从用户的观点看,Windows应用程序有一个窗口,窗口上有若干菜单、命令按钮、图标等界面图形元素;而窗口背后是一些对应的程序。

从MFC的面向对象模型看,一个Windows应用程序可以拆分成许多功能独立的对象,各自负责不同类型的工作,其最基本的构成对象可以分为两大类:程序对象和窗口对象。

一个应用程序的执行就像一场舞台演出。应用程序对象是幕后人员(导演、剧务等),它们负责安排并指挥“演员”(窗口)上场,准备和调度各种演出“资源”(如菜单、工具栏、图标等)。从操作系统来看,具有“法人”地位的是应用程序对象而非窗口对象,应用程序可以占有CPU时间和内存空间,还可拥有演出所需的“资源”。

Windows系统给每个对象分配一个“句柄”(handle),它是一个4字节长的整数值,是对象的唯一内部编号。应用程序通过句柄访问相应的对象。每个对象除了句柄外,还有一个对象标识符ID,可看作是对象的外部名。ID与句柄的区别主要在于:ID可以是

用户自行设定的;而句柄是系统内定的,对用户是透明的,这能防止应用程序直接访问对象的内部信息。

3. 事件驱动的程序运行机制

传统的文字用户界面的应用程序都有一个起点和终点,程序中的各模块(函数或对象)的控制过程和执行顺序是在编写程序时精心设置好的,是预知的、确定的,整个程序的运行过程是明确的。这种程序运行机制称为过程驱动。

Windows应用程序则采用的是事件驱动的程序运行机制:

事件发出消息,消息激活对象。

即应用程序的运行没有严格的起点和终点以及固定的流程,各对象之间没有规定的执行顺序,即程序的运行过程是不明确的,而是由事件及其消息控制着对象的激活和程序的运行过程。对象通常处于循环休眠状态,当发生了某个事件并有相应消息传递给对象,才会按照消息的指示执行程序代码,一次消息的处理工作完毕后又恢复休眠状态以等待下一个事件的发生及其消息的到来。

“事件”(event)是什么?事件的含义很广泛,简单地说,事件就是激活对象执行程序代码的一种动作。在Windows的操作环境中,无论系统产生的动作还是运行程序时用户产生的动作都称为事件。最常见的用户事件是鼠标事件和键盘事件。实际上,任何一个拥有设备驱动程序的系统输入设备(包括端口)都可以产生各种事件,发送自己的消息。

消息(message)是Windows定义(WinUser.h头文件)的一种特殊的数据结构(MSG),它打包了“事件”发生的相关信息,例如:消息接收者的句柄、事件的类型和来源、消息参数等。

Windows系统有各种各样的消息,消息的传递无时不在发生,即使电脑闲置时,还会有系统时间的消息传递出来。任何一个看似十分简单的动作都会引发窗口之间许多消息的交换。

在VC++中,什么对象可以产生或接收什么事件,是由MFC规定的。例如,命令按钮有鼠标单击(BN_CLICKED)和双击(BN_DOUBLECLICKED)事件;文本编辑框有改变文本(EN_CHANGE)、接受输入焦点(EN_SETFOCUS)、失去输入焦点(EN_KILLFOCUS)、单击垂直滚动条(EN_VSCROLL)等事件。

当在对象上发生了某个事件并发出了相应的消息后,接收者对象就要响应并处理该消息。每个需要响应的消息要求对应一个处理该消息的程序——事件驱动程序。响应消

息就是启动相应的事件驱动程序。Windows中将响应消息的过程称为“消息映射”。 1.2 Windows编程方法

使用VC++进行Windows编程有两种方法。 1. 基于Windows API函数的编程方法

API是应用程序编程接口(Application Programming Interface)的缩写,是Windows操作系统与Windows应用程序之间的标准接口。作为Windows操作系统的组成部分(shell),API提供了1000多个可供应用程序直接调用的C函数(在Windows API参考手册中有详细介绍),这些函数大致可分为三类:

■窗口管理函数:实现窗口的创建、移动和修改功能。 ■图形设备接口函数:实现与具体显示设备无关的图形操作。 ■系统服务函数:实现操作系统提供的各种实用服务功能。

使用API设计Windows应用程序,对于理解和掌握面向对象程序设计的思想和方法是相当关键的,是想成为Windows编程高手的必经之路。但它要求编程者必须熟知Windows应用程序的框架、事件驱动以及消息传递等内部机制,并且仍然必须编写大量代码,即它要求编程者具备较高的专业素质,这对初学者来说无疑是比较困难的。当然,使用API编程更具灵活性和个性化。

API支持所有Windows应用程序的开发系统,如VC++、VB、Java等。 2. 基于Microsoft MFC的编程方法

MFC是微软基础类库(Microsoft Foundation Class)的缩写。与API不同,MFC不是Windows操作系统的组成部分,而是微软公司专为VC++开发的配套软件包。与VC6配套的是MFC6.0。

MFC充分使用了面向对象方法中的继承和多态性技术,以类的方式封装了Windows应用程序所涉及的几乎所有的标准部件,如Windows应用程序的框架、各种标准的图形界面元素、其核心是它以C++的形式封装了Windows API的大部分函数。

MFC是一个很大的类层次体系(MFC6.0中包含了200多个类),它由一个称为CObject的类作为根基类,其他类大部分从该类派生而来,其整个类体系可大致划分为:

■基类

■应用程序框架类

包括:应用程序类、命令类、文档/视窗类、框架窗口类、文档模板类等 ■可视对象类

包括:窗口类、对话框类、控件类等 ■绘图类

包括:绘图工具类、设备描述表类等。 ■简单数据类型类 ■通用类

包括:数组类、类表类、映像类 ■文件和数据库类

包括:文件I/O类、ODBC类、DAO类、ADO类等 ■Internet和网络工作类

包括:Win32 Internet类、Windows Socket类类等

■对象链接和嵌入(OLE, Object Lingking and Embedding)类 ■调试和异常类

事实上,MFC封装了一个Windows应用程序操作的每一方面,因此使用MFC编程,编程者可以把注意力集中于自己程序所特有的部分,不需要事事亲自动手。与直接调用API函数相比,这将大大减少编程者编写的代码数量,使编程工作变得更容易;同时,程序标准部分由MFC的类来提供,可使程序变得更规范,更具可读性,效率也更高。

对编程新手来说,MFC的最大优点是它以高效简洁的方式为编程者做了所有标准部分的工作,特别是编程者可以在不需要写一行代码的情况下完成一个完整的应用程序框架(约需千行代码)。MFC中包含了成千上万行功能强大的、经过优化的Windows程序代码。编程者只要定义了某个类的对象,就可以调用该类的成员函数,轻轻松松完成本该由你自己一行行编写的程序代码。从这点上说,MFC极大地降低了程序开发难度。 1.3 窗口

窗口是Windows图形用户界面的最基本和最重要的部件,每一个Windows程序至少有一个窗口。故Windows图形用户界面也称窗口界面。

1. 框架窗口

框架窗口是应用程序的主窗口,负责管理其包容的其他窗口。一个应用程序的最顶层的框架窗口是应用程序启动时创建的第一个窗口。

MFC提供了由基类CWnd(窗口类)派生的4个框架窗口类:CFrameWnd、

CMDIFrameWnd、CMDIChildWnd和Cdialog,用于支持三种标准框架窗口:

■单文档(Single document)窗口

由CframeWnd类支持。文档窗口主要由边框、标题栏、菜单栏、工具栏、编辑区等组成。

■多文档(Multiple documents,MDI)窗口

由多个文档窗口组成,其中第一个打开的是主框架窗口,由CMDIFrameWnd类支持,其他为子框架窗口,由CMDIChildWnd类支持。子框架窗口没有菜单栏和工具栏,它们共享主窗口的菜单栏和工具栏。

■对话框(Dialog)

对话框是一种特殊类型的窗口,它没有菜单栏和工具栏,且一般不能改变窗口的大小。对话框由Cdialog类支持。

2. 对话框与控件

对话框是应用程序用于显示或提示,并且等待用户输入信息的弹出式窗口。对对话框的操作一般是通过在对话框上添加控件来实现的。对话框与控件密不可分,在每个对话框内都有一些控件,对话框依靠这些控件与用户交互。可以说,对话框是Windows应用程序中最重要的用户界面元素,是应用程序与用户进行交互的主要部件。

Windows把主要设置在对话框上的一些标准的图形界面元素定义为“控件”(Control),如文本框、列表框、命令按钮、复选框等,它们中的大多数可以捕获事件并向对话框发送消息。控件实际上也都是窗口,所有的控件类都是CWnd类的派生类。控件通常是作为对话框的子窗口而创建的。 §2 使用MFC进行Windows编程

VC++把开发一个应用程序的过程称之为“工程”(Project)。开发一个Windows应用程序的工程远比开发一个控制台应用程序工程要复杂得多,工作量也要浩大得多。好在VC++提供了一组强大的MFC工程的开发工具。

开发一个MFC工程通常有四个工程步骤: Step.1建立工程架构 Step.2设计图形用户界面

Step.3设计对象的事件驱动程序,编写相关代码 Step.4工程的编译、链接和运行

下面通过一个简单的编程实例(Case1)“计算圆的周长和面积”来介绍这四个步骤。

2.1建立工程架构

建立工程架构(skeleton),类似于一项工程的总体设计,它需要确定工程的类型,根据工程类型创建并组织各种基本组件,包括:所有的类、一些基本对象、工程所需的必要资源等。从而为工程搭建起一个完整的架构。

所谓工程类型,是以工程采用什么框架窗口作为主窗口来界定的。MFC支持三种标准工程类型:

■单文档(Single document)工程 以单文档窗口作为工程主窗口。 ■多文档(Multiple documents)工程 以多文档窗口作为工程的主窗口。 ■对话框(Dialog based)工程

以对话框作为工程主窗口。这是最紧凑、最简单的一种标准工程架构。

VC++为用户建立工程架构提供了一个强大的工具——MFC AppWizard(MFC应用程序向导)。

本实例采用的是对话框架构。 操作步骤

?1. 选择VC6主窗口菜单栏中的“文件|新建”菜单项,弹出一个如图3所示的“新建”对话框。

?2. 在对话框中选择“工程”(project)选项卡。选择MFC AppWizard[exe]项,在对话框右部输入工程的路径和工程名,然后单击“确定”按钮。

VC++以文件夹方式管理工程,如图3所示,case1是D:\\CPPMFC_PRJ\\下的一个文件夹,该工程的各种文件都将存放在这个文件夹中。

?3. 在系统弹出如图4所示的“MFC应用程序向导-步骤1”对话框中进行工程类型选择。先选择单选按钮“Dialog based”(对话框工程),再单击“下一个”按钮。

?4. 依次选择系统弹出的图5至图7(步骤2至步骤4)中对话框的选项,用户可以先全部采用默认选择,然后在编程过程中再按需要修改。单击图7中的“完成”按钮,或者在出现其中任一个对话框时单击“完成”按钮,表示默认其他选项,系统就不再弹出其他对话框。

?5. 系统弹出如图8所示的“新建工程信息”对话框,其中列出前面所做的选择内容,单击“确定”按钮。

至此,工程架构创建完成,系统回到VC6的集成开发环境,如图9所示。从该窗口的左窗格中可以观察到MFC AppWizard为一个对话框工程的架构创建的所有组件:

■CCase1Dlg等三个类

其中最主要的是CCase1Dlg类(工程case1的对话框类),它由Cdialog类派生。 ■case1Dlg.h等四个头文件

case1Dlg.h同样是最主要的一个头文件,它包含了CCase1Dlg类的声明。 ?选择左窗格的“File View”(文件视图)选项卡。双击case1Dlg.h,在右窗格会列出case1Dlg.h的程序清单。

■case1Dlg.cpp等四个C++源程序文件

case1Dlg.cpp不仅是最重要的源程序文件,而且是整个工程的核心。用户的编程工作主要是在这个文件上完成,它将包含实现具体应用功能的全部代码。

case1.cpp是工程的启动程序,类似于非Windows应用程序中的main函数。 ■另外,MFC AppWizard为工程生成了一个对话框窗体、一个工程图标等图形资源。见图。

至此,虽然一行代码都没有编写,但这个应用程序架构是可以运行的程序。进行编译链接后,其运行结果如图10所示。以这种方式建立的应用程序,其主窗口即是对话框。

2.2 设计图形用户界面

1.设计工具和设计方案

在完成了对话框工程架构的创建后,VC6窗口上弹出了MFC的对话框编辑器。 对话框编辑器主要用于创建、编辑对话框窗口,向对话框窗体上添加控件和调整布局。对话框编辑器主要包括如下组件:对话框窗体、控件箱、调整(布局)工具栏以及对话框和控件的属性对话框。

对话框窗体是MFC给每个工程默认生成的一个图形资源,它是CCase1Dlg类的图形化。所谓窗体即窗口毛坯,是还未完全成形的初始窗口,它如同一块“画布”,在它上面可以画上各种控件来构成工程所需的实际窗口。窗体的标题栏上显示的是本工程的名字,窗体上可设置网格,用于安排控件的位置和大小。在程序运行时,网格会自动消失。

MFC设计有20多个标准控件。控件箱是存放控件类的容器,这些控件类都以图标式按钮表示。开发人员可利用控件箱在对话框上创建各种所需的控件。控件箱中的控

件类从上到下依次是:

控件选择按钮 静态图片 静态文本框 编辑框 框架 命令按钮 复选框 单选按钮 组合框 列表框 水平滚动条 垂直滚动条 旋转按钮 进展条 滑块 热键 列表视图 树状视图 标签 动画 复合编辑框 日期选择 日历 IP地址 用户定制工具 扩展组合框

往对话框中添加控件,即在工程中创建了相应的控件对象,这可通过可视化操作实现:

■用鼠标按住控件箱上所选中的控件图标,拖到对话框中合适的位置。或 ■先单击控件箱上所选中的控件图标,然后移动鼠标到对话框中合适的位置。 添加到对话框上的控件,可以移动、复制、调整或删除,也可拖动鼠标缩放控件的大小。

对于本实例,可在对话框上建立如下控件: ■1个编辑框:用于用户输入半径值;

■5个静态文本框(静态文本框只能显示文本而不能输入文本),其中: 2个静态文本框分别用于显示圆周长和圆面积,

其余3个静态文本框分别用于对上述三个文本框进行文字说明。

■2个命令按钮,“确定”和“退出”: 前者用于确定输入值,计算圆周长和圆面积,然后把结果显示在对应的输出框上;后者用于结束程序的执行。

2. 操作步骤

(1)设置主窗口的属性值

?单击对话框窗体,使其成为当前窗口(周围出现8个小方块)。

?从VC6窗口的菜单栏上的“查看”菜单中选择“属性”选项,或者用鼠标右击窗体,从弹出的快捷菜单中选择“属性”,都将弹出图11所示的“对话框属性”对话框。

?在“常规”(General)选项卡下:

该对话框对象的ID默认为:IDD_CASE1_DIALOG 将标题值设置为:计算圆周长和圆面积 单击“字体”按钮,进行字体属性的设置 其余属性采用默认值。 关闭属性对话框。

※上述设置在CCase1Dlg类的代码中立即被反映。

(2)添加编辑框控件

?用鼠标把控件工具箱上的编辑框拖到对话框窗体的合适位置,就建立了一个编辑框控件。

?用鼠标右击该编辑框,从弹出的快捷菜单中选择“属性”,弹出如图12所示的编辑框属性对话框。

?该编辑框对象的ID默认为:IDC_EDIT1

在“样式”(Styles)选项卡中设置相关属性值为:“靠左”(Left)和“垂直居中”(Center)。

其余属性采用默认值。 关闭属性对话框。 (3)添加静态文本框控件

?用鼠标把控件工具箱上的静态文本框拖到对话框窗体的合适位置,就建立了一个静态文本框控件。用同样的操作(或复制)建立其他四个静态文本框。

②用鼠标右击静态文本框,从弹出的快捷菜单中选择“属性”,弹出如图12所示的静态文本框属性对话框。

可对这5个静态文本框的属性值设置如下:

ID 标题 其他属性 IDC_STATIC1 输入半径: 居中,垂直居中 IDC_STATIC2 圆周长 居中,垂直居中 IDC_STATIC3 圆面积 居中,垂直居中

IDC_STATIC4 默认 靠左,垂直居中,Client edge

IDC_STATIC5 默认 靠左,垂直居中,Client edge 其中,IDC_STATIC4~5作为结果输出框,分别显示圆周长和圆面积; IDC_STATIC1~3分别作为输入框和输出框的文字说明。 (4)建立命令按钮控件

?建立2个命令按钮,它们的属性值设置如下: ID 标题 IDC_BUTTON1 确定 IDC_BUTTON2 退出

至此,控件在窗体上“摆放”完毕,可以用鼠标调整它们的大小和位置,也可以用调整工具栏调整控件之间的对齐方式、分布、大小规格,以及测试效果,设计好的图形用户界面如图所示。

2.3 设计对象的事件驱动程序 1.设计的工作和方法

对象的事件驱动程序在VC++中称为消息处理成员函数。对于对话框工程,作为工程的主界面,对话框对象需要响应并处理所有的控件消息。设计对话框的消息处理成员函数的工作主要包括:

■确定各个控件要传递的数据;

■确定对象的消息映射,即接收什么消息,建立什么消息处理函数; ■在消息处理函数的函数体内添加代码。

在实际应用中,读取或更新控件上的数据是对话框的非常重要的工作。MFC为此提供了一套标准方法:DDX(Data Exchange,数据交换)和DDV(Data Validation,数据校验)技术。

DDX通过“成员变量”(member variable)的方式实现对话框与控件之间的数据传递。如果要在对话框的消息处理函数中访问控件上的数据,就必须先在对话框类(CCase1Dlg类)中分别为这些控件添加(声明)一个成员变量,每一个成员变量绑定一个控件。在消息处理函数中是通过成员变量访问控件的。

成员变量是对话框的public数据成员。有两类成员变量: Value类成员变量:表示该变量的值为控件的数据。

Control类成员变量:表示该成员变量就代表控件本身,即它是控件的一个对象,其值实际上就是控件的句柄。

DDV用于数据的校验,例如自动校验字符串的长度和取值范围。 使用MFC DDX/DDV的流程是:

①首先定义用于接收控件数据的成员变量。例如,为编辑框IDC_EDIT1定义一个Value类的double型的成员变量,用于接收用户输入的半径值。定义变量的同时可以设定数据取值范围,提供校验。

②然后,在对应的消息处理成员函数中调用MFC函数传递数据。最简单的相关MFC函数有:

UpdateData() //更新成员变量,将控件上的数据传递给成员变量 UpdateData(FALSE) //更新控件,将成员变量的值传递给控件 2. 设计工具和设计方案

完成上述工作可使用VC++提供的一个强大工具——MFC Class Wizard(类向导)。它主要是用来管理工程中的对象和消息及其消息处理函数的,是MFC编程必不可少的重要工具。

对于本实例,可设计如下成员变量和消息处理函数:

■为编辑框IDC_EDIT1定义一个double型的成员变量m_r,用于接收用户在编辑框中输入的半径值;

■为静态文本框IDC_STATIC4和IDC_STATIC5分别定义一个Cstring型成员变量m_girth和m_area用于向静态文本框传递计算结果。静态文本框只能处理Cstring(字符串)型数据。

注意:成员变量名的前缀“m_”是MFC的风格。MFC也有自己的数据类型,不过与C++很相似,容易理解。

■为“确定”按钮建立一个“单击”(BN_CLICKED)消息处理函数,用于计算圆周长和圆面积,并显示计算结果。

■为“退出”按钮建立一个“单击”消息处理函数,用于结束程序的运行。

■建立一个对话框的初始化函数,对成员变量m_r、m_girth、m_area进行初始化。 2. 操作步骤 (1)添加成员变量

?打开“MFC Class Wizard”(类向导)对话框,如图所示。方法可以是:从VC6窗口的菜单栏上的“查看”菜单中选择“建立类向导”选项,或者用鼠标右击窗体,从弹出的快捷菜单中选择“建立类向导”,或者按快捷键Ctrl+W。

?选择“Member Variables”(成员变量)选项卡。从“Control IDs”(控件ID)列表框中,可以看到已经建立的各控件对象的ID。

?为编辑框添加成员变量:选择IDC_EDIT1,单击右边的“Add Variable…”(添加变量)按钮,弹出如图所示的“添加成员变量”对话框。

在第一个文本框(Member variable name)中输入成员变量名,如m_r; 在第二个下拉框(Category)中选择成员变量的种类,Value;

在第三个下拉框(Variable Type)中选择成员变量的数据类型,double。 单击OK按钮。回到“Member Variables”选项卡,设置变量m_r的取值范围,如最小值为0,最大值为1000.0。

?用同样的方法为静态文本框IDC_STATIC4和IDC_STATIC5添加Cstring型成员变量m_girth和m_area,最大字符个数为10。

(2)编写消息处理成员函数 ①主窗口的初始化函数

?切换到类向导的“Message Map”(消息映射)选项卡,如图所示。 ?在“Object IDs”(对象名)列表框中选择主窗口的对象名CCase1Dlg; 在“Messages”(消息)列表框中选择消息WM_INITDIALOG;

此时在“Member functions”(成员函数)列表框中会自动指出相应的MFC已定义的虚函数OnInitDialog。(主窗口对象是由MFC默认生成的)

?单击“Edit Code”(编辑代码)按钮,弹出Case1Dlg.cpp文件的编辑窗口,并显示OnInitDialog()函数的代码清单,找到注释提示处:

// TODO: Add extra initialization here(在此下边添加你初始化的代码) ?对于本实例,可添加如下代码: m_r=0.0;

m_girth=m_area=\

UpdateData(FALSE); //将数据传给控件并显示

②“确定”按钮的消息处理函数 类似上述方法,

?在“Object IDs”列表框中选择IDC_BUTTON1; 在“Messages”列表框中选择消息BN_CLICKED;

?单击“Add Function”(添加函数)按钮,此时在“Member functions”列表框中会出现一个新的函数名OnButton1,表示系统为该对话框生成了一个新成员函数;

?单击“Edit Code”(编辑代码)按钮,弹出Case1Dlg.cpp文件的编辑窗口,并显示OnButton1()函数的代码清单,找到注释提示处:

// TODO: Add your control notification handler code here (在此下边添加控件消息的处理代码) ?对于本实例,可添加如下代码:

UpdateData(); //将编辑框的数据传递给成员变量 double girth=2*3.1416*m_r; //计算圆周长 double area=3.1416*m_r*m_r; //计算圆面积

m_girth.Format(“.3f”,girth); //按格式写入成员变量 m_area.Format(\

UpdateDtata(FALSE); //将数据传给控件并显示

注:函数Fornat()是CString类的成员函数,用来把数据转换成CString类型的字符串。

③“退出”按钮的消息处理函数

?按上述方法,建立IDC_Button2的BN_CLICKED消息处理函数,并在注释提示处输入如下代码:

EndDialog(1); //关闭对话框 2.4 编译链接和运行程序

?用快捷键F7进行编译链接; ?用快捷键Ctrl+F5运行程序。

也可以使用Build菜单中的命令,或工具栏上的快捷按钮。 Case1V2

(1)把编辑框EDIT1的成员变量设置为Contrl类,即有: Member variable name:m_EDIT1 Category:Control Variable Type:CEdit

(2)“确定”按钮的消息处理函数OnButton1()

// TODO: Add your control notification handler code here

double r,girth,area; char s[10];

m_EDIT1.GetWindowText(s,10);

//调用编辑框的成员函数GetWindowText,将编辑框中的数据存到s中 r=atof(LPCTSTR)s); //将字符串s转换为数值 girth=2*3.1416*r; //计算圆周长 area=3.1416*r*r; //计算圆面积

m_girth.Format(“.3f”,girth); //按格式写入成员变量 m_area.Format(\

UpdateDtata(FALSE); //将数据传给控件并显示 注:LPCTSTR是MFC定义的字符串指针类型。 【Job1】 计算矩形(含正方形)的周长和面积 【Job2】 计算圆、矩形和三角形的周长和面积 §3 常用控件

本节介绍几类常用的控件:静态控件、编辑框控件、按钮控件、列表框控件、组合框控件以及滚动条控件。 3.1 静态控件

静态控件的基类是Cstatic。所谓“静态”,是指这类控件只能用于输出信息,而不能接收用户的输入信息,也不会产生任何消息。静态控件对象的ID值都默认为:IDC_STATIC。

有三种标准的静态控件:静态文本框(Static Text)、组框(Group Box)和图片框(Picture)。

静态文本框用于显示文本,主要起标注作用。“标题”(Caption)是其最重要的属性,标题值就是需显示的文本。

组框用于控件分组,使界面对象的功能从视觉上进行区分,主要用于对单选按钮进行分组。主要属性就是“标题”。

图片框有三个重要的属性:

Type(类型):图片类型可选择Icon(图标)、Bitmap(位图)、Enhanced Metafile(增强图元文件)、Frame(框架)、Rectangle(矩形区域)等。

Image(图象):当图片类型为Icon或Bitmap时,通过此属性可以指定资源的ID

(在Windows程序设计中,资源也是对象)。如果要在设计阶段把外部的图象插入到图片控件,则先要把外部的图象文件导入工程,才能成为可用资源。

Color(颜色):设置Frame的边框颜色或Rectangle的填充色。可选的颜色有break(黑)、while(白)、gray(灰)或者etched(有3D外观的腐蚀色)。 【Case2】图片框应用

??启动MFC AppWizard,创建一个对话框工程,工程名为case2。

?在控件箱中选择Picture控件,将其拖至对话框中,拖动该控件的边框,调整它的大小。

?将“确定”按钮的ID值改为“IDC_CHANGE”,将标题值改为“切换图片”。 ?选择“插入”菜单中的“资源”命令,弹出“插入资源”对话框,选择Bitmap(位图)资源,单击“新建”按钮。

?在左窗格的资源视图中,右击“Bitmap”并选择“Import”(导入)命令,弹出“导入资源”对话框。

?选择文件类型为“所有文件”,然后选择一个扩展名为.bmp、.jpg或 .jpeg的图形文件, 3.2 编辑框控件

编辑框(Edit Box)也称文本框,MFC的Cedit类封装了编辑框控件。 编辑框实际上是一个简易的文本编辑器,用户可以在编辑框中输入并编辑文本。 编辑框控件的主要属性 Align text

文本对齐方式,其值可以是Left、Right、Center(居中)。 Multiline

多行文本,默认是FALSE(单行)。 Number

只接受数字符号。 Horizontal scroll

添加水平滚动条,只对多行文本有效。 Vertical scroll

添加垂直滚动条,只对多行文本有效。 Password

屏蔽用户输入的信息,用特定字符统一显示,默认字符是“*”。 【Case2】密码输入

Step1:建立工程架构

使用MFC AppWizard建立一个对话框工程,工程名为:casePW Step2:设计窗口界面

使用对话框编辑器设计工程的窗口界面,如图所示。各控件的属性设置如下: 控件名 ID 标题 其他属性 编辑框 IDC_EDIT1 密码 静态文本 IDC_STATIC1 输入密码: 居中

静态文本 IDC_STATIC2 居中,Client edge 命令按钮 IDC_BUTTON1 确定 命令按钮 IDC_BUTTON2 退出 Step3:设计消息处理成员函数

使用MFC Class Wizard设计成员变量和消息处理函数。 (1)为对话框设置绑定相关控件的2个成员变量

控件名 成员变量名 种类 数据类型 字符个数 IDC_EDIT1 m_pwd Value Cstring 6 IDC_STATIC2 m_OK Value Cstring 20 (2)添加2个消息处理成员函数

①“确定”按钮的BN_CLICKED消息处理函数OnButton1( ),用于接收用户输入,检测密码的合法性。

为OnButton1()添加的代码如下:

//TODO Add your control notification handler code here UpdateDtata(); // if(m_pwd==”pwd123”) m_OK=”密码正确!”; else

m_OK=”密码错误!”; UpdateData(FALSE);

②“退出”按钮的BN_CLICKED消息处理函数OnButton2( ),用于结束工程的运行。

为OnButton2()添加的代码如下:

//TODO Add your control notification handler code here EndDialog(1);

Step4:工程的编译、链接和运行

【Job3】在Csae2的基础上,增加如下功能:当连续三次输入错误密码,则拒绝再次输入,并输出“对不起,再见!”。 3.3 按钮控件

MFC提供有三种标准按钮控件:命令按钮(Button)、复选框按钮(Check Box)和单选按钮(Radio Button),它们都是CButton类的派生类。

命令按钮也称按键按钮,可以触发某个命令的执行,这种按钮不会被锁定,响应过后会自动弹起恢复原状。

复选框和单选按钮都属于选择性按钮,都有两种状态:选择(1)和未选择(0)。它们的差别在于:复选框是相容性按钮,即在一组复选框中可同时有多个复选框处于选择状态;而单选按钮则是互斥性按钮,即在一组单选按钮中只能有一个处于选择状态,当一个单选按钮被选中时,同组的其他单选按钮自动落选。 【Case3】简单计算器(1)

本实例利用命令按钮和单选按钮设计一个只进行四则算术运算的简单计算器。 Step1:建立工程架构

使用MFC AppWizard建立一个对话框工程,工程名为:Calculator01 Step2:设计窗口界面

使用对话框编辑器设计工程的窗口界面,效果如图所示。各控件的属性设置如下: 控件名 ID 标题 其他属性 编辑框 IDC_EDIT1 无 Left 编辑框 IDC_EDIT2 无 Left

静态文本 IDC_STATIC1 无 Left, Client edge 静态文本 IDC_STATIC2 运算数1 Center 静态文本 IDC_STATIC3 运算数2 Center 静态文本 IDC_STATIC4 结果 Center 组框 IDC_STATIC5 运算符 单选按钮 IDC_RADIO1 +

单选按钮 IDC_RADIO2 - 单选按钮 IDC_RADIO3 × 单选按钮 IDC_RADIO4 ÷ 命令按钮 IDC_BUTTON1 = 命令按钮 IDC_BUTTON2 退出

Step3:设计消息处理成员函数

使用MFC Class Wizard设计成员变量和消息处理函数。 (1)为对话框设置绑定相关控件的成员变量

控件ID 成员变量名 变量类型 值域 IDC_EDIT1 m_num1 Value,double IDC_EDIT2 m_num2 Value,double IDC_STATIC1 m_show Value,Cstring 10

(2)在程序Calculator01Dlg.cpp的首部定义一个可被各控件访问的全局变量: int operator; //运算符编号 (3)添加6个成员函数

①建立各单选按钮的BN_CLICKED消息处理函数,用于确定运算。在函数中只需编写一个语句:

OnRadio1( )函数中为:operator=1; //加法 OnRadio2( )函数中为:operator=2; //减法 OnRadio3( )函数中为:operator=3; //乘法 OnRadio4( )函数中为:operator=4; //除法

②建立“=”命令按钮的BN_CLICKED消息处理函数OnButton1(),用于执行指定的算术运算并显示运算结果。代码如下:

//TODO Add your control notification handler code here double r; UpdateData(); switch(operator){

case 1: r=m_num1+m_num2;break; case 2: r=m_num1-m_num2; break; case 3: r=m_num1*m_num2; break;

case 4:

if(m_num!=0) r=m_num1/m_num2;

else{ MessageBox(“除数为0!”,”计算器”);return;} }

m_show.Format(“.3f”, r); UpdateData(FALSE);

③建立“退出”按钮的BN_CLICKED消息处理函数OnButton2( ),用于结束工程的运行。

为OnButton2()添加的代码如下:

//TODO Add your control notification handler code here EndDialog(1);

Step4:工程的编译、链接和运行

【Job4】在Case3中去掉“=”按钮,该作什么修改? 请完成工程Calculator01V2 3.4 列表框和组合框控件

列表框(ListBox)是一个列有若干可选项目的输入窗口,它允许用户从所列出的表项中进行单项或多项选择,被选择的项呈高亮度显示,并且一般带有一个垂直滚动条。

组合框(ComboBox)是由一个编辑框和一个列表框组成。用户既可以在编辑框中输入,也可以在列表框中选择一个表项来完成输入。 【Case4】简单计算器(2)

修改Case3,用一个列表框替换四个单选按钮,如图26所示。 Step1:建立一个对话框工程架构,工程名为:Calculator02 Step2:除了列表框外,其余同Case3。

在列表框的属性对话框里,设置ID为:IDC_LIST1;在“样式”(Styles)选项卡中清除对“排序”(Sort)选项的选择。如图27所示。

Step3:

(1)为对话框设置绑定相关控件的成员变量

除m_num1、m_num2和m_show外,再为列表框添加一个m_list变量,Control类,CListBox型。

(2) 建立对话框的WN_INITDIALOG消息函数OnInitDialog(),用于初始化列表框,

添加的代码如下:

//TODO Add extra initialization here m_list.AddString(“加”); m_list.AddString(“减”); m_list.AddString(“乘”); m_list.AddString(“除”);

函数AddString()的功能是在列表框中添加一个选项。 (3)添加消息处理函数 ①列表框的消息处理函数

选择列表框IDC_LIST1的消息为“LBN_SELCHANGE”,其对应的函数是OnSelchangeList1(),添加代码如下:

//TODO Add your control notification handler code here operator=m_list.GetCurSel()+1;

列表框的成员函数GetCurSel()的功能是获取列表框当前选项的序号,序号从0开始。

②“=”按钮和“退出”按钮的消息处理函数同Calculator01工程。

Step4:工程的编译、链接和运行 运行效果如图28所示。 3.5 滚动条控件

Windows系统很多窗口都有附加的滚动条,便于用户浏览显示内容。作为控件的滚动条其使用方法和特性与窗口中附加的滚动条十分相似,但功能不同。滚动条控件是一个输入数据的计数器控件,分为水平滚动条(Horizontal Scroll Bar)和垂直滚动条(Vertical Scroll Bar)两种。

水平和垂直滚动条的结构是一样的,两端都有一个滚动箭头按钮,中间有一个沿两端方向移动的滑块,在箭头按钮与滑块之间称之为滚页区。有三种滚动操作:

单击箭头按钮:执行慢速滚动,也称步进滚动,滚动单位为±1。 拖曳滑块:执行快速/自由滚动。

单击滚页区:执行滚页,页长由用户设定,如±10。

注意:滚动条通常部用于精确数据值的输入,而是作为可连续调整输入的工具。

【Case5】简单调色板

本实例利用水平滚动条以及三基色(红、绿、蓝)原理设计一个简单调色板。 Step1:由向导建立一个对话框工程架构,工程名为:Color Step2:界面设计效果如图29。其控件基本属性如下所示: 控件名 ID 标 题 其他属性 静态文本 IDC_DRAW 默认 Client edge 组框 默认 红色 编辑框 IDC_RED

滚动条 IDC_SCROLLBAR1

组框 默认 绿色 编辑框 IDC_GREEN 滚动条 IDC_SCROLLBAR2

组框 默认 蓝色 编辑框 IDC_BLUE 滚动条 IDC_SCROLLBAR3 命令按钮 IDC_BUTTON 退出 Step3:建立消息映射和消息处理函数 (1)为对话框设置绑定相关控件的成员变量:

控件ID 成员变量名 变量类型 值域 IDC_RED m_Red Value,int 0~255 IDC_GREEN m_Green Value,int 0~255 IDC_BLUE m_Blue Value,int 0~255 IDC_SCROLLBAR1 m_Scroll1 Control,SCrollBar IDC_SCROLLBAR2 m_Scroll2 Control,SCrollBar IDC_SCROLLBAR3 m_Scroll3 Control,ScrollBar

(2)建立对话框的WN_INITDIALOG消息函数OnInitDialog(),用于对有关变量进行初始化。添加的代码如下:

//TODO Add extra initialization here

m_Red=m_Green=m_Blue=0; //置成员变量的初值 //设置滚动条的值域和当前位置

m_Scroll1.SetScrollRange(0,255); m_Scroll1.SetScrollPos(m_Red); m_Scroll2.SetScrollRange(0,255); m_Scroll2.SetScrollPos(m_Green); m_Scroll3.SetScrollRange(0,255); m_Scroll3.SetScrollPos(m_Blue); UpdateData(FALSE);

(3)建立三个编辑框的ON_CHANGE消息函数On_ChangeRed()、On_ChangeGreen()和On_ChangeBlue(),用于接收成员变量的数据。这三个消息处理函数都只需添加一条语句:

UpdateData(FALSE);

(4)建立对话框的WM_HSCROLL消息函数On_Hscroll(),消息WM_HSCROLL是由滚动条发送的。

//TODO Add your control notification handler code here //接到红色滚动条的消息

if(pScrollBar->GetDlgCtrlID()==IDC_SCROLLBAR1)

{ if(nSBCode==SB_THUMBPOSITION)m_Red=nPos; //滑块滚动 if(nSBCode==SB_LINELEFT&&m_Red>0)m_Red--; //左箭头按钮 if(nSBCode==SB_LINERIGHT&&m_Red<255)m_Red++; //右箭头按钮 if(nSBCode==SB_PAGELEFT)m_Red-=10; //左滚页 if(nSBCode==SB_PAGERIGHT)m_Red+=10; //右滚页 m_Scroll1.SetScrollPos(m_Red); }

//接到绿色滚动条的消息

if(pScrollBar->GetDlgCtrlID()==IDC_SCROLLBAR2) { if(nSBCode==SB_THUMBPOSITION)m_Green=nPos; if(nSBCode==SB_LINELEFT&&m_Green >0)m_Green--; if(nSBCode==SB_LINERIGHT&&m_Green<255)m_Green++; if(nSBCode==SB_PAGELEFT)m_Green-=10; if(nSBCode==SB_PAGERIGHT)m_Green+=10;

m_Scroll2.SetScrollPos(m_Green); }

//接到蓝色滚动条的消息

if(pScrollBar->GetDlgCtrlID()==IDC_SCROLLBAR3) { if(nSBCode==SB_THUMBPOSITION)m_Blue=nPos; if(nSBCode==SB_LINELEFT&&m_Blue >0)m_Blue--; if(nSBCode==SB_LINERIGHT&&m_Blue <255)m_Blue++; if(nSBCode==SB_PAGELEFT)m_Blue-=10; if(nSBCode==SB_PAGERIGHT)m_Blue+=10; m_Scroll3.SetScrollPos(m_Blue); }

UpdateData(FALSE);

Draw(); //调用自定义成员函数,向调色板绘图

(5)为“退出”按钮的BN_CLICKED消息处理函数添加一调语句: EndDialog(1);

(6)为对话框添加public型的成员函数void Draw(),用于调制静态文本框IDC_DRAW的颜色。在VC++的“工程工作区”(左窗格)的“Class View”选项卡下,右击CcolorDlg再选择“Add member function”,在弹出的“添加成员函数”对话框中,先后输入函数的类型“void”和函数的名字“Draw”,按“确定”后在所显示的Draw函数框架内输入Draw函数的如下代码:

void CcolorDlg::Draw() { //设置调色板

CWnd *pWnd=GetDlgItem(IDC_DRAW); //获取静态文本的ID CRect rcClient; //建立矩形对象

pWnd->GetClientRect(rcClient); //获取IDC_DRAW所占矩形区域,

//并转换为矩形对象

//建立绘图对象

CDC *pDC=pWnd->GetDC(); //获取当前设备环境对象句柄 CBrush drawBrush; //建立画刷,设置画刷颜色 drawBrush.CreateSolidBrush(RGB(m_Red,m_Green,m_Blue);

//绘图

CBrush *pOldBrush=pDC->SelectObject(&drawBrush); //选取画刷 pDC->Rectangle(rcClient); //用画刷在矩形对象上绘图 //恢复原来状态

pDC->SelectObject(pOldBrush); }

Step4:工程的编译、链接和运行 运行效果如图31所示。 【Case6】仿真计算器

前面设计的计算器都比较简单,与实际计算器有较大差别。仿真计算器模拟实际计算器的基本使用方法,即数值和运算符都通过按键按钮输入,如图32所示。该工程的用户工作量较大,可分期完成。

第一期工程可只完成基本计算功能,如只实现“单一”计算功能,且不实现Backspace功能。下面是第一期工程的设计过程。

Step1:建立工程架构

使用MFC AppWizard建立一个对话框工程,工程名为:Calculator03 Step2:设计窗口界面

使用对话框编辑器设计工程的窗口界面。各控件的属性设置如下: 控件名 ID 标题 其他属性 编辑框 IDC_EDIT1 Right 命令按钮 IDC_BUTTON0 0 命令按钮 IDC_BUTTON1 1 命令按钮 IDC_BUTTON2 2 命令按钮 IDC_BUTTON3 3 命令按钮 IDC_BUTTON4 4 命令按钮 IDC_BUTTON5 5 命令按钮 IDC_BUTTON6 6 命令按钮 IDC_BUTTON7 7 命令按钮 IDC_BUTTON8 8 命令按钮 IDC_BUTTON9 9

命令按钮 IDC_BUTTONSIGN +/- 命令按钮 IDC_BUTTONDOT . 命令按钮 IDC_BUTTONEQU = 命令按钮 IDC_BUTTONADD + 命令按钮 IDC_BUTTONSUB - 命令按钮 IDC_BUTTONMUT × 命令按钮 IDC_BUTTONDIV \\

命令按钮 IDC_BUTTONMOD % 命令按钮 IDC_BUTTON10 1/x 命令按钮 IDC_BUTTONSQRT sqrt 命令按钮 IDC_BUTTONBKS Backspace 命令按钮 IDC_BUTTONCE CE 命令按钮 IDC_BUTTONQUIT Quit

对话框 默认 仿真计算器V1 Step3:建立消息映射和消息处理函数 (1)为对话框设置绑定编辑框控件的成员变量: 变量名为m_show,变量类型为Value,double

(2)为对话框定义若干全局变量,在VC++的“工程工作区”(左窗格)的“File View”选项卡下,双击CCalculator03Dlg.cpp,在程序的首部处输入如下代码:

int operator, decimal; double num, num1, right;

其中,operator用于存放运算符编号;

decimal用于表示小数,0整数,1小数; num和num1分别存放当前运算数和第一运算数; right用于存放位权。

另外,为执行平方根计算在该程序首部添加: #include “math.h”

(3)为对话框的初始化函数OnInitDialog()添加如下代码: // TODO: Add extra initialization here decimal=operator=0;

m_show=num=num1=0; UpdateData(FALSE);

(4)建立数字按钮“0”到“9”的BN_CLICKED消息的处理函数。 为按钮”1”的消息函数OnButton1()添加的代码: // TODO: Add your control notification handler code here if(decimal) { num+=1*right; right /=10; } else

num=num*10+1; m_show=num; UpdateData(FALSE);

其他数字按钮的消息函数类似,只需把其中的1改成相应数字即可。

(5)建立小数点的BN_CLICKED消息的处理函数OnButtondot(),添加代码如下: // TODO: Add your control notification handler code here decimal=1; right=1/10;

(6) 建立“+/-”按钮的消息处理函数OnButtonsign(),添加如下代码: // TODO: Add your control notification handler code here num=-num; m_show=num; UpdateData(FALSE); (7)建立运算符的消息处理函数

为加法函数OnButtonadd()添加如下代码:

// TODO: Add your control notification handler code here operator=1; num1=num; decimal=0; num=0;

m_show=num; UpdateData(FALSE);

为减法函数OnButtonsub()、乘法函数OnButtonmut()、除法函数OnButtondiv()、模运算函数OnButtonmod()添加的代码类似,只需对operator分别赋值2~5即可。

为平方根函数OnButtonsqrt()的代码,只需将上面程序段的第一条语句改成: num=sqrt(num);

(8)建立“=”按钮的消息处理函数OnButtonequ(),添加如下代码: // TODO: Add your control notification handler code here switch(optr){ case 1:

num1+=num;break; case 2:

num1-=num;break; case 3:

num1*=num;break; case 4:

if(num<0.0000001)

MessageBox(\除数为0!\仿真计算器\else

num1/=num; break; case 5:

num1=(int)num1%(int)num;break; default:

MessageBox(\错误算式!\仿真计算器\}

m_show=num1; UpdateData(FALSE); decimal=operator=0; num=num1=0;

Step4:工程的编译、链接和运行 §4 菜单

菜单是一系列命令的列表集合,可以选择菜单项来执行相应的命令。菜单是Windows应用程序必不可少的界面元素之一。

第二章 窗口的销毁

考虑单窗口情况: 假设自己通过new创建了一个窗口对象pWnd,然后pWnd->Create。则销毁窗口的调用次序: 1. 手工调用pWnd->DestroyWindow(); 2. DestroyWindow会发送WM_DESTROY; 3. WM_DESTROY对应的消息处理函数是OnDestroy(); 4. DestroyWindow会发送WM_NCDESTROY; 5. WM_NCDESTROY对应的消息处理函数是OnNcDestroy; 6. OnNcDestroy最后会调用PostNcDestroy; 7. PostNcDestroy经常被用户重载以提供释放内存操作。例如可以使用delete this; 通过这种方式,窗口对象对应的窗口和窗口对象本身都被释放了。 如果含有子窗口: 如果含有子窗口,则调用父窗口的DestroyWindow时,它会向子窗口发送WM_DESTROY和WM_NCDESTROY消息。 具体调用顺序参考下文的例子。 DestroyWindow对delete的影响: 应该说前者对后者并没有什么影响。但经常在DestroyWindow间接导致执行的PostNcDestroy中delete窗口对象指针,即delete this。 CView::PostNcDestroy中唯一的操作就是delete this;CframeWnd::PostNcDestory也是如此。而默认的CWnd::PostNcDestroy是空操作,CDialog中也没有对其进行重载,即也是空。 delete对Destroy的影响: delete会导致析构函数。CWnd的析构函数中有对DestroyWindow的调用,但必须保证: m_hWnd != NULL && this != (CWnd*) &wndTop &&this != (CWnd*)&wndBottom && this != (CWnd*)&wndTopMost &&this != (CWnd*)&wndNoTopMost。 Cdialog的析构函数中也有对DestroyWindow的调用,但条件比较松,只需要m_hWnd != NULL。另外Cdialog::DoModal也会调用DestroyWindow。 CFrameWnd的OnClose中会调用DestroyWindow,但其析构中不会调用DestroyWindow。 CView的析构也不会调用DestroyWindow。 一个SDI程序的销毁过程 有CMainFrame类、CMyView类。并且CMyView有两个子窗口CMyDlg和CmyWnd的实例。 点击退出按钮,CMainFrame会收到WM_CLOSE消息。CframeWnd(CMainFrame的父类)间接会调用CWnd::DestroyWindow;它首先向CMyView发送WM_DESTORY和WM_NCDESTROY消息,并引发相应的处理函数;然后向CMyDlg发送WM_DESTORY和WM_NCDESTROY消息,并引发相应的处理函数;然后向CMyWnd发送WM_DESTORY和WM_NCDESTROY消息,并引发相应的处理函数。 具体的执行顺序是: 1. 调用CMainFrame::DestroyWindow 2. CFrameWnd::OnDestroy 3. CMyView::OnDestroy 4. CmyWnd::OnDestroy 5. CmyDlg::OnDestroy 6. CmyWnd::PostNcDestroy 7. CmyWnd的析构 8. CmyDlg::OnDestroy 9. CmyDlg的析构 10. CMyView::PostNcDestroy 11. CmyView的析构 12. CMainFrame的析构 13. CMainFrame::DestroyWindow退出 上面情况是假设我们在CmyWnd和CmyDlg的PostNcDestroy中添加了delete this。如果没有添加,则7,10不会执行。 因为CView::PostNcDestroy中调用了delete this,所以然后会执行CMyView的析构操作。因为CframeWnd::PostNcDestroy中调用了delete this,所以最后执行CMainFrame的析构操作。 如果自己的CmyDlg和CmyWnd在PostNcDestroy中有delete this;则二者会被析构。否则内存泄漏。当然delete也可以放在CMyView的析构中做,只是不够OO。 总结 可以有两种方法销毁窗口对象对应的窗口和释放窗口对象指针。一种是通过DestroyWindow。这是比较好的方法,因为最后MFC会自动相应WM_CLOSE导致CframWnd::DestroyWindow被调用,然后会一次释放所有子窗口的句柄。用户需要做的是在PostNcDestroy中释放堆窗口对象指针。但因为某些对象是在栈中申请的,所以delete this可能出错。这就要保证写程序时自己创建的窗口尽量使用堆申请。 另一种是delete。Delete一个窗口对象指针有的窗口类(如CWnd,Cdialog)会间接调用DestroyWindow,有的窗口类(如CView,CframeWn)不会调用DestroyWindow。所以要小心应对。 二者是相互调用的,很繁琐。 一段很好的文章:(作者:闻怡洋) 一个MFC窗口对象包括两方面的内容:一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。 删除窗口最直接方法是调用CWnd::DestroyWindow或::DestroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd::DestroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd::DestroyWindow来删除对话框及其控件。 窗口对象本身的删除则根据对象创建方式的不同,分为两种情况。在MFC编程中,会使用大量的窗口对象,有些窗口对象以变量的形式嵌入在别的对象内或以局部变量的形式创建在堆栈上,有些则用new操作符创建在堆中。对于一个以变量形式创建的窗口对象,程序员不必关心它的删除问题,因为该对象的生命期总是有限的,若该对象是某个对象的成员变量,它会随着父对象的消失而消失,若该对象是一个局部变量,那么它会在函数返回时被清除。 对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。初学者在学习C++编程时,对new操作符的使用往往不太踏实,因为用new在堆中创建对象,就不能忘记用delete删除对象。读者在学习MFC的例程时,可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。 如前面讲述非模态对话框时所提到的,当调用CWnd::DestroyWindow或::DestroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。 不具有自动清除功能的窗口类如下所示。这些窗口对象通常是以变量的形式创建的,无需自动清除功能。 所有标准的Windows控件类。 1. 从CWnd类直接派生出来的子窗口对象(如用户定制的控件)。 2. 切分窗口类CSplitterWnd。 3. 缺省的控制条类(包括工具条、状态条和对话条)。 4. 模态对话框类。 具有自动清除功能的窗口类如下所示,这些窗口对象通常是在堆中创建的。 1. 主框架窗口类(直接或间接从CFrameWnd类派生)。 2. 视图类(直接或间接从CView类派生)。 读者在设计自己的派生窗口类时,可根据窗口对象的创建方法来决定是否将窗口类设计成可以自动清除的。例如,对于一个非模态对话框来说,其对象是创建在堆中的,因此应该具有自动清除功能。 综上所述,对于MFC窗口类及其派生类来说,在程序中一般不必显式删除窗口对象。也就是说,既不必调用DestroyWindow来删除窗口对象封装的窗口,也不必显式地用delete操作符来删除窗口对象本身。只要保证非自动清除的窗口对象是以变量的形式创建的,自动清除的窗口对象是在堆中创建的,MFC的运行机制就可以保证窗口对象的彻底删除。 如果需要手工删除窗口对象,则应该先调用相应的函数(如CWnd::DestroyWindow)删除窗口,然后再

删除窗口对象.对于以变量形式创建的窗口对象,窗口对象的删除是框架自动完成的.对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).对于具有自动清除功能的窗口对象,只需调用CWnd::DestroyWindow即可删除窗口和窗口对象。注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象.

原文地址 http://blog.163.com/jiket_213/blog/static/318024942008016105142610/

使用VC6.0实现窗口的任意分割

作者:张中庆 阅读人次:1471 文章来源:VCKBASE 发布时间:2006-8-25 网友评论(5)条

一、关于CSplitterWnd类

我们在使用CuteFtp或者NetAnt等工具的时候,一般都会被其复杂的界面所吸引,在这些界面中窗口被分割为若干的区域,真正做到了窗口的任意分割。 那么我们自己如何创建类似的界面,也实现窗口的任意的分割呢 ?在VC6.0中这就需要使用到CSplitterWnd类。CSplitterWnd看上去像是一种特殊的框架窗口,每个窗口都被相同的或者不同的视图所填充。当窗口被切分后用户可以使用鼠标移动切分条来调整窗口的相对尺寸。虽然VC6.0支持从AppWizard中创建分割窗口,但是自动加入的分割条总是不能让我们满意,因此我们还是通过手工增加代码来熟悉这个类。

CSplitterWnd的构造函数主要包括下面三个。

BOOL Create(CWnd* pParentWnd,int nMaxRows,int nMaxCols,SIZE sizeMin, CCreateContext* pContext,DWORD dwStyle,UINT nID);

功能描述:该函数用来创建动态切分窗口。 参数含义:pParentWnd 切分窗口的父框架窗口。 nMaxRows,nMaxCols是创建的最大的列数和行数。 sizeMin是窗格的现实大小。 pContext 大多数情况下传给父窗口。 nID是字窗口的ID号.

BOOL CreateStatic(CWnd* pParentWnd,int nRows,int nCols,DWORD dwStyle,UINT nID)

功能描述:用来创建切分窗口。 参数含义同上。

BOOL CreateView (int row,int col,CruntimeClass* pViewClass,SIZE sizeinit,CcreateContext* pContext);

功能描述:为静态切分的窗口的网格填充视图。在将视图于切分窗口联系在一起的时候必 须先将切分窗口创建好。

参数含义:同上。

从CSplitterWnd源程序可以看出不管是使用动态创建Create还是使用静态创建CreateStatic,在函数中都调用了一个保护函数CreateCommon,从下面的CreateCommon函数中的关键代码可以看出创建CSplitterWnd的实质是创建了一系列的MDI子窗口。

DWORD dwCreateStyle = dwStyle & ~(WS_HSCROLL|WS_VSCROLL); if (afxData.bWin4)

dwCreateStyle &= ~WS_BORDER; //create with the same wnd-class as MDI-Frame (no erase bkgnd)

if (!CreateEx(0, _afxWndMDIFrame, NULL, dwCreateStyle,

0, 0, 0, 0,pParentWnd->m_hWnd, (HMENU)nID, NULL)) return FALSE; // create invisible

二、创建嵌套分割窗口

2.1创建动态分割窗口

动态分割窗口使用Create方法。下面的代码将创建2x2的窗格。

m_wndSplitter.Create(this,2,2,CSize(100,100),pContext);

但是动态创建的分割窗口的窗格数目不能超过2x2,而且对于所有的窗格,都必须共享同一个视图,所受的限制也比较多,因此我们不将动态创建作为重点。我们的主要精力放在静态分割窗口的创建上。

2.2创建静态分割窗口

与动态创建相比,静态创建的代码要简单许多,而且可以最多创建16x16的窗格。不同的窗格我们可以使用CreateView填充不同的视图。

在这里我们将创建CuteFtp的窗口分割。CuteFtp的分割情况如下 三、关于对话框的分割

到目前为止,只有基于文档/视图的程序才能使用CSplitterWnd,而基于对话框的应用程序却不支持CSplitterWnd,但是如果我们在继承类中重载一些虚拟方法,也能使CSplitterWnd 在对话框程序中使用。从MFC的源程序WinSplit.cpp中可以看出,为了获得父窗口的地方程序都调用了虚拟方法GetParentFrame(),因此如果在对话框中使用,我们必须将它改为GetParent();因此我们将CSplitterWnd的下面几个方法重载。

virtual void StartTracking(int ht);

virtual CWnd* GetActivePane(int* pRow = NULL, int* pCol = NULL); virtual void SetActivePane( int row, int col, CWnd* pWnd = NULL ); virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

virtual BOOL OnNotify( WPARAM wParam, LPARAM lParam, LRESULT* pResult );

virtual BOOL OnWndMsg( UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult );

具体实现如下,实现中我将给出原有代码的主要部分以及修改后的代码以作对比。

在cpp文件中加入下面的枚举类型。

enum HitTestValue {

noHit = 0,//表示没有选中任何对象 vSplitterBox = 1, hSplitterBox = 2, bothSplitterBox = 3,

vSplitterBar1 = 101,//代表各个方向的水平分割条 vSplitterBar15 = 115,

hSplitterBar1 = 201,//代表垂直方向的各个分割条 hSplitterBar15 = 215,

splitterIntersection1 = 301,//代表各个交叉点 splitterIntersection225 = 525 };

CWnd* CxSplitterWnd::GetActivePane(int* pRow, int* pCol) {

ASSERT_VALID(this);

//获得当前的获得焦点的窗口

//下面注释粗体的是原有的代码的主要部分。 // CWnd* pView = NULL;

//CFrameWnd* pFrameWnd = GetParentFrame(); //ASSERT_VALID(pFrameWnd);

//pView = pFrameWnd->GetActiveView(); //if (pView == NULL) // pView = GetFocus(); CWnd* pView = GetFocus();

if (pView != NULL && !IsChildPane(pView, pRow, pCol)) pView = NULL; return pView; }

void CxSplitterWnd::SetActivePane( int row, int col, CWnd* pWnd) {

CWnd* pPane = pWnd == NULL ? GetPane(row, col) : pWnd;

//下面加注释粗体的是原有代码的主要部分。 //FrameWnd* pFrameWnd = GetParentFrame(); //ASSERT_VALID(pFrameWnd);

//pFrameWnd->SetActiveView((CView*)pPane); pPane->SetFocus();//修改后的语句 }

void CxSplitterWnd::StartTracking(int ht)

{

ASSERT_VALID(this); if (ht == noHit) return;

// GetHitRect will restrict ''''m_rectLimit'''' as appropriate

GetInsideRect(m_rectLimit);

if (ht >= splitterIntersection1 && ht <= splitterIntersection225)

{

// split two directions (two tracking rectangles)

int row = (ht - splitterIntersection1) / 15;

int col = (ht - splitterIntersection1) % 15;

GetHitRect(row + vSplitterBar1, m_rectTracker);

int yTrackOffset = m_ptTrackOffset.y; m_bTracking2 = TRUE;

GetHitRect(col + hSplitterBar1, m_rectTracker2);

m_ptTrackOffset.y = yTrackOffset; }

else if (ht == bothSplitterBox) {

// hit on splitter boxes (for keyboard) GetHitRect(vSplitterBox, m_rectTracker); int yTrackOffset = m_ptTrackOffset.y; m_bTracking2 = TRUE;

GetHitRect(hSplitterBox, m_rectTracker2);

m_ptTrackOffset.y = yTrackOffset; // center it

m_rectTracker.OffsetRect(0, m_rectLimit.Height()/2);

m_rectTracker2.OffsetRect(m_rectLimit.Width()/2, 0);

} else {

// only hit one bar

GetHitRect(ht, m_rectTracker); }

//下面加注释的将从程序中删去。

//CView* pView = (CView*)GetActivePane();

//if (pView != NULL && pView->IsKindOf(RUNTIME_CLASS(CView)))

//{

// ASSERT_VALID(pView);

// CFrameWnd* pFrameWnd = GetParentFrame(); //ASSERT_VALID(pFrameWnd);

//pView->OnActivateFrame(WA_INACTIVE, pFrameWnd); // }

// steal focus and capture SetCapture(); SetFocus();

// make sure no updates are pending

RedrawWindow(NULL, NULL, RDW_ALLCHILDREN | RDW_UPDATENOW);

// set tracking state and appropriate cursor m_bTracking = TRUE;

OnInvertTracker(m_rectTracker); if (m_bTracking2)

OnInvertTracker(m_rectTracker2); m_htTrack = ht; SetSplitCursor(ht); }

BOOL CxSplitterWnd::OnCommand(WPARAM wParam, LPARAM lParam) {

if (CWnd::OnCommand(wParam, lParam)) return TRUE;

//下面粗体的是原程序的语句

//return GetParentFrame()->SendMessage(WM_COMMAND, wParam, lParam);

return GetParent()->SendMessage(WM_COMMAND, wParam, lParam);

本文来源:https://www.bwwdw.com/article/b8bg.html

Top