Visual C++开发入行真功夫(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-13 10:46:31

点击下载

作者:三扬科技

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Visual C++开发入行真功夫

Visual C++开发入行真功夫试读:

前言

危机不足惧,我有“真功夫”

刚刚走过的一年,之所以不平凡,是因为席卷全球的经济危机不期而至。在IT行业,外企、国企“裁员不断”,原本就不容乐观的就业形式迎来真正的寒冬。值此考验全体就业者与从业者之际,你入行了吗?你晋升了吗?你跳槽了吗?你再就业了吗?

如此严峻的经济形势之下,面对国内声势浩荡的程序员大军,作为软件行业的老板,他们在考虑什么?企业到底需要什么样的软件开发人员?作为程序员,如何提高自身竞争力,在众人之中脱颖而出?那些在IT行业大门之前踯躅不前的入行者,如何真正踏进企业的大门?有过开发语言或工具的入门学习经历,再加上一腔热忱,这是大多数人的现状。很显然,这是远远不够的,企业需要的是真枪实弹的项目开发能力,需要广阔的知识背景及过硬的动手能力。“知识要深,功夫要真”,这正是本系列丛书的主旨。

知识有多深,功夫有多真?

面向入行读者“万事开头难”,很多编程爱好者正在为“入门”而不懈努力,开发类入门书籍也因此长盛不衰。然而,真正阻碍更多人迈入程序员大门的那道坎是“入行”。“入行”要求掌握可以直接参与实践工作或团队开发的实用技术。“入行真功夫”丛书完全从准从业者的切身需求出发,介绍先进理念,培养编码技术,锤炼软件架构与设计能力,使其从知其然不知其所以然的“门外汉”,快速成长为能纯熟运用所学完成任务的“业内人”。

针对实际问题,案例驱动讲解“入行真功夫”丛书围绕一个个精选案例展开讲解,按照“问题的提出(Why?)问题的解决(How?)→讲解与提高(What?)→常见使用场合与错误(Where?)”的流程进行阐述。先将整个案例拆解为多个功能模块,并通过不同技术实现该功能模块。当掌握了各功能模块的具体实现后,再将各模块还原为一个整体。而正是在这“合→分→合”的过程中,读者不仅学会了各个技术点,更掌握了真实项目开发的流程。

超值多功能DVD,全方位提升功力

学习者的需求来自方方面面,“入行真功夫”的光盘为此殚精竭虑。精心录制的多媒体教学视频,讲解细致,内容充实,可大幅度提高学习效率;精挑细选的“面试题库”,将各企业及各技术门类的面试、笔试题一网打尽,随用随查,实用便捷;囊括就业分析、面试指南、岗位须知、职业指导等各类内容的“求职指南”,不啻为入行者步入职场的知识宝库。

入门到入行,必看“真功夫”

技术背景

无论在工业生产还是日常生活中,C++编程语言都会被广泛应用。Microsoft Visual C++是微软公司推出的一个集成开发环境,它为用户提供了一个使用C++快速开发设计的框架体系。Visual C++将大量的Windows API封装后,以MFC的方式提供给用户,从而简化了开发人员的编程工作,大大提高了开发效率。同时对于初学者来说,借助于Visual C++开展学习进程,能够实现快速入门。

Visual C++ 2008与旧版本Visual C++6.0和Visual C++2005开发环境相比,增加了很多新的特性,能够使得C++应用开发更加简单快捷。很多编程爱好者渴望使用最新开发环境,学习并掌握Visual C++的开发流程,却苦于没有合适的参考书籍,为了满足读者的需求,我们精心编著了此书。希望通过此书能够帮助读者提高自身能力,快速跻身于Visual C++的开发行列。

阅读提示

本书每个章节严格按照“问题的提出(Why?)→问题的解决(How?)→讲解与提高(What?)”的流程进行阐述,让读者了解每个知识点的来龙去脉,而非简单的知识讲授。主要体例说明如下。

大师的话

由专家对该章技术点提出看法或进行点评,读者可从中领会核心思想与技术精髓。

入行目标

通过“入行目标”指明学习方向,读者可以有的放矢,明确学习重点与实践收获。

案例场景

提出具体需求,并给出当前的解决方案。通过分析当前解决方案的不足之处,读者可以真正领悟本章技术点的优势与必要性。

功夫要诀

在讲解相关技术点的同时,以“功夫要诀”的形式向读者传授从业者在实际开发过程中的宝贵经验及实用技巧。

入行提示

对于一些工作实践中容易出错或需要特殊强调的地方,通过“入行提示”的形式提醒读者注意,对其加深印象,牢固记忆。

内容导读

本书共分18章,逐步推进,整体结构如下图所示。本书整体结构思维拓扑图

各章的内容如下表所示。

视频资源

本书提供的主要视频资源结构如下图所示。

求职指南

Foreword

光盘说明

本书配套光盘主要包含目录结构如下所示。

售后服务

本书由三扬科技组织编写,参加编写的有范立锋、唐瑶、何飞、李达、钟呈祥、裴升、杨焕、于琦、程峰、吕正超、吴新伟、霍晶馨、王毅等。我们希望能够获知读者对本书的看法,包括读者对本书内容组织和讲解方式的看法,以及读者希望我们下一次完善的地方。编写过程中我们力求精益求精,虽然尽了最大的努力,但由于笔者能力有限,难免存在一些错误及不足之处,敬请读者批评指正。感谢您购买本书,希望本书能够成为您的良师益友。

售后服务网址:http://books.sunyang.net.cn

售后服务QQ群:71642151 59734652

售后服务信箱:sunyangsoft@126.com飞思科技产品研发中心

联 系 方 式

咨 询 电 话:(010)88254160 88254161-67

电 子 邮 件:support@fecit.com.cn

服 务 网 址:http://www.fecit.com.cn http://www.fecit.net

通 用 网 址:计算机图书、飞思、飞思教育、飞思科技、FECIT第1章 走进Visual C++ 2008

大师的话

Visual C++ 2008为.NET开发提供了强大的新语法支持。它使用的新优化技术已经使Microsoft产品的运行速度大大提高。它通过新的编译模式来确保Microsoft. NET框架通过语言基础结构(Common Language Infrastructure, CLI)的一致性和可验证性,并且具有新的interop模型,这不仅提供了本机和托管环境的无缝合并,而且还在跨这些边界的情况下提供了完全控制。

入行目标

熟悉Visual C++ 2008的开发环境

熟悉Visual C++ 2008的新增特性

掌握.NET框架编程

利用Visual C++ 2008生成控制台程序

利用Visual C++ 2008生成窗口程序1.1 案例场景

C++应用程序一般都是基于控制台的应用程序,控制台应用程序无论从界面美观还是程序性能都不能与Windows应用程序媲美。本案例将生成一个VC++的Windows应用程序,并输出“VC++祝你成功”。1.1.1 输出简单文字程序

本关任务

利用Visual C++ 2008的向导生成一个基于对话框的应用程序,在对话框的界面中显示“VC++祝你成功”,运行效果如图1-1所示。图1-1 对话框显示文字信息1.1.2 我们现在能做的……

利用C++的控制台,我们很难做到如图1-1所示的运行效果,下面使用VC++来创建一个基于控制台的应用程序,输出“VC++祝你成功”的信息。

01 启动Visual C++ 2008,并创建Win32工程。

02 在工程文件中添加输出“VC++祝你成功”信息的代码。其实现步骤如下。

01 依次选择“开始”·“程序”·“Microsoft Visual Studio 2008”·“Microsoft Visual Studio 2008”命令,启动VC++2008开发环境,运行结果如图1-2所示。图1-2 Microsoft Visual Studio 2008开发环境

02 在Microsoft Visual Studio 2008开发环境中选择“文件”·“新建”·“项目”命令,弹出“新建项目”对话框。在该对话框中,在“项目类型”选择列表中选择“Win3 2”;在“模板”选择列表中选择“Win32控制台应用程序”;在“名称”文本框中输入项目的名称,这里输入“sanyang”;在“位置”文本框中输入项目创建路径,这里输入“D:\VC Code”,如果用户想要修改项目创建路径,可以单击“浏览”按钮,选择项目创建的路径;再选择“创建解决方案的目录”复选框,并在“解决方案名称”文本框中输入该项目的解决方案名称,这里输入“sanyang”,与项目名称相同,如图1-3所示。图1-3 “新建项目”对话框

03 单击“确认”按钮,进入“Win32应用程序向导”对话框,该对话框显示创建当前项目的概述信息,运行结果如图1-4所示。图1-4 Win32应用程序向导

04 单击“完成”按钮,则完成Win32应用程序的创建操作,这时Microsoft Visual Studio 2008开发环境中的左侧“解决方案资源管理器”视图中显示刚刚创建的项目的各种信息,执行结果如图1-5所示。图1-5 创建项目后的Microsoft Visual Studio 2008开发环境

05 在“解决方案资源管理器”视图的“sanyang.cpp”文件中编写以下代码:

06 在Microsoft Visual Studio 2008开发环境中,单击工具栏中的按钮,运行本工程的实例,运行结果如图1-6所示。图1-6 在控制台的应用程序中输入信息

入行提示

可以看出,使用以前学到的知识,虽然可以输出文字,但是不能生成一个对话框的窗口,而是在控制台中输入指定的信息。那么如何生成一个窗口应用程序呢?如何在界面上输出文字呢?这个问题都有待于在学习本章内容的过程中逐步解决。1.2 Visual C++ 2008的集成开发环境

Visual Studio集成开发环境(IDE)提供了一组工具,它集成了创建、编译、连接和测试Windows应用程序的功能。开发环境提供这一组工具,完全是为了开发和调试更加方便,Visual C++ 2008包括了Visual C++ 2008编译器工具、Visual C++库及Visual C++开发环境,下面将分别进行介绍。1.2.1 Visual C++ 2008的组件工具

Microsoft Visual C++ 2008提供了强大而灵活的开发环境,可用于创建基于Microsoft Windows的和基于Microsoft.NET的应用程序。它可作为集成开发系统使用,也可以作为一组独立的工具使用。Visual C++由如图1-7所示的组件组成。图1-7 Visual C++ 2008的组件关系

下面将分别介绍这3种组件工具。

1.编译器工具

Visual C++ 2008编译器工具是由源代码编译成目标代码,在编译过程发现程序的错误。比如由于程序中存在不可识别的代码而引起的错误,或者由程序结构引起的错误。该编译器支持传统本机代码开发人员和面向虚拟机平台(如CLR,公共语言运行库)的开发人员。Visual C++ 2008包括面向x64和Itanium的编译器。该编译器仍支持直接面向x86计算机,并针对这两种平台优化了性能。

2.Visual C++库

Visual C++库包括行业标准活动模板库(ATL)、Microsoft基础类(MFC)库及各种标准库(如标准C++库),这些标准库由iostream库、标准模板库(STL)和C运行时库(CRT)组成。CRT包括已知引起安全问题的函数的安全增强替代项。STL/CLR库为托管代码开发人员引入了STL。具有数据封送新功能的C++支持库,其设计意图在于简化面向CLR的程序,这些库减少了程序员的工作量。

3.Visual C++开发环境

该开发环境为项目管理与配置(包括更好地支持大型项目)、源代码编辑、源代码浏览和调试工具提供强力支持。该环境还支持Intelli Sense,在编写代码时,该功能可以提供智能化且特定于上下文的建议。在VC++的编辑区,能够自动识别C++语言的关键字和Windows的关键字,有助于编程人员在编写程序时检查错误。其调试工具方法为断点调试、堆栈调试及跟踪调试等。

除常规的图形用户界面应用程序外,Visual C++还允许开发人员生成Web应用程序、基于Windows的智能客户端应用程序及适用于客户端和智能客户端移动设备的解决方案。C++是世界上最流行的系统级语言,而Visual C++则为开发人员提供了生成软件的世界级工具。1.2.2 认识Visual C++ 2008开发环境

安装完Visual C++ 2008后,在操作系统中的任务栏中依次选择“开始”·“程序”·“Microsoft Visual Studio 2008”·“Microsoft Visual Studio 2008”命令,启动VC++2008开发环境,运行结果如图1-8所示。图1-8 Visual C++ 2008开发环境

从图1-8可以看出,Visual C++ 2008开发环境窗口中包括标题栏、菜单栏、工具栏、工作区窗口、编辑窗口和输出窗口6个部分。

标题栏:窗口最上面的是标题栏,注明当前项目名称。如“sanyang-Microsoft Visual Studio”,sanyang是项目的名称。

菜单栏:标题栏下面是菜单栏,菜单栏包括了Visual C++ 2008的全部操作命令。默认的菜单栏相当于一个工具栏,因为它和工具栏一样可以拖曳到开发环境的任意位置。

工具栏:工具栏通常包括一些常用的操作命令。Visual C++ 2008提供了创建项目、打开项目等31个工具图标。通过用鼠标右键单击工具栏,可以弹出相关的工具栏快捷菜单,如图1-9所示。图1-9 Visual C++ 2008提供的工具栏

工具区窗口:工具栏的左下方是工作区窗口,包括4个视图。

解决方案资源管理器:该管理器提供项目及其文件的有组织的视图,并且提供对项目和文件相关命令的便捷访问。与此窗口关联的工具栏提供适用于列表中突出显示的项的常用命令。

类视图:用来显示当前项目中定义的C++类,展开后可查看各类的成员函数、成员变量,以及全局变量、函数和类型定义。

属性管理器:用来保存当前程序中使用到的资源,如对话框、菜单、图标等,展开可以对资源进行编辑。

资源管理器:用来显示所创建项目包含的文件,把项目中的文件分成两大类,展开Source File可以查看项目中所有的.cpp文件,而展开头文件可以查看项目中的所有.h文件。

编辑窗口:工具栏右下方是编辑窗口,用来显示当前编辑的C++程序文件及资源文件。

输出窗口:窗口的底部是输出窗口(Output),如果进入程序调试状态,主窗口还将弹出一些调试窗口。窗口的最下面是状态栏,显示当前操作或所选择命令的提示信息等。1.2.3 设置开发环境

在Visual C++ 2008开发环境中,存在两种方式设置选项:一种是可以应用于Visual C++ 2008工具选项,这些选项将应用到每个项目的环境中,另一种可以设置某个特殊的选项,例如,如何决定在编译和链接时处理项目代码的选项。

用户可以在菜单栏中依次选择“工具”→“选项”命令,弹出“选项”对话框,如图1-10所示。图1-10 “选项”对话框

这里仅存在少量的选项,例如,在“选项”对话框中选择“字体和颜色”,可以在右侧的属性中设置字体的格式和颜色等;在“选项”对话框中选择“项目和解决方案”,可以在右侧的属性中设置代码中的头文件与库的属性进行设置。其余的选项,读者可以自行查看设置。

入行提示

熟悉Visual C++ 2008的开发环境,对于程序的开发将起到事半功倍的效果,读者要熟练掌握通过Visual C++ 2008开发环境的配置,熟练进行代码的编写和调试。1.3 Visual C++ 2008的新增特性

Visual C++ 2008的集成开发环境为用户提供了一个快速编程的框架,大大提高了编程的效率。但是,要真正掌握Visual C++ 2008,还必须对C/C++语言有深入的了解,理解MFC库和Windows下的编程方法。在Windows下编程,通常要靠调用Windows API加以实现。Visual C++ 2008将大量的Windows API进行封装,通过MFC的方式提供给程序开发人员,程序开发人员通过MFC,可以方便地对程序进行各种操作,从而大大简化了程序开发人员的编程工作,提高程序开发人员的工作效率。

Visual C++ 2008与之前的版本相比增加了许多新特性,下面将分别进行介绍。1.3.1 Visual C++集成开发环境(IDE)

在ATL(活动模板库)、MFC和Win32应用程序中创建的对话框是目前最新的操作Windows Vista样式。使用Visual Studio 2008创建新项目时,插入应用程序中的所有对话框将符合Windows Vista样式指南。如果重新编译使用早期版本的Visual Studio创建的项目,所有现有对话框将保持以前的外观。

ATL项目向导现在具有一个用于为所有用户注册组件的选项。从Visual Studio 2008开始,ATL项目向导创建的COM组件和类型库在注册表的HKEY_CURRENT_USER节点中注册,除非选择了为所有用户注册组件。

写入注册表可以重定向。随着Windows Vista的引入,写入注册表的特定区域要求程序在提升模式下运行。但不需要始终在提升模式下运行Visual Studio。无须任何编程更改,用户重定向可以自动将注册表写入从HKEY_CLASSES_ROOT重定向到HKEY_CURRENT_USER。

类设计器现在为本机C++代码提供有限支持。在早期版本的Visual Studio中,类设计器仅用于Visual C#和Visual Basic。C++现在可以使用类设计器,但只能在只读模式下使用。1.3.2 Visual C++库

STL/CLR库由标准模板库(STL)和标准C++库的子集打包而成,与C++和.NET Framework公共语言运行库(CLR)一起使用。通过STL/CLR,现在可以在托管环境中使用所有的容器、迭代器和STL算法。

MFC库已经添加了18个新类或现有类中的150多个方法,用于支持Windows Vista操作中的功能,或改进当前MFC类中的功能。使用新的CNet Address Ctrl类可以输入和验证IPv4和IPv6地址或DNS名称。新的CPager Ctrl类简化了Windows中导航控件的使用。新的CSplit Button类简化了Windows Splitbutton控件的使用,便于我们选择默认或可选操作。

C++支持库引入了封送处理库,封送处理库为在本机和托管环境之间封送数据提供了一个易于使用的优化方法。封送处理库是其他更复杂、效率更低的方法(例如使用PInvoke)的替代方法。

ATL Server作为共享源项目发布,大多数ATL Server基本代码已作为共享源项目发布在Code Plex上,而不是作为Visual Studio 2008的一部分进行安装。在Atlenc.h中的数据编码和解码类,以及atlutil.h和atlpath.h中的实用工具函数和类现在已成为ATL库的一部分。Microsoft将继续支持早期版本的Visual Studio中包含的ATL Server版本,只要这些版本的Visual Studio是受支持的。Code Plex将继续以社区项目的形式开发ATL Server代码。Microsoft不支持Code Plex版本的ATL Server。1.3.3 Visual C++编译器和链接器

编译器支持托管增量生成。如果指定此选项,则编译器在引用的程序集更改时不会重新编译代码,它将执行增量生成。仅当更改影响依赖代码时才会重新编译文件。编译器支持Intel双内核微体系结构。编译器包含在代码生成过程中对Intel双内核微体系结构进行的优化。此优化对其他芯片组没有影响。内部函数支持新一代的AMD和Intel芯片组。多个新的内部指令帮助在新型AMD和Intel芯片组中提供更强大的功能。

链接器更改,用户账户控制信息现在由Visual C++链接器(link.exe)嵌入到可执行文件的清单文件中。链接器现在具有“/DYNAMICBASE”选项,用于启用Window s Vista的地址空间布局随机化功能。1.4 体验Visual C++ 2008之旅

在Visual C++ 2008开发环境中,可以根据用户的需求创建不同的工程,下面将介绍两种比较常用的项目创建:创建MFC单文档应用程序和创建CLR应用程序。1.4.1 创建MFC单文档应用程序

视频精讲:光盘\video\01\创建MFC单文档应用程序.swf

在Visual C++ 2008开发环境中创建Windows应用程序变得非常容易,下面就通过MFC创建一个本地的Windows应用程序,其具体实现步骤如下。

01 在Microsoft Visual Studio 2008开发环境中选择“文件”·“新建”·“项目”命令,弹出“新建项目”对话框。在该对话框中的“项目类型”选择列表中选择“MFC”;在“模板”选择列表中选择“MFC应用程序”;在“名称”文本框中输入项目的名称,这里输入“sanyang1”;在“位置”文本框中输入项目创建路径,这里输入“D:\VC Code”,如果用户想要修改项目创建路径,可以单击“浏览”按钮,选择项目创建的路径;选择“创建解决方案的目录”复选框,并在“解决方案名称”文本框中输入该项目的解决方案名称,这里输入“sanyang1”,与项目名称相同,如图1-11所示。图1-11 “新建项目”对话框

02 单击“确定”按钮,进入“MFC应用程序向导”对话框,该对话框显示创建当前项目的概述信息,运行结果如图1-12所示。图1-12 MFC应用程序向导

03 单击“下一步”按钮,进入“应用程序类型”对话框。在该对话框中,在应用程序类型选项中选择“单文档”单选按钮,其他选项默认即可,如图1-13所示。图1-13 应用程序类型

04 单击“下一步”按钮,进入“复合文档支持”对话框。在该对话框中,在“复合文档支持”选项中选择“无”单选按钮,如图1-14所示。

05 单击“下一步”按钮,将进入到“文档模板字符串”对话框。在该对话框中,在“文件扩展名”文本框中输入“sanyang1”,其他文本框中的内容将自动生成,如图1-15所示。图1-14 复合文档支持图1-15 文档模板字符串

06 单击“下一步”按钮,将进入“数据库支持”对话框。由于本实例并没有操作任何数据库,因此,在“数据库支持”选项中选择“无”单选按钮,如图1-16所示。图1-16 数据库支持

07 单击“下一步”按钮,进入“用户界面功能”对话框。在该对话框中,“主框架样式”选项设置对话框的各种图标,“工具栏”选项则设置当前工具栏是否标准,如图1-17所示。图1-17 用户界面功能

08 单击“下一步”按钮,进入“高级功能”对话框。在该对话框中,均采用默认选项即可,如图1-18所示。图1-18 高级功能

09 单击“下一步”按钮,进入“生成的类”对话框。在该对话框中,显示生成4个类的名称,其他文本框的内容将自动生成,如图1-19所示。图1-19 生成的类

10单击“完成”按钮,则完成创建MFC项目的创建。这时,在Visual C++ 2008开发环境的左侧将在“解决方案资源管理器”视图中显示系统自动生成的各种文件,如图1-20所示。图1-20 MFC项目在解决方案资源管理器中自动生成的信息

11单击工具栏中的按钮,运行本实例,运行结果如图1-21所示。图1-21 MFC项目创建后自动产生的对话框1.4.2 创建CLR应用程序

视频精讲:光盘\video\01\创建CLR应用程序.swf CLR(Common Language Runtime,公共语言运行库)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。

CLR应用程序是基于.NET框架应用程序,该应用程序使得.NET框架更加简单地创建网络应用程序和网络服务程序。基于CLR的代码可以使用任何语言编写,同时提供服务框架、数据访问、Win表单等服务功能。

1.CLR项目的结构

C++/CLI是C++程序员编写.NET框架的最好工具,Visual C++ 2008集成开发环境集成了CLR程序向导,可以创建的.NET框架项目类型如下。

类CLR:创建具有托管扩展支持的C++DLL。用来创建供其他.NET Framework应用程序使用的托管组件。在默认情况下,所创建的项目是一个DLL,它与CRT、ATL或MFC等本机库没有任何链接关系,也没有任何静态变量。

控制台应用程序:创建使用托管扩展的控制台应用程序。在默认情况下,所创建的项目是一个EXE,由于该项目与CRT库之间建立了链接,所以它在默认情况下会包含混合模式的代码。

空项目:创建具有支持托管扩展的适当编译器和链接器选项的空项目。用该项目向托管环境移植现有的C++源文件。在默认情况下,所创建的项目是一个EXE,由于该项目与CRT库之间建立了链接,所以它在默认情况下会包含混合模式的代码。

Windows窗体应用程序:使用托管扩展为带有Windows用户界面的应用程序创建一个项目。在默认情况下,所创建的项目是一个EXE,由于该项目与CRT库之间建立了链接,所以它在默认情况下会包含混合模式的代码。

Windows控件库:使用托管扩展为Windows控件库创建一个项目。在默认情况下,所创建的项目是一个DLL,它与CRT、ATL或MFC等本机库没有任何链接关系,也没有任何静态变量。

Windows服务:使用托管扩展创建一个Windows服务项目。在默认情况下,所创建的项目是一个EXE,由于该项目与CRT库之间建立了链接,所以它在默认情况下会包含混合模式的代码。

2.CLR应用程序的创建

在Visual C++ 2008开发环境中创建Windows应用程序变得非常容易,下面将介绍在该开发环境中创建CLR应用程序项目的具体实现步骤。

01 在Microsoft Visual Studio 2008开发环境中选择“文件”·“新建”·“项目”命令,弹出“新建项目”对话框。在该对话框中的“项目类型”选择列表中选择“CLR”;在“模板”选择列表中选择“Windows窗体应用程序”;在“名称”文本框中输入项目的名称,这里输入“sanyang2”;在“位置”文本框中输入项目创建路径,这里输入“D:\VC Code”,如果用户想要修改项目创建路径,可以单击“浏览”按钮,选择项目创建的路径;选择“创建解决方案的目录”复选框,并在“解决方案名称”文本框中输入该项目的解决方案名称,这里输入“sanyang2”,与项目名称相同,如图1-22所示。图1-22 “新建项目”对话框

02 单击“确定”按钮,“Windows窗体应用程序”创建完成。这时的Microsoft Visual Studio 2008开发环境如图1-23所示。图1-23 Windows窗体应用程序环境设置

在头文件的Form1.h节点中,单击鼠标右键,在弹出的快捷菜单中选择“查看代码”命令,则在03右侧对话框中显示该文件自动生成的代码,具体代码如下:

04 在Microsoft Visual Studio 2008开发环境中,单击工具栏中的按钮,运行本工程的实例,运行结果如图1-24所示。图1-24 CRL项目创建后自动产生的对话框1.5 回到案例场景

视频精讲:光盘\video\01\本关任务.swf

利用Visual C++ 2008的向导设置一个窗体的应用程序,在对话框的界面中显示“VC++祝你成功”,运行效果如图1-25所示。

本关任务图1-25 本关任务1.5.1 基本思路

通过本章的学习,现在我们可以采用如下流程来实现这种在窗体中显示文字的制作,具体实现过程如下。

01 创建单文档的MFC项目。

02 创建一个简单的窗体,并编写窗体中的文字。

03 在头文件中声明窗体的对象与窗体头文件的包含操作。

04 在资源文件中编写显示窗体的代码。

其具体实现步骤如下。

01 在Visual C++ 2008开发环境中,依次选择工具栏中的“文件”·“新建”·“项目”命令,弹出“新建项目”对话框。在该对话框的在“项目类型”选择列表中选择“MFC”,在“模板”选择列表中选择“MFC应用程序”,在下面的“名称”文本框中输入当前创建项目的名称,在“位置”文本框中输入当前项目的路径,其他选项采用默认设置即可,如图1-26所示。图1-26 “新建项目”对话框

02 单击“确定”按钮,进入到“欢迎使用MFC应用程序向导”对话框,如图1-27所示。图1-27 欢迎使用MFC应用程序向导

03 单击“下一步”按钮,进入“应用程序类型”对话框。在该对话框中,在“应用程序类型”选项中选择“单文档”单选按钮,其他选项选择默认即可,如图1-28所示。图1-28 “应用程序类型”对话框

04 单击“完成”按钮,完成MFC应用程序的创建。这时,在“解决方案资源管理器”视图中显示系统自动生成的各种代码文件,选择“类视图”,用鼠标右键单击“sanyang3”节点,在弹出的对话框中选择“添加”→“资源”命令,如图1-29所示。图1-29 在类视图中新建资源

05 弹出“添加资源”对话框,在“资源类型”列表中选择“Dialog”,如图1-30所示。图1-30 “添加资源”对话框

06 单击“新建”按钮,在当前开发环境的右侧中显示“Dialog”资源,如图1-31所示。图1-31 “Dialog”资源

07 在Dialog资源任意区中单击鼠标右键,在弹出的快捷菜单中选择“添加类”命令,在弹出的对话框中的“类名”文本框输入新建类的名称,这里输入的类名是“Csanyang”,其他文本框内容采用默认值即可,如图1-32所示。图1-32 “欢迎使用MFC类向导”对话框

08 单击“完成”按钮,完成添加类的操作。这时,在右侧显示自动生成的对话框,将鼠标放入“CDialog”位置后,在属性对话框中单击图标,在下拉列表中选择“WM_PAINT”消息并进行添加,如图1-33所示。图1-33 添加“WM_PAINT”消息

09 在WM_PAINT消息对话框中添加“On Paint()”方法,这时,在编写代码区域中显示系统自动生成的On Paint()方法,如图1-34所示。图1-34 On Paint方法区域

10返回到“解决方案资源管理器”视图中,打开在头文件节点中的“sanyang3.h”,在该文件中编写包含“sanyang.h”文件与声明一个“Csanyang”类的指针对象,如图1-35所示。图1-35 在sanyang3.h文件中编写代码

11打开在源文件节点中的“sanyang3.cpp”文件,并查询构造函数“BOOL Csanyang3App:Init Instance()”,在该函数的最后编写显示当前窗口的代码,如图1-36所示。图1-36 在sanyang3.cpp文件中编写代码1.5.2 代码演练

根据如上所述的具体制作步骤,现给出部分关键代码如下。(1)在On Paint()中设置在对话框显示的文字与文字的位置,具体代码如下:(2)在sanyang3.h文件中设置包含sanyang.h和Csanyang指针对象,具体代码如下:(3)在sanyang3.cpp文件中构造函数最后编写显示对话框的代码,具体代码如下:

在Visual C++ 2008开发环境下,单击按钮,可以得到本关任务的运行结果。1.6 本章小结与习题

本章主要介绍了如何在Visual C++ 2008开发环境下创建Win32应用程序、MFC单文档应用程序、CLR应用程序,并且熟悉了Visual C++ 2008开发环境各种设置,现在我们将对这些内容中的关键点进行回顾和巩固。1.6.1 重点回顾

Visual C++ 2008解决方案和项目由数目众多的各种不同文件和类构成,这些类和文件都是自动创建的。

解决方案资源管理器视图使用非常频繁,该视图通过视图菜单激活,显示与当前解决方案和项目相关的所有文件。这些文件按源文件、头文件和资源文件分类显示,要打开一个具体的文件,双击该文件即可。

类视图是Visual C++ 2008开发实现中的另一个常用视图,利用该视图可以非常迅速而简单地找到类及其成员函数,并执行这些类的各种功能。

在默认情况下,当创建基于对话框的应用程序时,Visual C++ 2008将打开对话框模板资源、各种资源视图和资源属性,因此可以首先修改默认对话框。

如何利用Visual C++ 2008开发环境创建MFC单文档应用程序和创建CLR应用程序是经常用到的,需要读者完全掌握。1.6.2 课后练习

1.Visual C++ 2008的组件有哪些,并说明它们之间的关系。

2.Visual C++ 2008与之前的版本相比新增了哪些特性?

3.Visual C++ 2008开发环境主要包含哪些部分,并逐一列出。

4.创建Win32应用程序,并在控制台中输入“你好,三杨科技”。

5.创建MFC应用程序,并弹出一个带“你好,三扬科技”字样的对话框。第2章 C++基础语法

大师的话

C++的目标是为程序开发提供一个优良的设计工具,以编写模块化程度高、可重用性和可维护性很好的程序。

入行目标

熟悉C++基础语法

了解面向对象方法思想

理解C++面向对象方法特征

学习使用C++语言的特性2.1 案例场景

C++是一个内容庞大、方法复杂的编程语言。与传统的面向过程的程序语言相比,C++语言的最大特征是支持面向对象程序设计,它引入了类、继承、多态和重载等面向对象的新机制。本章将介绍C++语言的语法要素和C++程序的基本结构,以及面向对象编程的基本概念和方法。2.1.1 员工工资单计算程序

本关任务

完成一个工资单计算程序。该程序用于实现以下几种工种工资的计算:老板,不管工作多长时间总是有固定的周薪;销售员,工资为一小部分基本工资加上销售额的一定的百分比;计件工,收入取决他生产的工件数量;小时工,收入以小时计算,再加上加班费。作为工资单,不但需要计算出工资数,还应该将员工的姓名和工种等信息显示出来。2.1.2 我们现在能做的……

我们可以使用C语言实现这个工资单计算程序。此时,需要为每个工种编写计算工资的函数。在输出时,为每个员工编写输出函数,然后调用计算工资函数计算出工资并输出。使用C语言编写的程序可读性差,将员工姓名、工种和工资3个互相联系的实体分开处理,代码凌乱;灵活性差,在员工改变工种的情况下,需要分别修改输出姓名、工种和调用的计算工资函数。这些问题在基于面向对象思想的C++语言编写时,将迎刃而解。2.2 C++的基本语法

视频精讲:光盘\video\02\C++的基本语法.swf

在这一小节里,将介绍数据类型与变量、常量的关系,各种常量和变量的性质和定义,还介绍了C++的控制流程、函数及引用与指针的区别等知识点。2.2.1 C++基本数据类型

基本数据类型是C++预定义的数据类型,C++的基本数据类型包括字符型、整数型、实型(单精度和双精度)和无值型。每种基本数据类型都使用一个关键字来表示。接下来将介绍数组、指针和类等派生数据类型,是按照C++的语法要求在基本数据类型基础之上建立起来的,表2-1列出了C++的基本数据类型。

有时需要将某个数据当做另外的数据类型来处理,这时可以使用运算符“()”进行强制类型转换,如下所示:2.2.2 标识符

标识符是由若干个字符组成的字符序列,用来命名程序中的一些实体,例如常量名、变量名、函数名、类名、结构名、联合名、对象名、类型名和标号名等。在程序中用户是通过标识符来定义和引用这些对象的,C++语言中构成标识符的语法规则如下。

标识符由字母(a~z, A~Z)、数字(0~9)或下画线(_)组成。

第一个字符必须是字母或下画线。例如,example1、Birthday、My_Message、Mychar、Myfriend、thistime是合法的标识符;8key、b-milk、-home是非法的标识符。

在VC++中标识符最多由247个字符组成。

在C++标识符对大小写字母是敏感的,即大小写字母被认为是两个不同的标识符。例如,book和Book被认为是两个不同的标识符。

关键字不能作为新的标识符在程序中使用,但标识符中可以包含关键字。例如,intx、myclass是合法的标识符。

入行提示

为标识符取名时,应该尽量使用能够反映其用途的单词或缩写,这样可提高程序的可读性。2.2.3 常量和变量

1.常量

常量是指在程序运行的整个过程中始终保持不变的量,在表达式中常量是可以明确表示出的值。常量不同于变量,主要表现在常量不在内存中占有编译空间,而且常量的值不能被修改。

在C++程序中,按照数据类型常量可以分为:整型常量、字符常量、浮点常量和字符串常量。

1)整型常量

整型常量即整数,包括长整型(long)、短整型(short)、有符号型(int)和无符号型(unsigned int)。在C++中整数有3种表示形式:十进制、八进制和十六进制。

C++语言允许在整数后面加上一些字符作为后缀修饰数据类型,用做后缀的字符有:u或U字符,表示无符号整数;l或L字符,表示长整数;l或L与u或U的组合,表示无符号长整数。

十进制整数:以非0开头,后跟0~9范围内的数,例如:

八进制整数:以0开头,后跟0~7范围内的数,例如:

十六进制整数:以0X或0x开头,后跟0~9范围内的数及A~F或a~f范围内的字母,例如:

2)字符型常量

字符型常量包括普通字符常量和转义字符常量。

普通字符常量:由一对单引号括起来的单个字符,例如:

这里需要注意的是,'a'和'A'表示的是两个不同的常量。

转义字符常量:是一种特殊表示形式的字符常量,是以“\”开头,后跟一些字符组成的字符序列,表示一些特殊的含义。表2-2列出了常用的转义字符及其描述。

表2-1中的转义字符分为两类:一类是控制字符,另一类是字符符号。

'\n'是控制字符,起换行的作用。当屏幕输出函数在输出数据中遇到'\n'时,将光标移到下一行行首。

\'是字符符号,当屏幕输出函数在输出数据中遇到'\''时,输出一个单引号。

\ooo表示八进制转义序列,反斜杠后面可以是一个、两个或三个八进制字符序列,例如:'\123'。

\xhh表示十六进制转义序列,反斜杠后面可以是一个或两个十六进制字符序列,例如:'\x12'、'\x1A'。

3)浮点型常量

浮点型常量即实数,由整数和小数两部分组成。在C++程序中,浮点型常量包括单精度(float)数、双精度(double)数、长双精度(long double)数3种。

浮点型常量有两种表示方式:定点数表示法和指数表示法。

定点数表示法:由整数和小数两部分组成,中间用十进制的小数点隔开。字符f或F作为后缀表示单精度数,例如:

指数表示法(科学记数法):由尾数和指数两部分组成,中间用E或e隔开,例如:

指数表示法必须有尾数和指数两部分,并且指数只能是整数。

4)字符串常量

字符串常量简称字符串,是由一对双引号括起来的零个或多个字符序列,例如:

在字符串中可以包含空格符、转义字符或其他字符。字符串常量不同于字符常量,两者是有区别的,主要表现在以下几个方面。

字符常量的标识符是单引号,字符串常量的标识符是双引号。

存储方式不同,例如有以下几个字符或字符串:

字符串常量"m"占两个字节,一个字节存放字符m,另一个字节存放字符串结束标志\0;而字符常量'm'仅占一个字节,用来存放字符m。在每个字符串的尾部系统会自动加上字符串结束标志"\0",而字符型常量却不加"\0"。

字符串常量和字符常量所能进行的运算是不同的,例如:

2.变量

变量是存储数据值的空间。数值的类型有多种,对应于整数、小数(浮点数)、字符,分别有整型变量、浮点型变量、字符型变量。当然除了这几种类型外,变量还有其他具体的分类。整型变量还可具体分为无符号型、长整型和短整型。浮点型也可分为单精度型、双精度型和长双精度型。此外还可以分为静态变量、外部变量、寄存器变量和自动存储变量。下面分别对各种变量类型进行介绍。

1)整型变量

整型变量是用来存储整数的。整型变量又可具体分为多种,最基本的整型变量是用类型说明符int声明的符号整型,语法格式如下:

这里int是类型说明符,Counter是变量的名字。整型变量可以是有符号型、无符号型、长型、短型或像上面定义的普通符号整型。整型是16位的,长整型是32位的,短整型等价于整型,以下是几种整型变量的声明示例:

从上面的示例可以看出,当定义长整型、短整型、符号整型或无符号整型时,可以省略关键字int。

2)浮点类型变量

顾名思义,浮点类型变量是用来存储带有小数的实数。浮点型都是有符号的。在C++中有3种不同的浮点类型,以下是对这3种不同类型的声明示例:

3)字符型变量

字符型变量中所存放的字符是计算机字符集中的字符。对于PC上运行的C++系统,字符型数据用8位单字节的ASCII码表示。程序用类型说明符char来声明字符型变量,语法格式为:

charch;

这条声明语句声明了一个字符型变量,标识符为ch。当以这种形式声明变量之后,程序可以在表达式中引用这个变量,关于语句和表达式的知识在后面将会介绍。

字符数据类型事实上是8位的整型数据类型,可以在数值表达式中,与其他的整型数据同样使用。当应用字符数据类型时,字符型变量可以是有符号的,也可以是无符号的。对于无符号的字符型变量可以声明为:

除非声明为无符号型,否则在算术运算和比较运算中,字符型变量一般作为8位有符号整型变量处理。

3.const常量类型

不同于一般的常量和变量,C++还提供了常量类型const,它用来声明一个值不能被改变的变量。const型变量所代表的常值只在其作用域内有效,声明一个const型变量只需在数据类型说明前加上关键字const即可,例如:

用const声明的变量必须用常量或常量表达式初始化。一旦变量被声明为const型编译器将禁止任何试图修改该变量的操作。

const还常用来限定函数的参数和返回值。函数的参数如果使用const声明,说明在函数中不能修改该参数。如果函数返回一个指针或引用,则使用const声明返回值表示调用函数时不能用返回值来改变返回值所指或所引用的变量。在下面的例子中Func A()的返回值使用了const声明,因此调用Func A()函数时,不能通过函数返回值来改变它所指变量x的值。

const关键字还可以用来限定成员函数的修改操作,如下面声明的成员函数Member Fun()就不能修改其类的成员变量:2.2.4 指针和引用

在C/C++中的指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。指针初始化的时候,可以指向一个地址,也可以为空。指针变量声明的一般形式为:

符号“*”是指针类型说明符,声明变量是一个指针型变量。

引用是C++语言在C语言一般变量和指针变量基础上引进的概念,引用就是为一个已声明的变量起一个别名,它在逻辑上不是独立的,它的存在具有依附性。所以,引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。引用最大的用处是作为函数的参数,它可以在被调函数中通过改变形参来改变主调函数中的实参。声明一个引用时需要在其名称前加符号“&”。声明一个引用一般采用如下格式:

指针和引用的实例如下:

入行提示

采用引用函数传递方式,只需在函数定义时使用引用作为形参,而在函数调用时直接使用一般变量作为实参。2.2.5 数组与字符串

1.数组

数组属于构造类型,它是一组具有相同类型数据的有序集合,其中每个数据称为数组的元素。数组按其下标的个数分为一维数组、二维数组和多维数组。

一维数组的声明方式如下:

二维数组的声明方式如下:

对于声明的数组程序,运行后系统将自动为数组分配一块连续的存储空间,数组名表示这块存储空间的起始位置。数组名后面的括号中的常量表达式表示数组的长度,即数组所包含元素的数量,以下是一些声明数组的语句:

数组元素的下标从0开始直到数组长度减1,例如上例中的score数组是score[0]、score[1]……、score[29]。

声明数组时可以用一个花括号括起的常量列表对数组进行初始化,列表中的每一项对应一个元素的初始值,初始值的数据类型应与数组的数据类型一致,例如:

二维数组和多维数组的初始化与一维数组类似,可以按照数组的排列顺序对各元素赋初值,但为了更直观,常采用分行赋初值的方法。以下是分别采用这两种赋值方法的例子:

其中2、4、6分别为a[0]的元素a[0][0]、a[0][1]和a[0][2]的初始值。初始值的个数可以比数组元素的个数少,这时未提供初始值的元素被置为0,如:

在利用数组编程时,一般需要使用循环语句,循环语句的使用方法参看2.2.6小节。下面的例子中使用for循环语句和输入/输出流语句,利用数组实现输入10个数并按输入次序相反的顺序输出这10个数。

2.字符串

字符串是用一对双引号引起来的字符序列。C++语言没有提供字符串类型,字符串变量是作为一维字符数组来处理的。为了方便运算,字符串末尾必须加上一个字符串结束符“\0”,但“\0”不是字符串的有效字符,求字符串长度的时候,不能将它计算在内。

对于字符串常量,C++编译程序自动在字符串的末尾加上字符“\0”,因此可以直接用一个字符串常量来初始化一个字符数组。一个简单的字符串常量的初始化例子如下:

经过上述初始化后,字符数据中每个元素的初始值如下s[0]='H'、s[1]='e'、s[2]='l'、s[3]='l'、s[4]='o'、s[5]='\0'。该字符数组长度为6,但字符串长度为5。

由于双引号用做字符串的界限符,所以在字符串中必须以转义字符“\"”表示双引号。例如:Please enter\"good\",编译器将这个字符串解释为:Please enter"good"。

既然字符串本质上是一个字符数组,而我们又可以通过指针引用数组,所以可以通过指针来使用字符串。上面字符串初始化语句可改写为:

除了通过使用字符数组和字符指针表示字符串外,Visual C++ 2008的编译器还提供了标准C++的字符串类std:string。为了使用这个类,必须包括头文件“string.h”。微软基础类MFC也提供了一个用于Windows编程的字符串类CString,在后续章节中会看到它的用法。利用字符串类可以直接进行字符串的赋值、连接、比较和输出等操作。2.2.6 C++的流程控制

C++程序的语句按其功能可以划分为两类:操作运算语句和流程控制语句。而基本的控制结构有3种:顺序结构、选择结构和循环结构。下面分别介绍这3种控制结构。

1.顺序控制语句

顾名思义,所谓顺序结构,就是指按照语句在程序中的先后次序一条一条地顺次执行。顺序控制语句是一类简单的语句,上述的操作运算语句即是顺序控制语句,包括表达式语句、输入/输出等。

1)表达式语句

一个表达式由常量、变量、函数调用和运算符组成,每个表达式都将产生一个值,并且具有某种类型,称为该表达式的类型。

运算符是表示各种不同运算的符号,参与运算的数据称为操作数。运算符实际上是系统预定义的函数名,而执行运算操作就是调用相应的函数。按运算符和操作数的性质,运算符可分为算术运算符、逻辑运算符、关系运算符和其他运算符,表2-3列出了C++所有运算符。

在对一个表达式求值时,优先级高的运算符先运算,优先级低的运算符后运算。表2-1左栏的数字表示运算符的优先级,数字越小优先级越高。表2-1右栏表示运算符的结合性,如果一个运算符对其操作数从左向右进行指定的运算,称这个运算符是右结合性,反之称为左结合性。当一个表达式出现多种运算时,运算符的优先级和结合性决定了该表达式的运算顺序。

2)输入语句

在C++程序中没有提供输入/输出语句,输入/输出功能是通过函数(scanf、printf)或流的控制而实现的,其中输入/输出流(I/O流)是输入或输出的一系列字节。C++定义了运算符“<<”和“>>”的iostream类,这里只介绍如何利用C++的标准输入/输出流实现数据的输入/输出功能。

当程序需要执行键盘输入时,可以使用抽取操作符“>>”从输入流cin中抽取键盘输入的字符和数字,并把它赋给指定的变量,例如:

3)输出语句

当程序需要在屏幕上显示输出时,可以使用插入操作符“<<”向输出流cout中插入字符和数字,并把它在屏幕上显示输出,例如:

与输入一样,这里的插入操作符“<<”与位移运算符“<<”是同样的符号,但这种符号在不同的地方其含义是不一样的。

在C++程序中,cin与cout允许将任何基本数据类型的名字或值传给流。而且书写格式较灵活,可以在同一行中串连书写,也可以分写在几行,提高可读性。

2.选择控制语句

选择语句又称为分支语句,它通过对给定条件进行判断,从而决定执行哪个分支。因此,在编写选择语句之前,应该首先明确判断条件是什么,并确定当判断结果为“真”或“假”时应分别执行什么样的操作。C++程序中提供的选择语句有两种:“if……else”语句和“switch”语句。

1)if……else语句

if语句的语法格式为:

当该语句被执行时会对条件表达式进行判断,如果为真则执行后面的语句。当语句序列只包含一条语句时,包围该语句序列的花括号可以省略。例如:判断用户的输入,如果输入的数值大于0,则在屏幕上显示“正数”,具体实现代码如下。

编译器必须在if条件表达式的后面找到一个作为语句结束符的分号“”,以标志if语句的结束。

if……else语句的语法格式为:

当该语句被执行时会对条件表达式进行判断,如果为真,则执行语句序列1;否则,执行语句序列2。例如:判断用户的输入,如果输入的数值大于0,则在屏幕上显示“正数”;否则在屏幕上显示“不是正数”,具体实现代码如下。

当多个if……else语句嵌套时,为了防止出现二义性,C++语言规定,由后向前必须保证每一个else都与其前面最靠近的if进行配对。如果一个else的上面又有一个未经配对的else,则先处理上面的(内层的)else的配对。例如,判断a、b、c 3个数中的最大值的代码如下。

当多个if……else语句嵌套时,在容易误解的地方可以按照语法关系加上花括号来标识逻辑关系的正确性,如上例可以改写为:

2)switch语句

switch语句是多分支的选择语句。嵌套的if语句可以处理多分支选择,但是用switch语句更加直观。switch语句的语法格式为:

switch语句的执行顺序是:首先对“整数表达式”进行计算,得到一个整型常量结果,然后从上到下寻找与此结果相匹配的常量表达式所在的case语句,以此作为入口开始顺序执行入口处后面的各语句,直到遇到break语句,才结束switch语句,转而执行switch结构后的其他语句。如果没有找到与此结果相匹配的常量表达式,则从“default:”标识处开始执行语句序列n+1。例如,根据考试成绩的等级输出百分制分数段的代码如下。

default语句是默认的,switch后面括号中的表达式只能是整型、字符型或枚举型表达式。在各个分支中的break语句起着退出switch语句的作用,而case语句起标号的作用,并且标号不能重名。在switch语句中的case语句可以共用一组语句序列,各个case(包括default)语句的出现次序可以任意。每个case语句中不必使用花括号,但是整体的switch结构一定要写一对花括号。同时,switch结构也可以嵌套使用。

3.循环控制语句

C++提供了3种循环控制语句:while语句、do……while语句及for语句。3种语句结构相似,都包含了进入循环的条件、开始循环体、退出循环条件3个部分。同时这3种语句完成的功能也类似,区别仅在于进入和退出循环的方式不同。

1)while语句

当条件满足时进入,重复执行循环体,直到条件不满足时退出。while语句的语法格式为:

该语句被执行时首先对条件表达式进行判断,若判断结果为假(false或0),则跳过循环体,执行while结构后面的语句;若判断结果为真(true或非0),则进入循环体,执行其中的语句序列。执行完一次循环体语句后,修改循环变量,再对条件表达式进行判断。若判断结果为真,则继续执行一次循环体语句。重复执行此流程直到判断结果为假时,退出while循环语句,转而执行后面的语句,即“先判断后执行”。

while循环由4个部分组成:循环变量初始化、判断条件、循环体、改变循环变量的值。例如:计算sum=1+2+3+……+10的while循环结构如下:

如果循环体包含一个以上的语句,则应该用花括号括起来,以块语句形式出现。在程序中应仔细定义循环变量的初始值和判断条件的边界值,否则语句执行顺序可能会出现问题。另外在循环体中,改变循环变量的值很重要。如果循环变量的值恒定不变,或者当条件表达式为一常数时,将会导致无限循环(也即死循环)。若要退出一个无限循环,必须在循环体内用break等语句退出。

2)do……while语句

无条件进入,执行一次循环体后判断是否满足条件,当条件满足时重复执行循环体,直到条件不满足时退出。do……while语句的语法格式为:

该语句被执行时,一旦流程到达do便立即开始执行循环体语句,然后再对条件表达式进行判断。若条件表达式的值为真(非0),则重复执行循环体语句;否则退出,即“先执行后判断”。计算sum=1+2+3+……+10的do……while循环结构如下:

虽然do……while语句与while语句实现功能相似,但是二者之间还是存在差别的。while语句的循环体有可能一次都不被执行,而do……while循环必须至少执行一次。

do……while结构与while结构中都具有一个while语句,很容易混淆。为明显区分它们,do……while循环体即使是一个单语句,习惯上也使用花括号括起来,并且while(表达式)直接写在花括号“}”的后面。这样的书写格式可以与while结构清楚地区分开,例如:

3)for语句

当循环变量在指定范围内变化时,重复执行循环体,直到循环变量超出了指定的范围时退出。for语句的语法格式为:

其中,表达式1可以称为初始化表达式,一般用于对循环变量进行初始化或赋初值;表达式2可以称为条件表达式,当它的判断条件为真时,就执行循环体语句,否则终止循环,退出for结构;表达式3可以称为修正表达式,一般用于在每次循环体执行之后,对循环变量进行修改操作;循环体是当表达式2为真时执行的一组语句序列。例如,计算sum=1+2+3+……+10的for循环结构如下:

for语句将循环体所用的控制都放在循环顶部统一表示,显得更直观。除此之外,for语句还充分表现了其灵活性。比如,表达式3并不仅限于修正循环变量的值,还可以是任何操作。例如:for(sum=0,i=1;i<=10;sum+=i,i++)。注意,此时for语句没有循环体,也即循环体是一个空语句。

有时,表达式2被省略。即不判断循环条件,循环无终止进行下去。这时候,需要在循环体中有跳出循环的控制语句。

4)多重循环

循环嵌套是指循环语句的循环体内又包含另一个循环语句,即循环套循环。例如:九九乘法表的代码如下:

4.跳转语句

在C++中,除了提供顺序执行和选择控制、循环控制语句外,还提供了一类跳转语句。这类语句的总体功能是中断当前某段程序的执行,并跳转到程序的其他位置继续执行。常见的跳转语句有3种:break语句、continue语句与goto语句。其中,前两种语句不允许用户自己指定跳转到哪里,而是必须按照相应的原则跳转,而后一种语句可以由用户事先指定想要跳转到位置并按照用户的需要进行跳转。

1)break语句

该语句用于结束当前正在执行的循环(for、while、do……while)或多路分支(switch)程序结构,转而执行这些结构后面的语句。在switch语句中,break用来使流程跳出switch语句,继续执行switch后的语句。在循环语句中,break用来从最近的封闭循环体内跳出。例如,下面的代码在执行了break之后,继续执行“a+=1;”处的语句,而不是跳出所有的循环:

2)continue语句

该语句用于结束当前正在执行的这一次循环(for、while、do……while),接着执行下一次循环,即跳过循环体中尚未执行的语句,接着进行下一次是否执行循环的判定。在for循环中,continue用来转去执行表达式2。在while循环和do……while循环中,continue用来转去执行对条件表达式的判断。

continue语句和break语句的区别在于,continue语句只结束本次循环,而不是终止整个循环的执行。而break语句则是结束本次循环,不再进行条件判断。例如:输出1~100之间的不能被7整除的数的代码如下:

其中,当i被7整除时,执行continue语句,结束本次循环,即跳过cout语句,转去判断i<=100是否成立。只有i不能被7整除时,才执行cout函数,输出i。2.2.7 C++的函数

在C++程序中调用函数之前,首先要对函数进行定义。如果调用此函数在前,函数定义在后,就会产生编译错误。为了使函数的调用不受函数定义位置的影响,可以在调用函数前进行函数的声明。这样,不管函数是在哪里定义的,只要在调用前进行函数的声明,就可以保证函数调用的合法性。函数定义的一般形式如下:

上面语法格式中各部分说明如下。

函数名:一个符合C++语法要求的标识符,定义函数名与定义变量名的规则是一样的,但应尽量避免用下画线开头,因为编译器常常定义一些下画线开头的变量或函数。函数名应尽可能反映函数的功能,它常常由几个单词组成。如在VC中的按下鼠标左键的响应函数为On LButton Down,这样就较好地反映了函数的功能。

参数表列:由0个或多个变量构成,用于向函数传送数值或从函数带回数值,每一个参数都有自己的类型。

返回类型:指定函数用return返回的函数值的类型,如果函数没有返回值,返回类型应为void。

函数体:在花括号中的语句称为函数体,一个函数的功能通过函数体中的语句来完成。

例如:函数dec求两个数的差值的代码如下:

1.函数的定义

在C++中的每一个函数都是从4个方面来进行定义的:类型、函数名、形式参数表、函数体。定义一个函数的语法格式为:

类型:该函数的返回值的类型,此类型可以是C++中除函数、数组类型之外的任何一个合法的数据类型,包括普通类型、指针类型和引用类型等。函数的返回值通常指明了该函数处理的结果,由函数体中的return语句给出。一个函数可以有返回值,也可以无返回值(称为无返回值函数或无类型函数)。此时需要使用保留字void作为类型名,而且函数体中也不需要再写return语句,或者return的后面什么也没有,每个函数都有类型,如果在函数定义时没有明确指定类型,则默认类型为int。

函数名:一个有效的C++标识符,遵循一般的命名规则。在函数名后面必须跟一对圆括号“()”,用来将函数名与变量名或其他用户自定义的标识符区分开。在圆括号中可以没有任何信息,也可以包含形式参数表。C++程序通过使用这个函数名和实参表可以调用该函数。主函数的名称规定取编译器默认的名称main()。

形式参数表:又称参数表,写在函数名后面的一对圆括号内。它可包含任意多个(含0个,即没有)参数说明项,当参数个数多于一个时,参数说明项之间必须用逗号分开。每个参数说明项由一种已定义的数据类型和一个变量标识符组成,该变量标识符称为该函数的形式参数,形参前面给出的数据类型称为该形参的类型。每个形参的类型可以为任一种数据类型,包括普通类型、指针类型、数组类型、引用类型等。一个函数定义中的<参数表>可以被省略,表明该函数为无参函数,若<参数表>用void取代,则也表明是无参函数,若<参数表>不为空,同时又不是保留字void,则称为带参函数。

函数体:是一条复合语句,它以左花括号开始,到右花括号结束,中间为一条或若干条C++语句,用于实现函数执行的功能。

2.函数的调用

在C++中,除了主函数main由系统自动调用外,其他函数都是由主函数直接或间接调用的。函数调用的语法格式为:

实参应该与函数定义中的形参表中的形参一一对应,即个数相等、次序一致且对应参数的数据类型相同或相容。每个实参是一个表达式,并且必须有确定的值。几种函数的调用形式如下:

将函数调用作为一条表达式语句使用,只要求函数完成一定的操作,而不使用其返回值。若函数调用带有返回值,则这个值将会自动丢失,例如:

对于具有返回值的函数来说,把函数调用语句看做语句一部分,使用函数的返回值参与相应的运算或执行相应的操作,例如:2.3 C++面向对象的特征

视频精讲:光盘\video\02\C++面向对象的特征.swf

C++是一种面向对象的编程语言,包含了面向对象的3个基本特征:封装、继承和多态。2.3.1 封装

封装是面向对象的特征之一,是对象和类概念的主要特性。封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机结合,形成“类”,其中数据和函数都是类的成员。

封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,即特定的访问权限来使用类的成员。

例如,在抽象的基础上,我们可以将时钟的数据和功能封装起来,构成一个时钟类。时钟类的声明如下:

可以看到通过封装使一部分成员充当类与外部的接口,而将其他的成员隐蔽起来,这样就达到了对成员访问权限的合理控制,使不同类之间的相互影响减少到最低限度,进而增强数据的安全性和简化程序的编写工作。2.3.2 多态

多态(Polymorphism)按字面的意思就是“多种形状”。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为和一个或更多的它的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说,允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function)实现的。

多态把不同的子类对象都当做父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。

C++通过虚函数实现多态。虚函数可以让成员函数操作一般化,用基类的指针指向不同的派生类的对象时,基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数,而不是基类中定义的成员函数(只要派生类改写了该成员函数)。若不是虚函数,则不管基类指针指向的哪个派生类对象,调用时都会调用基类中定义的那个函数。

虚函数首先是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。定义虚函数的语法格式如下:

为了说明虚函数的作用,假设有两个类的定义如下:

在调用成员函数时,语句如下:

在这个例子里,a虽然是指向A的指针,由于被调用的foo函数为虚函数,所以,被调用的函数foo()实际上是B的。2.3.3 继承

人类认识复杂社会的一种重要工具就是抽象。通过抽象,得到了问题中主要的、起决定作用的特性,而抛弃了那些次要的、无足轻重的特性,使得对事物的本质有了深刻地认识。为了深入地认识社会,人们总是把本质相同的事务归为一类,以降低认识事物的复杂性。类和类之间通过概括和特化,形成了层次关系。上层比下层更普遍更一般,下层比上层更具体更特殊。上层从下层抽象而来,下层继承上层的特性,也有与上层的差异。例如,汽车、客运汽车和小轿车就具有这种层次关系。上层的类称为父类、超类或基类,下层的类称为子类或派生类。描述类的层次性的机制是继承,继承的过程称为派生。

在程序设计中,为了能利用已有的成果,减少编程的工作量,实现代码复用,类也使用了继承的机制。利用继承机制,新的类可从已有的类中获得数据成员和成员函数,并且可根据需要增加新的成员。

1.派生类的声明

已有的类称为基类,由基类继承而来的类称为派生类。派生类定义的一般形式是:

上述语法格式中各个部分说明如下。

派生方式关键字为private、public和protected,分别表示私有继承、公有继承和保护继承。默认的继承方式是私有继承。继承方式规定了派生类成员和类外对象访问基类成员的权限,将在下节介绍。

派生类成员是指除了从基类继承来的成员外,还有新增加的数据成员和成员函数。正是通过在派生类中新增加成员来添加新的属性和功能来实现代码的复用和功能的扩充的。派生类的声明的一个例子如下:

2.派生类的访问权限

根据派生方式的不同,从基类继承来的成员的属性也不同。无论哪种方式,基类中的私有成员不允许外部函数访问,也不允许派生类中的成员访问,但可以通过基类的公有成员访问。

派生类有3种访问权限:公有派生、保护派生和私有派生。它们在基类中的公有成员在派生类中的属性不同,由于继承方式不同,所以对类成员的访问权限也不同。各种派生方式区别如下。

公有派生:在基类中的所有公有成员在派生类中也都是公有成员。

保护派生:在基类中的所有公有成员和保护成员在派生类中是保护成员。

私有派生:在基类中的所有公有成员和保护成员在派生类中是私有成员。

3.派生类直接访问基类成员

派生类不能直接访问基类的私有成员,若要访问,则必须使用基类的接口,即通过其成员函数,实现的方法有以下两种。

在基类的声明中增加保护成员,将基类中提供给派生类访问的私有成员定义为保护成员。

将需要访问基类私有成员的派生类成员函数声明为友元。

派生类直接访问基类成员的一个例子如下:2.4 C++的特性

视频精讲:光盘\video\02\C++的特性.swf

C++语言对C语言进行了很多扩展,本节只介绍几种C++的特性。C++对C的其他扩充请读者在以后的学习和编写程序中体会。2.4.1 结构和类

1.结构

结构是用同一个名字引用的相关的集合(或称聚合体),它是用其他类型的对象构造出来的派生数据类型。说到派生,其实也不难理解:在结构中可包含多种不同数据类型的变量,例如,int型、float型、char型、long型,甚至数组合指针及自引用指针等,在结构中包含这些数据类型的变量,所以说结构是派生而来的。结构的一个实例如下:

其中关键字struct定义了一个结构player,它带有3个成员变量,分别为int型的number、char*型的name和nationality。

结构的初始化方式有两种,分别如下。

用初始化列表初始化,即在声明结构变量时,在变量名后用等号连接在花括号中的初始化值列表来初始化该结构变量,初始化值用逗号分开,例如:

利用访问结构成员初始化,例如:

结构的重要性是不言而喻的,它和指针可用来构造更复杂的数据结构,例如:链表、队列、堆栈和树。

2.类

在解释类之前,我们必须了解对象:称现实世界中客观存在的事物为对象,只要是客观世界你能想得到的实物、抽象的物都可以是一个对象,例如:整数是一个对象、汽车是一个对象、人也是一个对象。

C++可使用对象名、属性和操作3要素来描述对象,我们用对象结构图来描述一个对象,如图2-1所示。图2-1 对象结构图

用对象结构图来描述一个人叫小刚的人,如图2-2所示。图2-2 用对象结构图描述小刚

和结构一样,类也是一种用户自己构造的数据类型,类是在C++下的,所以类遵循C++的规定,C++中声明的一般形式为:

类中至少具有以下3种类型成员的一种:

私有(Private)成员:只有在类中说明的函数才能访问该类的私有成员,而在该类外的函数不可以访问私有成员。

公有(Public)成员:类外面也可访问公有成员,成为该类的接口。

保护(Protected)成员:这种成员只有该类的派生类可以访问,其余的在这个类外不能访问。

类与结构既有相似又有不同,class似与struct一样是声明一个聚合体,而且类也有数据成员,但是类声明的对象的初始化则与结构大相径庭(对象的初始化是使用构造函数来完成的,构造函数将在后边介绍)。在结构中的成员均能被外部函数调用,并不像类一样对不同的成员设置不同的权限。在结构中是不可以有函数的,而在类中可以存在函数。2.4.2 构造函数和析构函数

在类中,函数名和类名相同的函数称为构造函数。C++允许同名函数,也就允许在一个类中有多个构造函数。如果类中没有显式定义构造函数,编译器将为该类产生一个默认的构造函数,这个构造函数可能会完成一些工作,也可能什么都不做。构造函数不能被显式地定义类型,即使是void型也不可以。实际上,构造函数默认为void型。

当一个类的对象进入作用域时,系统会为其数据成员分配足够的内存,但是系统不一定将其初始化。和内部数据类型对象一样,外部对象的数据成员总是初始化为0。局部对象不会被初始化。构造函数就是被用来进行初始化工作的。当自动类型的类对象离开其作用域时,所站用的内存将释放回系统。如果构造函数没有参数,那么声明对象时也不需要括号。

当在声明类对象时,如果没有指定参数,则使用默认参数来初始化对象。使用默认参数的构造函数的一个例子如下:

一个类中可以有多个构造函数,这些构造函数必须具有不同的参数表。在一个类中需要接受不同初始化值时,就需要编写多个构造函数,但有时候只需要一个不带初始值的空的构造函数。

当一个类的对象离开作用域时,析构函数将被调用(系统自动调用)。析构函数的名字和类名一样,不过要在前面加上“~”。对一个类来说,只能允许一个析构函数,析构函数不能有参数,并且也没有返回值。析构函数的作用是完成一个清理工作,如释放从堆中分配的内存。

我们也可以只给出析构函数的形式,而不给出起具体函数体,其效果是一样的,如上面的例子中的~Box()。但在有些情况下,析构函数又是必需的。如在类中从堆中分配了内存,则必须在析构函数中释放。2.4.3 函数的重载

函数重载是指同一个函数名可以对应多个函数的实现,例如可以给实现求和功能的函数add()定义多个函数实现。其中,一个函数实现是求两个int型数之和,另一个实现是求两个浮点型数之和,再一个实现是求两个复数之和。每种实现对应着一个函数体,这些函数的名字相同,但是函数的参数的类型不同,这就是函数重载的概念。函数重载在类和对象的应用尤其重要。

函数重载要求编译器能够唯一地确定调用一个函数时应执行哪个函数代码,即采用哪个函数实现。确定函数实现时,要求从函数参数的个数和类型上来区分。这就是说,进行函数重载时,要求同名函数在参数个数上不同,或者参数类型上不同。否则,将无法实现重载。

1)不同参数类型的重载函数

下面通过一个在参数类型不同的重载函数的例子说明如何重载函数:

该程序中,main()函数中调用相同名字add的两个函数,前边一个add()函数对应的是两个int型数求和的函数实现,而后边一个add()函数对应的是两个double型数求和的函数实现,这便是函数的重载。

以上程序输出结果为:

2)参数个数上不同的重载函数

下面在通过一个在参数个数上不相同的重载函数的例子说明如何进行函数的重载:

在该程序中出现了函数重载,函数名min对应有3个不同的实现,函数依据参数个数不同进行区分。这里的3个函数实现中,参数个数分别为2、3和4,在调用函数时根据实参的个数来选取不同的函数实现。

函数重载在类和对象中应用比较多,尤其是在类的多态性中。在以后我们将碰到更多的类型不同的函数重载,尤其是在结合类的继承性和指针类型的不同,而这些都是我们以后用Visual C++ 2008编程中经常要用到的。2.4.4 纯虚函数和抽象类

纯虚函数是一种特殊的虚函数,它的一般格式如下:

很多情况下,在基类中不能对虚函数给出有意义的实现,而把它说明为纯虚函数。具体实现将留给该基类的派生类去完成,这就是纯虚函数的作用。

带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。

抽象类的主要作用是将相关的内容组织在一个继承层次结构中,该结构提供一个公共的根,相关的子类就是从这个根派生而来的。抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。

抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类。2.4.5 new与delete运算符

new与delete是C++语言特有的运算符,用于动态分配和释放内存。

1.new运算符

new用于为各种数据类型分配内存,并把分配到的内存首地址赋给相应的指针。new的功能类似于malloc()函数。使用new的格式为:

其中,<数据类型>可以是基本数据类型,也可以是由基本类型派生出来的类型;<指针变量>取得分配到的内存首地址。

new有以下3种使用形式。

给单个对象申请分配内存:

该代码段首先定义了一个指向整型对象的指针,然后为该对象申请内存空间,如果申请成功,则ip指向一个int型对象的首地址。

给单个对象申请分配内存的同时初始化该对象:

该代码段首先定义了一个指向整型对象的指针,然后为该对象申请内存空间,如果申请成功,则ip指向一个int型对象的首地址,并将该地址的内容初始化为68。

同时给多个对象申请分配内存:

该代码段首先定义了一个指向整型对象的指针,然后为5个int型对象申请内存空间,如果申请成功,则ip指向一个int型对象的首地址。

用new申请分配内存时,不一定能申请成功。若申请失败,则返回NULL,即空指针。因此,在程序中可以通过判断new的返回值是否为0来获知系统中是否有足够的空间供用户使用。

2.delete运算符

当程序不再需要由new分配的内存空间时,可以用delete释放这些空间。使用delete的格式为:

其中,<指针变量>保存着用new申请分配的内存地址。中括号表示用delete释放为多个对象分配的地址,在中括号中不需要加对象的个数。

入行提示

在使用new和delete时,应注意以下几点。

用new运算符申请分配的内存空间,必须用delete释放。

对于一个已分配内存的指针,只能用delete释放一次。

delete作用的指针对象必须是由new分配内存空间的首地址。

用new运算符为多个对象申请分配内存空间时,不能提供初始化。2.4.6 this指针

当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向接受该函数的调用对象的指针,在程序中可以使用关键字this指针来引用该指针,因此称该指针为this指针。this指针是C++实现封装的一种机制,它将成员和用于操作这些成员的成员函数联系在一起。

对于每一个类,它的所有成员函数中都隐式地声明了this指针,指向该成员函数所在的对象的地址(即函数“当前”引用的对象)。例如,在下面的代码里,getn()中的this指针,指向的是test1对象。

由于this指针被声明为*const,它是一个指针常量,因此不能改变它的值,但可以改变它所指对象的值。例如如下的代码:

利用this指针,getn()可重写如下:

在本书后面的程序中,由于在成员函数中需要引用接受函数调用的对象,而不是该对象的成员,也经常使用this指针。2.5 回到案例场景

视频精讲:光盘\video\02\本关任务.swf

本关任务

完成一个工资单计算程序。该程序计算以下几种工种的工资,老板:不管工作多长时间他总是有固定的周薪;销售员:工资为一小部分基本工资加上销售额一定的百分比;计件工:收入取决他生产的工件数量;小时工:收入以小时计算,再加上加班费。作为工资单,不但需要计算出工资数,还应该将员工的姓名和工种等信息显示出来。2.5.1 基本思路

通过本章的学习,我们可以通过以下流程建立员工工资单计算程序。

01 建立雇员类作为基类。

02 建立继承类Boss作为处理老板工资的类。

03 建立继承类Commission Worker作为处理销售员工工资的类。

04 建立继承类Piece Worker作为处理计件员工工资的类。

05 建立继承类Hourly Worker作为处理销售员工工资的类。

06 输出每个员工的工资单。

具体步骤如下。

01 声明类Employee,它的公有成员函数包括:构造函数,该函数有两个参数,第一个参数是雇员的姓,第二个参数是雇员的名;析构函数,用来释放动态分配的内存;两个“get”函数,分别返回雇员的姓和名;纯虚函数earnings和虚函数print。

02 声明类Boss,该类是通过公有继承从类Employee派生出来的。它的公有成员函数包括:构造函数,该函数有3个参数,即雇员的姓和名,以及周薪。为了初始化派生类对象中基类部分的成员first Name和last Name,雇员的姓和名传递给了类Employee的构造函数;“set”函数用来把新值赋绐私有数据成员weekly Salary;虚函数earnings用来定义如何计算Boss的工资;虚函数print用于输出雇员类型,然后调用Employee:print()输出员工姓名。

03 声明类Commission Worker,该类是通过公有继承从类Employee派生出的。它的公有成员函数包括:构造函数,该函数有5个参数,即姓、名、基本工资、回扣及产品销售量,并将姓和名传递给了类Employee的构造函数;“set”函数用于将新值赋给私有数据成员salary、commission和quantity;虚函数earnings用来定义如何计算Commission Worker的工资;虚函数print用于输出雇员类型,然后调用Employs:print()输出员工姓名。

04 声明类Piece Worker,该类是通过公有继承从类Employee派生出来的。公有成员函数包括:构造函数,该函数有4个参数,即计件工的姓、名、每件产品的工资及生产的产品数量,并将姓和名传递给了类Employee的构造函数;“set”函数用来将新值赋给private数据成员wage Per Piece和quantity;虚函数earnings用来定义如何计算Piece Worker的工资;虚函数print用于输出雇员类型,然后调用Employee:print()输出员工姓名。

05 声明类Hourly Worker,该类是通过公有继承从类Employee派生出来的,公有成员函数包括:构造函数,该函数有4个参数,即姓、名、每小时工资及工作的时间数,并将姓、名传递给了类Employee的构造函数;“set”函数将新值赋给private数据成员wage和hours;虚函数earnings用来定义如何计算Hourly Worker的工资;虚函数print用于输出雇员类型,然后调用Employee:print()输出员工姓名。

06 在main函数里,为每个员工声明对应类的对象,计算工资并输出。2.5.2 代码演练

根据如上所述的具体制作步骤,现给出部分关键代码如下。(1)Employee类的定义与实现:(2)Boss类的定义与实现:(3)Commission Worker类的定义与实现:(4)Piece Worker类的定义与实现:(5)Hourly Worker类的定义与实现:(6)输出员工工资单:

编译链接该工程后,得到运行结果如图2-3所示。图2-3 员工工资单程序运行结果2.6 本章小结与习题

我们已经学习了C++编程语言的相关知识,包括基本语法、C++面向对象的特征及其特性等,下面将对前边学习的内容做一些简单的回顾。2.6.1 重点回顾

利用修饰符const可以保护基本类型的变量的值。这将防止在程序内直接修改变量,并且将在常量的值被修改时报告编译器错误。

可以在一个程序的所有代码块之外声明变量,这时它们具有全局命名空间作用域。除名称与全局变量相同的局部变量所在的部分以外,可以在整个程序内访问具有全局命名空间作用域的变量。即使这样,使用作用域解析运算符仍然可以访问它们。

ISO/ANSI C++为重复执行语句块提供了3种基本方法:for循环、while循环和do……while循环。for使循环重复执行给定的次数。While使循环在给定条件返回true时继续。do……while至少执行一次循环,然后使循环在给定条件返回true时继续。

关键字continue使我们跳过循环中本次迭代的剩余语句,而直接进入下次迭代。关键字break导致循环立即退出,break如果位于case语句最后,还将导致循环从switch中退出。

在本地C++应用程序中,new运算符动态分配自由存储器中的内存。当根据请求成功分配内存之后,该运算符返回一个指向提供的存储区域首部的指针,如果因故不能分配内存,则默认抛出一个使程序终止的异常。

从本地C++函数中返回引用或指针时,应该确保被返回的对象具有正确的作用域。永远不要返回本地C++函数的局部对象的指针或引用。

重载函数是名称相同、但形参列表不同的函数。当调用重载函数时,编译器基于我们指定的实参数量和类型,选择调用哪一个函数。

类的所有非静态函数都包含this指针,它指向调用函数的当前对象。

在数值类的非静态函数成员中,this指针是个内部指针;而在引用类中,this指针是一个句柄。

对象是构造函数创建的,构造函数的首要任务是给类对象的数据成员(字段)赋值。

为了提供类对象所特有的动作,大多数基本运算符都可以被重载。我们应该以符合基本运算符正常解释的方式,实现自定义类的运算符函数。

如果编写派生类的构造函数,则不仅必须安排初始化派生类的数据成员,还必须正确安排初使化基类的数据成员。

在基类中的函数可以被声明为virtual,这样将允许在执行时根据调用该函数的当前对象类型,选择派生类中出现的该函数的其他定义。

类的事件成员在特定事件发生时,可以通过调用一个或多个已经注册的该事件的处理函数来发出信号。2.6.2 课后练习

1.所有程序均可用3种控制结构编写:______、______和______。

2.______选择结构用于在条件为true时执行一个操作,条件为false时执行另一个操作。

3.将一组指令重复特定次数称为______重复。

4.事先不知道一组指令重复次数时,可以用______值终止重复。

5.利用继承能够实现______。这种实现缩短了程序的开发时间,促使开发人员复用已经测试和调试好的高质量软件。

6.为了将基类指针转换为派生类指针,由于编译器认为这种操作是危险的,所以要使用______。

7.什么是虚函数?举一个适合使用虚函数的例子。

8.区分虚函数与纯虚函数。第3章 Windows程序工作原理

大师的话

基于Windows程序的设计是以事件为驱动、消息路由为基础、窗口函数处理为目的的。对于Windows程序的特性,我们要掌握事件的触发、消息的产生、获得、分发、判断和处理等,这也是本章的重点。

入行目标

熟悉消息和事件的概念

掌握窗口过程

掌握SDK编程的概念

掌握窗口程序创建的流程3.1 案例场景

Windows应用程序的创建往往是从窗口的创建开始的,通过窗口对象的操作,产生相应的消息处理函数。在第1章中,利用MFC应用程序的新建向导,通过简单的几个步骤就能生成一个颇具规模的窗口程序。但是由于MFC对消息路由进行了封装,我们不能掌握消息路由的本质,本章将以Win SDK为基础,创建一个简单的窗口程序,揭示消息路由的本质,以使读者能够深入掌握Windows程序事件驱动的特性。3.1.1 窗口程序

本关任务

在Visual Studio 2008下,创建一个基于Win SDK的窗口,并在该窗口中输出“欢迎进入《VC入行真功夫》的世界!”,窗口的显示效果如图3-1所示。图3-1 本关任务3.1.2 我们现在能做的……

在仅有第1章知识的基础上,我们目前只能利用MFC生成一个简单的对话框窗口,其基本流程如下。

01 启动Visual Studio 2008,新建一个基于MFC的单文档工程。

02 在“资源视图”中通过“添加资源”对话框为工程添加一个Dialog资源。

03 为对话框添加类定义,然后,加载并显示该对话框。

具体步骤如下。

01 启动Visual Studio 2008,依次选择“文件”·“新建”·“项目”命令,打开VC++的“新建项目”对话框,如图3-2所示,在“项目类型”栏中选择“MFC”,在“模板”栏中选择“MFC应用程序”,并在“名称”文本框中输入要创建的工程名,本例的工程名为“We Can Do Now”。图3-2 “新建项目”对话框

单击“确定”·“下一步”按钮至如图3-3所示的画面,在“应用程序类型”中选择“单文档”单选按钮,并单击“完成”按钮结束MFC工程的创建工作。图3-3 选择应用程序类型

02 在“资源视图”中单击鼠标右键,在弹出的快捷菜单项中依次选择“添加”·“资源”命令,打开“添加资源”对话框,选择“Dialog”并单击“新建”按钮为工程添加一个对话框资源,如图3-4所示。图3-4 新建对话框资源

03 双击新添加的对话框模板,在弹出的“MFC类向导”中为对话框定义一个类,本例为CMy Dialog,单击“完成”按钮结束对话框类的创建工作,如图3-5所示。图3-5 定义对话框类

在应用程序类中声明一个对话框类的指针对象,并在应用程序类的成员函数Init Instance()中给出创建、显示该对话框的代码,如下:

}

经过如上3步之后,就得到了一个基于MFC的对话框窗口,其运行结果如图3-6所示。图3-6 运行结果

入行提示

MFC虽然大大提高了程序的开发效率,但程序员如果不了解Windows程序的创建和运行机制,就不可能很好地运用MFC进行编程。对于初学者而言,总是感觉MFC晦涩难懂,所以,有必要对Windows程序的创建过程和运行机制进行剖析。3.2 Windows编程原理

视频精讲:光盘\video\03\Windows编程原理.swf

Windows程序的运行机制是以事件驱动为基础的,Windows程序等待事件的发生,而事件则通常是由操作系统传递给应用程序的,并通过消息处理函数对外部的消息进行处理。应用程序不会直接和外部设备进行通信,它们之间的通信是通过Windows来完成的。在Windows下编程通常需要使用API(Application Programming Interface)函数,Windows为开发人员提供了1000多个API函数。另外,也可以使用微软提供的MFC(Microsoft Foundation Classes)类库进行程序设计。3.2.1 消息和事件

事件是由应用程序触发的,而消息是操作系统感知到的应用程序的事件,该事件将被转换为消息发送到应用程序的消息队列中。消息可以由操作系统产生,也可以由另一个消息产生,消息的处理函数就是窗口过程函数。

消息是通过MSG结构来实现的,该结构体的定义如下:

通过触发事件产生的消息及由操作系统产生的消息都将被放入应用程序的消息队列中,通过Get Message()函数可以从队列中取出这些消息,并利用Dispatch Message()函数把它们分发给MSG结构中为窗口所指定的窗口过程。值得注意的是,有些消息是不经过消息队列的,这类消息将在操作系统调用窗口的过程中直接被送往窗口进行处理。

入行提示

除了由操作系统产生消息外,用户也可以定义自己的消息,定义的格式一般为WM_USER+XXXXX,表示该消息是用户消息,而WM_USER是在winuser.h中被定义的宏。

消息的投递一般使用API函数Post Message()和Send Message(),它们的函数原型如下。

Post Message():

Send Message():

Send Message()函数和Post Message()函数的参数含义是一致的。二者之间的区别在于Post Message()函数将消息放入消息队列后马上返回,而Send Message()直到窗口过程处理完消息后才返回。3.2.2 WinSDK编程

Win SDK编程就是直接调用Windows API进行编程的方式,Windows提供给程序员上千个API函数,以方便程序员编程。API(Application Programming Interface)即应用程序编程接口,它是应用程序和操作系统之间的接口,Windows应用程序不直接和硬件通信,而是通过调用Win API函数与硬件交互。SDK(Software Developers Kit)即软件开发包,对于从事SDK程序开发的人员来说,Win SDK开发包是必备的,但SDK开发包不仅仅是指Win SDK,比如视频监控系统,采集卡的厂商会提供SDK开发包,这些开发包提供了API函数和帮助文档。3.2.3 应用程序的窗口

窗口是应用程序的重要组成部分,窗口的构成元素一般为菜单栏、标题栏、状态栏、工具栏、父窗体及子窗体等,子窗体必须依赖于父窗体而存在,当主窗体销毁时,子窗体也自动被销毁。

句柄是窗口资源的标识,它标识资源在系统中所占用的内存块,应用程序通过窗口句柄对窗口进行操作。除了窗口句柄之外,任何一种资源都有它自己的句柄,比如光标句柄、位图句柄等。窗口ID是窗口在应用程序中的唯一标识,通过窗口ID可以获取窗口句柄。3.2.4 Windows程序和操作系统

Windows应用程序和操作系统之间的关系相当于主仆关系,即Windows程序不与外部设备直接通信,而是由操作系统接收外部事件,然后通过消息通知应用程序应该做什么不应该做什么。用户在任意时刻都可能进行不同的操作,比如选择菜单命令、单击鼠标左/右键等,操作系统解析这些事件,把它们翻译成消息,然后,回调应用程序的窗口过程函数,对事件进行处理。

Windows程序、操作系统和外部输入硬件之间的关系如图3-7所示。图3-7 Windows程序、操作系统和外部输入设备之间的关系

用户操作输入设备,通过鼠标选择菜单命令或者通过键盘输入字符等,这些事件都由操作系统获得,操作系统把这些事件消息投递到应用程序的消息队列中,应用程序取出这些消息,并对其进行预处理,比如丢弃某些无谓的消息或对消息进行预编译等,然后再转发给操作系统,操作系统接收到消息后调用窗口过程函数,对消息进行处理。3.3 SDK创建Windows应用程序

视频精讲:光盘\video\03\SDK创建Windows应用程序.swf

在C语言中创建一个程序需要由主函数main()作为程序的入口。在Windows环境下,使用SDK创建一个最简单的应用程序,也需要创建主函数Win Main()。这个函数由操作系统调用,它负责创建和显示窗口。但仅仅创建了窗口,这个程序仍是没有生命力的,它还需要一个窗口函数Wnd Proc()来接收操作系统的消息。3.3.1 WinMain()函数

Win Main()函数是Windows程序的入口函数,它的作用是为程序的其他部分进行初始化,创建并显示窗口。Win Main()函数的原型声明如下:

在任意时刻,某个应用程序可能启动多个程序实例,操作系统通过应用程序的实例句柄来区分它们。换句话说,相同应用程序的多个实例句柄是互不相同的。

Win Main()函数需要完成下面的任务。

设置窗口类型。

注册窗口。

创建窗口。

显示窗口。3.3.2 设置窗口类型

如果把窗口比喻成一台机器,我们首先要设计出这台机器,然后为这台机器注册商标,才能推向市场,而设计这台机器的过程就是设计窗口的过程设计窗口时,不仅要根据需要设定窗口的大小、风格、背景、图标、鼠标的形状及是否有菜单栏等,还要为窗口编写一个窗口过程函数。在Windows应用程序中,可以通过窗口类型结构体WNDCLASSEX来实现窗口设计,即对结构体变量的填充就是对窗口类型的设置,这大大简化了我们编程的复杂度和强度。窗口类型结构体WNDCLASSEX的定义如下:

该结构体成员的含义如下。

参数style的常用值如表3-1所示。

参数lpfn Wnd Proc所指向的函数是消息处理器,当消息被分发,操作系统会根据消息的窗口句柄调用相应的窗口过程函数。这个窗口过程函数是一个回调函数,由系统自动调用。应用程序不必关心该函数的实现细节及何时被调用等。

参数h Icon决定了窗口显示的图标样式,它通常可以通过函数Load Icon()获取。Load Icon()函数的定义如下:

其中,参数h Instance是应用程序的句柄,参数lp Icon Name是图标的名字,当参数lp Icon Name为系统图标时,参数h Instance一定被设置为NULL。系统图标的分类如表3-2所示。

参数h Cursor决定了鼠标移入窗口时的光标形状,通常可以通过Load Cursor()获取光标。Load Cursor()和Load Icon()的原理基本一致,这里不再赘述,请读者自行参阅MSDN中Load Cursor()的详细解释。

参数hbr Background决定了窗口重绘的背景颜色,通常可以通过函数Get Stock Object()来获得系统的标准画刷,该函数的原型定义如下:

其中,参数fn Object表示Stock对象的类型,其可取值如表3-3所示。

参数h Icon Sm决定了应用程序最小化时显示的图标。

在了解了WNDCLASSEX类结构体成员的含义之后,下面将通过一段代码演示如何设计窗体类:

入行提示

回调函数本质就是一个指针函数,在调用某个函数(通常是API函数)时,用户将自己的一个函数(这个函数为回调函数)的地址作为参数传递给被调用的函数,在需要时,该函数会利用传递的地址调用回调函数,并在回调函数中处理消息或完成指定的操作。要实现回调函数,首先要实现一个函数指针,让这个指针指向功能函数,然后利用函数指针,隐式调用函数,下面是一个使用回调函数的例子。(1)声明功能函数:(2)声明指针函数:(3)声明调用函数caller。

函数的参数是返回值为void,参数为空的函数指针:(4)主函数实现:3.3.3 注册窗口

窗口类设计完成后,需要使用Register Class Ex()函数向操作系统注册该窗口,注册函数的原型为:

该函数的功能是为接下来调用Create Window()函数或Create Window Ex()函数时所使用的窗口注册一个窗口类。其中,参数Ipwcx是一个指向WNDCLASSEX结构的指针,该结构的对象在被传递给Register Class Ex()函数之前,必须先对结构内的各个属性值进行适当的填充。如果函数执行成功,则返回值是一个能唯一标识该注册类的原子,否则返回0。

使用Register Class Ex()函数为我们在3.3.2小节中设计的窗口进行注册的代码如下:

入行提示

窗口只有被注册,才可能被创建和显示,所以,注册窗口要在创建窗口之前,这个顺序不能颠倒。3.3.4 创建窗口

当窗口被设计完成并成功注册后,就可以进行创建了。通过Create Window()函数可以实现创建一个窗口的功能,其定义如下:

函数的功能是创建一个层叠窗口、弹出式窗口或是一个子窗口。如果函数执行成功,则返回值即为新窗口的句柄,反之,则为NULL。

其中,参数lp Class Name可以是任何用函数Register Class Ex()注册的类名,或是任何预定义的控制类名。

对于参数lp Window Name,如果窗口风格指定了标题条,则由lp Window Name指向的窗口标题显示在标题条上。而当使用Create Window()函数来创建例如按钮、选择框或静态控件时,可以使用lp Window Name参数来指定控制文本。

参数dw Style的取值可以是下列窗口风格的组合。

WS_BORDER:创建一个单边框的窗口。

WS_CAPTION:创建一个有标题框的窗口(包括WS_BORDER风格)。

WS_CHILD:创建一个子窗口,该风格不能与WS_POPUP风格合用。

WS_CHILDWINDOW:与WS_CHILD相同。

WS_CLIPCHILDREN:当在父窗口内绘图时,排除子窗口区域。在创建父窗口时使用这个风格。

WS_CLIPSIBLINGS:排除子窗口之间的相对区域,也就是说,当一个特定的窗口接收到WM_PAINT消息时,WS_CLIPSIBLINGS风格将所有层叠窗口排除在绘图之外,只重绘指定的子窗口。如果未指定WS_CLIPSIBLINGS风格,并且子窗口是层叠的,则在重绘子窗口的客户区时,就会重绘邻近的子窗口。

WS_DISABLED:创建一个初始状态为禁止的子窗口,一个禁止状态的窗口不能接收来自用户的输入信息。

WS_DLGFRAME:创建一个带对话框边框风格的窗口,这种风格的窗口不能带标题条。

WS_GROUP:指定一组控制的第一个控制。这个控制组由第一个控制和随后定义的控制组成,自第二个控制开始,每个控制具有WS_GROUP风格,每个组的第一个控制带有WS_TABSTOP风格,从而使用户可以在组间移动。用户随后可以使用光标在组内的控制间改变键盘焦点。

WS_HSCROLL:创建一个有水平滚动条的窗口。

WS_ICONIC:创建一个初始状态为最小化状态的窗口,它与WS_MINIMIZE风格相同。

WS_MAXIMIZE:创建一个具有最大化按钮的窗口,该风格不能与WS_EX_CONTEXTHELP风格同时出现,同时必须指定WS_SYSMENU风格。

WS_OVERLAPPED:产生一个层叠的窗口,层叠窗口通常有一个标题条和一个边框,这与WS_TILED风格相同。

WS_OVERTAPPEDWINDOW:创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_T HICKFRAME、WS_MINIMIZEBOX及WS_MAXMIZEBOX风格的层叠窗口,它与WS_TILEDWINDOW风格相同。

WS_POPUP:创建一个弹出式窗口,该风格不能与WS_CHILD风格同时使用。

WS_POPUPWINDOW:创建一个具有WS_BORDER、WS_POPUP及WS_SYSMENU风格的窗口,WS_CAPTION和WS_POPUPWINDOW必须同时设定才能使窗口可见。

WS_SIZEBOX:创建一个可调边框的窗口,与WS_THICKFRAME风格相同。

WS_SYSMENU:创建一个在标题条上带有菜单的窗口,使用此风格时必须同时设定WS_CAPTION风格。

WS_TABSTOP:创建一个控制,这个控制在用户按下“Tab”键时可以获得键盘焦点,而当再次按下“Tab”键时,键盘焦点将转移到下一具有WS_TABSTOP风格的控制上。

WS_THICKFRAME:创建一个具有可调边框的窗口,与WS_SIZEBOX风格相同。

WS_TILED:产生一个层叠的窗口,层叠窗口通常拥有一个标题和一个边框,这与WS_OVERLAPPED风格相同。

WS_TILEDWINDOW:创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、MS_THICKFRAME、WS_MINIMIZEBOX及WS_MAXMIZEBOX风格的层叠窗口,这与WS_OVERLAPPEDWINDOW风格相同。

WS_VISIBLE:创建一个初始状态为可见的窗口。

WS_VSCROLL:创建一个有垂直滚动条的窗口。

对一个层叠或弹出式的窗口,参数X是屏幕坐标系窗口左上角的初始X坐标。对于层叠窗口的子窗口来说,X是子窗口左上角相对父窗口客户区左上角的初始X坐标,如果该参数被设为CW_USEDEFAULT,则系统为窗口选择默认的左上角坐标并忽略Y参数。注意,CW_USEDEFAULT只对层叠窗口有效,如果为弹出式窗口的子窗口设定,则X和Y参数均被设为0。

对一个层叠或弹出式的窗口,参数Y是屏幕坐标系窗口左上角的初始Y坐标。对于子窗口,参数Y是子窗口左上角相对父窗口客户区左上角的初始Y坐标。对于列表框,参数Y是列表框客户区左上角相对父窗口客户区左上角的初始Y坐标。如果层叠窗口是使用WS_VISIBLE风格创建的,并且X参数被设为CW_USEDEFAULT,则系统将忽略Y参数。

参数n Width是以设备单元指明窗口的宽度。对于层叠窗口,n Width或是屏幕坐标的窗口宽度或是CW_USEDEFAULT。若n Width是CW_USEDEFAULT,则系统为窗口选择一个默认的高度和宽度:默认宽度为从初始X坐标开始到屏幕的右边界,默认高度为从初始Y坐标开始到目标区域的顶部。注意,CW_USEDFEAULT只对层叠窗口有效,如果为弹出式窗口或子窗口设定CW_USEDEFAULT标志,则参数n Width和n Height均被设成为0。

参数n Height是以设备单元指明窗口的高度。对于层叠窗口,n Height是屏幕坐标的窗口高度。若参数n Width被设为CW_USEDEFAULT,系统将忽略n Height参数。

参数h Wnd Parent是指向被创建窗口的父窗口或所有者窗口的句柄,它对弹出式窗口是可选的。当创建子窗口或嵌套窗口时,必须提供一个有效的窗口句柄。

对于层叠或弹出式窗口,参数h Menu将指定窗口使用的菜单,如果使用了菜单类,则h Menu可以为NULL。而对于子窗口,h Menu参数则指定了该子窗口的标识(一个整型量),对话框使用这个整型值将事件通知父类,因此,这个值对于相同父窗口的所有子窗口必须是唯一的。

参数lp Param通过一个CREA TESTRUCT结构传递,它是lp Create Params的成员。另外,如果应用程序调用Create Window()函数创建一个多文档(MDI)客户窗口,lp Param必须指向一个CLIENTCREA TESTRUCT结构。

使用Create Window()函数创建层叠窗口的关键代码如下:

入行提示

如果想让窗口具备拖曳文件的风格,可以使用API函数Create Window Ex()实现,其函数定义如下:

其中,参数dw Ex Style指定了窗口的扩展风格,它的可取值如下。

WS_EX_ACCEPTFILES:指定创建的窗口接受拖曳文件的风格。

WS_EX_APPWINDOW:窗口显示时,将一个顶层窗口放置到任务条上。

WS_EX_CLIENTEDGE:指定窗口有一个带阴影的边界。

WS_EX_CONTEXTHELP:窗口的标题条包含一个问号标志。当用户单击了问号时,鼠标光标变为一个问号的指针,如果单击了一个子窗口,则子窗口接收到WM_HELP消息。子窗口应该将这个消息传递给父窗口过程,父窗口再通过HELP_WM_HELP命令调用Win Help函数。这个Help应用程序显示一个包含子窗口帮助信息的弹出式窗口。WS_EX_CONTEXTHELP不能与WS_MAXIMIZEBOX和WS_MINIMIZEBOX同时使用。

WS_EX_CONTROLPARENT:允许用户使用“Tab”键在窗口的子窗口间搜索。

WS_EX_DLGMODALFRAME:创建一个带双边的窗口,该窗口可以通过在dw Style中指定WS_CAPTION风格来创建一个标题栏。

WS_EX_LEFT:窗口具有左对齐属性,这是默认设置的。

WS_EX_LEFTSCROLLBAR:如果外壳语言是如Hebrew、Arabic,或其他支持reading order alignment的语言,标题条(如果存在)则在客户区的左部。

WS_EX_LTRREADING:窗口文本以Left到Right(自左向右)属性的顺序显示。

WS_EX_MDICHILD:创建一个MD子窗口。

WS_EX_NOPA TARENTNOTIFY:指明以这个风格创建的窗口在被创建和销毁时不向父窗口发送WM_PARENTNOTIFY消息。

WS_EX_OVERLAPPED:WS_EX_CLIENTEDGE和WS_EX_WINDOWEDGE的组合。

WS_EX_PALETTEWINDOW:WS_EX_WINDOWEDGE、WS_EX_TOOLWINDOW和WS_WX_TOPMOST风格的组合。

WS_EX_RIGHT:窗口具有普通的右对齐属性,这依赖于窗口类。只有在外壳语言是如Hebrew、Arabic或其他支持读顺序对齐(reading order alignment)的语言时该风格才有效,否则,忽略该标志并且不作为错误处理。

WS_EX_RIGHTSCROLLBAR:垂直滚动条在窗口的右边界,这是默认设置的。

WS_EX_RTLREADING:如果外壳语言是如Hebrew、Arabic,或其他支持读顺序对齐(reading order alignment)的语言,则窗口文本是自左向右(Right到Left)的读出顺序。若是其他语言,该风格被忽略并且不作为错误处理。

WS_EX_STATICEDGE:为不接受用户输入的项创建一个三维边界风格。

WS_EX_TOOLWINDOW:创建工具窗口,即窗口是一个游动的工具条。工具窗口的标题条比一般窗口的标题条短,并且窗口标题以小字体显示。工具窗口不在任务栏里显示,当用户按下“Alt+Tab”组合键时工具窗口不在对话框里显示。如果工具窗口有一个系统菜单,它的图标也不会显示在标题栏里,但是,可以通过单击鼠标右键或“Alt+Space”组合键来显示菜单。

WS_EX_TOPMOST:指明以该风格创建的窗口应放置在所有非顶层窗口的上面并且停留在其他窗口之上,即使窗口未被激活。使用函数Set Window Pos()即可设置和移去这个风格。

WS_EX_TRANSPARENT:以这个风格创建的窗口在窗口下的同属窗口已重画时才可以重画。3.3.5 显示和更新窗口

当窗口被创建后,接下来的任务就是要显示窗口。通过调用Show Window()函数可以实现窗口的显示或隐藏,该函数的定义如下:

函数的功能是为窗口设置显示状态。如果窗口以前可见,则返回非零值,如果窗口以前被隐藏,则返回零。其中,参数h Wnd表示窗口句柄,参数n Cmd Show指定了窗口的显示方式,其可取值如下。

SW_HIDE:隐藏窗口并激活其他窗口。

SW_MAXIMIZE:最大化指定的窗口。

SW_MINIMIZE:最小化指定的窗口并且激活在程序中的下一个顶层窗口。

SW_RESTORE:激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志。

SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。

SW_SHOWDEFAULT:依据在STARTUPINFO结构中指定的SW_F LAG标志设定显示状态,STARTUPINFO结构是由启动应用程序的程序传递给Create Process()函数的。

SW_SHOWMAXIMIZED:激活窗口并将其最大化。

SW_SHOWMINIMIZED:激活窗口并将其最小化。

SW_SHOWMINNOACTIV A TE:窗口最小化,激活窗口仍然维持激活状态。

SW_SHOWNA:以窗口原来的状态显示窗口,激活窗口仍然维持激活状态。

SW_SHOWNOACTIV A TE:以窗口最近一次的大小和状态显示窗口,激活窗口仍然维持激活状态。

SW_SHOWNORMAL:激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。

显示窗口后,接下来要做的就是更新窗口。更新窗口需要使用API函数Update Window(),其定义如下:

其中,参数h Wnd代表已经被创建了的窗口的句柄。这个函数会发送WM_PAINT消息刷新指定窗口的客户区域,这个消息不进入消息队列,而是直接发给窗口处理函数。

完成更新后,窗口的创建工作就完全结束了。

入行提示

通过上述内容,我们知道了创建窗口的一般过程是:首先,创建一个窗口类,声明WNDCLASSEX结构,并对该结构体中的各个参数进行填充;其次,注册窗口,利用API函数Register Class Ex()对窗口进行注册;然后,利用API函数Create Window()创建窗口;最后,再利用API函数Show Window()和Update Window()显示、更新窗口。3.3.6 窗口函数

仅仅创建了窗口是不够的,它就像一台没有安装操作系统的电脑,没有任何的作用,因此,我们还必须为其定义窗口过程。简单来讲,窗口过程就是消息处理器。窗口过程的定义有其固定的格式,其原型如下:

注意,用户可以自定义窗口过程函数的名字,但不能更改其参数。

窗口过程是一个回调函数,它是由操作系统调用的,应用程序对于这个函数何时被调用并不关心。操作系统获知用户的事件消息,然后调用窗口函数对消息进行处理。窗口函数的代码如下:

窗口过程函数处理了菜单命令消息WM_COMMAND,其低位代表控件ID;WM_PAINT消息是窗口的重绘消息,显示窗口时这个消息是必需的;WM_DESTROY是销毁窗口的消息。而Def Window Proc()函数则为消息的默认处理函数,其作用是对应用程序没有处理的消息进行默认处理。3.4 回到案例场景

视频精讲:光盘\video\03\本关任务.swf

本关任务

在Visual Studio 2008下,创建一个基于Win SDK的窗口,并在该窗口中输出“欢迎进入《VC入行真功夫》的世界!”,窗口的显示效果如图3-8所示。图3-8 本关任务的窗口显示效果3.4.1 基本思路

通过本章的学习,我们已经对Windows应用程序的运行机制有了基本的了解,因此,现在可以利用Windows应用程序来完成在本章开篇所设定的任务,其基本流程如下。

1新建一个Windows应用程序。

其具体步骤如下。

2添加显示指定文字的代码。

01 启动Visual Studio 2008,依次选择“文件”·“新建”·“项目”命令,打开VC++的“新建项目”对话框,如图3-9所示,在“项目类型”栏中选择“Win32”,在“模板”栏中选择“Win32项目”,并在“名称”文本框中输入要创建的工程名,本例的工程名为“Aimed Work”。图3-9 新建Win32工程

然后,保持默认设置并单击“确定”·“完成”按钮结束Win32工程的创建工作。

02 在窗口函数Wnd Proc()中添加显示“欢迎进入《VC入行真功夫》的世界!”文本的代码,完成在窗口中显示指定文本的功能。3.4.2 代码演练

窗口函数Wnd Proc()的关键代码如下:3.5 本章小结与习题

本章主要介绍了Windows的编程原理及创建Windows应用程序的基本过程等,下面我们将对这些内容中的关键知识点进行回顾和巩固。3.5.1 重点回顾

Windowss程序的入口函数是Win Main()。

窗口的生成过程是:①填充窗口的结构体;②注册窗口;③创建窗口;④显示窗口;⑤更新窗口。

通过窗口类结构体WNDCLASSEX可以设置窗口的属性。

注册窗口类的函数是Regisger Class Ex(),通过这个函数可以向操作系统注册一个窗口。

创建窗口的函数是Create Window(),通过这个函数可以创建一个窗口,并返回窗口句柄。

显示窗口的函数是Show Window(),显示窗口时要注意显示方式。

更新窗口的函数是Update Window(),窗口只有被更新了才能正常显示。

窗口过程函数Wnd Proc()是一个回调函数,当有消息需要被处理时,操作系统将自动调用该函数完成指定的操作。3.5.2 课后练习

1.Windows程序的入口函数是_____________。

2.创建并且显示一个窗口的过程是填充窗口类结构体、__________、____________、显示窗口、_________________。

3.针对窗口消息的处理函数是_____________________。这一函数是回调函数,由操作自动调用。

4.窗口类结构体的名字_________________________。

5.注册窗口的API函数是____________________。

6.创建窗口的API函数是_____________________。

7.显示窗口的API函数是______________________。

8.更新窗口的API函数是_______________________。

9.事件和消息的区别是什么?

10.创建一个窗口,并在窗口中显示一段文字。第4章 MFC创建应用程序

大师的话

作为一个应用程序框架,MFC内容丰富、功能强大。文档/视图结构是MFC的基石,它能将管理应用程序数据的代码和产生这些数据的代码分离出来,理解它是有效使用MFC的关键。

入行目标

理解MFC文档的概念

理解MFC视图的概念

学习使用MFC创建单文档应用程序

学习使用MFC创建多文档应用程序

学习使用MFC创建基于对话框应用程序4.1 案例场景

应用程序是指为了完成某项或某几项特定任务而被开发的,并运行于操作系统之上的计算机程序。MFC为程序员编写应用程序提供了一种特有的程序结构,应用程序数据以特定的方式存储和处理。MFC程序的结构包括两个面向应用的实体——文档和视图。在本章中将介绍这两种实体的概念,并演示如何使用MFC的应用程序向导创建应用程序。4.1.1 MFC创建多视图的多文档应用程序

本关任务

通过MFC应用程序向导建立一个多文档应用程序。当选择“窗口”→“斜体窗口”命令时,程序重新打开一个窗口。在新的窗口中,以斜体加下画线的方式显示同一个文档的内容,即同一个文档的不同视图。4.1.2 我们现在能做的……

在未学习通过MFC创建应用程序时,我们不得不使用C和SDK编写应用程序。这时需要找到在屏幕显示数据的方式;若存在多种数据形式,还需要协调不同数据及数据的不同显示。

当应用程序具有多个文档时,每个文档数据具有不同的显示方式,程序员要自己编写代码管理每种不同的文档类型,以及其显示于屏幕的用户界面。当在任意一种文档数据的表示修改数据时,这个修改会引起其他显示的更新,这时就需要同时协调用户界面交互和管理应用程序。

不可否认,使用这种方法编写应用程序,程序员管理数据和编写程序框架的工作量非常大。幸运的是,MFC提供了一种通用的方法——文档/视图结构解决这些问题,它将数据的管理分为管理数据和显示数据两个部分,同时MFC还完成了自动生成应用程序框架的任务,使得程序员能将精力放在具体的、个性化的功能实现上。4.2 文档和视图

视频精讲:光盘\video\04\文档和视图.swf

计算机的一个应用是信息管理,因此数据的处理是一般软件都要完成的一项工作。采用传统的编程方法,数据处理是一项复杂的任务,并且每一个程序员都可能有不同的处理方法。为了统一和简化数据处理方法,Microsoft公司在MFC中提出了文档/视图结构的概念。4.2.1 文档的概念

文档的概念在MFC应用程序中的适用范围很广,一般说来,文档是能够被逻辑地组合的一系列数据,包括文本、图形、图像和表格数据。一个文档代表了用户存储或打开的一个文件单位。文档的主要作用是把对数据的处理从对用户界面的处理中分离出来,集中处理数据,同时提供了一个与其他类交互的接口。

在程序中的文档是作为文档类的对象定义的,文档类从MFC库中的CDocument类派生,通过添加它的数据成员来存储应用程序需要的数据,还要添加成员函数来支持对数据的处理。CDocument类的主要成员函数如表4-1所示。

以这种方式处理应用程序数据,使MFC能够提供标准的机制来管理作为整体的应用数据集合,并在磁盘上存储和检索文档对象中包含的数据。这些机制是文档类从MFC库中定义的基类继承的,因此,在不编写任何代码的情况下,就能够使应用程序自动获得大量功能。4.2.2 视图的概念

视图是文档在屏幕上的一个映像,它就像一个观景器。用户通过视图看到文档,也通过视图改变文档,视图充当了文档与用户之间的媒介物。而应用程序通过视图向用户显示文档中的数据,并把用户的输入解释为文档的操作。与定义文档的方法类似,通过从MFC库中的CView派生的方法来定义自己的视图类。CView类的主要成员函数如表4-2所示。

入行提示

视图对象和显示视图的窗口是截然不同的,显示视图的窗口被称为框架窗口。视图实际上是在框架窗口的客户区中显示。4.2.3 文档和视图的关系

一个视图总是与一个文档对象相关联,用户通过与文档类相关联的视图与文档进行交互。文档对象可以拥有任意多个与其相关的视图对象,各个视图对象可以提供文档数据或文档数据子集的不同表示方法。例如,在处理文本时,不同的视图可以显示来自相同文档的独立文本块。文档和视图关系图如图4-1所示。图4-1 文档和视图关系图

MFC提供的是文档与对应视图相结合,以及使各个框架窗口与当前活动视图相结合的机制。文档对象自动维护着指向相关视图的指针列表,而视图对象拥有存储相关文档对象的指针的数据成员。各个框架窗口都存储着一个指向当前活动视图对象的指针。文档、视图和框架窗口之间的协作,是由另一个名为文档模板的MFC类对象安排的。

连接文档和视图的关系图如图4-2所示。图4-2 连接文档和视图的关系图

文档模板不仅管理程序中的文档对象,还管理与文档相关的窗口和视图。在应用程序中每种文档类型都需要一个文档模板。一个文档模板可以管理多个相同类型的文档。由图4-2可知,文档模板创建了文档对象和框架窗口对象,而文档的视图是由框架窗口对象创建的。4.3 使用MFC创建应用程序

视频精讲:光盘\video\04\使用MFC创建应用程序.swf

基于MFC的Windows应用程序有4种基本的类型,他们均包含以下4个基本的类。

应用程序类CMy App。

框架窗口类CMy Wnd。

视图类CMy View,该类定义如何在CMy Wnd对象创建的窗口客户区中显示CMy Doc对象包含的数据。

文档类CMy Doc,该类定义包含应用程序数据的文档。

在MFC中各种基本类的继承关系如图4-3所示。图4-3 MFC应用程序基类图

这些类的实际名称可能因特定的应用程序而异,但从MFC类派生的情况却大致相同。通常不需要在应用程序中扩充定义文档模板类,因此,在SDI程序中使用标准MFC类的CSingle Doc Template即可;创建MDI类时,使用CMulti Doc Template类即可。

在图4-3中的箭头从基类指向派生类,它显示了MFC库类中十分复杂的继承关系,事实上,它们还只是整个MFC结构中非常小的一部分。在编写应用程序时将看不到任何基类的定义,但在程序中派生类的继承成员不仅直接来自基类,还来自MFC层次结构中各个间接基类。所以,为了确定程序中某个类具有哪些成员,需要知道该类都继承了哪些类。

在创建基于MFC的应用程序时,主要用到Visual C++ 2008中的以下3个工具。

使用“应用程序向导”创建基本的程序框架。这一步完全自动生成,无须添加任何代码。

在“类视图”中添加类和资源。在类视图的右键菜单中会出现“Add”→“Class”菜单项添加新类。也可以为程序添加新的资源,如位图、图标等。

使用“资源编辑器”对资源进行修改。如控件的名称、资源的ID等。

使用“应用程序向导”创建应用程序的过程就如创建控制台程序一样简单。首先,依次选择“文件”→“新建”→“项目”命令,打开“新建项目”对话框,如图4-4所示。图4-4 “新建项目”对话框

这里选择“MFC”作为项目类型,选择“MFC应用程序”作为使用的模板。随后需要输入应用程序名称,该名称将是为该应用程序生成基本类的名称。单击“确定”按钮之后,将出现“MFC应用程序向导”对话框,如图4-5所示,该对话框提供了应用程序相关的选项。图4-5 “MFC应用程序向导”对话框

由图4-5看到,该对话框解释了当前有效的项目设置。Visual C++ 2008允许我们创建单文档、多文档和基于对话框3种类型的应用程序。4.3.1 创建单文档应用程序

Visual C++ 2008提供了“MFC应用程序向导”,引导客户一步步设置待生成的应用程序框架的属性。

1.使用“MFC应用程序向导”创建单文档应用程序

从“MFC应用程序向导”对话框左边列表中选择“应用程序类型”选项,可以看到可选择的应用程序类型。默认选中的为“多个文档”单选按钮,即多文档应用程序。选择“单文档”单选按钮后即可创建单文档应用程序,左上角显示的应用程序表示将变为单个窗口,如图4-6所示。图4-6 选择创建单文档应用程序

在通常情况下,应取消默认选中的“使用Unicode库”复选框。否则应用程序将要求以Unicode格式输入,生成的文件也将以Unicode字符存储,这样的文件在要求输入ASCII文本的程序将无法读出,在该页面中其他选项保持默认的选择即可。

在“MFC应用程序向导”对话框左边的列表中选择“文档模板字符串”选项,这时可以输入该程序要创建的文件的扩展名,还可以输入筛选器名,它是要在“Open”和“Save As”对话框中出现的对滤器的名称,可以对文件列表进行过滤,如图4-7所示。图4-7 选择“文档模板字符串”选项

在“MFC应用程序向导”对话框左边的列表中选择“用户界面功能”选项,这时出现一组与应用程序界面相关的选项,如图4-8所示,这些选项的说明如表4-3所示。图4-8 选择“用户界面功能”选项

在“MFC应用程序向导”对话框左边的列表中选择“高级功能”选项,将出现如图4-9所示的页面。图4-9 “高级功能”选项

其中,需要了解的是“打印和打印预览”和“区分上下文的帮助”复选框。“打印和打印预览”复选框会给应用程序中的菜单添加标准的“打印”、“打印预览”和“打印设置”菜单项。而且应用程序向导还将提供实现这些功能的框架,有关打印的具体知识还将在第7章详细讲述。“区分上下文的帮助”复选框提供一组基本的支持上下文敏感帮助的功能。如果希望使用该功能,还需要给帮助文件添加具体的内容。

在“MFC应用程序向导”对话框左边的列表中选择“生成的类”选项,将出现在代码中生成的类的列表,如图4-10所示。图4-10 选择“生成的类”选项

选择列表中的每一项,下面的文本框将显示出赋予该类的名称、存储类定义的名称、使用的基类及包含类成员函数实现的文件名称。类的定义总是包含在.h文件中,成员函数的实现总是包含在.cpp文件中。

在了解了生成应用程序常用的选项,进行正确的设置后,单击“完成”按钮,“MFC应用程序向导”生成了一个可直接编译运行的单文档应用程序。由于未添加任何实现功能的代码,这个应用程序并不能完成具体的功能。但它为程序搭建了应用程序的基本框架,从对数据管理和维护的文档类,显示和编辑数据的视图类,到应用程序与用户交互的框架窗口类均已实现。因此,程序员只需在该框架内实现应用程序需完成的具体功能即可。

2.Visual C++ 2008编程界面“应用程序向导”生成的所有文件都存储在指定位置的与应用程序同名的文件夹中。IDE提供了多种方法查看与项目相关的信息,表4-4列出了几个主要的方法。

3.查看项目文件

选择“解决方案资源管理器”选项卡,单击项目名左边的+号展开文件列表,然后依次单击头文件夹、源文件夹和资源文件夹左边的+号,将看到完整的项目文件列表,如图4-11所示。图4-11 项目文件列表

其中,双击某个文件名即可查看该文件的内容,选中的文件内容显示在右边的编辑窗口中。试着打开Read Me.txt文件,将看到该文件简要解释了构成该项目的各个文件内容,如图4-12所示。图4-12 编辑窗口查看Read Me.txt文件内容

4.查看类“类视图”选项卡提供对项目中定义的类进行查看的功能。当需要查看某个类的实现时,从“类视图”选项卡可直接进入该类实现的代码之中。

在“类视图”选项卡中,展开项目名项,显示出项目中定义的类。单击某个类名,该类的成员就会显示在下面的窗格中,如图4-13所示。图4-13 类视图

5.设置项目属性

在“解决方案资源管理器”选项卡中用鼠标右键单击项目名,并从弹出的快捷菜单中选择“属性”命令,打开“属性页”对话框,如图4-14所示。图4-14 项目属性页

左边窗格显示的是属性组,选中某个属性组就可以使该组属性显示在右边的窗格中。单击其中某个属性可修改该属性的值,在属性名右边的下拉列表框中选择该属性的新值即可,在某些情况下需要输入新值。

在“属性页”对话框的顶部,可以看到当前的项目配置及编译该项目的目标平台。这两个属性同样可以修改,其他可用值在各个属性的下拉列表中。

6.创建可执行模块

在应用程序编写完成后,需要生成可执行模块,此时依次选择“生成”→“生成解决方案”命令,或者按“F7”快捷键即可。

初次编译并连接某个程序时需要一些时间,但第二次及以后的编译过程将非常快,这是由于Visual C++ 2008具有预编译头文件的功能。在头一次编译过程中,编译器把编译头文件产生的输出保存到.pch文件中。在以后的编译过程中,如果在头文件中的源代码没有被修改,编译器就重用这个.pch文件,从而节省头文件的编译时间。

7.运行程序

选择“调试”→“开始执行(调试)”命令,或者按“Ctrl+F5”快捷键即可执行编译连接成功的程序。4.3.2 创建多文档应用程序

创建多文档应用程序的过程与创建单文档应用程序的过程很相似,只选取“MFC应用程序向导”页面中的“应用程序”选项的默认值“多个文档”即可。

选择生成多文档应用程序时,查看“生成的类”选项,可以看到该应用程序比单文档程序多出了一个类,如图4-15所示。图4-15 多文档应用程序生成的类

额外的类是MFC类CMDIChild Wnd派生的CChild Frame。该类为出现在CMain Frame对象创建的应用程序窗口内部的文档视图提供框架窗口。单文档应用程序只有一个文档,此文档又只有一个视图,故该视图中显示在主框架窗口的客户区中。而在多文档应用程序中,可以有多个打开的文档,各个文档可以有多个视图。为了实现这一点,程序内每个文档视图都有自己的、由CChild Frame类对象创建的子框架窗口。

编译链接多文档应用程序并执行,得到如图4-16所示的运行结果,可以看到除主程序窗口外,还有多个单独的文档窗口。图4-16 多文档应用程序4.3.3 创建基于对话框应用程序

Windows应用程序虽然提供了菜单和工具栏等界面元素,但它们对用户交互输入功能的处理是有限的。而对话框除了用来显示提示信息之外,还用于接收用户的输入数据。在MFC中,对话框的功能被封装在CDialog类中,而CDialog类是CWnd类的派生类。

使用“MFC应用程序向导”可以创建一个基于对话框的应用程序,这种程序运行后先出现一个对话框。一般的安装程序就是一个基于对话框的程序。在“应用程序类型”选项中选择“基于对话框”单选按钮即可创建基于对话框的应用程序。此时查看“生成的类”选项,可以看到该应用程序只有两个类,如图4-17所示。图4-17 基于对话框应用程序生成的类

利用向导创建应用程序框架后,程序员可根据程序具体该功能,为该对话框类添加代码。并且可以为该对话框添加控件资源。有关对话框和控件的具体知识将在第6章详细讲述。4.4 回到案例场景

视频精讲:光盘\video\04\本关任务.swf

通过MFC应用程序向导建立一个多文档应用程序。当选择“窗口”→“斜体窗口”命令时,程序重新打开一个窗口。在新的窗口中,以斜体加下画线的方式显示同一个文档的内容,即同一个文档的不同视图。4.4.1 基本思路

通过本章的学习,我们现在可以通过以下流程完成多视图的多文档应用程序。

01 建立多文档应用程序框架。

02 定义多视图与文档相联系的文档模板类对象。

03 获取当前活动窗口中的文档。

04 在新窗口中显示文档的斜体加下画线的视图。

具体步骤如下。

01 通过MFC应用程序向导创建一个多文档应用程序Multi View,即在“应用程序类型”选项中选择“多个文档”单选按钮。将“文档模板字符串”选项中的“文件扩展名”设为“.txt”。在“生成的类”选项中将视图类的基类设为CEdit View类,这样在客户区为一个多行文本编辑器。

02 建立一个视图类的派生类CItalic View用于定义显示斜体加下画线文档的函数。定义文档模板类的对象,通过该对象将文档、视图和新产生的窗口联系起来。

03 添加“斜体窗口”菜单项控件,并为控件指定属性和响应函数。在响应函数里,通过当前活动窗口指针获得文档内容,并创建新的框架窗口。

04 改写CItalic View的成员函数On Draw(),实现在新窗口中,斜体显示文档的功能。4.4.2 代码演练

根据上述步骤,现给出实现本关任务的代码如下。(1)声明文档模板对象,关联新窗口、文档和视图:(2)在菜单项的响应函数中获取文档内容:(3)在新窗口中显示文档的斜体加下画线视图:

编译链接Multi View项目,在编辑窗口输入一些文字,然后选择“窗口”→“斜体窗口”菜单命令,可以看到在新打开的窗口中显示斜体加下画线的文本,如图4-18所示。图4-18 多视图多文档应用程序运行结果4.5 本章小结与习题

在本章中介绍了MFC应用程序的基础——文档与视图的概念。还介绍了使用MFC应用程序向导创建MFC应用程序的机制,创建单文档、多文档和基于对话框的3种应用程序的过程。4.5.1 重点回顾

文档是能够被逻辑地组合的一系列数据,包括文本、图形、图像和表格数据。

视图是文档在屏幕上的一个映像。

一个视图总是与一个文档对象相关联,文档对象可以拥有任意多个与其相关的视图对象。各个视图对象可以提供文档数据或文档数据子集的不同表示方法。

文档模板类对象用来把文档、视图和窗口捆绑在一起。单文档应用程序的文档模板类为CSingle Template,多文档应用程序的文档模板类为CMulti Doc Template。

基于MFC的应用程序有4种基本的类:应用程序类、框架窗口类、视图类和文档类。

使用“MFC应用程序向导”可以生成单文档、多文档和基于对话框的应用程序。4.5.2 课后练习

1.文档的概念。

2.视图的概念。

3.文档与视图的关系。

4.单文档应用程序的文档模板类为______,多文档应用程序的文档模板类为______。

5.MFC的应用程序有哪些基本的类?

6.在______查看项目文件,在______查看类。

7.如何编译链接应用程序,如何执行可执行文件?第5章 深入MFC框架技术

大师的话

MFC(Microsoft Foundation Classes),即微软基础类。它是由微软提供的,用于在C++环境下编写应用程序的一个框架和引擎,其中定义了应用程序的一般处理流程,并使用面向对象技术对Windows API进行了封装,隐藏在Windows下使用C++编程的大量内部细节。在开发应用程序的过程中,编程人员可以通过对MFC类库中已有类的继承,生成功能更加强大的类以供自己所用。

入行目标

了解MFC的类层次结构

了解MFC的RTTI实现机制

了解MFC动态创建的实现机制

了解MFC序列化的实现机制

了解MFC消息映射的实现机制

了解MFC命令路由的实现机制5.1 案例场景

MFC对Windows API提供了面向对象的封装,从而隐藏了在Windows下编写C++代码的大量内部细节,它的灵活、高效为开发人员在设计开发应用软件时提供了极大的方便,因此,本章将讲解MFC框架的关键技术。5.1.1 模拟MFC机制

本关任务

在Visual Studio 2008下,设计并制作能够模拟MFC机制(如动态创建)的应用程序,其运行结果如图5-1所示。图5-1 模拟MFC机制5.1.2 我们现在能做的……

凭借着在第4章中所学的知识,目前我们只要新建一个基于MFC Application的单文档工程就可以得到MFC框架所提供的所有的关键技术,一个空的MFC单文档程序的执行结果如图5-2所示。图5-2 空的MFC单文档

入行提示

从当前的实现方法和执行结果中不难看出,我们目前仅仅是通过VC++开发工具的“新建项目”向导自动生成了一个MFC框架,它自带了MFC所有的关键技术,我们并没有参与到这些技术的实现当中,这个问题将有待于在学习本章内容的过程中逐步解决。5.2 MFC的类层次

MFC(微软基础类)也是一种应用程序框架,定义了应用程序的一般处理流程,用于对Windows API实现基于面向对象技术的封装,隐藏在Windows下使用C++编程的大量内部细节。在开发应用程序的过程中,编程人员可以通过对类库中已有类的继承,生成功能更加强大的类库以供自己所用。

在MFC中类的层次结构(即继承关系)如图5-3所示。图5-3 MFC中类的层次结构

从图中可知,在MFC中大多数的类都派生于CObject类,它的主要作用是为子类提供一些基本的功能,这些派生类构成了MFC应用程序的基本框架,它们各自的功能描述如表5-1所示。

下面将对上述表中各个类的功能进行具体的讲解。

1)CCmd Target类

CCmd Target类是MFC的消息映射基础类,MFC为该类设计了许多的成员变量及函数以解决消息映射的问题。派生于CCmd Target的类可用于处理当用户选择菜单或单击按钮等操作时所产生的Command消息。

在实际的开发过程中,我们通常很少直接从CCmd Target中派生类。当想要生成一个处理按键消息的类时,只需从继承于CCmd Target类的框架子类CView、CWin App、CDocument、CWnd和CFrame Wnd中选择一个来充当父类即可。

2)CWin Thread类

CWin Thread类是MFC中用于封装线程的类,它的成员函数可以使MFC应用程序创建和管理包括UI及工作者在内的线程。每个MFC应用程序都至少应该使用一个从CWin Thread派生的类,应用程序类CWin App就是一个代表。

3)CWin App类

CWin App类通常代表应用程序自己,它封装了应用程序的初始化、运行及终止的过程。基于框架的应用程序必须有且仅有一个派生于CWin App的类的对象,并在完成窗口的创建工作之前执行对该对象的构造。

应用程序类的对象需要完成以下工作。

初始化应用程序。

建立文档模板结构。

循环检索消息队列中的消息并将这些消息发送到指定的地方。

执行应用程序退出时的清理工作。

4)CDocument类

CDocument类是在使用文档/视图结构的应用程序中文档对象的基类,它为应用程序的文档对象提供了基本的功能,包括新建、串行化数据等。

5)CWnd类

CWnd类是所有MFC窗口的基类,它封装了窗口的基本操作,包括窗口的创建、销毁、设置窗口风格等,以及窗口对大部分消息的默认响应。开发人员可以直接从CWnd派生其他类,但通常情况下我们并不这么做,而是通过继承CWnd的派生类生成新类。

6)CFrame Wnd类

CFrame Wnd类往往用于创建应用程序的主窗口,并定义了大量管理视图和文档对象的成员函数及变量。在编写文档/视图结构的应用程序时,视图对象等将作为CFrame Wnd的子窗口实现对客户区的共享,并被CFrame Wnd有序排列。

7)CView类

CView类是在使用文档/视图结构的应用程序中视图对象的基类,它是用户的主要操作界面。在应用程序中,一个视图对象通常只对应一个文档对象,但一个文档对象却可以关联多个视图对象,并且每个视图对象都以不同的形式来显示文档中的数据。

在上述CObject类的派生类中,CWin App类、CDocument类、CCmd Target类及CWin Thread类构成了应用程序的结构类,代表了应用程序的基本结构元素。换句话说,当一个应用程序开始运行时,这些类将最先实现初始化。

入行提示

在类的层次结构中,应用程序类CWin App是一个基于MFC应用程序的最外层对象容器,它不仅拥有诸如实例句柄等需要被传送到Win Main()函数中去的参数,还包含了应用程序的主框架窗口,当主框架窗口被关闭时,应用程序也就跟着结束了。因此,开发人员必须为程序创建一个全局的应用程序对象。5.3 MFC的关键技术

MFC的关键技术包括:MFC程序的初始化过程、运行时的类型识别RTTI、动态创建、序列化、消息映射及命令路由6种,下面将对这些技术的功能和实现机制进行详细介绍。5.3.1 RTTI

视频精讲:光盘\video\05\RTTI.swf

RTTI(Run-Time Type Identification)即运行时类型识别,该项技术使得应用程序能够使用基类的指针或引用来检查它们所指对象的实际派生类型。每个从CObject派生的类都有一个静态的CRuntime Class结构的对象同它关联以保存类的一些基本信息,实现在运行的过程中获得类实例或其基类信息的功能。表5-2是关于CRuntime Class中比较重要的一些成员的介绍。

光有CRuntime Class结构还不够,为了实现运行时类型识别,还需要执行以下几步。(1)令需要添加运行时类信息支持的类派生于CObject类。(2)在类的声明中添加DECLARE_DYNAMIC()宏。(3)在类的实现文件中添加IMPLEMENT_DYNAMIC()宏。

执行上述3步操作之后,代码的形式如下:

其中,宏DECLARE_DYNAMIC()的作用是提供基本的运行时类型识别声明。在afx.h头文件下,宏DECLARE_DYNAMIC()的定义如下:

因此,在进行宏替换之后,上述代码中DECLARE_DYNAMIC()为C***Class类所执行的操作实际上为:

在上述代码中值得注意的是,class C***Class是一个静态常量,即它在所有的类对象中是唯一的,因此,通过对比这个成员所指向的地址便可实现对两个对象是否属于同一类型的判断。

而宏IMPLEMENT_DYNAMIC()的作用则是初始化类的CRunt ime Class对象的各个域,提供基本的运行时类型识别实现,其定义如下:

其中,IMPLEMENT_RUNTIMECLASS()宏调用的定义如下:

因此,在进行宏替换之后,上述代码中IMPLEMENT_DYNAMIC()为C***Class类所执行的操作实际上为:

至此,我们可以看出,MFC为支持运行时类型识别所采用的机制是:首先,为每个类添加一个静态的CRuntime Class对象,在该对象中保存了类的相关信息,包括类名、类结构的大小、创建函数的入口地址等;其次,当判断某个对象是否属于某个类时,只需要将该对象的CRuntime Class成员与指定类的CRuntime Class成员进行比较,或是通过_Get Base Class()得到基类的CRuntime Class对象并进行判断即可。CObject类的成员函数Is Kind Of()可用于执行这种运行时的类型识别,函数内部调用了CRuntime Class:Is Derived From(),其关键代码如下:5.3.2 动态创建

视频精讲:光盘\video\05\动态创建.swf

动态创建同样需要CRuntime Class对象的支持,它指的是在运行的过程中以某个类为蓝本,通过调用CRuntime Class对象的成员函数Create Object()完成对象的动态创建工作。

实现动态创建的方法与实现RTTI的方法类似,只是要将DECLARE_DYNAMIC()宏和IMPLEMENT_DYNAMIC()宏分别替换为DECLARE_DYNCREA TE()宏和IMPLEMENT_DYNCREA TE()宏,替换后代码的形式如下:

其中,宏DECLARE_DYNCREA TE()的作用是提供动态创建的声明。在afx.h头文件下,宏DECLARE_DYNCREA TE()的定义如下:

而宏IMPLEMENT_DYNCREA TE()的作用则是提供动态创建的实现,其定义如下:

从上述宏定义中可知,DECLARE_DYNCREA TE()宏包含了DECLARE_DYNAMIC()宏,并在此基础上定义了一个静态成员函数Create Object(),该函数使用C++操作符和类的默认构造函数为其所属类动态创建一个对象。而IMPLEMENT_DYNCREA TE()宏则用于实现类对象的动态创建。因此,在进行宏替换后,实现动态创建的操作实际上为:

至此,我们可以看出,MFC为支持动态创建所采用的机制是:首先,通过DECLARE_DYNCREATE()宏为动态创建进行声明;然后,IMPLEMENT_DYNCREATE()宏会把Create Object()函数的入口地址作为初始化参数传递给CRunt ime Class对象的m_pfn Create Object成员变量,在CRuntime Class:Create Object()内部将调用该成员所指向的Create Object()函数,这样,就完成了类对象的创建工作。

入行提示

由于DECLARE_DYNCREA TE()宏中包含了DECLARE_DYNAMIC()宏,所以,所有支持动态创建的类都必然支持运行时的类型识别。5.3.3 序列化

视频精讲:光盘\video\05\序列化.swf

序列化也需要得到CRuntime Class结构的支持,它指的是从一个持久性存储介质中读出或者写入一个对象的过程。在MFC中,为了实现序列化,可以执行以下几步。(1)令需要添加序列化支持的类继承于CObject类或其派生类。(2)重载CObject类的Serialize()成员函数。(3)在类的声明中添加DECLARE_SERIAL()宏。(4)定义一个无参构造函数。(5)在类的实现文件中添加IMPLEMENT_SERIAL()宏。

重载Serialize()的目的是让文档对象在执行打开/保存操作时,能够读取和存储复杂的类对象,其基本形式如下:

DECLARE_SERIAL()宏的作用是提供序列化的声明。在afx.h头文件下,宏DECLARE_SERIAL()的定义如下:

而宏IMPLEMENT_SERIAL()的作用则是提供序列化的实现,其定义如下:

上述宏定义中,CArchive类是作为待序列化的对象与存储介质之间的一种媒介而存在的,该类的对象可以被看做是一个二进制流,用于实现对象的可序列化读写。使用CArchive对象时特别值得注意的是,对象的可序列化读写不能同时进行,即对于给定的CArchive对象在同一时间内只能执行单向地存储或读取操作。

在实现序列化的过程中,DECLARE_SERIAL()宏首先通过_DECLARE_DYNCREA TE()宏为类定义了动态创建,然后,再以友元的形式重载操作符“>>”。IMPLEMENT_SERIAL()宏给出了该重载函数的具体实现,它通过类的Create Object()函数及“>>”操作符调用CArchive对象的Read Object()成员函数,以根据所获得的CRuntime Class结构的内容从存储介质中读出对象。另外,用于保存对象的“<<”运算符的实现则是通过CArchive类的Write Object()成员函数实现的,其代码如下:

下面来看一下CArchive类的Write Object()及Read Object()成员函数的关键代码:

Write Object():

Read Object():

至此,我们可以看出,序列化存储对象的基本流程是:首先,写入初始化对象CRuntime Class结构中的信息;然后,调用对象的Serialize()方法实现序列化。而序列化读取对象的基本流程则是:第一,获取对象的CRuntime Class结构的信息;第二,根据所得的信息动态地创建对象;第三,调用对象的Serialize()方法初始化对象的数据成员。5.3.4 消息映射

视频精讲:光盘\video\05\消息映射.swf

在MFC中,消息映射机制的基本实现思想是:首先,将每个类所感兴趣的消息都关联到其对应的消息响应函数上生成消息映射表,然后,将基础类与其派生类的消息映射表连接起来,形成更大的消息映射网,这样,当有消息发生时,通过对比MFC窗口的消息映射表,应用程序便可知当前消息是否有可执行的消息处理函数。

为了将一个类所感兴趣的消息及其消息响应函数对应起来生成消息映射表,我们定义了如下结构:

有了上述结构,通过一个AFX_MSGMAP_ENTRY类型的数组_message Entries[],就可以容纳当前类所有感兴趣的消息映射项了。

为了契合MFC的消息传递机制(即可以把自己不处理的消息传送给别的类进行处理),我们还要增加一个结构体,以根据类之间的关系把所有的AFX_MSGMAP_ENTRY数组串联起来,实现对各个MFC对象的消息映射表的查找。该结构体的定义如下:

通过在每个需要响应Windows消息的类中声明一个该结构的变量message Map,并让其p Base Map指针指向基类或另一个类的message Map变量,就可以形成本小节开头所讲到的消息映射网,其结构如图5-4所示。图5-4 消息映射网结构

形成MFC对象的消息映射网之后,为了方便查找,我们还需要在想要响应Windows消息的类中插入以下两个函数。

_Get Base Message Map():该函数的功能是获取当前类的基类的消息映射表,其定义如下。

Get Message Map():该函数的功能是获得当前类自身的消息映射表,其定义如下。

至此,我们就构造出了在MFC中实现消息映射机制所需的数据结构及函数,通过DECLARE_MESSAGE_MAP()宏可以为每个想要实现消息映射的类提供对这些数据结构和函数的声明。DECLARE_MESSAGE_MAP()宏在afx.h头文件中的定义如下:

声明了所需的数据结构及函数之后,消息映射网的形成就要靠BEGIN_MESSAGE_MAP()、ON_COMMAND()和END_MESSAGE_MAP()3个宏来实现。在afx.h头文件中,它们的定义如下:

下面就以继承于CDialog类的CMy Dialog类为例,讲解在响应WM_PAINT消息时以上3个宏的作用。未执行宏替换前的代码形式如下:

执行宏替换之后,得到的代码如下:5.3.5 命令路由

视频精讲:光盘\video\05\命令路由.swf

在MFC中的消息大致可分为3类,即命令消息(WM_COMMAND)、Windows消息(WM_***)及控件消息(WM_NOTIFY)。对于一般的Windows消息WM_***,所有派生于CWnd的类都可以接收,因此,MFC为处理这类消息所制定的路由方向是从派生类到基类的。而对命令消息WM_COMMAND而言,由于派生于CCmd Target的类都有资格接收,MFC规定了处理这类消息时所采用的路由机制,如表5-3所示。

下面将为大家详细地介绍MFC在处理命令消息时信息的流动过程。(1)通过Afx Wnd Proc()函数发送一个命令消息。

Afx Wnd Proc()函数是一个全局函数,每执行一次就发送一个命令消息,函数的定义如下:

Afx Wnd Proc()函数内部调用了Afx Call Wnd Proc(),其定义如下:(2)根据多态性,确定调用哪个对象的Window Proc()。

过程(1)中的Window Proc()函数是一个虚函数,对它的调用依赖于p Wnd指针所指向的对象,Window Proc()的定义如下:

即如果是命令消息WM_COMMAND, CWnd:Window Proc()将根据当前this指针所指对象来决定需要调用哪个类的(如CFrame Wnd类或CView类等)On Command()函数。假设this指针指向框架类,则其最终会调用CWnd对象的On Command(),而该函数又呼叫了另一个虚函数On Wnd Msg(),此时,同样要由this指针的指向决定具体的调用对象。由于前面假设了this指针是指向框架类的,所以,这里将调用框架类的Oncmd Msg(),其定义如下:(3)开始消息路由。

从过程2的p View→On Cmd Msg()可知,最先调用的是CView类的On Cmd Msg(),其定义如下:

此时将体现出视图View在处理命令消息时的次序,即先视图本身后文档对象,因此,这里首先调用CWnd类的On Cmd Msg()函数。由于CWnd类并没有重载该函数,所以实际上调用的是CWnd的基类CCmd Target的On Cmd Msg(),其定义如下:

如果在消息映射表中查找到相应的消息项,就调用其对应的消息响应函数,结束命令路由;否则,根据视图View处理命令消息的次序,退回到CView:On Cmd Msg()处,并调用CDocument:On Cmd Msg(),其定义如下:

如果在映射表中没有找到相应的消息项,则退回到p View→On Cmd Msg()处,并按照框架Frame处理命令消息的次序,继续执行CWnd:On Cmd Msg()。如前所述,由于CWnd类没重载On Cmd Msg(),所以实际上调用的是CCmd Target:On Cmd Msg()。如果在映射表中还是没找到相应的消息项,就返回至CFrame Wnd:On Cmd Msg()处,转而呼叫CWin App:On Cmd Msg()(亦即调用CCmd Target:On Cmd Msg())。如果仍然找不到对应的信息,则命令消息也无处可去了,只能将false返回到Window Proc()中,调用Def Window Proc()结束命令路由。5.4 回到案例场景

视频精讲:光盘\video\05\本关任务.swf

本关任务

在Visual Studio 2008下,设计并制作能够模拟MFC机制(如动态创建)的应用程序,其运行结果如图5-5所示。图5-5 模拟MFC动态创建机制5.4.1 基本思路

在Win32控制台程序中模拟MFC动态创建机制的基本流程如下。(1)编辑并实现MFC的动态创建机制。

01 定义CRuntime Class结构。

02 给出DECLARE_DYNAMIC、DECLARE_DYNCREA TE、IMPLEMENT_DYNAMIC及IMPLEMENT_DYNCREA TE等宏的定义。

03 按照MFC中类的层次结构定义类,并为需要支持动态创建的类进行声明。(2)编辑并实现对动态创建机制的测试。

04 给出类中各成员函数的具体实现,并初始化类的CRuntime Class对象。

01 定义想要实现动态创建机制的类。

02 给出类中各成员函数的具体实现。

具体的实现步骤如下。

03 给出main()函数,实现对动态创建机制的测试。(1)编辑并实现MFC的动态创建机制

01 在Win32工程中新建一个C++头文件MFC.h,在其中对CRuntime Class对象的结构进行定义。

02 在MFC.h中给出用于声明动态创建的DECLARE_DYNAMIC()和DECLARE_DYNCREA TE()宏的定义,以及用于给出动态创建实现的IMPLEMENT_DYNAMIC()及IMPLEMENT_DYNCREA TE()等宏的定义。

03 在MFC.h中,按照MFC的类层次结构给出各个类的定义,并分别使用DECLARE_DYNCREA TE()宏和DECLARE_DYNAMIC()宏为需要支持动态创建的类及其他类进行声明。

04 在Win32工程中新建一个C++源文件MFC.cpp,在其中给出步骤3定义的各个类的成员函数的具体实现,并通过用IMPLEMENT_DYNCREA TE()宏和IMPLEMET_DYNAMIC()宏初始化类的CRuntime Class对象,实现对动态创建机制的支持。(2)编辑并实现对动态创建机制的测试

01 为测试上述步骤中已编辑好的动态创建机制,需要在Win32工程中再新建一个C++头文件My Test.h,并在该头文件中用DECLARE_DYNCREA TE()宏定义想要实现动态创建机制的类。

02 在Win32工程中新建一个对应于My Test.h的C++源文件My Test.cpp,在该源文件中给出My Test.h定义的各个类的成员函数的具体实现,并用IMPLEMENT_DYNCREA TE()宏实现动态创建机制。

03 在My Test.cpp中编写main()函数,实例化应用程序类及CRuntime Class结构的指针对象,并根据在CRuntime Class对象中保存的类的信息完成对动态创建机制的测试。5.4.2 代码演练

下面将给出实现本关任务的关键代码。(1)MFC. h中,按照类的层次结构定义类的关键代码:(2)在MFC.cpp中实现类定义的关键代码:(3)在My Test.h中定义测试动态创建机制类的关键代码:(4)在My Test.cpp中实现类定义的关键代码:

经过如上步骤之后,就实现了本关任务中具有MFC动态创建机制的应用程序的设计制作,其执行结果如图5-6和图5-7所示。图5-6 模拟MFC动态创建机制1图5-7 模拟MFC动态创建机制25.5 本章小结与习题

本章主要介绍了MFC的类层次和MFC框架的关键技术,包括运行时的类型识别、动态创建、序列化、消息映射及命令路由,下面我们将对这些内容中的关键知识点进行回顾和巩固。5.5.1 重点回顾

在MFC中大多数的类都派生于CObject,它的主要作用是为子类提供一些基本的功能,而这些派生类就构成了MFC应用程序的基本框架并实现了特定的功能。

CCmd Target类是MFC的消息映射基础类,派生于CCmd Target的类可用于处理当用户选择菜单或单击按钮等操作时所产生的Command消息。

CWin Thread类是MFC中用于封装线程的类,每个MFC应用程序都至少应该使用一个从CWin Thread派生的类,应用程序类CWin App就是一个代表。

CWin App类通常代表应用程序自己,它封装了应用程序的初始化、运行及终止的过程。基于框架的应用程序必须有且仅有一个派生于CWin App的类的对象,并在完成窗口的创建工作之前执行对该对象的构造。

CDocument类是在使用文档/视图结构的应用程序中文档对象的基类,它为应用程序的文档对象提供了基本的功能,包括新建、串行化数据等。

CWnd类是所有MFC窗口的基类,它封装了窗口的基本操作,包括窗口的创建、销毁、设置窗口风格等,以及窗口对大部分消息的默认响应。

CFrame Wnd类往往用于创建应用程序的主窗口,并定义了大量管理视图和文档对象的成员函数及变量。

CView类是在使用文档/视图结构的应用程序中视图对象的基类,它是用户的主要操作界面。在应用程序中,一个视图对象通常只对应一个文档对象,但一个文档对象却可以关联多个视图对象,并且每个视图对象都以不同的形式来显示文档中的数据。

运行时类型识别使得应用程序能够使用基类的指针或引用来检查它们所指对象的实际派生类型,该项技术的实现需要得到CRuntime Class结构、DECLARE_DYNAMIC()宏及IMPLEMENT_DYNAMIC()宏的支持。

动态创建指的是在运行的过程中以某个类为蓝本,通过调用CRuntime Class对象的成员函数Create Object()完成对象的动态创建工作,它需要CRunt ime Class对象、DECLARE_DYNCREA TE()宏和IMPLEMENT_DYNCREA TE()宏的支持。

序列化需要得到CRuntime Class对象、DECLARE_SERIAL()宏和IMPLEMENT_SERIAL()宏的支持才可实现。

支持序列化的类必然支持动态创建和运行时的类型识别。

消息映射的实现主要仰仗于类之间的消息映射网,而消息映射网的形成则要依赖BEGIN_MESSAGE_MAP()、ON_COMMAND()和END_MESSAGE_MAP()3个宏。

MFC中的消息大致可分为3类,即命令消息(WM_COMMAND)、Windows消息(WM_***)及控件消息(WM_NOTIFY)。

在MFC中,处理一般Windows消息WM_***的路由方向是从派生类到基类的,而对于命令消息WM_COMMAND则有一套特定的路由机制。5.5.2 课后练习

1.在MFC框架中类的层次结构是怎样的?

2.应当如何理解CObject类的作用?

3.RTTI、动态创建和序列化三者之间有什么联系?

4.在MFC中,消息映射的实现机制是怎样的?

5.在MFC中,处理命令消息的路由次序是怎样的?

6.编辑代码模拟在MFC中的类层次。第6章 对话框和常用控件

大师的话

从编程的角度来讲,对话框是一种窗体,另外,添加到对话框上的每一个控件也都属于窗体的范畴,因此,在VC++的MFC的类层次结构中,所有的对话框及对话框上的控件都从CWnd类派生而来。这就意味着对窗体所能进行的任何操作都可以应用到对话框及其通用控件中,而通过CWnd类提供的大量的功能函数,开发人员能够很方便地实现对对话框及对话框控件的管理和控制。

大师的话

了解对话框的基本概念和功能

了解模态对话框的概念和使用方法

了解非模态对话框的概念和使用方法

掌握各种通用控件的功能及使用方法

掌握属性页和属性单的概念及使用方法

掌握制作透明对话框的基本方法和过程6.1 案例场景

在Windows应用程序中,对话框是使用得最多,也是最重要的一种用户界面对象,而在实际的应用过程中,大部分对话框都是通过添加到其上的通用控件来获取用户的输入以实现用户与计算机间的通信的。本章将从对话框的基本概念和生成方式、通用控件的功能和使用方法、属性单和属性页的使用方法,以及特殊效果窗体的制作方法等方面入手,为大家详细介绍如何在VC++的MFC中设计制作窗体。6.1.1 类QQ的窗体设计

本关任务

在Visual Studio 2008下设计并制作一个具有类QQ窗体风格的应用程序,其执行效果如图6-1所示。图6-1 类QQ风格的窗体6.1.2 我们现在能做的……

仅凭当前所学的知识,我们还无法完成本关任务中这种具有类QQ窗口风格的应用程序的制作。因此,我们目前只能通过创建Windows Forms程序来简单地生成一个Form窗体,如图6-2所示。图6-2 Form窗体

入行提示

从当前的实现方法和执行结果中不难看出,我们目前仅仅是通过Windows Forms程序自动生成了一个窗体,其显示效果与本关任务中所要求的相差太远。这个窗体并没有类似QQ窗体的界面风格,也不具备基本的界面功能,而这些问题都有待于在学习本章内容的过程中逐步解决。6.2 对话框

对话框是一种次要窗体,通常在响应某个菜单命令时弹出,它为实现用户与计算机间的信息交互提供了一种标准途径。6.2.1 对话框的基本概念

对话框是一种能够包含各类通用控件的特殊窗口,通过那些添加到其上的控件,对话框可以完成指定的命令或任务。从使用方面来说,对话框可以分为模态对话框和非模态对话框两类。而从编程及设计的角度来讲,MFC中的对话框则通常包含两个部分,即对话框模板和对话框类。

对话框模板:利用对话框模板,开发人员可以在对话框上添加适当的控件并进行合理布局,Windows将根据相应的模板完成对话框的创建和显示。另外,对话框在创建伊始会收到WM_INITDIALOG消息,响应该消息的事件处理函数为On Init Dialog(),其作用是完成对话框的初始化工作。

对话框类:开发人员可以为每个对话框定义一个派生于CDialog的新类用以实现为对话框指定的特殊功能。

设计对话框的目的就是为了向其中添加、布置各种通用控件完成特定的操作,而通过调整这些控件的属性和样式并编辑适当的代码,开发人员可以实现对这些控件的控制及与用户间的信息交互。6.2.2 模态对话框

视频精讲:光盘\video\06\模态对话框.swf

以排他方式工作的对话框被称为模态对话框,在Windows系统中最常见的模态对话框是“打开文件”对话框。当这类对话框出现时,用户虽然可以看见应用程序的其他对象,但在将其关闭之前,输入焦点是不能切换到拥有该对话框的应用程序的其他窗口中的,即用户无法实现与这些对象间的交互。

在Visual Studio 2008的MFC单文档工程中,可以通过对话框类的成员函数Do Modal()来创建并显示一个模态对话框,其基本流程如下。

01 为MFC单文档工程添加一个对话框资源。

02 为该对话框资源声明一个派生于CDialog的类。

03 创建并调用模态对话框。

具体步骤如下。

01 在“资源视图”中单击鼠标右键,在弹出的快捷菜单中选择“添加”→“资源”命令,打开“添加资源”对话框,为MFC单文档工程添加一个新的Dialog资源,如图6-3所示。图6-3 添加对话框资源

02 双击对话框模板,并通过弹出的“MFC类向导”为新添加的对话框资源声明一个派生于CDialog的类,如图6-4所示。图6-4 为对话框模板声明对应的类

03 在应用程序App类的成员函数Init Instance()中实例化该对话框类的对象,并使用其成员函数Do Modal()生成模态对话框,具体代码如下。

经过如上3步之后,就成功地创建了一个模态对话框,当该对话框被调用时,其所属应用程序的其他对象将不可用,直到模态对话框被关闭为止,程序的执行结果如图6-5所示。图6-5 模态对话框6.2.3 非模态对话框

视频精讲:光盘\video\06\非模态对话框.swf

同理,以非排他方式工作的对话框被称为非模态对话框,在Word中的“查找和替换”对话框就是一个最常见的非模态对话框。与模态对话框不同,当非模态对话框出现时,用户还能够实现与应用程序其他对象间的交互操作。

在Visual Studio 2008的MFC单文档工程中,非模态对话框的创建和显示并不是由Do Modal()完成的,而是通过CDialog类的Create()和CWnd类的Show Window()成员函数实现的,它们各自的语法形式如下。

Create()函数:

该函数的作用是使用对话框模板资源创建一个无模态对话框。在Create()函数的两种表达形式中,参数lpsz Template Name和n IDTemplate分别用于指明所使用的对话框模板资源的名字和ID值,而参数p Parent Wnd则指向该对话框的父窗体对象。

Show Window()函数:

该函数的作用是为窗口设置可见的状态。其中,参数n Cmd Show规定了当前窗口将以何种方式被显示,其可取值及含义如表6-1所示。

创建并显示一个非模态对话框的基本流程如下。

01 为MFC单文档工程添加一个对话框资源。

02 为该对话框资源声明一个派生于CDialog的类。

03 创建并显示非模态对话框。

具体步骤如下。

01 使用“添加资源”对话框向MFC单文档工程中添加一个新的Dialog资源,详情请参考在上一小节中创建并显示一个模态对话框的步骤1。

02 双击对话框模板,并通过弹出的“MFC类向导”为新添加的对话框资源声明一个派生于CDialog的类,详情请参考在上一小节中创建并显示一个模态对话框的步骤2。

03 在应用程序App类的头文件中声明一个该对话框类的指针对象,并在App类的构造函数中将其赋值为NULL,然后,在App类的成员函数Init Instance()中通过对话框类的成员函数Create()和CWnd类的Show Window()实现非模态对话框的创建及显示,具体代码如下:

经过如上3步之后,就成功地创建了一个非模态对话框,当该对话框被调用时,用户仍然可以与其所属应用程序的其他对象进行交互,程序的执行结果如图6-6所示。图6-6 非模态对话框

入行提示

模态对话框与非模态对话框的根本区别就在于启动对话框的方式不同。由于Create()函数并不会像Do Modal()一样启用新的消息循环,而且在显示了对话框后Create()会立即返回(Do Modal()在对话框被关闭后才返回),所以非模态对话框并不会独占用户输入。另外,对于非模态对话框而言,关闭操作是通过CWnd类的成员函数Destroy Window()实现的,而不是CDialog类的End Dialog(),这一点与模态对话框也是不同的。6.3 控件

控件常用于在对话框上获取用户输入以实现人机交互。一般地,屏幕上所有能够接受输入的区域都可以视为窗口,换句话说,控件实际上也是一种特殊的窗口,它们通常是被当做对话框的子窗口而创建的。

在VC++的MFC中,每一种控件都有其对应的MFC控件类,而所有的控件类又都派生于CWnd类,通过这些控件类开发人员可以很方便地实现对控件的控制并处理控件事件。在MFC中所包含的通用控件及其对应的控件类如表6-2所示。

下面将对MFC中各种通用控件的功能及使用的方法和过程进行详细介绍。6.3.1 静态控件

视频精讲:光盘\video\06\静态控件.swf

静态控件包括静态文本控件和Picture控件这两种,通常用于显示文本数据和图片,是一种单项交互的控件。在对话框模板中添加静态控件时,所有静态控件的ID都被默认设置成IDC_STATIC,因此,如果想要为静态控件添加控制变量或事件处理函数,必须先为它们重新指定一个唯一的ID值。修改ID值的操作可以在静态控件的“属性”窗口中进行,如图6-7所示。图6-7 修改静态控件ID值

在Visual Studio 2008下使用静态控件显示文本和图片的基本流程如下。

01 向MFC单文档工程中添加所需的资源和静态控件。

02 设置静态文本控件及Picture控件的属性,并为Picture控件添加控制变量。

其具体步骤如下。

03 重载对话框类的On Init Dialog()函数,在函数中为Picture控件加载位图。

01 使用“添加资源”对话框向MFC单文档工程导入一个Bitmap资源并新建一个Dialog资源,然后在对话框模板中添加一个静态文本控件和一个Picture控件,如图6-8所示。图6-8 添加所需资源

02 在“属性”窗口中将静态文本控件的Caption属性设置为待显示的文本内容,然后,修改Picture控件的ID值使其唯一,更改“Type”属性为“Bitmap”,如图6-9所示,再通过“添加成员变量向导”为其添加一个控制变量。图6-9 修改静态控件的属性

03 重载对话框模板的On Init Dialog()函数,在函数中加载位图,并通过CStatic类的成员函数Set Bitmap()将其显示到Picture控件中,具体代码如下。

经过如上3步之后,就在对话框模板上添加了一个用于显示文本信息的静态文本控件和一个用于显示位图图片的Picture控件,其执行结果如图6-10所示。图6-10 静态控件的使用6.3.2 下压按钮

视频精讲:光盘\video\06\下压按钮.swf

下压按钮通常用于在响应用户的单击消息时执行某项命令以完成特定的功能。通过在“属性”窗口中设置下压按钮控件的Caption属性值,开发人员可以对显示在下压按钮上的文本信息进行修改。另外,还可以使用CWnd类提供的成员函数Modify Style()来为下压按钮设置特殊的显示风格,该函数的语法形式如下:

函数的功能是更改窗体的风格。其中,参数dw Remove指明了将被移除的窗体风格,而参数dw Add指明了需要添加的窗体风格。

下面将通过一个具体的实例为大家讲解在对话框模板中显示带位图的下压按钮的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的资源和下压按钮控件,并为控件添加控制变量。

02 重载对话框模板的On Init Dialog()函数,在函数中设置下压按钮控件的显示风格并加载位图。

03 为下压按钮添加消息处理程序以响应单击事件。

具体步骤如下。

01 使用“添加资源”对话框为MFC单文档工程新建一个对话框模板并导入一个Bitmap资源,然后,为对话框模板添加两个下压按钮控件,如图6-11所示,再通过“添加成员变量向导”为它们添加控制变量。图6-11 添加所需资源

02 重载对话框模板的On Init Dialog()函数,在函数中通过CWnd类的Modify Style()成员函数为下压按钮控件设置显示风格,再使用CWnd类的Set Bitmap()成员函数加载位图,具体代码如下:

03 通过“事件处理程序向导”为两个下压按钮分别添加响应BN_CLICKED消息的事件处理程序,完成对所单击按钮的提示功能,具体代码如下:

经过如上3步之后,就在对话框模板上添加了能够显示位图图片的下压按钮控件,并通过各自的单击事件处理函数完成了相应的提示功能,其执行结果如图6-12所示。图6-12 下压按钮的使用6.3.3 单选按钮

视频精讲:光盘\video\06\单选按钮.swf

单选按钮通常是由一个可选中的圆形按钮和一个描述选项含义的字符串组成的,用于表示一组互斥的关联选项。换句话说,在一组单选按钮中,同一时间只能有一个按钮被选中。单选按钮的基本状态有3种,即选中、未选中及不确定。通过CButton类的成员函数Get Check()和Set Check()可以实现对单选按钮状态的获取和设置,它们各自的语法形式如下。

Get Check():

该函数的功能是获取单选按钮的当前状态。其中,函数返回0时表示按钮没有被选中,返回1时表明选中,而返回2时则代表按钮的状态不确定。

Set Check():

该函数的功能是设置单选按钮的当前状态。其中,当参数n Check为0时表示按钮未被选中,为1时表明选中,而为2时则代表按钮的状态不确定。

下面将通过一个具体的实例为大家讲解在对话框模板中使用单选按钮获取用户输入的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的资源及下压按钮控件、单选按钮控件,并为单选按钮控件添加控制变量。

02 为下压按钮添加消息处理程序以响应单击事件,并在函数中实现获取用户所选项的功能。具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加两个单选按钮、1个静态文本控件及1个下压按钮,如图6-13所示。图6-13 添加所需的资源

02 通过“事件处理程序向导”为下压按钮添加响应BN_CLICKED消息的事件处理程序,并在程序中完成获取用户所选项的功能,具体代码如下:

经过如上两步之后,就在对话框模板上添加了一组互斥的单选按钮,并通过下压按钮的单击事件处理函数实现了对所有单选按钮当前状态的获取和判断,其执行结果如图6-14所示。图6-14 单选按钮的使用6.3.4 复选框

视频精讲:光盘\video\06\复选框.swf

复选框通常由一个可选中的方形框和一个描述选项含义的字符串组成,用于表示一组可多选的复选框。也就是说,在一组复选框中,同一时间允许有多个复选框被选中。与单选按钮类似,复选框的基本状态也分为3种:选中、未选中及不确定,另外,通过CButton类的成员函数Get Check()和Set Check()同样可以实现对复选框状态的获取和设置。由于在上一小节中已经给出了Get Check()和Set Check()函数的语法形式,这里不再赘述。

下面将通过一个具体的实例为大家讲解在对话框模板中使用复选框获取用户输入的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的资源及下压按钮控件、复选框控件,并为复选框控件添加控制变量。

02 为下压按钮添加消息处理程序以响应单击事件,并在函数中获取、判断用户所选择的结果。具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加两个复选框、1个静态文本控件及1个下压按钮,如图6-15所示。图6-15 添加所需的资源

02 通过“事件处理程序向导”为下压按钮添加响应BN_CLICKED消息的事件处理程序,并在程序中获取、判断用户所选择的项目,具体代码如下:

经过如上两步之后,就在对话框模板上添加了一组可多选的复选按钮,并通过下压按钮的单击事件处理函数实现对所有复选按钮当前状态的获取及判断,其执行结果如图6-16所示。图6-16 复选框的使用6.3.5 编辑框控件

视频精讲:光盘\video\06\编辑框控件.swf

编辑框是一个可以接受键盘输入和实现文本输出的控件,在“属性”窗口中开发人员可以对编辑框的属性进行适当的设置。编辑框控件的常用属性及其含义如表6-3所示。

开发人员可以使用CWnd类的成员函数Get Window Text W()和Set Window Text W()来获取用户的输入,并对编辑框中待显示的内容进行设置。下面将通过一个具体的实例为大家讲解在对话框模板中使用编辑框获取用户输入的方法和过程,其基本流程如下。

向MFC单文档工程中添加所需的资源及下压按钮控件、编辑框控件,并为编辑框控件添加控01制变量。

具体步骤如下。

02 为下压按钮添加消息处理程序以响应单击事件,在函数中获取用户的输入。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加1个编辑框控件、1个静态文本控件及1个下压按钮控件,如图6-17所示,再通过“添加成员变量向导”为编辑框添加控制变量。图6-17 添加所需的资源

02 通过“事件处理程序向导”为下压按钮添加响应BN_CLICKED消息的事件处理函数,并在函数中获取用户在编辑框中输入的字符,具体代码如下:

经过如上两步之后,就在对话框模板上添加了一个用于接收用户输入的编辑框控件,并通过对下压按钮单击消息的响应实现了对输入数据的获取,其执行结果如图6-18所示。图6-18 编辑框的使用6.3.6 列表框控件

视频精讲:光盘\video\06\列表框控件.swf

列表框控件通常用于显示单列的数据项。VC++的MFC为列表框控件提供了多个功能函数以满足开发人员的操作需求,其中,比较常用的函数如下。

Get Count():

该函数的功能是返回列表框中所包含字符串的个数。

Get Text():

该函数的功能是从列表框中获取一个字符串。其中,参数n Index指明了待获取的字符串在列表框中的编号,而参数r String则指向一个用于接收该字符串的CString对象。

Add String():

该函数的功能是向列表框中添加一个字符串。其中,参数lpsz Item指明了待添加的非空字符串。

Delete String():

该函数的功能是从列表框中删除一个字符串。其中,参数n Index指明了待删除的字符串的编号。

Get Cursel():

该函数的功能是获得当前所选中的列表项。

下面将通过一个具体的实例为大家讲解在对话框模板中使用列表框编辑数据的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的对话框资源及列表框等相关控件,并为控件添加变量。

02 添加处理程序以获取用户输入并将其显示到列表框中。

具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加1个编辑框控件、1个静态文本控件、1个列表框控件及1个下压按钮控件,如图6-19所示,再通过“添加成员变量向导”为编辑框和列表框添加控制变量。图6-19 添加所需的资源

02 通过“事件处理程序向导”为下压按钮添加响应BN_CLICKED消息的事件处理函数,以获取用户在编辑框中输入的字符并将它们添加到列表框中,具体代码如下:

经过如上两步之后,就在对话框模板上添加了一个用于接收用户输入数据的编辑框控件,并通过下压按钮的事件处理函数实现将输入字符添加至列表框中的功能,其执行结果如图6-20所示。图6-20 列表框的使用6.3.7 组合框控件

视频精讲:光盘\video\06\组合框控件.swf

组合框控件相当于是编辑框和列表框的组合,其组合形式主要有3种:简单组合框、下拉组合框及下拉列表框,它不仅可以接受新的输入,还允许用户对列表框中的已有列表项进行选择。通过设置组合框的Type属性开发人员可以轻松地实现组合框的形式转换。

VC++的MFC也为组合框控件提供了多个功能函数以满足不同的操作需求,其中,比较常用的函数如下。

Get Count():

该函数的功能是返回组合框的列表框中所包含字符串的个数。

Get LBText():

该函数的功能是从组合框的列表框中获取一个字符串。其中,参数n Index指明了待获取的字符串在列表框中的编号,而参数r String则指向一个用于接收该字符串的CString对象。

Add String():

该函数的功能是向组合框的列表框中添加一个字符串。其中,参数lpsz String指明了待添加的非空字符串。

Delete String():

该函数的功能是从组合框的列表框中删除一个字符串。其中,参数n Index指明了待删除的字符串编号。

Get Cursel():

该函数的功能是确定组合框中的哪一项被选中。

下面将通过一个具体的实例为大家讲解在对话框模板中使用组合框获取数据的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的对话框资源及组合框等相关控件,并为控件添加变量。

02 重载对话框类的On Init Dialog()函数,在函数中为组合框的列表框添加数据。

具体步骤如下。

03 添加处理程序以获取用户的输入或选择项。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加1个静态文本控件、1个组合框控件及1个下压按钮控件,如图6-21所示,再通过“添加成员变量向导”为组合框添加控制变量。图6-21 添加所需的资源

02 为对话框类重载成员函数On Init Dialog(),并在函数中通过Add String()向组合框的列表框添加数据,具体代码如下:

03 通过“事件处理程序向导”为下压按钮添加响应BN_CLICKED消息的事件处理函数,以获取用户在编辑框中输入的字符或在列表框中选择的项,具体代码如下:

经过如上3步之后,就在对话框模板上添加了一个可以接收输入并允许用户在列表框中选择已有数据的组合框控件,并通过下压按钮的事件处理函数实现获取组合框信息的功能,其执行结果如图6-22所示。图6-22 组合框的使用6.3.8 滚动条控件

视频精讲:光盘\video\06\滚动条控件.swf

滚动条控件是由两端的滚动箭头和中间的一个滚动框组成的,用户既可以通过鼠标单击两端的箭头使滑块移动,也可以直接用鼠标拖动使其移动。在VC++中,滚动条控件通常有水平滚动条和垂直滚动条这两种,常用的操作滚动条控件的功能函数如下。

Set Scroll Range():

该函数的功能是为滚动条设置相应的取值范围。其中,参数n Min Pos对应的是最小位置处的值,而参数n Max Pos则对应最大位置处的值。

Set Scroll Pos():

该函数的功能是为滚动条控件指定新的位置并重画控件以显示这种改变。其中,参数n Pos即为滚动条的新位置。

Get Scroll Pos():

该函数的功能是获得滚动条的当前位置。

下面将通过一个使用滚动条控件控制颜色渐变的实例为大家讲解在对话框模板中使用滚动条控件的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的对话框资源及垂直滚动条控件,并为控件添加变量。

02 重载对话框类的On Init Dialog()函数,在函数中对滚动条控件的滚动范围及起始位置进行设置。

03 添加处理程序以响应滚动条的滚动事件。

04 重载对话框类的On Paint()函数,实现以渐变色创建笔刷并填充矩形区域的功能。

具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加1个垂直滚动条控件,如图6-23所示,再通过“添加成员变量向导”为该滚动条添加控制变量。图6-23 添加所需的资源

02 为对话框类重载成员函数On Init Dialog(),并在函数中通过Set Scroll Range()函数为滚动条控件设置滚动的范围,使用Set Scroll Pos()函数设置滚动条的起始位置,具体代码如下:

03 通过“事件处理程序向导”为对话框模板添加响应WM_VSCROLL消息的事件处理函数On VScroll(),以获取滚动条的位置变化,具体代码如下:

04 为对话框类重载成员函数On Paint(),并在函数中使用滚动条控件的当前值设置笔刷颜色用于填充矩形区域,具体代码如下:

经过如上4步之后,就在对话框模板上添加了一个滚动条控件,并根据滚动条的当前位置动态地设置笔刷颜色以实现对矩形区域的填充,其执行结果如图6-24所示。图6-24 滚动条的使用6.3.9 列表控件

视频精讲:光盘\video\06\列表控件.swf

列表控件通常用于以图标或表格的形式显示并管理数据,通过修改其View属性可以实现列表显示风格的转变。在VC++中,常见的用于操作列表控件的功能函数如下。

Insert Column():

该函数的功能是向列表控件中插入新的一列。其中,参数n Col对应的是新列的编号,参数lpsz Column Heading给出了新列的标题,参数n Format指明了本文信息的对齐方式,参数n Width规定了列的宽度,而参数n Sub Item则给出了与列相关的子项编号。

Insert Item():

该函数的功能是向列表控件中插入一个新项。其中,参数n Item指明了项目待插入的位置编号,而参数lpsz Item则为包含该新项标签的字符串地址。

Set Item Text():

该函数的功能是更改一个列表项或子项的文本信息。其中,参数n Item指明了列表项的编号,参数n Sub Item指明了列表项的子项,而参数lpsz Text则给出了新的文本信息。

Get Item Count():

该函数的功能是获得列表控件的数据项的个数。

Get Item State():

该函数的功能是返回一个列表项的状态。其中,参数n Item为待返回状态的列表项编号,而参数n Mask则指明了需要返回的状态。

Get Item Text():

该函数的功能是返回一个列表项或子项的内容。其中,参数n Item指明了待返回的列表项编号,而参数n Sub Item则指明了待返回的子项编号Set Extended Style():

该函数的功能是设置列表控件当前的扩展风格。其中,参数dw New Style是一个扩展风格的组合。

下面将通过一个具体的实例为大家讲解在对话框模板中使用列表控件的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的对话框资源及列表等相关控件,并为控件添加变量。

02 重载对话框类的On Init Dialog()函数,在函数中对列表控件的风格进行设置并添加数据。

03 添加处理程序以实现对用户选中项的文本信息的获取功能。

具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在模板上添加1个列表控件、1个编辑框控件和1个静态文本控件,如图6-25所示,再通过“添加成员变量向导”为列表控件及编辑框控件添加控制变量。图6-25 添加所需的资源

02 为对话框类重载成员函数On Init Dialog(),在函数中设置列表控件的显示风格,然后,通过CList Ctrl类的成员函数Insert Column()向列表控件添加列,并使用Insert Item()和Set Item Text()函数为新添加的列插入数据,具体代码如下:

03 通过“事件处理程序向导”为列表控件添加响应NM_CLICK消息的事件处理函数On NMClick List1(),以获取用户选中的列表项的内容,具体代码如下:

经过如上3步之后,就在对话框模板上添加了一个列表控件,并通过响应控件的NM_CLICK消息实现了对用户所选列表项的文本信息的获取,其执行结果如图6-26所示。图6-26 列表的使用6.3.10 Tab控件

视频精讲:光盘\video\06\Tab控件.swf

标签控件常被用于根据不同的索引显示不同的信息。在VC++中,当想要向不同的标签页中添加不同的子控件时,常用的实现方法有两种:一种是事先添加好所有的可用控件,然后,在处理标签控件的TCN_SELCHANGE消息时,根据当前的标签索引动态地显示或隐藏相应的控件,但这样做会使得整个界面显得很凌乱;另一种方法是通过子对话框来实现,即将每个标签页下所对应的控件添加到一个对应的子对话框中,然后,在响应标签控件的TCN_SELCHANGE消息时,根据当前的标签索引在标签页中动态地关联并显示其对应的子对话框。

下面将通过一个具体的实例为大家讲解在对话框模板中使用Tab控件的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加所需的资源及Tab等控件,并为它们声明变量。

02 重载对话框类的On Init Dialog()函数,在函数中为Tab控件添加新的标签页,并关联、显示对应的子对话框。

03 添加消息处理程序以实现根据当前索引显示不同信息的功能。

具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,在模板上添加1个Tab控件,如图6-27所示;然后,添加两个对话框模板,并将它们的Border属性和Style属性分别设置为None及Child,如图6-28所示;最后,在子对话框上添加、编辑静态文本子控件,并为所有的对话框资源及Tab控件声明变量。图6-28 编辑子对话框的属性

02 重载对话框类的成员函数On Init Dialog(),在函数中使用CTab Ctrl类的成员函数Insert Item()为Tab控件添加两个新的标签页,并将两个子对话框分别关联、显示到其对应的标签页中,具体代码如下:

03 通过“事件处理程序向导”为Tab控件添加响应TCN_SELCHANGE消息的事件处理函数On Tcn Selchange Tab1(),根据当前的标签索引显示或隐藏对应的子对话框,具体代码如下:

经过如上3步之后,就在对话框模板上添加了一个Tab控件,并通过响应控件的TCN_SELCHANGE消息实现了根据当前索引显示对应文本信息的功能,其执行结果如图6-29和图6-30所示。图6-29 Tab的使用1图6-30 Tab的使用26.4 属性单和属性页

视频精讲:光盘\video\06\属性单和属性页.swf

在VC++中,开发人员通常使用CProperty Sheet类结合CProperty Page类来创建具有选项卡风格的界面,以实现类似6.3.10小节中通过Tab控件完成的功能。在制作这种具有选项卡风格的界面时,一个CProperty Page类(属性页)的对象代表了一个单独的选项卡,而一个CProperty Sheet类(属性单)的对象则对应于显示所有选项卡的窗口。CProperty Page类派生于CDialog类,而CProperty Sheet类则是CWnd类的子类。

属性单也有模态和非模态之分,这一点与对话框相同,另外,两种属性单的创建和显示方法也与模态对话框、非模态对话框的创建过程类似,即模态属性单通过Do Modal()实现,而非模态属性单则通过Create()生成。下面将通过一个具体的实例为大家讲解在Visual Studio 2008下使用属性单和属性页的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加属性页资源,并为它们声明派生于CProperty Page的类。

02 在应用程序App类的成员函数Init Instance()中为属性单添加属性页,并实现模态属性单和非模态属性单的创建、显示。

具体步骤如下。

01 通过“添加资源”对话框为MFC单文档工程新建两个IDD_PROPPAGE_LARGE类型的Dialog资源(即属性页),并在模板上分别添加、编辑1个静态文本控件,如图6-31和图6-32所示。图6-31 添加、编辑属性页1图6-32 添加、编辑属性页2

02 在应用程序App类的成员函数Init Instance()中通过CProperty Sheet类的成员函数Add Page()为属性单添加两个属性页,并分别使用Do Modal()和Create()显示一个模态属性单及一个非模态属性单,具体代码如下:

经过如上两步之后,就实现了显示模态属性单和非模态属性单的功能,其执行结果如图6-33所示,其中,左边显示的是模态属性单,右侧的则是非模态属性单。图6-33 模态属性单及非模态属性单

入行提示

模态属性页除了在生成方法及操作模式方面与非模态属性页有所不同外,它们之间另一个比较明显的差别就在于模态属性页会自动生成3个按钮——“确定”、“取消”和“应用”,而非模态属性页则必须通过开发人员的手动添加才能实现类似的功能。6.5 透明对话框的实现

视频精讲:光盘\video\06\透明对话框的实现.swf

为了开发更具特色的应用程序,开发人员通常需要为应用程序制作一些特殊的显示效果,例如,使窗体透明等。通过API函数Set Layered Window Attributes()可以设置窗体的扩展风格以实现窗体的透明化,函数的语法形式如下:

函数的功能是设置窗体的透明色。其中,参数hwnd为窗口的句柄;参数cr Key是一个COLORREF类型的值用于指明生成窗体时所使用的透明色;参数b Alpha描述了窗体的不透明度,当其值为0时,窗口完全透明,当为255时,窗口完全不透明;而参数dw Flags则指明了待执行的动作。

另外,在使用Set Layered Window Attributes()函数时,需要先手动导入User32.DLL动态链接库。下面将通过一个具体的实例为大家讲解在Visual Studio 2008下制作透明对话框的方法和过程,其基本流程如下。

01 向MFC单文档工程中添加一个对话框资源。

具体步骤如下。

02 重载对话框类的On Init Dialog()函数,在函数中为对话框设置扩展风格以实现透明显示。

01 通过“添加资源”对话框为MFC单文档工程新建一个对话框模板,并在应用程序App类的成员函数Init Instance()中将其实例化。

02 重载对话框类的On Init Dialog()函数,在函数中手动加载动态链接库User32.DLL,并通过Set Layered Window Attributes()函数设置透明颜色值以实现对话框的透明显示,具体代码如下:

经过如上两步之后,就实现了一个透明的对话框,其执行结果如图6-34所示。图6-34 透明对话框6.6 回到案例场景

视频精讲:光盘\video\06\本关任务.swf

本关任务

在Visual Studio 2008下设计并制作一个具有类QQ窗体风格的应用程序,其执行效果如图6-35所示。图6-35 类QQ风格的窗体6.6.1 基本思路

在学习了本章内容之后,我们现在完全可以使用对话框模板和通用控件来实现这种类QQ风格窗体的设计制作,其基本流程如下。

01 向MFC单文档工程添加所需的资源及控件,并为它们声明控制变量。

02 重载对话框类的On Init Dialog()函数,在函数中对待显示的窗体进行初步设置。

03 为控件添加事件处理程序以完成窗体布局的转换。

具体步骤如下。

04 编辑代码以实现按照窗体布局的变化重新对控件进行设置的功能函数。

01 通过“添加资源”对话框为MFC单文档工程新建1个Dialog模板和3个Icon资源,并在模板上添加3个下压按钮及1个列表控件,调整控件布局至如图6-36所示,然后,为这些资源、控件声明相应的控制变量。图6-36 添加、编辑所需的资源

02 重载对话框类的On Init Dialog()函数,在函数中设置列表控件的显示风格、获得4个控件的初始位置,并调用一个自定义的功能函数Show Icon Item()以调整、显示当前窗体。

03 分别为3个下压按钮控件添加响应BN_CLICKED消息的事件处理程序,以根据用户的选择及时修改控件的显示位置,调整窗体布局。

04 编辑Show Icon Item()函数的代码,根据用户的选择重新为列表控件插入数据,并加载对应的图标资源,以实现按照窗体布局的变化重新设置控件的显示内容的功能。6.6.2 代码演练

下面将给出实现本关任务的步骤2至步骤4的关键代码。(1)重载的On Init Dialog()函数:(2)下压按钮控件的事件处理程序:(3)Show Icon Item()函数的实现:

经过如上4步之后,就实现了本关任务中类QQ窗体的设计制作,其执行结果如图6-37和图6-38所示。图6-37 类QQ风格的窗体1图6-38 类QQ风格的窗体26.7 本章小结与习题

本章主要介绍了对话框的基本概念、生成模态对话框和非模态对话框的方法、通用控件各自的功能和使用方法、属性页和属性单的概念和用法,以及制作透明对话框的具体过程等,下面我们将对这些内容中的关键知识点进行回顾和巩固。6.7.1 重点回顾

对话框是一种能够包含各种通用控件的特殊窗口,通过这些添加到其上的控件对话框可以完成指定的命令或任务。

对话框通常有两种形式,即模态对话框和非模态对话框,它们无论在生成方法方面还是在操作方式方面都有很大的不同。

控件实际上也是一种特殊的窗口,它们通常是被当做对话框的子窗口而创建的。

静态控件包括静态文本控件和Picture控件两种,通常用于显示文本数据和图片。

下压按钮通常用于在响应用户的单击消息时执行某项命令以完成特定的功能。

在一组单选按钮中,同一时间只能有一个按钮被选中。

在一组复选框中,同一时间允许有多个复选框被选中。

编辑框是一个可以接受键盘输入和实现文本输出的控件,通过“属性”窗口开发人员可以对编辑框的属性进行适当的设置。

列表框控件通常用于显示单列的数据项。

组合框控件相当于是编辑框和列表框的组合,它不仅可以接受新的输入,还允许用户对列表框中的已有列表项进行选择。

滚动条控件是由两端的滚动箭头和中间的一个滚动框组成的,用户既可以通过鼠标单击两端的箭头使滑块移动,也可以直接用鼠标拖动使其移动。

列表控件通常用于以图标或表格的形式显示并管理数据,通过修改其View属性可以实现列表显示风格的转变。

标签控件常被用于根据不同的索引以显示不同的信息。

在VC++中,开发人员通常使用CProperty Sheet类结合CProperty Page类来创建具有选项卡风格的界面。

通过API函数Set Layered Window Attributes()可以设置窗体的扩展风格以实现窗体的透明化,使用该函数时需要加载User32.DLL动态链接库。6.7.2 课后练习

1.模态对话框和非模态对话框之间有什么区别?

2.应当如何理解控件?

3.如何在Tab控件上添加控件?

4.属性单和属性页之间有怎样的联系?如何实现模态和非模态属性页?

5.编辑代码实现使用静态文本空间显示位图的功能。

6.编辑代码实现具有图标的标签页。第7章 打印

大师的话

MFC应用程序框架大大简化了打印工作,还提供了打印预览机制,它完全能胜任Windows商业化程序中的打印和打印预览功能的实现。

入行目标

了解利用MFC打印原理

了解利用MFC打印控制技术

理解打印流程

掌握打印文档的方法

掌握打印图像的方法

掌握打印表格的方法7.1 案例场景

文件的打印与打印预览功能在应用程序中比较常见,但它们的实现却比较复杂。与文件的操作功能相同,程序设计人员可以利用MFC类库机制,帮助程序员构建出打印与打印预览功能。MFC类库已经完成了大部分的工作,并且提供了打印预览机制,程序员需要填充为完成的部分即可。本章将介绍如何利用MFC类库实现打印与打印预览功能。7.1.1 打印学生成绩单

本关任务

完成学生成绩单的打印。学生成绩单的主体是表格形式,表格的内容包括在第一行表头中所列的以下几项:学生学号、姓名、平时成绩、考试成绩、综合成绩等。同时,支持页眉的打印和打印预览。7.1.2 我们现在能做的……

在没有学习MFC类库提供的打印机制前,最直接的实现方法是调用Windows API中支持打印机的函数。Windows提供了诸如Start Doc()、End Doc()、Start Page()、End Page()等API函数与打印机进行通信。

一个程序必须在可以打印到打印机之前使用Create DC()函数为打印机创建一个设备上下文。在应用程序为打印机请求设备上下文后,则通过调用Start Doc()函数开始打印处理。Start Doc()需要一个DOCINFO结构作为参数,该结构描述了应用程序将要打印的文档。开始打印之后,应用程序必须调用Start Page()函数开始每一页,调用End Page()函数结束每一页。以下函数内容展示了将文本信息输出到打印机所需步骤的简单实现。

首先,通过函数Create DC()创建打印机设备环境,数据结构DOCINFO作为保存要打印文档的属性信息。当打印环境准备就绪,开始绘制表格打印,并且需要一页一页打印,打印完第一页立即结束打印第一页,如此反复直到所有文档打印完毕,结束打印。

入行提示

显然,这种类型的简单实现只适合于非常小的打印作业。如果有更大型打印作业的应用程序,应该给用户提供在处理过程中停止打印作业的方法。可以显示一个带“Cancel”按钮的对话框,警告用户正在进行的打印作业,并给用户中断作业的机会。还可以用“异常中止过程”来代替,这是应用程序定义的一个函数,用来在应用程序打印时处理消息。API函数Set Abort Proc()可以设置异常中止过程,并把异常中止过程的地址传递给开始打印作业之前的那个函数。7.2 打印概述

视频精讲:光盘\video\07\打印概述.swf

由上一小节可以看出,若在Windows下采用传统的API方法设计打印程序是一项非常繁重的任务。不仅获得一个针对打印机的设备环境,然后调用几十个打印函数,设计复杂的数据结构,还要考虑分页、编写回调过程、启动、禁止窗口和预览的其他细节。而采用MFC方法编写打印程序,上述问题就变得非常容易。它无须关心底层函数,比如start Page()或者End Page(),也无须为打印机缓冲或者处理中断获得DC,所需要做的就是重载一个或多个虚函数。

MFC默认的打印机制已经比较智能了,但在实际应用中仍存在一些有待解决的问题。所以,在介绍具体的打印控制技术之前,我们先讲解一下建立打印功能过程中常见的问题。

不同设备的不同分辨率。

设备环境。

映射模式的选择。

下面介绍MFC打印机制解决这些问题的方法。7.2.1 分辨率

分辨率的定义是单位长度内包含的像素数目。根据涉及对象的不同,分辨率表达的含义也会有所不同。这里主要探讨与打印相关的图像分辨率、显示分辨率和输出分辨率。

图像分辨率:在图像中存储的信息量。这种分辨率有多种衡量方法,典型的是以“像素/英寸”(pixel per inch, ppi)来衡量的。图像分辨率是描述图像本身精细程度的一个量度。图像分辨率和图像尺寸(高×宽)的值一起决定文件的大小及输出的质量,该值越大图形文件所占用的磁盘空间也就越多。图像分辨率以比例关系影响着文件的大小,即文件大小与其图像分辨率的平方成正比。如果保持图像尺寸不变,将图像分辨率提高一倍,则其文件大小增大为原来的4倍。

显示分辨率:主要指屏幕分辨率,即屏幕图像的精密度,它是指显示器所能显示的像素的多少。通常被表示成水平和垂直方向上的像素数量,比如640×480等;而在某些情况下,它也可以同时表示成“像素/英寸”(pixel per inch, ppi),比如72ppi;同时也可以表示为图形的长度和宽度,如8×6英寸。显示器可显示的像素越多,画面就越精细,同样的屏幕区域内能显示的信息也越多,所以分辨率是个非常重要的性能指标。

打印机分辨率:是指在打印输出时横向和纵向两个方向上每英寸最多能够打印的点数,通常以“点/英寸”(dot per inch, dpi)表示。它一般只用一个数字来标识,即垂直分辨率和水平分辨率都是这个数字。如一台产品的分辨率是360dpi,表示每平方英寸的区域表现力最高可以达到水平360个点,垂直360个点。打印分辨率是衡量打印机打印质量的重要指标,它决定了打印机打印图像时所能表现的精细程度,分辨率越高,数值越大,也就意味着产品输出的质量越高。

入行提示

从技术角度说,“像素”(pixel)只存在于计算机显示领域,而“点”(dot)只出现于打印或印刷领域。像点是硬件设备中最小的显示单位,而像素既可以代表一个点,也可以是多个点的集合。当每个像素只代表一个像点时,两者的含义是相同的;不过在大多数情况下,两者是完全不同的。

显示器分辨率与打印机分辨率均属于设备分辨率。设备分辨率与用该设备处理的图像分辨率是两个既有联系又有区别的概念。例如在打印一幅图像时,首先涉及图像本身的分辨率,打印预览时涉及显示器的分辨率,最后打印输出时,又涉及打印机的分辨率。

设备分辨率是由硬件设备的生产工艺决定的,尽管可以通过软件的方法调整设备的分辨率,但它们都有一个局限的最高分辨率,用户不能对它有任何突破。图像本身是否精细只与图像自身的分辨率有关,而与处理它的硬件设备的分辨率无关,但图像的处理结果是否精细却与处理它的设备的分辨率直接相关。如果图像本身分辨率很低,那么就算打印机分辨率再高也得不到高质量的图片。

设备分辨率与图像分辨率在一般情况下是不同的,所以当同一幅图像显示在屏幕上的尺寸和打印出来的尺寸也是不同的。假设在分辨率设置为1024×768(对角线有1028个像素)的17英寸屏幕上显示一幅图像,其大小与屏幕相同。当使用分辨率为600dpi的打印机打印这张图时,其大小却是1.71×1.28(单位:英寸)。这是因为在屏幕上一英寸的长度,是由75个像素所组成的,而到打印机上一英寸却由600个像素点组成。所以,屏幕上的一英寸的长度对应到打印机上时,缩小近八分之一左右。

由于不同的设备与待处理的图像具有不同的分辨率,为了图像在显示和打印时能够按比例高质量地输出,在打印图像时需要正确地设置各个设备的分辨率。7.2.2 设备环境

为了体现Windows的设备无关性,应用程序的输出不直接面向显示器或打印机等物理设备,而是面向一个称之为设备环境(Device Context, DC)的虚拟逻辑设备。所谓的设备(Device)泛指各种与数据输出有关的设备,如:屏幕、打印机等。所谓的环境(Context)则是指输出各种设备的数据。设备环境也称设备上下文,从根本上说,设备环境DC是一个Windows数据结构。当要将数据输出至某设备时,你就需要为该设备准备一个DC对象,以便保存要输出的数据。在Windows中不使用DC就无法进行输出,在使用任何GDI绘图函数之前,用户必须建立一个设备环境,应用程序每一次绘图操作均按照设备环境中设置的绘图属性进行。

采用MFC方法编程,MFC提供了不同类型的设备环境类,包括CDC、CPaint DC、CClient DC、CWindows DC和CMeta File DC等,其中CDC是基类,其他的都是其派生类。

对于打印机设备环境对象来说,应用程序框架会直接将句柄附在对象上。MFC在开始打印时会调用函数On Prepare Printing(),在调用此函数的同时会自动创建一个打印机设备环境。在打印输出时,CDC对象代表一个打印机设备环境。用户可以重载On Prepare DC()函数来修改用于显示或打印文档的设备环境。

入行提示

对于显示器和打印机设备环境对象来说,应用程序框架会直接将句柄附在对象上;而对其他设备环境(如内存设备环境),为了将对象与句柄相联系,在构造完对象之后,还必须调用一个成员函数进行初始化。7.2.3 映射模式

Windows应用程序绘制图形时使用的是一种逻辑单位,每个逻辑单位的大小由映射模式决定,这个逻辑单位既可以与设备单位(打印机上的一个像素点)相同,也可以是一种物理单位(如毫米),还可以是用户自定义的一种单位。在Windows应用程序中,只要与输出有关系,都要使用映射模式(Mapping Mode)。GDI使用映射模式将逻辑坐标转换为适当的设备坐标。下面将介绍映射模式的一些基本知识,并介绍一些常见问题的解决方案。

1.映射模式基本知识

当Windows应用程序在其客户区绘制图形时,必须给出在客户区的位置,其位置用x和y两个坐标表示,x表示横坐标,y表示纵坐标。在所有的GDI绘制函数中,这些坐标使用的是一种“逻辑单位”。所谓映射模式,就是一种假想的“逻辑坐标”系统,在逻辑坐标中,“10点”逻辑单位可能相当于屏幕上20点或激光打印机上100点。当GDI函数将输出送到某个物理设备上时,Windows将逻辑坐标转换成设备坐标(如屏幕或打印机的像素点)。逻辑坐标和设备坐标的转换是由映射模式决定的。映射模式被存储在设备环境中。Get Map Mode()函数用于从设备环境得到当前的映射模式,Set Map Mode()函数用于设置设备环境的映射模式。

1)逻辑坐标

逻辑坐标是独立于设备的,它与设备点的大小无关。使用逻辑单位,是实现“所见即所得”的基础。当程序员在调用一个画线的GDI函数Line To,画出25.4mm(1英寸)长的线时,他并不需要考虑输出的是何种设备。若设备是VGA显示器,Windows自动将其转化为96个像素点;若设备是一个300dpi的激光打印机,Windows自动将其转化为300个像素点。

2)设备坐标

Windows将在GDI函数中指定的逻辑坐标映射为设备坐标,在所有的设备坐标系统中,单位以像素点为准,水平值从左到右增大,垂直值从上到下增大。

Windows中包括以下3种设备坐标,以满足各种不同的需要。

客户区域坐标。它包括应用程序的客户区域,客户区域的左上角为(0,0)。

屏幕坐标。它包括整个屏幕,屏幕的左上角为(0,0)。屏幕坐标用在WM_MOVE消息中(对于非子窗口)及下面的Windows函数中:Create Window()和Move Window()(都对于非子窗口)、Get Message()、Get Cursor Pos()、Get Window Rect()、Window From Point()和Set Brush Org()中。用函数Client To Screen()和Screen To Client()可以将客户区域坐标转换成屏幕区域坐标,或反之。

全窗口坐标。它包括一个程序的整个窗口,包括标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用Get Window DC()函数得到的窗口设备环境,可以将逻辑单位转换成窗口坐标。

2.逻辑坐标与设备坐标的转换方式

映射方式定义了Windows如何将GDI函数中指定的逻辑坐标映射为设备坐标。在继续讨论映射方式前,需要对Windows有关映射模式的一些术语有所了解:我们将逻辑坐标所在的坐标系称为“窗口”,它依赖于逻辑坐标,可以是像素点、毫米或程序员想要的其他尺度;将设备坐标所在的坐标系称为“视口”,它依赖于设备坐标(像素点)。

通常,视口和客户区域等同。但是,如果程序员用Get Window DC或Create DC获取了一个设备环境,则视口也可以指全窗口坐标或屏幕坐标。点(0,0)是客户区域的左上角。x的值向右增加,y的值向上增加。

对于所有映射模式,Windows都用下面两个公式将窗口坐标转换成视口坐标:x Viewport=(x Window-x Win Org)*(x View Ext/x Win Ext)+x View Orgy Viewport=(y Window-y Win Org)*(y View Ext/y Win Ext)+y View Org

其中,(x Window, y Window)是待转换的逻辑点,而(x Viewport, y Viewport)是转换后的设备点。如果设备坐标是客户区域坐标或全窗口坐标,则Windows在创建一个对象前,还必须将这些坐标转换成屏幕坐标。(x Win Org, y Win Org)是逻辑坐标的窗口原点,而(x View Org, y View Org)是设备坐标的视口原点。在默认的设备环境中,这两个点均设置为(0,0),但它们可以改变。此公式意味着,逻辑点(x Win Org, y Win Org)总被映射为设备点(x View Org, y View Org)。

Windows还能将视口(设备)坐标转换为窗口(逻辑)坐标,换算公式如下:x Window=(x Viewport-x View Org)*(x Win Ext/x View Ext)+x Win Orgy Window=(y Viewport-y View Org)*(y Win Ext/y View Ext)+y Win Org

3.映射模式

逻辑坐标对应屏幕或打印机的像素是由映射模式来规定的。Windows共提供了以下8种映射方式,如表7-1所示。

这8种映射方式可以分为以下3类,详细内容如下。

MM_TEXT映射模式:坐标被映射到像素,x值向右递增,y值向下递增。可用它来表示设备坐标。

固定比例映射模式:MM_HIENGLISH、MM_HIMETRIC、MM_LOMETRIC、MM_LOENGLISH、MM_TWIPS。其中,MM_TWIPS常用于打印机。

可变比例映射模式:MM_ISOTROPIC、MM_ANISOTROPIC。这两种模式允许我们改变它们的比例因子和坐标原点。应用这两种模式,如用户改变窗口的尺寸,绘制的图形大小也会发生相应的变化。

4.相关函数

要想改变某个DC的映射模式可使用函数CDC:Set Map Mode(),它的语法格式如下:

在上述代码中,参数n Map Mode指定新的映射模式,取值可为如表7-1所示的映射方式名称。

查询当前的映射模式可使用函数CDC:Get Map Mode(),其具体语法结构如下:

将视口坐标系原点,即用户看到的坐标系原点,设置为原点的坐标值使用函数CDC:Set Viewport Org(),该函数返回值为之前设置为原点的坐标值,它有两种表现形式,具体语法结构如下:

该函数设计参数说明如下。

参数x、y:要设置为视口原点位置的点坐标。

参数point:要设置为视口原点位置的点坐标。

查询当前的视口原点坐标值使用函数CDC:Get Viewport Org(),该函数返回当前视口原点坐标值,具体语法结构如下:

设置窗口坐标系原点使用函数CDC:Set Window Org(),该函数返回值为之前窗口设置为原点的坐标值,它有两种表现形式,具体语法结构如下:

该函数涉及的参数说明如下。

参数x、y:要设置为视口原点位置的点坐标。

参数point:要设置为视口原点位置的点坐标。

查询当前的窗口原点坐标值使用函数CDC:Get Window Org(),该函数返回当前窗口原点坐标值,具体语法结构如下:

许多MFC库函数只能在设备坐标下工作,比如CWnd的成员函数都以设备坐标作为参数(所有在实际窗口上单击获得的坐标都是逻辑坐标);然而CDC的所有成员函数都以逻辑坐标作为参数。为了解决这个问题,在设置了设备环境的映射模式及相应的参数以后,可以用CDC的LPto DP函数和DPto LP函数在逻辑坐标系和设备坐标系之间进行转换。

函数CDC:LPto DP()将会依照映射模式、客户区窗口与文件原点的设置,把传入函数的对象的坐标,由逻辑坐标系统转换至物理坐标系统(设备/装置坐标系统)。但是转换后x、y坐标值的范围为-32768~32767。若超过此范围则转换后的坐标值将被设置为-32768或32767。它有3种表现形式,其语法结构如下:

该函数涉及的参数说明如下。

参数lp Points:指向要转换坐标的点(CPoint)对象数组的指针。

参数n Count:传入函数的点对象数组中点对象的个数。

参数lp Rect:指向要转换坐标的CRect对象的指针。

参数lp Size:指向要转换坐标的CSize对象的指针。

函数CDC:DPto LP()会依照映射模式、客户区窗口与文件原点的设置,把传入函数的对象的坐标,由物理坐标系统(设备/装置坐标系统)转换至逻辑坐标系统。它有3种表现形式,其语法结构如下:

该函数涉及的参数说明如下。

参数lp Points:指向要转换坐标的点(CPoint)对象数组的指针。

参数n Count:传入函数的点对象数组中点对象的个数。

参数lp Rect:指向要转换坐标的CRect对象的指针。

参数lp Size:指向要转换坐标的CSize对象的指针。7.3 打印原理

视频精讲:光盘\video\07\打印原理.swf

运用MFC的实现打印工作是由应用框架和视图类两部分共同完成的,视图类和应用框架分别完成不同的工作。为了有效地使用打印机,必须了解相关类的成员函数的调用次序,并且知道应该重载哪些函数。本节将介绍MFC提供的打印机资源、保存打印信息的类及打印的流程。7.3.1 加载打印机资源

程序建立打印与打印预览功能时,必须加载MFC所提供的afxprint.rc文件,在该文件中定义了一些建立MFC打印与打印预览所需要的标准命令标识符和程序资源。打印与打印预览的默认标识符分别为:

01 ID_FILE_PRINT和ID_FILE_PRINT_PREVIEW,加载该文件的步骤如下。

请将方案信息区切换到“资源视图”窗口,用鼠标右键单击资源文件的文件夹,在弹出的快捷菜单中选择“资源包括”命令,如图7-1所示。图7-1 “资源包括”命令

02 在弹出的“资源包括”对话框的“编译时指令”文本框中,输入“#include"l.CHS\afxprint.rc"”,最后单击“确定”按钮完成设置,具体设置如图7-2所示。图7-2 “资源包括”对话框7.3.2 CPrintInfo类

在MFC的打印结构中,CPrint Info结构是一个重要组成部分,它维护了单个打印任务所需要的所有参数。CPrint Info还携带了与打印相关的信息,包括输出是送往打印机还是打印预览窗口,以及打印当前页。这个结构贯穿了MFC的打印周期。CPrint Info类中几个较重要的成员变量如表7-2所示。

CPrint Info类中较为重要的成员函数如表7-3所示。7.3.3 MFC打印流程

MFC的打印机制主要由CView类所完成,与文件存取的序列化机制类似,我们必须重载再定义某些CView类的成员函数,才能建立出符合需求的打印机制。在MFC中打印的流程如图7-3所示,它说明了可被重载函数的功能、用途及调用顺序。图7-3 MFC应用程序的打印流程

如图7-3所示,在打印数据的过程中,当CView:On Prepare Printing()函数调用了CView:Do Prepare Printing()函数后,会将数据输出至打印机的设备环境,并传递给CView:On Paint()函数。当执行完打印需要的绘图动作(如建立页首、页尾等)后,CView:On Paint()函数又把这个DC传给On Draw()函数。而On Draw()函数不会考虑这个DC所代表的是哪一种装置,而是直接将Document对象的内容往DC上画。所以,当程序具备了将Document对象保存内容显示在屏幕上的能力时,同时也具备了将数据输出到其他装置的基本能力。因此,On Paint()函数所执行的是针对打印机工作而执行的绘图工作,在On Draw()函数中则是将Document对象的数据输出到DC上。这样使得程序设计人员的工作更单纯,不需要因为输出装置的不同,运用不同的数据输出的方式。另外,将数据输出到窗口的客户区时,CView类提供了一个On Paint()函数供程序设计人员重载,完全针对显示于屏幕上的数据所执行的特定绘图工作。当所有页面打印完成后,调用CView:On End Printing()删除GDI对象,结束打印工作。

下面依次对图7-3中涉及的函数进行说明。

1.CView:On Prepare Printing()函数

函数CView:On Prepare Printing()完成设置打印机、创建打印机DC等工作。比如,若没有安装打印机,则该函数将提示用户安装打印机。用户程序可以向其中加入别的初始化代码,比如,计算打印文档所需要的总页数等。该函数返回值为true或者false,具体语法结构如下:

在上述代码中,参数p Info是指向CPrint Info类的指针。

CView:On Prepare Printing()函数是必须重载的第一个函数。然后需要调用视类中的打印机初始化函数Do Prepare Printing()。在调用Do Prepare Printing()前,会自动显示CPrint Dialog的对象“打印”对话框。在该对话框中,除了可以让用户设置打印机的相关参数外,还可以设置打印文件的起止页数。先针对对话框内容填写页数等属性,执行过Do Prepare Printing()后,这些数据保存在CPrint Info类的变量中,如图7-4所示。图7-4 “打印”对话框

2.CView:Do Prepare Printing()函数

函数CView:Do Prepare Printing()返回true或者false,若返回false打印终止,具体语法结构如下:

在上述代码中,参数*p Info是指向CPrint Info类的指针。

如果不希望显示“打印”对话框,有两种方法可以解决:一种方法是在调用Do Prepare Printing()前把p Info的成员函数m_b Direct设成true;另一种方法是新设一个ID为ID_FILE_PRINT_DIRECT的选项,并且在映射表中加入如下代码:

这样设置也可以迫使On File Print()把p Info的成员函数m_b Direct设置成true。

利用On Prepare Printing()函数设置打印页数的代码如下:

3.CView:On Being Printing()函数

开始打印文档前需要调用函数CView:On Being Printing(),用户可以加入另一些对于打印过程的初始化代码,比如分配打印过程中将要使用的“笔”(CPen)、“刷子”(CBrush)等。该函数没有返回值,语法结构如下:

该函数涉及的参数说明如下。

参数*p DC:指向打印机设备环境的指针。

参数*p Info:指向CPrint Info类的指针,如果只用于屏幕显示,此参数为NULL。

4.CView:On Prepare DC()函数

MFC在函数On Prepare DC()中做一些操作与页面的初始化操作有关,用于在打印前准备打印设备环境。例如:打印页码、窗口大小、原点、视图大小、原点等。同时该函数在视类显示文档内容时也被调用,默认的代码中该函数调用基类中的On Prepare DC()函数。该函数没有返回值,语法结构如下:

该函数涉及的参数同On Being Printing()函数。

5.CView:On Print()函数

On Print()函数用于完成具体的打印过程。实际上,应用程序向导自动生成的源代码中没有这个函数的框架,而这个函数对打印的实现是通过调用函数On Draw(CDC*p DC)完成的,也就是说,把打印机的设备环境指针p DC传递给函数On Draw()。该函数没有返回值,语法格式如下:

该函数涉及的参数同On Being Printing()函数。

6.CView:On Draw()函数

On Draw()函数提供3种重要的服务:在框架窗口、预览窗口和打印机上显示数据服务,具体在什么地方显示,取决于作为函数参数传递的设备环境。该函数没有返回值,语法结构如下:

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载