[Title will be auto-generated]

Page 1


www.BOOKOO.com.cn

Visual C++6.0 编程实例与技巧

博 库 中国

美国

台湾

版权所有 翻印必究


ᴗ ߽ ໄ ᯢ

ᇍҢमᑧ㔥 ZZZ %22.22 FRP FQ ੠ ៪ ZZZ %22.22 FRP ϟ䕑ⱘ԰ક ҙ䰤Ѣᆊᒁ‫ݙ‬㞾Ꮕ⾕Ҏ䯙䇏 मᑧ݀ৌ %22.22 ,QF ֱ⬭ϔߛⱘ⠜ᴗᴗ߽ Ӵ䕧 থ㸠 ߎ⾳ ᪁ᬒ

ࣙᣀԚϡ䰤Ѣ Ӵ᪁

ߎ⠜

໡ࠊ

ሩ⼎ ࠊ԰Ў⺕Ⲭ៪‫Ⲭܝ‬

ㄝ⦄೼Ꮖ᳝ⱘঞᇚᴹᡔᴃথሩ᠔ѻ⫳ⱘ⬉ᄤ੠ ៪᭄ᄫ䕑 ԧ ॄࠊ 䬰‫ ڣ‬䆒ゟ㔥キ Ϟ䕑 ϟ䕑 ᳾㒣मᑧ݀ৌ %22.22 ,QF 䆌ৃ

ӏԩҎϡᕫ᪙㞾Փ⫼԰ક

ⱘ䖬ᰃ䴲ଚϮⳂⱘ

᮴䆎ᰃߎѢଚϮⳂ

᳾㒣मᑧ㔥ⱘ䆌ৃ ӏԩҎϡᕫׂᬍ ߴ䰸मᑧ㔥ⱘᴗ ߽ໄᯢ੠ᴗ߽ㅵ⧚ֵᙃ

मᑧ㔥㞾㸠ᓔথ៪䞛⫼ⱘᡔᴃ᥾ᮑ ᡔᴃ᠟↉ফ⊩ᕟֱ ᡸ

ӏԩҎϡᕫ։ᆇ ⸈ണ %22.22

ⱘଚᷛ

मᑧ

ঞⳌ݇೒ᔶㄝЎ %22.22 ,QF


Visual C++6.0 编程实例与技巧

内 容 提 要 本书主要介绍 Visual C++6.0 编程技术 内容涉及相当广泛 既包括 Visual C++常规编程技术和应用程序基础的介绍 又有图形用户界面 ActiveX 控件 多媒体 数据库等高级话题的介绍 通过这些内容的学 习 会使用户充分领略到 Visual C++事件驱动可视编程技术的威力所在 书中内容实例丰富 讲解清晰 力避代码复杂冗长 简短的实例特 别有助于初学者仿效理解 把握问题的精髓;能够帮助读者快速建立对应 用程序框架的整体认识 本书是学习 Visual C++编程人员不可多得的参 考书

过去 Windows 编程是项非常复杂而且难以驾驭的任务 如今 这 已成为历史 由于 Visual C++强大开发工具的出现 编程技术的更新 使得编写类似于 Windows 98 这样的图形用户界面应用程序不再是不可 能的事情 用户可以非常容易地创建出像菜单栏 工具栏 按钮 对话 框 窗口等高级而又通用的图形元素 可以充分体验编程的乐趣 将自 己的研究成果以专业的水准提供给别人 本书主要针对 Windows 9X/NT 系统

介绍了应用程序的 Visual C++编程

本书的侧重点是理论与实践

相结合 遵循循序渐进 由浅入深的认知特点来安排各个章节的内容顺 序 从而使读者达到学以致用的目的 通过本书 读者将不仅学会如何 创建基本的 Windows 程序 也要学到如何在程序中添加一些必要的东西 以达到特定的目的

同时

还要学到如何设计事件驱动程序来响应

www.BOOKOO.com.cn

1


Visual C++6.0 编程实例与技巧

Windows 消息 创建定制对话框 绘制窗口 打印文挡 显示位图等 除此之外

本书还要介绍一些高级技术 如 DirectX OpenGL

书中所有实例均是在 Windows 98 环境下用 Visual C++ 6.0 开发的 并且均调试通过 读者可按照所附源代码重建应用 由于书中所有实例 均做得比较简短 需要录入的工作量并不大 所以非常适于仿效学习 本书适用对象: 本书的内容及安排适于三种对象学习 Visual C++编程 不懂 C++和 Windows 程序结构的人 书中为这类读者专门安排了 第三章

C++基础 和第四章 第五章关于 Windows 应用程序组织结

构的内容 通过这几章学习 读者应该能够很快建立起对 C++和 Windows 结构的认识

已熟悉这部分内容的人也可作为复习而快速浏览一下

懂 C++但不熟悉 Windows 应用程序结构的读者 可以阅读第四章 第五章关于 Windows 应用程序组织结构的内容 而跳过第三章

C++

基础 对于以上两部分都已熟悉的读者 可以把以上两部分都跳过 而直 接阅读后面的高级部分 所以 凡是想学习 Visual C++6.0 编程的人 本 书均是你理想的良师益友 以上都要求读者对于 Windows 操作系统有一个大致的了解 但不 要求你是这方面的专家 只要熟悉 Windows 应用常有的对话框 工具条 菜单 按钮等界面特征与操作方法即可 当然 如果你理解一些基本概 念 如内存管理 指针 类和消息 你会发现 书中文本和范例代码更 容易读懂 本书主要内容: 第一章 Visual C++ 6.0 概述 www.BOOKOO.com.cn

2


Visual C++6.0 编程实例与技巧

概略介绍 Visual C++ 6.0 的特点 新增功能以及配置要求 第二章 Visual C++ 6.0 开发环境 介绍 Visual C++ 6.0 的开发平台 Visual Developer Studio 98

这是

Visual C++ 集成开发环境 IDE ,Visual C++应用程序开发的全过程就 是在这里完成的 它把 Visual C++的各种工具都集成到了这个平台中 从它的环境菜单中可以访问各种实用工具来完成应用的编辑 链接 调 试等 所以学习环境的使用 了解它的组成是进一步学习 Visual C++编 程的前提 第三章 C++基础 这是为不了解 C++的读者准备的快速教程 已熟悉此部分的读者可 以跳过它直接阅读后续章节

欲更详细学习 C++的读者请参考其它书

籍 第四章 Windows SDK 应用程序结构 这里是为读者了解 Windows 应用程序执行内幕提供一个机会 而 并非真的要读者学习已过时且复杂难懂的 Windows SDK 编程 有一个 怪现象

从 Windows SDK 时代走过来的人学 VC 编程非常容易 而上

来就学 VC 编程的人却举步维艰

这并非后者编程能力差 由于 Visual

C++把大部分应用程序框架的代码编写及执行流程都封装了起来 对于 初学者学习编写简单程序来说是简化了编程任务 但当读者在此基础上 欲进一步提高时

就遇到了困难

因此

在学习用 Visual C++进行

Windows 编程之前 适当了解一下 Windows 应用程序执行内幕 对于读 者进一步学习将大有裨益 而用 Windows SDK 编写 Windows 应用程序 是了解 Windows 应用程序执行内幕的绝佳工具 虽然这种编程方法已经 过时 但 Visual C++没有留给读者太多值得思考的东西 只能死记硬背 www.BOOKOO.com.cn

3


Visual C++6.0 编程实例与技巧

一些函数的功能而不知所以然 SDK 的代码编程却把一切都昭然若揭 Windows 程序执行内幕一览无余 学习了 Windows 程序执行内幕后再回 过头来学习 Visual C++就会有换把快刀闹革命的感觉 一切都是那么轻 松自然 第五章 Visual C++应用程序框架结构 虽然读者经过第四章的学习已经掌握了 Windows 应用程序结构内 幕

但这只是一个必要条件 还必须了解 Visual C++留给读者的应用程

序结构是什么样子 了解不同种类型 AppWizard 生成的应用程序框架的 组成及各部分的分工 这是往应用框架中添加用户自己的代码实现用户 编程意图的先决条件 已熟悉这部分的读者可以跳过以上两章而直接进 入更高级的内容 第六章 对话框与对话框控件 对话框资源是 Windows 应用程序中用得最多的界面元素 它用于 向用户显示响应信息

询问用户输入等

也有完全基于对话框的

Windows 应用程序 像 Windows 操作系统中的各种实用工具 如计算器 应用程序等 所以 学习 Visual C++编程少不了对话框与对话框控件的 学习

本章亦介绍了一个简单的基于对话框应用程序的编写 第七章 如何创建一个 SDI 应用程序 这一章讲述 Windows 应用中用得最多的单文档应用程序 SDI 编

程 所举例子浅显易懂 单文档应用程序的四个类即视类 文档类 框 架类 应用类之间的编程关系可以作为学习 Visual C++编程的样板 搞 懂了这些多文档应用程序

MDI

自然也就懂了

第八章 动态链接库 DLL 编程 DLL 是 Windows 操作系统下为实现资源和功能共享所依赖的关键 www.BOOKOO.com.cn

4


Visual C++6.0 编程实例与技巧

技术

进行多语言混合编程如 MS Fortran Powerstation 与 Visual C++

Visual Basic 等均需要用到 DLL 技术 甚至连时下红得走眼的 ActiveX 控件其本身也是一个动态链接库 学习动态链接库编程技术也是必备的 技能之一 第九章 多文档应用程序

MDI

编程

SDI 应用程序窗口一次只能显示一个文档 这对于大多数应用程序 来说已足够了 MDI 应用程序则可以一次显示多个文档 最著名的 MDI 应用程序的实例代表是 MS Word 字处理软件 在其中可同时打开多个 文档进行编辑操作 不过 现在 MDI 应用程序有些过时了 Microsoft 没有设法把多个文档挤在一个框架窗口中 而是建议为了满足多文档操 作的需要而运行一个应用程序的多个实例 这样会更容易 更易实现 本章也举了个多文档编程的例子 第十章 Visual C++6.0 多媒体程序设计 本章旨在讲述用 Visual C++6.0 进行多媒体应用程序设计的一般过 程 读者可以仿效开发较基本的音频 视频多媒体应用程序 至于有关 多媒体更深层次的专业内容请读者查阅相关专门书籍 本章既讲述了使 用传统的 Windows 媒体控制接口 MCI

又介绍了新一代多媒体开发

接口 ActiveX DirectX OpenGL 第十一章 Visual C++6.0 数据库应用程序开发 各种大型商业应用程序与工程软件开发都不可避免要访问数据库 甚至实时更新数据库记录 本章讲述了 Visual C++数据库应用程序的开 发 包括通过 ODBC 和 DAO 两种数据库驱动接口来操纵和访问数据的 应用程序

www.BOOKOO.com.cn

5


Visual C++6.0 编程实例与技巧

6.0 编程实例与技巧

Visual C

第一章 Visual C 进入 90 年代中期以来

6.0 概述

随着 Windows 操作系统在全球的盛行

GUI(图形用户界面)应用程序设计也在全球领域内风靡起来

现在很难

看到有哪个程序员在开发面向字符(DOS)的应用 程序 了 DOS 已失去 了市场 随之即来的是 功能丰富灵活 操作简单易学的可视化应用程 序设 计时代的到来 (OOP)方法

这是一个划时代的变革

结合面向对象程序设计

这必将有力推 动新一轮信息技术革命的到来

随着计算机多媒体技术

图形图像技术

计算机通信与网络技术的

发展 应用程序设计也需要有强大的可视化设计工具来支持 Visual C++ 就是 Microsoft 公司推出的支持可 视化编程 的集成环境 一般来说 可视化技术包含两方面的含义:一是软件开发阶段的可视化 即可 视化 编程 可视化编程使编程工作成为一件轻松愉快 饶有趣味的事情 二 是利用计算机图 形技术和方法

对大量的数据进行处理

并用图形图

像的方式形象而具体地加以显示 本章首先就 Visual C++ 6.0 的特点 软硬件配置及安装作一简要讨 论 1.1 Visual C++ 6.0 的特点 Visual C++ 6.0 是 Microsoft 公司推出的 VC 最新版本 它是在早期版 本的基础上不断改变 完善发展而来 用于支持 Win32 平台(Windows 95

www.BOOKOO.com.cn

6


Visual C++6.0 编程实例与技巧

98

NT4.0

5.0)应用程序(applicatio n) 服务(service)和控件(control)的

开发 1. Visual C++ 6.0 集成开发环境(IDE) Visual C++ 6.0 开发环境 Developer Studio 是由 Win32 环境下运行的 一套集成开发 工具所组成

包括文本编辑器(text editor)

资源编辑器

项目建立工具(proje ct build facilities)

优化编译器

(optimizing compiler)

增量连接器(incremental li nker)

源代码浏览器

(source code browser)

集成调试器(integrated debugger)等

(resource editor)

2. 使用向导(Wizard)——计算机辅助应用程序设计 在 Visual C++ 6.0 中

可以使用各种向导(Wizards)

MFC 类库

(Microsoft Founda tion Cla ss Library)和活动模板库(Active Template Library 简称 ATL)来开发 Windows 应用程序 向导

实质上是一种计算机辅助程序设计工具

生成各种不同类型应用程序风格的基本框架

用于帮助用户自动 例如

使 用 MFC

AppWizard 来生成完整的从开始文件出发的基于 MFC 类库 的源文件 如资源文件;使用 MFC ActiveX Control Wizard 生成创建 ActiveX 控件所 需要的 全部开始文件(如源文件 头文件 资源文件 模块定义文件 项目文件 对象描述语言文 件等);使用 ISAPI Extension Wizard 生成创 建 Internet 服务器(Sever)或过滤器(Filter)所 需要的全部文件;使用 ATL COM AppWizard 来创建 ATL 应用程序;使用 Custom AppWizard 来创建 自定义的项目类型 并将其添加到创建项目时的可用项目类型列表中 创建应用程序的基本框架后 (class) function)

可以使用 Class Wizard 来创建新类

定义消息处理函数(message handler) 从对话框(dialog box)

覆盖虚拟函数(virtual

表单 视图(form view)或者记录视图

www.BOOKOO.com.cn

7


Visual C++6.0 编程实例与技巧

(record view) 的 控 件 中 获 取 数 据 并 验 证 数 据 的 合 法 性

添加 属性

(property)

事件(event)和方法(mathod)到自动化对象(automation object)

中 此外

还可以使用 WizardBar 来定义消息处理函数 覆盖虚拟函数

并浏览实现文件(.cpp) 3. 方便编程的集成数据库访问 Visual C++ 6.0 允许用户建立强有力的数据库应用程序: 可以使用 ODBC 类(开放数据库互连)和高性能的 32 位 ODBC 驱动程 序来访问各种数据库管理系统 如 Visual Foxpro 5.0 6.0 Access SQL Sever 等 可以使用 DAO 类(数据访问对象)通过编程语言来访问和操纵数据库 中的数据并管理数据库 数据库对象与结构 4. 强有力的 Internet 支持 Visual C++ 6.0 对 Internet 提供更强有力的支持: Win32 Internet API (WinInet)使 Internet 成为应用程序的一部分并简 化了对 Internet 服务(FTP

HTTP Gopher)的访问

ActiveX 文档可以显示在整个 Web 浏览器(如 Internet Explorer)或 OLE 容器(如 Microsoft Office Binder)的整个客户窗口中 ActiveX 控制可以用在 Internet 和桌面应用程序中 Asynchronous Monikers 使应用程序可以异步下载文件和控制属性 可以使用 CHttpServer CHttpFilter CHttpSeverContext CHttpFilter Context 和 CHt tp Stream 类来创建动态链接库以便添加功能到 Internet 服务器和 Web 页中

www.BOOKOO.com.cn

8


Visual C++6.0 编程实例与技巧

1.2 Visual C++ 6.0 的软

硬件配置

Visual C++ 6.0 运行所需的软 硬件配置应满足以下要求: Windows 95 或 Windows NT IBM PC 及其兼容机

最好具有 80486 以上的微处理器

16MB 以上内存 建议使用 32MB 内存 最小安装需要 140MB 的可用硬盘空间 典型安装需要 200MB 的可 用硬盘空间 CD-ROM 安装需要 50MB 的可用硬盘空间 完整安装需要 300MB 的可用硬盘空间 高密软盘驱动器 14 英寸 VGA 彩显 最好采用 15 英寸 CD-ROM 驱动器(用于联机信息) 1.3 Visual C++ 6.0 的新增功能 Visual C++ 6.0 相对于其前期版本 Visual C++ 5.0 来说 新增 改进 的 功能不少 所有新增特征均可从 Visual Studio 98 光盘集的 MSDN 98 CD-ROM 上查阅到 凡是安装了 MSDN Library 联机帮助的读者 均可 从 Visual C++ 6.0 下的“What

s New”主题部分联机查阅 这里仅就与读

者常用部分的新增功能作一概要介绍 1.3.1 智能提示功能 这个功能使编辑源代码速度更快

错误更少

当写一个函数时

未写全函数名前 系统会根据当前输入自动弹出一条信息 提示编程人 员该函数拥有的参数类型及个数 写完的函数名

可以按回 车键让系统自动补足尚未

这对于那些名字较长较难记忆的函数特别有用

www.BOOKOO.com.cn

既 提

9


Visual C++6.0 编程实例与技巧

高了输入速度 又防止写错函数名或者漏掉函数参数 当书写一个类对 象时 在键入“. ”或在对象指针后键入“ ”后 系统会自动弹出一个列表 框

提示编程人员该类对象所拥 有的全部成员变量和成员函数

可以

用垂直滚动条上下滚动 找到所要的变量或函数后双击 它或按回车键 则该变量或函数名会自动输入到“.”到“ ”后面 方便了输入 而更经常 的使用方法是 在“.”或“ ”后输入两三个函数名的字母 系统会据此将 高亮光跳到 你想要的变量或函数名上 量或函数名

直接敲回车键即可补全整个变

如图 1.1

图 1.1 智能提示示例 1.3.2 新的联机帮助 Visual C++ 6.0 的联机帮助已做成一个独立于 Visual Studio 的 HTML 文本 它利用 Internet Explorer 浏览器来完成浏览 检索功能 这样做 的好处很明显

编写程序与检索帮助可同时 进行

不会因为为了查询

帮助文件而中断源程序的编辑工作 这在于 MSDN 脱离 Visual Studi o IDE(集成开发环境)而运行于 IE 不会占用 IDE 的控制权 可以把 MSDN

www.BOOKOO.com.cn

10


Visual C++6.0 编程实例与技巧

最小化到任务栏而在 IDE 源代码窗口进行编程工作

在需要时随时激

活 MSDN 帮助窗口 如图 1.2

图 1.2 新的 MSDN 联机帮助利用 Internet Explorer 1.3.3 新的项目风格 可以创建非 MFC 标准的类似 Windows 资源管理器的文档窗口 拥 有左右两个视窗 这对于编写某些实用工具类应用程序提供了极大的方 便 1.3.4 中文语言支持 在 Visual C++ 5.0 版中 在项目配置第一步对话框中不支持中文 资

www.BOOKOO.com.cn

11


Visual C++6.0 编程实例与技巧

源编辑时需从资

图 1.3 新的类 Explorer 项目风格及类 IE 工具条特征 源 视图(Resources View)中专门指定中文支持能力 而在 Visual C++ 6.0 版中 缺省即支持中文 因此可在资源编辑时直接使用中文静态文 本 1.3.5 工具条新特征 Visual C++ 6.0 中应用程序工具条可选为类似 Internet Explorer 特征 这更有利于开发 Web 浏览器特征的应用程序 参看图 1.3 除了上述新增特征外 大量的新增功能比如新的编译 调试 链接 自动化对象模块 向导及项目等功能均难以一一列出 读者可以在安装 了 MSDN(Microsoft Developer Network)帮助库后 进行联机检索

www.BOOKOO.com.cn

12


Visual C++6.0 编程实例与技巧

第二章 Visual C++ 6.0 开发环境 Visual C++ 6.0 开发环境由一套综合的开发工具所组成 提供了良好 的可视化编程环境 可以对 C 和 C++程序进行各种操作 包括建立 打 开

浏览

编辑

保存

标单击工具按钮来完成

编译

链 接和调试等

这些操作都可借助鼠

方便快捷

本章详细介绍 Visual C++ 6.0 开发环境的各种功能 并介绍相对以前 版本的新增特点 2.1 Visual C++ 6.0 主窗口界面 在按第一章所介绍的步骤将 Visual C++ 6.0 安装到 Windows 98 中后 单击“开始 ”按钮

从开始菜单中单击“程序”菜单项

可以看到 Visual

C++ 6.0 菜单命令已加入到开 始菜单的子菜单中 单击 Visual C++ 6.0 进入开发环境 IDE(图 2.1)

www.BOOKOO.com.cn

13


Visual C++6.0 编程实例与技巧

图 2.1 Visual C++ 6.0 集成开发环境 IDE Developer Studio 由标题栏 菜单栏 工具栏组成 屏幕最上端是标题栏 标题栏用于显示应用程序名和打开的文件名 标题栏的颜色用于指明对应窗口是否为激活的 标题栏左端为控制菜单 框 是用于打开窗口控制菜单的图标 用鼠标单击该图标或按 Alt+空格 键 将弹出窗口控制菜单 窗口控制菜单用于控制窗口的大小和 位置 如还原 关闭 最大化和最小化等 标题栏的右边有三个控制按钮 从 左到右分别为 最小化按钮

还原按钮和关闭按钮

这些按钮用于快速

设置窗口大小 例如 使窗口填充整个屏幕 将窗口最小化为图标或关 闭窗口 标题栏的下面是菜单栏和工具栏

工具栏的下面是两个窗口

www.BOOKOO.com.cn

一是

14


Visual C++6.0 编程实例与技巧

工作区窗口 二是源代码编辑窗口 这两窗口的下面是输出窗口 用于 显示项目建立过程中所产生的错误信息

屏幕 最底端是状态栏

给出

当前操作或所选命令的提示信息 2.2 Visual C++ 6.0 工具栏 工具栏由若干操作按钮组成

分别对应着某些菜单选项或命令的功

能 可以直接用鼠标单击这些按钮来完成指定的功能 工具栏按钮大大 简化了用户的操作过程 并使操作过程可视化 不再是抽象的命令行序 列 Visual C++ 6.0 包含有 10 种工具栏 缺省时 屏幕工具栏区域显示 有两个工具栏 即 Standard 工具栏和 Build 工具栏

Standard 工具栏主

要由以下工具按钮组成(图 2.2)

图 2.2 Standard 工具栏 New Text File Open

创建新的文本文件

打开已有的文档

Save 保存文档 Save All Cut

保存所有打开的文件

剪切选定的内容到剪贴板中

Copy 复制选定的内容到剪贴板中 Paste

在当前插入点处插入到剪贴板中

www.BOOKOO.com.cn

15


Visual C++6.0 编程实例与技巧

Undo

取消最后的操作

Redo

重复先前取消的操作

Workspace

显示或者隐藏工作区窗口

Output:显示或者隐藏输出窗口 Window List:管理当前打开的窗口 Find in Files:在多个文件中搜索字符串 Find:激活查找工具 Go Back:在被查者的主题列表中回移 Go Forward:在被查者的主题列表中前移 Stop Topic Retrieval:停止主题搜索过程 Refresh View:刷新 InfoViewer 窗口 Home Page: 显示 InfoViewer 主页 Search:搜索联机文档 Build 工具栏主要由以下工具按钮组成(图 2.3): Select Active Project:选择活动项目 Select Active Configuration:选择活动配置 Compile:编译文件 Build:建立项目 Stop Build:停止项目的建立过程 Execute program:执行程序 Go

启动或者继续程序的执行

Insert

Remove Breakpoints

插入或删除断点

www.BOOKOO.com.cn

16


Visual C++6.0 编程实例与技巧

图 2.3 Build 工具栏 如果要在屏幕上 显示或者隐藏某个工具栏 请在屏幕工具栏区域单 击鼠标右按钮 从工具栏快捷菜单选择或者清除相应的工具栏 用户可 以用这种方法来突出自己的习惯使用的界面 特征 比如 去掉 Build 工 具栏 代之 WizardBar 工具栏与 Build miniBar 工具栏或许会更好 用些 2.3 Visual C++ 6.0 菜单栏 菜单栏由多个菜单项组成 与 Windows 98 95 操作一致 选择菜单 有两种方法 一种是用鼠标左按钮单击所选的菜单 另一种是键盘操作 即同时按下 Alt 键和所选菜单的热键字母(带下划线的字母 如 File 中的 F)

选中某个菜单后

就会出现相应的下拉式子菜单

在下拉式 子菜

单中 有些菜单项的右边对应着相应的快捷键(如 Save 对应 Ctrl+S) 表 示按快捷键将直 接执行菜单命令 这样可以减少进入多层菜单的麻烦 有些菜单选项后面带有三个圆点符( 如(New

)

表示选择该项后将自

动弹出一个对话框 有些菜单选项后面带有黑三角箭头(

) 表示选择

该项后将自动弹出级联菜单 若下拉式子菜单中的某些菜单选项显示为 灰色 则表示这些选项在当前条件下不能选择 此外

在窗口的不同位置单击鼠标右按钮将弹出不同快捷菜单

中可以选择与当前位置数极为相关的要频繁执行的命令

www.BOOKOO.com.cn

17


Visual C++6.0 编程实例与技巧

菜单 2.3.1 “File”菜单 在“File”菜单中包含用于对文件进行操作的命令选项(图 2.4)

图 2.4 “File”菜单 1. “New”选项 该选项用于打开“New”对话框 从“New”对话框可以创建新的文档 项目或者工作区 (1) 创建新的文件

如果要创建新的文件

请从“New”对话框的

“Files”选项卡(图 2.5) 中单击要创建的文件 类型 然后在“File”文本框中键入文件的名字 如果要添加新文件到已有的项目中 请选 中“Add to project”复选框并选 择项目名 Visual C++ 6.0 可以创建的文件类型有 Active Server Page:创建服务器页 Binary File:创建二进制文件

www.BOOKOO.com.cn

18


Visual C++6.0 编程实例与技巧

Bitmap File:创建位图文件 C C++ Header File:创建 C C++头文件 C++ Source File:创建 C++源文件 Cursor File:创建光标文件 HTML File:创建 HTML 页 Icon File 创建宏文件 Resource Script:创建资源脚本文件 Resource Template:创建资源模板文件 SQL Soript File

创建 SQL 脚本文件

Text File 创建文本文件

图 2.5 “New”对话框的“Files”选项卡 (2) 创建新的项目 如果要创建新的项目 从“New”对话框的“Project”选项卡(图 2.6)单击

www.BOOKOO.com.cn

19


Visual C++6.0 编程实例与技巧

要创建的项目类型

然后在“Project Name”文本框键入项目的名字

果要添加新的项目到打开的工作区中 请选择“Add to current workspace” 选项 否则将自动创建包含新项目的新工作区 如果要使用新项目中已有项目的子项目 请选择“Dependency of”复 选框并指定项目名 Visual C++ 6.0 能够创建以下项目类型 ATL COM AppWizard

创建 ATL 应用程序

Cluster Resource Type Wizard:创建 Resource DLL 应用 Custom AppWizard:创建自定义的 AppWizard Database Project:直接创建数据库项目 DevStudio Add-in Wizard:创建自动化宏 Extended Stored Proc Wizard ISAPI Extensiopn Wizard

创建 SQL Server 应用程序

创建 Internet 服务器或过滤器

Makefile 创建 Make 文件 MFC ActiveX ControWizard:创建 ActiveX 控件程序 MFC AppWizard(dll):创建 MFC 动态链接库 MFC AppWizard(exe):创建 MFC 可执行程序 New Database Wizard

创建 SQL 服务器数据库

Utility Project:创建容器类项目 Win32 Application: 创建 Win32 应用程序 Win32 Console Application

创建 Win32 控制台应用程序

Win32 DynamicLink Library:创建 Win32 动态链接库 Win32 Static Library:创建 Win32 静态库

www.BOOKOO.com.cn

20


Visual C++6.0 编程实例与技巧

图 2.6 “New”对话框的“Project”选项卡 请注意 以上配置选项随安装类型(典型安装 完全安装 自定义安 装等)或版本类型(学习 版

专业版

企业版)的不同而有所不同

并且

对于从事某一专业的工程人员来说并非所有 选项都能用得上 (3) 创建新的工作区 如果要创建新的工作区 择一种工作区类型 字

请从“New”对话框的“Workspace”选项卡选

然后在“Workspace name”文本框中键入工作区的名

缺省下只有“Blank Workspace” 选项

用于创建一空白工作区

(4) 创建新的文档 如果要创建新的文档

请从“New”对话框的“Other Documents”选项

卡单击要创建的文档 类型 然后在“File”文本框中键入文档的名字 如 果要添加新的文档到已有的项目中 请选中“Add to project”复选框 然 后选择项目名 www.BOOKOO.com.cn

21


Visual C++6.0 编程实例与技巧

Visual C++ 6.0 可以创建诸如 Excel 工作表 Excel 图表 PowerPoint 演示文稿和 W ord 文档等文档类型 2. “Open”选项 该选项用于打开已有的文件 如 C++文件 源文件

定义文 件

工作区文件

项目文件等

按钮是 Btandard 工具栏左边数第二个

Web 文件 宏文件 资 与“Open”选项对应的

其上需有 打开的文件夹图案

选择“Open”选项将弹出“Open”对话框 与 Windows 操作方法相同 可以从中选择要打开 文件所在驱动器

路径以及文件名

3. “Close”选项 该选项用于关闭已打开的文件 件

如果系统中包含有多个已打开的文

那么使用该选项就会将当前活动窗口或选定的窗口中的文件关闭

激活或选择某一窗口可以通过鼠标单击该窗口来完成 如果某个文件改 动后未被保存就被关闭 则系统会提示用户是否保存该文件 4. “Open Workspace”项 与“Open”选项类似 打开工作区文件

该选项也是用于打开已有的文件

但主要用于

选择该项将弹出“Open Workspace”对话框

5. “Save Workspace”项 该选项保存打开的工作区项目 6. “Close Workspace”项 该选项用于关闭打开的工作区 7. “Save”项 该选项用于保存活动窗口或者当前选定窗口中的文件内容

与该选

项对应的操作按钮是 Stam dard 工具栏左边第三个按钮 其上会有一个 软盘图案 www.BOOKOO.com.cn

22


Visual C++6.0 编程实例与技巧

如果所保存的文件是新文件 用户输入有效的文件名

则系统会弹出“Save as”对话框

提示

如果当前文件是以只读(Raed-Only)方式打开

的 那么与“Save”选项对应的操作按钮 就会显示为灰色 表示该选项不 可用 当在工作区窗口或输出窗口中操作后

该按钮变灰表示不可用

用鼠标点出源代码编辑窗口使鼠标光标移入窗口才能使之有效 8. “Save as”项 该选项的功能与“Save”选项类似

也是保存打开的文件

不过该选

项是将打开的文件用新的文件名加以保存 如果保存文件时想要保留该 文件的备份 而不想覆盖原来的文件 那么 就可以使用“Save as”选项 将文件用另一名字保存起来 这样就不会使新文件覆盖原来 的旧文件 选择“Save as”选项会自动弹出“Save as”对话框 可以输入欲保存文 件所在的驱动器和路径以及相应的文件名 9. “Save All”项 该选项用于保存所有窗口内的文件内容

而不仅仅是当前活动窗口

或选定窗口的文件内容 如果某一窗口中的文件未被保存过 系统就会 自动提示为该文件输入有效的文件名 与该选项对应的操作按钮是 Standard 工具栏左边第四个按钮 上绘 有三个重叠的软盘图案 10. “Page Setup”项 该项用于设置和格式化打印结果 选择该选项将弹出“Page Setup”对 话框(图 2.7)

www.BOOKOO.com.cn

23


Visual C++6.0 编程实例与技巧

图 2.7 “Page Setup”对话框 可以建立每个打印页的标题和脚注

并设置上

右边距

表 2.1 列出了每个格式码对应的标题和脚注的类型 表 2.1 标题和脚注的格式码 格式码

使用结果

&C

正文居中

&P

加入页号

&D

加入系统日期

&R

右对齐正文

&F

使用文件的名字

&T

加入系统时间

&L

左对齐正文

11. “Print”项 该项用于打印当前活动窗口中的内容

选择该项将弹出“Print”对话

框 可以对打印范围和打印机类型进行设置 12. “Recent Files”项 选择该项将打开级联菜单

其中列出了最近打开过的文件名

用鼠

标单击某个名字即可打开相应文件

www.BOOKOO.com.cn

24


Visual C++6.0 编程实例与技巧

13. “Recent Workspaces”项 选择该选项将打开级联菜单

其中包含有最近打开的工作区

用鼠

标单击某个名字即可打开相应的工作区 以上两选项为打开操作提供了一种快捷方法 14. “Exit”项 选择该选项将退出 Visual C++ 6.0 开发环境 在退出前 系统会自动 提示用户保存名窗口的内容 菜单 2.3.2 “Edit”菜单 在“Edit”菜单中包含用于编辑或者搜索的命令选项(图 2.8)

图 2.8 “Edit”菜单 www.BOOKOO.com.cn

25


Visual C++6.0 编程实例与技巧

1. “Undo”选项 该选项用于取消最近一次的编辑修改操作 2. “Redo”选项 该选项用于最近一次的“Undo”操作 可以恢复被“Undo”命令取消的 修改操作 3. “Cut”选项 该选项将当前活动窗口中帧选定的内容拷贝到剪贴板中

然后再将

其从当前活动窗口中删除 可以用鼠标选定所要剪切的内容 方法为将 光标移到要剪切内容的开始并按住鼠标左按钮 然后拖动鼠标移到要剪 切的内容的末尾 这时

整个被选内容就会反白显示 “Cut”选 项通常

与“Paste”选项联合使用来移动选定的内容 4. “Copy”选项 该选项将当前活动窗口中被选定的内容拷贝到剪贴板中

但并不将

其从当前活动窗口中删除 “Copy”选项通常与“Paste”选项联合使用来复 制选定的内容 5. “Paste”选项 该选项用于将剪贴板中的内容插入到当前光标所在的位置中

必须

先剪切或者复制选定到剪 贴板后 才能进行粘贴 6. “Delete”选项 该选项用于删除被选定的内容

删除以后

还可以使用“Undo”命令

来恢复删除操作 7. “Select All” 该选项用于选择当前活动窗口中的所有内容 8. “Find”选项 www.BOOKOO.com.cn

26


Visual C++6.0 编程实例与技巧

该选项用于查找指定的字符串 选择“Find”选项将弹出“Find”对话框 (图 2.9)

图 2.9 “Find”对话框 在“Find”对话框中 可以在“Find What”文本框中输入查找的字符串 并设置查找的方 向(Up 或 Down) 此外 还可以根据需要进行区分大小 写字符串查找(Match Case)进行调整查 找(Match Whole Word only) 这 时就应选择相应的复选框 对话框中的“Regular Express ion”选项特别有 用 选择该选项将按正则表达式来查找文件中匹配的文本 正则表达式 是 指用特殊的字符序列去匹配文件中的某个文本模式 如同 Dos 中的 通配符一样 表 2.2 列出了正则表达式的查找模式及其含义 表 2.2 正则表达式的查找模式 查找模式 *

含义

匹配任意一个字符 匹配单个字符

^

示例 Exam 匹配 Exam Example 和 Exam1 Exam 匹配 Exam2 和 Examp 不匹配 Example

匹配以指定字符串结果的字符串

^Exam 匹配以 Exam 开头的

各行 +

配以指定字符串结果的字符串

+Exam 匹配 is Exam 和

notExam

www.BOOKOO.com.cn

27


Visual C++6.0 编程实例与技巧

匹配以指定字符串结果的每一行

$

Exam$匹配以 Exam 结果的

各行 匹配指定字符集中的字符

Exam 1

9

匹配 Exam4 但不

匹配 ExamS 匹配与指定字符一致的字符

Exam A Zn

0

9

匹配

ExamSn5 但不匹配 ExamAm9 { }

匹配花括号晕指定的字符串的任意序列 和 Exzm#

可匹配 Exam Exam #

Exm {#

}*

#

9. “Find in Files”选项 该选项用于在多个文件间搜索文本 符串

而且搜索的对象可以是文本字

也可以是正则表达式

10. “Replace”选项 该选项用于替换指定的字符串 选择“Replace”选项将弹出“Replace” 对话框 可以在“ Find What ”文本框中输入替换的文本串 再在“Replace With

”文本框中输入被替换 的文本串

择匹配方式(如剪字匹配 11. “Go To

与“Find”选项一样

大小写区分或不区分

也可以选

正则表达式等)

”选项

选择该项将弹出“Go To”对话框 可以指定如何将光标移到当前活动 窗口的指定位置(如指定的行号 地址 书签 对象的定义位置 对象的 引用位置等) 菜单项标题后的“

”说明单击该项后要弹出对话框以提供进一步的

信息 12. “Bookmarks”选项 选择该选项将弹出“Bookmarks”对话框 可以设置或取消书签 书签 www.BOOKOO.com.cn

28


Visual C++6.0 编程实例与技巧

用于在源文件中做标记 13. “Advanced”选项 选择了该项将弹出级联菜单

其中包含有用于编辑或者修改的高级

命令 例如 增量式搜索 将选定内容全部转换为大写或者小写 显示 或者隐藏制表符等 14. “Breskpoints”选项 选择该项将弹出“Breskpoints”对话框(图 2.10) 可以设置 删除和查 看断点

图 2.10 “Breskpoints”对话框 断点实际上是告诉调试器应该在何时何地中断程序的执行过程

便检查程序代码 度量和寄存器值 必要的话可以修改 继续执行或中 断执行 在 Visual C++ 6.0 中 断 点有位置断点 数据断点 消息断点 和条件断点等类型 所有已设置的断点都出现在“Breskpoints” 对话框底

www.BOOKOO.com.cn

29


Visual C++6.0 编程实例与技巧

部的“Breskpoints”列表框中 可以使用“Breskpoints”列表框检查程序中的 所 有断点 也可以从列表中删除某一断点 位置断点通常在源代码的指定行

函数的开始或指定的内存地址处

设置 当程序执行到指定位置时 位置断点将中断程序的执行 数据断 点是在某一变量或表达式上设置

当变量或表 达式的值改变时

数据

断点将中断程序的执行 消息断点是在窗口函数 Wnd Proc 上设置 当 接 收到指定的消息时 消息断点中断程序的执行 条件断点是一种位置断 点 仅当指定的条 件为真时中断程序的执行 “Breskpoints”对话框的“Location” “Data” “Message”选项卡分别用 于设置断点

数据断点和消息断点

断点 然后单击“Codition”按钮

条件断点的设置必须先设置位置

从弹出的“Poreakpont Condition”对话

框来指定中断程序进行的条件 15. “List Members”选项 当把光标放入某类名或该类成员函数区域内

然后

选择该选项

系统列出该类成员列表 包括成员变量及成员函数 可对源程序的编辑 起到提示作用 16. “Type info”选项 把光标放在类名或其成员函数以及变量名上

然后选择该选项

统列出相应的简短类型信息 17. “Parmeter Info”选项 把鼠标光标放在函数名上 信息供参考

对于查阅

在后选择该项

系统列出该函数型参考

校对函数的使用非常有利

18. “Complate Word”选项 选择该选项 可以把未写完的系统 API 函数或 MFC 函数名自动补足 www.BOOKOO.com.cn

30


Visual C++6.0 编程实例与技巧

上述最后四个选项也可通过在源代码窗口右击鼠标得到

在弹出式

菜单上列出了与之相同的四个选择项 菜单 2.3.3 “View”菜单 在“View”菜单中包含用于检查源代码和调试信息的命令选项(图 2.11)

图 2.11 “View”菜单 1. “ClassWinzard”选项 选择该项将启动 Class Winzard

Class Winzard 是一个运用于 MFC

应用程序的专用工具 使用它可以 创建新类 新类是从处理 Windows 消息和记录集(Recordset)的主 框架基类派生的 映射消息给与窗口 对话框 控件 菜单选项和加速键有关的处 理函数 创建新的消息处理函数 删除消息处理函数

www.BOOKOO.com.cn

31


Visual C++6.0 编程实例与技巧

查看已经拥有处理函数的消息并跳转到相应的处理代码中去 定义成员变量用于自动初始化 收集并验证输入到对话框或表单 视图(Form View)中的数 据 创建新类时

添加自动化方法和属性

2. “Resource Symbols”选项 选择该项将打开资源符号浏览器(图 2.12) 从中可以浏览和编辑资源 符号 资源符号是映射到整数值上的一串字符 可以在源代码或资源编 辑器中通过资源符号引用资源

图 2.12 资源符号浏览器 3. “Resource Includos”选项 选择该项将自动弹出“Resource Includos”对话框(图 2.13) 从中可以 修改资源符号文件名和预处理器指令 缺省时 系统自动将资源符号保存在文件 resource.h 中 如果同一目

www.BOOKOO.com.cn

32


Visual C++6.0 编程实例与技巧

录中有多个资源文件 就必须改变系统缺省的资源符号文件名 为此 可以在“Symbol Header File”文本框中键 入新的名字以便保存资源文件 的资源符号 Visual C++ 6.0 首次读取非 Visual C++格式的文件时 会自动将其所 有的包含头文件置成只读状态 另外 可以建立用于保存只读符号的头 文件

借助于“Read-Only Symbol Directi ives”文本框可以将保存有只读

符号的头文件包含进来 只读符号是指在编辑期间不允许修 改的符号 例如

要由多个项目共享的符号就应置成只读状态

图 2.13 “Resource Includos”对话框 通常情况下

只要在一个资源文件中保存所有的资源就足以满足要

求了 但 Visual C++ 6. 0 允许用户使用多个文件来存放资源 这时就必 须用“Compile-Time Directives”文本框 框用于列出以下资源文件

“Compile-Time Directives”文本

这些文件在编译时将成为可 执行文件的一

部分 www.BOOKOO.com.cn

33


Visual C++6.0 编程实例与技巧

独立于主资源文件中的资源而创建和编辑的文件 含有编译指示的文件 含有用户定制资源的文件 此外 “Compile-Time Directives”文本框还用于包含标准的 MFC 资源 文件 4. “Full Screen”选项 选择该项将按全屏幕方式显示活动窗口

切换到全屏幕方式后

以单击“Toole Ful Scee n”按钮或按 ESC 键切换回原来的显示方式 5. “Workspace”选项 如果工作区窗口未显示 选择该选项将显示工作区窗口 6. “Output”选项 在输出窗口显示程序建立过程(编译

键接等)的有关信息或错误信

息 并显示调试运行时的输出结果 7. “Debug Windows”选项 选择该选项将弹出级联菜单

用于显示调试信息窗口

这些命令选

项只有在调试运行状态时才可用 “Watch”选项 在 Watch 窗口显示变量或者表达式的值 另外 还可以输入和编辑所要 观察的表达式 “Call Stack”选项 选择该选项将弹出 Call Stack 窗口 从中显示 所有已被调用但还未返回的函数

可以用“Options”对话框为“Debug”选

项卡来设置有关的选项 “Memory”选项 选择该项将弹出 Memory 窗口 从中显示内存 的当前内容

至于显示格式可以用“Options”对话框的“Debug”选项卡来

设置 www.BOOKOO.com.cn

34


Visual C++6.0 编程实例与技巧

“Variables”选项

显示当前语句和前一条语句中所使用的变量信

息和函数返回值信息 这里的变量局部于当前函数或由 this 所指向的对 象 “Registers”选项 选择该项将弹出 Registers 窗口 从中显示各通 至于显示格式

用寄存器及 CPU 状态寄存器的当前内容

可以用

“Options”对话框为“Debug”选项卡来设置 “Disassembly”选项 选择该项将显示有关的反式编代码及源代码 以使用户直接进入反汇编调试或混合调试(即汇编调试与反汇编调试同 时进行)

显示格式可以用“Options”对 话框的“Debug”选项卡来设置

8. “Refresh”选项 选择该选项将刷新选定的内容 9. “Prlperties”选项 选择该选项可以从弹出的属性对话框设置或查阅对象的属性 菜单 2.3.4 “Insert”菜单 使用“Insert”菜单中的命令选项(图 2.14) 的资源 插入文件到文档中

可以创建新的类

创建新

添加新的 ATL 对象到项目中 等等

图 2.14 “Insert”菜单

www.BOOKOO.com.cn

35


Visual C++6.0 编程实例与技巧

1. “New Class”选项 选择该将弹出“New Class”对话框 可以创建新的类并添加到项目中 2. “New Form”选项 选择该项将弹出“New Form”对话框 可以创建新的对话框并添加到 项目中

该选项是添加新的对话框的快捷方法

3. “Resource”选项 选择该项将弹出“Insert resurce”对话框 可以创建新的资源或插入资 源到资源文件中 4. “Resource Copy”选项 选择该项可以创建选定资源的备份 即复制选定的资源 5. “File as Text”选项 选择该项将弹出“Insert File”对话框 可以选择要插入到文档中的文 件 6. “New ATL Object”选项 选择该项将启动 ATL Object Wizard 以便添加新的 ATL 对象到项目 中 菜单 2.3.5 “Project”菜单 “Project”菜单中的命令选项(图 2.15)用于管理项目和工作区

www.BOOKOO.com.cn

36


Visual C++6.0 编程实例与技巧

图 2.15 “Project”菜单 1. “Set Active Project”选项 选择指定的项目为工作区中的活动项目 2. “Add to Project”选项 选择该项将弹出级联菜单

用于添加文件

文件夹

数据链接以及

可再用部件增加到项目中 “New”选项

选择该项将弹出“New”对话框

可以在工作区中创

建新的文档 “New Folder”选项 选择该项将弹出“New Folder”对话框 可以 插入新的文件夹到 项目中 “Files”选项 选择该项将弹出“Insert Files Into Project”对话框 可 以插入已 有的文件到项目中 “Data Comection”选项

选择该项可以添加数据链接到活动的项

目中 “Comporent and Contrds”选项 选择该项将弹出“Component and Controls Gallery ”对话框(图 2.16)

可以插入可再用部件或已注册的

ActiveX 控件到项目中 插入时相当 于插入相关的头文件(.H 文件)和实 现文件(.CPP 文件)

并更新工作区窗口中的信息

www.BOOKOO.com.cn

37


Visual C++6.0 编程实例与技巧

3. “Dependencies”选项 选择该项将弹出“Project Dependecies”对话框 可以编辑项目的依赖 关系 4. “Scttings”选项 选择该项将弹出“Project Settings”对话框(图 2.17) 可以为项目配置 指定不同的设置说明

图 2.16 “Component and Controls Gallery”对话框

www.BOOKOO.com.cn

38


Visual C++6.0 编程实例与技巧

图 2.17 “Project Settings”对话框 5. “Exprot Makefile”选项 选择该项将按外部 Make 文件格式导出可建立的项目 6. “Insert Project into Workspace”选项 选择该项将弹出“Insert Project into Workspace”对话框 可以插入已有 的项目到工作区中 菜单 2.3.6 “Build”菜单 “Build”菜单中的命令选项(图 2.18)用于编译 建立和执行应用程序

www.BOOKOO.com.cn

39


Visual C++6.0 编程实例与技巧

图 2.18 “Build”菜单 1. “Compile”选项 该选项用于编译显示在源代码编辑窗口中的源文件

用于检查源文

件中是否有语法错误 如果在编译过程中检查出语法错误(警告或错误) 那么将在输出窗口中显示错误信息 可以向前或向后浏览输出窗口中的 错误信息

然后按 F4 键在源代码窗口显示相应的代码行

2. “Build”选项 通常 Windows 应用程序都是由多个文件组成的 而这些文件可能分 别来自编译器 程序员 操作系统甚至第三方厂商 这就使整个应用程 序变得相当复杂

如果应用程序又是由多个开 发组分别开发的

那么

其结构组成就更为复杂 因此 在编译 链接整个应用程序时 就要 花 费很多的精力和时间 这时 就可以使用 Build 这个有力的工具 Build 查看项目中的所有文件 并对最近修改过的文件(其标志日期比可执行文 件日期要新)进行编译和链接 如果建立过程中检测出某些语法错误 就将它们显示在输出窗口中

www.BOOKOO.com.cn

40


Visual C++6.0 编程实例与技巧

3. “Rebuild All”选项 该选项与“Build”选项的唯一区别在于“Rebuild All”选项在编译和链 接项目中的文件时 不管其标志日期是何时 一律重新进行编译和链接 一般情况下 应避免使用“Rebuild All”而用“Build”来改善与提高系统编 译速度 4. “Batch Build”选项 该选项用于一次建立多个项目 框

选择该项将弹出“Batch Build”对话

可以指定要建立的项目 5. “Clean”选项 该选项用于删除项目的中间文件和输出文件 6. “Start Debug”选项 选择该选项将弹出级联菜单

选项“Go”

包含有启动调试器控制程序运行的子

“Step In to” “Run To Cursor”和“Attach to Process”

其中

“Go”选项 从当前语句开始执行程序直到遇到断点或遇到程序结 束 “Stcp Into”选项 用于单步执行程序 并在遇到函数调用时进入 函数内部再从头单步执行 “Run to Cursor”选项 用于在调试运行程序时 使程序在运行到 当前光标所在位置时停止

事实上 这相当于设置一个临时断点

“Attach to Process”选项 在调试过程中直接进入到正在运行的进 程中 启动调试器后 “Debug”菜单将代“Build”菜单出现在菜单栏中 使用 “Debug”菜单可以控制程序的执行 此外 调试器启动后 “Edit”菜单和 “View”菜单中与调试有关的选 项将可以使用 www.BOOKOO.com.cn

“View”菜单包含用于显 41


Visual C++6.0 编程实例与技巧

示调试信息的命令

从“Edit”菜单可以访问“Br eakpoints”对话框

从中

可以插入或编辑不同类型的断点 7. “Debugger Remate Connection”选项 选择该项将弹出“Remate Connection”对话框

可以对远程调试链接

设置进行编辑 8. “Execute”选项 该选项用于运行程序 Visual C++系统将根据被运行程序的目标格式 自动调用相应的环境(如 MS-DOS,Windows 95 98 或 Windows NT 等) 9. “Set Active Configuration”选项 该选项用于选择活动项目的配置 如 Win32 Release 和 Win32 Debug 10. “Configurations”选项 选择该项将弹出“Configurations”对话框 可以编辑项目的项目配置 11. “Profile”选项 剖视器“Profile”用于检查程序运行行为 利用剖视器提供的信息 可 以找出代码中哪些部分是高效的 哪些部分要更加仔细地加以检查 此 外

利用剖视器还可以给出未执行代码 区域的诊断信息

不是用于查错

而是用于使程序能更好地运行

例如

剖视器通常

确定算 法是否

高效 函数是否被频繁使用 代码片段是否在测试过程中被覆盖 等等 使用剖视器之前 必须通过“Project Sttings”对话框的“Link”选项打开 “剖视使能”( Enable profiling) 接”(Link incrementally)

注意

打开“剖视使能”将关闭“增量链

如果要进行行剖视 还必须包括调试信息

选择“Profile”选项将弹出“Profile”对话框(图 2.19)

从中可以进行以

下剖视 函数计时(Eunction timing) 函数计时记录每个函数被调用了多少 www.BOOKOO.com.cn

42


Visual C++6.0 编程实例与技巧

次(命中次数)以及每个函数和被调用函数中所花的时间 函数计数(Function count) 函数计数记录每个函数被调用的次数 (命中次数)

要启动函数计数功能

必须启动定制批处理文件来指定要

剖视的源模块和行 方法为先选择“Cust om”

然后在“Custom Settings”

文本框中选择要访问的批处理文件

图 2.19 “Profile”对话框 函数覆盖(Funcion Couerage)

函数覆盖记录函数是否被调用过

通过函数覆盖 可以知道代码的哪一部分未被执行 剖视器列出所有被 剖视的函数

在被执行的函数前用星号标记

行计数(Line count) 行记数记录每行被执行了多少次(命中次数) 要使用行计数功能 必须使用其批处理文件指定要剖视的源模块和行 方法为先选择“Custom”

然后在“ Castom Settings”文本框中选择要访问

的批处理文件 行覆盖(Line coverage)

行覆盖用于确定代码的哪一部分未被执

行 剖视器列出所有被剖视执行

被执行的行前用星号标记

通常对整个程序进行剖视是没有意义的 剖视器使用 PROFILERINI

www.BOOKOO.com.cn

43


Visual C++6.0 编程实例与技巧

文件来指定在系统安装目录下的 BIN 图像创建 PROFILE.INI 文件 PROFILER.INI 文件是 profiler 部分用于指定要忽略的库和目标文件 (.obj) 缺省时 PROFILER.INI 排除 Win32 库 MFC 库和 C 运行时间 库 可以在“Advanced Settings”文本框中指定剖视时要包括或排除哪些 函数或代码区

例如

使用“

EXC MY.OBJ

INC MyFunc”将排除

MyFunc 函数外的 MY.OBJ 中的所有函数 使用“

E XC ALL INC

MY.CPP(4-8)”将排除 4 8 行外的 MY.CPP 文件中的所有代码行 而使 用“ SF MyFunc”将只剖视 MyFunc 及其调用的函数 此外 可能使用“Merge”选项将多个剖视会话的信息组合在一起形成 综合报告 以便获取 更加精确的信息 菜单 2.3.7 “Debug”菜单 启动调试器后

“Debug”菜单将取代“Build”菜单出现在菜单栏中

“Debug”菜单中包含调试过程经常要用到的命令选项(图 2.20) 1. “Go”选项 该选项用于在调试过程中从当前语句启动或继续运行程序(等价于 Build 工具栏的“Go”按钮 )

执行时

断点处停止

这样

趣的程序段

从而提高调试速度

程序会一直正常运行

直到到达

在调试过程中就可以越过 某些已知正确或不感兴

www.BOOKOO.com.cn

44


Visual C++6.0 编程实例与技巧

图 2.20 “Debug”菜单 2. “Restort”选项 在调试过程中 我们经常会进行一种循环操作 首先找到一条“错误” 语句

接着对其做些 修改

然后再从头开始对程序进行调试执行

而确定刚刚修改的语句是否按期望的结果执行

这时就可以使用

“Restort”选项 选择该选项 系统重新装载程序到内存并放弃所有变量 的当前值(断点表达式和观察点表达式仍可用) 3. “Sap Delbrggizg”选项 该选项用于中断当前的调试过程并返回正常的编辑状态 4. “Bresks”选项 在当前位置暂停程序运行 5. “Apply Code Change”选项 该选项用于使修改部分代码生效

以便随时修改调试中发现问题的

程序代码 6. “Step Into”选项 www.BOOKOO.com.cn

45


Visual C++6.0 编程实例与技巧

在调试过程中单步执行程序

而且当程序执行到某一函数调用语句

时 进入该函数内部从头单步执行 7. “Step Over”选项 在调试过程中单步执行程序 但当程序执行到某一函数调用语句时 不进入该函数内部 而是直接执行该调用语句 接着再执行调用语句后 面的语句 8. “Step Out”选项 该选项与“Step Into”选项配合使用 当执行“Step Into”命令进入函数 内部并开始从头单步执行时 若发现并不需要对该函数的内部进行单步 调试 那么就可以使用“Step Out” 选项使程序暂时中止单步运行状态直 接向下运行 处停止

直到从该函数内部返回

在该函数调用语 句后面的语句

重新恢复单步运行状态

9. “Run to Cursor”选项 该选项用于在调试运行程序时 时停止

事实上

使程序在运行到当前光标所在位置

这相当于设置一个临时断点

10. “Step Into Specific Function”选项 该选项用于单步执行选定的函数 11. “Exceptions”选项 选择该项将弹出“Exceptions”对话框(图 2.21) 显示与当前程序有关 的所有异常

可以控制调试器如何处理系统异常和用户自定义异常

www.BOOKOO.com.cn

46


Visual C++6.0 编程实例与技巧

图 2.21 “Exceptions”对话框 12. “Threacls”选项 选择该选项将弹出“Threacls”对话框 显示调试过程中可用的所有线 程 可以挂起和恢复线程并设置焦点(Focus) 13. “Modules”选项 选择该项将弹出“Module List”对话框

显示了组成当前项目的所有

代码模块以及前后调用顺序 14. “Show Next Staement”选项 选择该项将显示正在执行的代码行 15. “Quick Watch”选项 选择该项将弹出“Quick Watch”对话框 可以查看及修改变量和表达 式或将变量和表达式添加到“Watch”窗口 菜单 2.3.8 “Tools”菜单 “Tools”菜单中的命令选项(图 2.22)用于浏览程序符号

定制菜单与

工具栏 激活常用的工具(如 Spy++等)或者更改选项设置等

www.BOOKOO.com.cn

47


Visual C++6.0 编程实例与技巧

图 2.22 “Tools”菜单 1. “Source Browse”选项 缺省情况下

在建立项目时

编译器会创建与项目中每一程序文件

信息有关的.SBR 文件 实用程序 BSCMAKE 将汇编这些.SBR 文件为单 个浏览信息数据库

浏览信息数据库的名字为项目 基类名加上扩展

名.BSC 组成 选择“Source Browse”选项将弹出浏览窗口(图 2.23) 从中显示与程序 中所有符号(类

函数

数据

类型)有关的信息

对象或上下文

浏览窗口的外观和控制 是不同的

www.BOOKOO.com.cn

对于不同类型的

48


Visual C++6.0 编程实例与技巧

图 2.23 浏览窗口 通常 使用浏览窗口主要可以查看以下信息 源文件中所有符号的信息 包含某个符号定义的源代码行 利用某个符号的所有源代码行 基类和派生类之间的关系图 调用函数和被调用函数之间的关系图 缺省时

系统在建立项目中自动创建浏览信息库

当然

为了加速

项目的建立过程 可以关闭缺省设置 需要时再打开 可以使用“Project Setting”对话框的“Browse Info”选项 卡来设置是否在建立项目过程中创 建浏览信息文件 打开工作区时 Visual C++ 6.0 就自动打开相应的浏览信息数据库 如果要查看其他项目的浏览信息

可以使用“File”菜单的“Open”命令打

开相应的浏览信息数据库 2. “Close Source Browser File”选项 选择该项用于关闭打开的浏览信息数据库

www.BOOKOO.com.cn

49


Visual C++6.0 编程实例与技巧

3. “Spy++”选项 Spy++是 Win32 实用程序 用于给出系统的进程 线程 窗口和窗 口消息的图形表示

使用 Spy++ 可以做以下工作

显示系统对象(包括进程

线程和窗口)间的图形关系

搜索指定的窗口

进程或消息

线程

查看选定对象的属性 例如 对于窗口 可能查看窗口标题 窗 口句柄 窗口函数 边界矩形 窗口客户区 实例句柄等 对于进程 可以查看模块名 线程号 线程号

线程状态等

CPU 时间等 对于线程 可以查看模块名

对于消息

可以查看窗口句柄

嵌套深度

数值等 从视图中直接选择一个窗口

线程 进程或消息

使用查找工具选择鼠标指定的窗口 选 择 “Spy++” 选 项 将 启 动 Spy++ “Windows 1”的窗口(图 2.24)

Spy++ 启 动 后

打开标题为

该窗口为 Spy++的“Windows”视图 用于

显示所有窗口的树形关系并控制系统 中的活动对象 Spy++还包含以下 三个视图 “Process”视图 Windows 98 或 Windows NT 支持多进程 每个进 程有自己的一个或多个线程 口

每个线程有与自己相关的一个多顶层窗

每个顶层窗口又可以拥有一系列子窗口

使用“Process”视图可以

查看特定的系统进程(通常对应一个正执行的程序) “Threads”视图 “Threads”视图是所有线程及其相关窗口的一个平 面列表

“Threa ds”视图中不包括进程

但可以从“Threads”视图快速找

到拥有选定线程的进程 “Message” 视 图

每个窗口都有相应的消息流 www.BOOKOO.com.cn

可以使用 50


Visual C++6.0 编程实例与技巧

“Message”视图查看消息流

也可以为线程或进程创建“Message”视图

从而可以查看传递给由指定线程或进程所拥 有的所有窗口的消息

图 2.24 Spy++窗口 4. “Cuotomize”选项 选择该项将弹出“Cuotomize”对话框(图 2.25) 可以对命令

工具栏

工具菜单和键盘加速键进行定制

例如

加命令到“Tools”菜单 中 删除“Tools”菜单中的命令 更改命令加速键 修改工具栏等等

www.BOOKOO.com.cn

51


Visual C++6.0 编程实例与技巧

图 2.25 “Cuotomize”对话框 5. “Options”选项 选择该项将弹出“Options”对话框(图 2.26) 可以对 Visual C++ 6.0 的 环境设置进行更改(如调试器设置 窗口设置 目录设置 工作区设置等)

www.BOOKOO.com.cn

52


Visual C++6.0 编程实例与技巧

图 2.26 “Options”对话框 6. “Macro”选项 创建和编辑宏文件 7. 其他选项 “Tools”菜单中的其他命令选项将启动相应用户自定义工具(如“MFC Tracer”

“Regist or Control”等)

菜单 2.3.9 “Window”菜单 在“Window”菜单中包含用于控制窗口属性的命令选项(图 2.27)

www.BOOKOO.com.cn

53


Visual C++6.0 编程实例与技巧

图 2.27 “Window”菜单 1. “New Window”选项 选择该选项将打开新的窗口 从中显示当前文档信息 2. “Split”选项 将窗口拆分为多个面板

为同时查看同一文档的不同内容提供了方

便 3. “Docking View”选项 打开或者关闭窗口的船坞化(docking)特征 用程序窗口的边界

船坞窗口总是附属于应

或者浮动于屏幕上的任意位置

4. “Close”选项 关闭选定的活动窗口 5. “Close All”选项

www.BOOKOO.com.cn

54


Visual C++6.0 编程实例与技巧

关闭所有打开的窗口 6. “Next”选项 激活下一个未船坞化的窗口 7. “Previous”选项 激活上一个未船坞化的窗口 8. “Cascade”选项 该选项用于将当前所有打开的窗口在屏幕上重复排放

就像一叠卡

片一样 这样 用户就可以很容易地查看打开窗口的数目以及相应的文 件名

这种排列窗口的方法缺点是只能看 到最顶层窗口中的内容

9. “File Horizoxtally”选项 选择该项将使当前所有打开的窗口在屏幕上纵向平铺

每个打开的

窗口都具有同样的形状和大小 这种排列窗口的方法优点是可以同时浏 览所有打开的窗口内容

缺点是如果打开的窗 口过多

那么每个窗口

就会太小 10. “File Vertically”选项 选择该项将使当前所有打开的窗口在屏幕上横向平铺 11. 打开窗口的历史记录 在“Tile Vertically”选项下面列出的是最近打开的窗口的文件名 用鼠 标单击某个文件 名即可显示相应的窗口 12. “Windows”选项 选择该项将打开“Windows”对话框 可以管理刚打开的窗口 菜单 2.3.10 “Help”菜单 通过 Help 菜单 可以了解 Visual C++ 6.0 的帮助窗口不再使用 Visual

www.BOOKOO.com.cn

55


Visual C++6.0 编程实例与技巧

Studio 窗口

而是单 独的 MSDN(Microsoft Develper Network)窗口 它

应用 HTML Help 技术 使得查询帮助与编 写代码可以同时进行 可以 把帮助窗口 MSDN Visual Studio 98 窗口平铺在桌面 或者把 MSD N 窗 口暂时最小化到状态条 需要时随时激活 这样一边编程一边查询帮助 效率极大提高( 若是编程行家

他会经常查询联机文档而不是去抱书

本) 选择 Help 菜单的“Contents”

“Search”或“Index”选择均可以激活

MSDN 窗口 另外 在源代码编辑窗口中 选定对象(把光标置于其上) 后按 F2 功能键 亦能启动 MSDN 窗口 启 动后的 MSDN 窗口(图 2.28) 如果在“Help”菜单中选择了“Use Extension Help”(使用扩展帮助)选 项 则上述操作中 不再出现 MSDN 窗口 而是被“帮助主题 Microsoft Developer Studio Extension Help ”选项取消选中标志

即可重新出现

MSDN 窗口(图 2.29)

www.BOOKOO.com.cn

56


Visual C++6.0 编程实例与技巧

图 2.28 “MSDN”帮助窗口

www.BOOKOO.com.cn

57


Visual C++6.0 编程实例与技巧

图 2.29 扩展帮助窗口 2.4 项目及项目工作区 Developer Studio 以项目工作区(project workspace)的方式来组织文 项 项目和项目配置 通过项目工作区窗口可以查看和访问项目中所有 元素 每个项目工作区由工作区目条中的项目工作区文件所组成

项目工

作区文件用于描述工作区及其内容 扩展名.dsw 工作区目条是项目工 作区的根目录

添加到项目工作区中的项目可以 位于其他路径甚至不

同的驱动器中 首次创建项目工作区时

将创建一个项目工作条目

一个项目工作

区文件以及相关的文件(包括一个项目文件和一个工作区选项文件) 作区选项文件用于存储项目工作区设置

扩 展名为.oopt

每个项目由一组项目配置和一组源文件组成

创建项目时

系统缺

省自动为每个平台创建两种项目配置 即 Win32 Debug Win32 Release Win32 Release 配置不包含调试信息并可以选 择优化配置 通常情况下 系统缺省创建的项目配置所指定的设置说明对于项目设置说明进 行更 改

可以使用“Project Settings”对话框 建立项目后

可以添加任何其他目条的文件到项目中

添加文件到

项目中并不改变文件的物理位置 项目仅仅是记录文件的名字和位置 并在工作区窗口显示图标以便指明在项目中该 文件与其他文件的关 系 在项目工作区中 项目之间可以有以下关系 顶层项目(top-level project)

顶层项目与任何其他项目不具有依

www.BOOKOO.com.cn

58


Visual C++6.0 编程实例与技巧

赖关系 不包含任何其他项目的子项目 每一个项目工作区至少有一个 顶层项目 子项目(Subproject) 项目与另一项目有依赖关系 建立包含子项 目的项目时 必须先建立子项目 任何项目都可以是其他项目的子项目 相互关联(inter dependencies)

项目与其他类型项目(如 Visual

Basic 项目)有依赖关系 创建或者打开项目工作区时 Developer Studio 将在项目工作区窗口 中显示与项目有关的信息 图 2.30 是一个典型的项目工作区窗口 项目工作区窗口主要由三个面板构成 即 FileView Resource View Class View 由于 帮助窗口已被做成单独的联机帮助 MSDN 窗口 故 在工作区窗口中 Visula C++ 6. 0 只有三个视 图(Visual C++ 5.0 版本中 还有一个 InfoViewor 视图) 每个面板用于指定项目工 作区中所有 项 目的不同视图 每个面板至少有一个顶层文件夹 顶层文件夹由组成项 目视图的元素组成

通过扩展文件夹可以显示视图的详细信息

视图

中每个文件夹可以包含其他文件夹或各种 元素(如子项目 文件 资源 类和标题等) 1. Class View 面板 Class View 面板用于显示项目中定义的 C++类(图 2.31) 扩展顶层文 件夹可以显示 类

扩展类可以显示该类的成员

通过 Class View 视图 可以定义新类 直接跳转到代码(如类定义 函数或者方法定义等)

创建函数或方法声明等

www.BOOKOO.com.cn

59


Visual C++6.0 编程实例与技巧

图 2.30 项目工作区窗口

图 2.31 Class View 视图 2. Resource View 面板

www.BOOKOO.com.cn

60


Visual C++6.0 编程实例与技巧

Resource View 面板用于显示项目中包含的资源文件 扩展顶层文件 夹可以显示资源类型(图 2.32)

扩展资源类型可以显示其下的资源

3. File View 面板 File View 面板显示不项目之间的关系以及包含在项目工作区中的文 件

扩展顶层文件夹可以显示包含在项目中的文件(图 2.33)

图 2.32 Resource View 面板

www.BOOKOO.com.cn

61


Visual C++6.0 编程实例与技巧

图 2.33 File View 面板 2.5 资源与资源编辑器 资源作为一种界面成份 可以从中获取信息并在其中执行某种动作 Visual C++ 6.0 可以处 理的资源有加速键(Accelerator) 位图(Bitmap) 光标(Cursor) 对话框(Dialog Box) Table)

图标(Icon) 菜单(Menu) 串表(String

工具栏(Toolbar)和版本信息(Version In formation)等

2.5.1 资源编辑器 Developer Studio 提供有功能强大且易于使用的资源编辑器 用于创 建和修改应用程序的资源 使用资源编辑器 可以创建新的资源 修改 已有的资源

拷贝已有的资源以及删除不再 要的资源

例如

用加速

键编辑器处理加速键表 用图形编辑器处理图形资源(工具栏 位 图 光标和图标等) 用对话编辑器处理对话框 用菜单编辑器处理菜单 等 等 创建或者打开资源时

系统将自动地打开相应的编辑器

编辑器打

开后 单击鼠标右按钮将弹出快捷菜单 其中列有与当前资源有关的命 令 例如 对于对话编辑器 其快捷菜单含有 “Cut” “Copy” “Paste” “Insert ActiveX Control” “Size to Content” Top Edges”

“CH Check Mnemonics”

“Properties”等命令 其中

“Align Left Edges” “Align

“Class Wizar d”

“Events”和

“Insert ActiveX Control”命令用 于插入新的

ActiveX 控 件 到 对 话 框 中 ;“Class Wizard” 命 令 用 于 启 动 Class Wizard;“Prope rties”命令用于启动属性对话框 1. 创建新的资源

www.BOOKOO.com.cn

62


Visual C++6.0 编程实例与技巧

从“Insert”菜单选择“Resource”命令 弹出“Insert Resource”对话框(如 图 2.34 所示 ) 如果要创建新的资源 从“Resource Type”列表选择资源 类型 然后单击“New”按钮

图 2.34 “Insert Resource”对话框 新创建的资源将加入到当前资源文件中 此外

可以单击 Resource 工具栏中的相应按钮(图 2.35)来创建新的

资源

www.BOOKOO.com.cn

63


Visual C++6.0 编程实例与技巧

图 2.35 Resource 工具栏 Resource 工具栏包含以下按钮: New Dialog:创建新的对话框资源 New Menu:创建新的菜单资源 New Cursor:创建新的光标资源 New Icon:创建新的图标资源 New Bitmap:创建新的位图资源 New Toolbar:创建新的工具栏资源 New Accelerator:创建新的加速键表资源 New String Table:创建新的串表资源 New Version:创建或者打开版本信息资源 Resource Symbols:浏览和编辑资源文件中的资源符号 2. 查看和修改资源 可以使用项目工作区的窗口的 Resource View 面板来查看资源 首次 打开 Resource View 面板时 系统自动压缩每个资源分类 可以单击“+” 标记来扩展为每一分类 可以使用菜单命令来复制

移动

粘贴或者删除资源

也可以通过

双击打开相应的编辑器来修改资源 也可以用资源属性对话框来修改资 源的语言属性或条件属性 3. 导入位图 光标或图标 可以将单独的位图

光标或图标文件导入到资源文件中 方法为:

(1) 在 Resource View 面板单击鼠标右按钮 从快捷菜单选择“Import” 命令 弹出“Impor t Resource”对话框 (2) 从对话框选择要导入的.BMP(位图) .ICO(图标) .CUR(光标)文 www.BOOKOO.com.cn

64


Visual C++6.0 编程实例与技巧

件 (3) 选择后单击“Import”按钮即可将文件添加到当前资源文件中 此外 还可以使用快捷菜单的“Export”命令将位图 光标或图标从资 源文件导出到单独的文件中 4. 资源模板 除了创建资源文件外

还可以创建资源模板

可以在资源模板的基础上创建新的资源

例如

资源模板创建后

要创建多个含有 Help

按钮和公司徽标的对话框 则可以先创建含有 He lp 按钮和公司微标的 对话框模板 然后基于对话框模板创建新的对话框 这些对话框都含有 Help 按钮和公司徽标 2.5.2 资源符号 资源符号 用于在源代码或资源编

资源符号由映射到整数值上的文本串组成

辑器中引用资源或对象 在创建新的资源或对象时 系统自动为其提供 缺省符号名(如 IDDABOUTBOX)和符号 值

缺省时 符号名和符号值

自动保存在系统生成的资源文件 resource.h 中 可以使用资源属性对话框来改变资源的符号名或符号值

方法为:

(1) 在 Resoure View 面板选择要处理的资源 (2) 从“Edit”菜单选择“Properties”命令或按 Alt+Enter 键 弹出相应的 资源属性对话框 (3) 在“ID”文本框输入新的符号名或符号值 或从已有的符号列表选 择一种符号

如果输入新的符号名 系统会自动为其赋值

也可以在文本编辑器中直接修改 resource.h 文件来改变与多个资源 或对象有关的符号

www.BOOKOO.com.cn

65


Visual C++6.0 编程实例与技巧

符号名通常带有描述性的前缀来表示所代表的资源或对象类型 如 加速键或工具栏前缀 为“IDR”

对话框前缀为“IDD”

光标前缀为

“IDC” 图 标前缀为“IDI” 位图前 缀为“IDB” 菜单项的前缀为“IDM” 命令前缀为“ID”

控件前缀为“IDC”

串表中的串前缀为“IDS”

消息

框中的串前缀 为“IDP” 符号值通常有一定的限制 例如 资源(加速键 位图 光标 对话 框 图标 菜单 串表 及版本信息)的符号值范围为十进制的 0 32767 而资源构件(如对话框控件或串表中的串) 的符号值范围为 0 65534 或 -32767

32767

2.5.3 资源符号浏览器 随着应用程序的大小和复杂程度的增加 不断增多

与其相关的资源符号也会

要手工跟踪分散在不同文件中的大量资源符号是相当困难

的 资源符号浏览器简化了符号的管理 使用资源符号在浏览器可以: 快速浏览已有资源符号的定义以便了解每个资源的符号值 已使 用的资源符号列表及与每个符号相关的资源 创建新的资源符号 更改资源的符号名和符号值 删除不再使用的资源符号 快速切换到某个资源所对应的编辑器中 2.5.4 对话编辑器 对话框作为一种 Windows 资源 用于显示并从用户处获取信息 对 话编辑器用于创建或者编辑对话框资源或对话框模板

www.BOOKOO.com.cn

使用对话编辑

66


Visual C++6.0 编程实例与技巧

器 可以作以下工作: 添加

排列或编辑控件

改变控件的制表顺序(Tab Order)或助忆键(Mnemonic Key) 调整对话布局 添加或编辑 ActiveX 控件 创建用户自定义控件 导入 Visual Basic 表单到对话资源中 测试对话框 图 2.37 是打开某一对话框资源后的对话编辑器 对话编辑器打开时 将显示对话框工具栏和控件工具栏

图 2.37 对话编辑器 1. 添加并编辑控件 创建对话框的第一步就是添加控件到对话框中

可以添加到对话框

中的控件主要有图片(Pic ture) 静态文本(Static Text) 编辑框(Edit Box)

www.BOOKOO.com.cn

67


Visual C++6.0 编程实例与技巧

或组框(Group Box) 按钮(Button)

复选框(CheekBox) 单选钮(Radio

Button) 组合框(Combo Box) 列表框(List Box) 水 平滚动条(Horizontal Scroll Bar) 垂直滚动条(Vertical Scroll Bar)

微调控件(Spin)

进展条

(Progress) 轨道条(Slider) 热键(Hot Key)

列表控件(List Control)

形 控件(Tree Control) 制表控件(Tab Control)和动画(Animate)等 这些 控件类型都显示在 控件工具栏中(图 2.38) 此外 还可以根据需要创建 新的定制控件(Custom Control) 添加控件最简单的方法就是将控件从控件工具栏拖到对话编辑窗口 的指定位置后释放鼠标 此外 还可以先从控件工具栏单击要添加的控 件类型(如果要添加多个同一类型的控件 则应同时按住 Ctrl 键) 然后 移光标到要添加控件的位置

单击并拖动鼠标

当控件大小满足 要求

时释放鼠标

www.BOOKOO.com.cn

68


Visual C++6.0 编程实例与技巧

图 2.38 控件工具栏 添加控件后可以单击来选择要修改的控件

或者使用控件工具栏的

选择工具或按住 Shift 键 再单击来选择多个控件 选择控件后 就可以 对其进行移动 复制 删除或调整 也可以拖 动尺寸句柄(图 2.37 所示) 来缩放控件 此外

还可以使用控件属性窗口来修改控件属性

2. 格式化对话框 对话编辑器提供有专门的工具用于格式化对话框 在对话工具栏中(图 2.39 所示)

这些工具都显示

对话工具栏包含以下工具按钮:

图 2.39 对话工具栏 Test:运行对话框以测试对话框的外观和行为 Align Left:将选定的控件按左对齐格式放置 Align Right:将选定的控件按右对齐格式放置 Align Top:将选定的控件按上对齐格式放置 Align Bottom:将选定的控件按下对齐格式放置 Central Vertical:将选定的控件按中心垂直对齐格式放置 Central Horizontal:将选定的控件按中心水平对齐格式放置 Space Across:使选定的控件两两水平间隔相同 Space Down:使选定的控件两两垂直间隔相同

www.BOOKOO.com.cn

69


Visual C++6.0 编程实例与技巧

Make Same Width:使选定的控件有相同的宽度 Make Same Height:使选定的控件有相同的高度 Make Same Size:使选定的控件有相同的宽度和高度 Toggle Grid:在显示或隐藏网格间切换 Toggle Grides:在显示或隐藏尺间切换 此外 在进入对话编辑状态时 屏幕菜单栏将增加“Layout”菜单 可 以使用其中的命令选项来格式化对话框 3. 改变制表顺序和助忆键 制表顺序是指在对话框中按 Tab 键将输入焦点从一个控件移到另一 个控件的顺序 通常都是从左到右 从上到下 每个控件的 Tab stop 属 性用于确定控件是否接收输入焦点 如果要改变控件的制表顺序

请从“Layout”菜单选择“Tab Order”命

此时每个控件 的左上角标有一个数字

表顺序中的序号 的制表顺序

依次单击各个控件

用于指明该控件在当前制

于 是控件被单击的顺序即为新

设置后按回车键结束 Tab Order 方式

多数情况下 使用 Tab 键和箭头键就可将输入焦点从一个控件移到 另一个控件 但 Visual C+ +允许用户定义助忆键 以便直接按助忆键即 可跳到相应的控件中 对于具有可见 标题的控 件(如按钮 复选框或单 选钮等)

定义助忆键的方法为先选择该控件

然后从“Edit”菜单 选择

“Properties”命令 在属性窗口的“Caption”文本框输入符号 记符

对于无可见标题的控件

再输入助

可以先为其定义静态文本作为标题

然后在文本中欲作为助记符的 字母前加上

4. 使用 ActiveX 控件 ActiveX 控件是基于部件对象模型(Component Object Model www.BOOKOO.com.cn

简称 70


Visual C++6.0 编程实例与技巧

COM)的控件

在使用 Acti veX 控件时

首先必须从可再用控件库

(Component and Control Gallery)中添加 ActiveX 控 件到项目中 插入 ActiveX 控件后 ActiveX 控件将出现在对话编辑器的控件工具栏中 并 可 以像其他控件那样拖放到正在建立的对话框中 每个 ActiveX 控件 都有唯一的属性集 可 以使用控件的属性窗口来设置和修改其属性 5. 使用定制控件 定制控件(Custom Control)是具有专门格式的动态键接库(DLL)或者 用于向 Windwos 系统界面 添加额外特性和功能的对象文件 定制控件 既可以是已有对话框控件的变体

也可以是全新 的控件

可以设置定

制控件在对话框中的位置 输入其标题 标识其类名(应用程序以此名 字 作为控件的注册名)并输入 32 位的十六进制值来设置其风格 设计包含定制控件的对话框时

定制控件显示为灰色方块

测试运

行对话框时 定制控件也显示为灰色 而且其行为不能被模拟 可以使 用属性窗口来设置和修改定制控件的属性 6. 表单视图对话框 表单视图(Form View)是包含对话控件且与 CView 类兼容的窗口模 块 有些应用程序的主要功能就是用于数据输入 这时应用程序的主视 图除了包含用于输入数据的对话控件外 要建立表单视图

不再包 含其他内容

首先要用定制控件的方法创建对话框

并在对话

框属性窗口的“Style” 选项卡 中设置不同的风格属性(在“Style”下拉列 表框选择“Child”

在“Border”下拉列表框 选择“None”并清楚“Visiable”

复选框) 在“General”选项卡清除对话框的标题 然后 用 MFC 类库中 的 CForm View 类将表单视图嵌入到程序中 可以用同样的方法来创建对话栏

不同在于要用 MFC 类库中的

www.BOOKOO.com.cn

71


Visual C++6.0 编程实例与技巧

CDialog Bar 类来嵌入对话栏到 程序中 7. 测试运行对话框 Visual C++ 6.0 可以在对话框编辑器中测试运行对话框 从而可以及 时了解对话框的布局和 行为 选择“Layout”菜单的“Test”命令或单击对 话框工具栏的“Test”按钮 可以输入文本

即可进入 测试运行状态 在测试运行状态

以组合框或列表框进行选择

测试对话框 的热键是否

有效 等等 2.5.5 菜单编辑器 菜单编辑器用于创建并编辑菜单资源

使用菜单编辑器

可以创建

标准菜单和菜单选项 为菜单或菜单选项定义热键 加速键和状态栏提 示

也可以创建快捷菜单

以便用鼠标右控钮 来执行要频繁使用的命

令 建立菜单或菜单选项后 可以用 Class Wizard 为菜单选项编写要 执 行的代码

图 2.40 为打开某一资源后的菜单编辑器

图 2.40 菜单编辑器 1. 创建菜单和菜单选项 www.BOOKOO.com.cn

72


Visual C++6.0 编程实例与技巧

进入菜单编辑器后

就可以在菜单栏中创建菜单和菜单选项

要创建菜单栏中的菜单

方法为:

(1) 在菜单栏中选择新项方框 或按 Tab 键(向右移) Shift+Tab 键(向 左移)或左右箭头键移到新项方框 如果要在某一菜单前插入新的菜单 则移到该菜单处按 Ins 键 (2) 输入菜单名 开始输入名字时 系统弹出“Menu Item Properties” 对话框(图 2.41)

在对话框的“Caption”文本框输入菜单名

图 2.41 “Menu Item Properties”对话框 如果要为菜单定义助忆符

则在相应的字母前加

同一菜单栏上的助忆符不 相冲突 项

注意

要确保

如果要建立单项菜单而不带菜单选

则应清除“Prop-up”复选框 创建菜单后就可以为其添加菜单选项 方法为:

www.BOOKOO.com.cn

73


Visual C++6.0 编程实例与技巧

(1) 选择菜单的新项方框 或者选中某个已有菜单选项再按 Ins 键 新项自动插在该项之前 (2) 输入菜单选项的名字 Properties”对话框

开始输入名字时

系统弹出“MenuItem

在对话框的“Caption”文本框输入菜单选项名 如果

要为菜单定义助忆符 则在相应的字 母前加 (3) 在“ID”文本框输入菜单选项的 ID 号或选取已有的 ID 号 如果不 输入 ID 值

则系统根 据选项名称自动生成一个 ID 值

(4) 在菜单选项的属性对话框可为菜单选项指定风格 有些菜单选项可能还含有下级子菜单(即级联菜单)

要创建级联菜

单 方法为: (1) 在菜单中欲显示级联菜单的位置按 Ins 键 输入菜单选项名称 开始输入时 系统弹出 “Menu Item Properties”对话框 或者选取已有的 菜单选项 然后按 Alt+Enter 键 (2) 在菜单选项的属性对话框中选中“Pop-up”复选项 于是该项便被 标以级联菜单符号(

)

且在该项的右侧出现新项方框

(3)为级联菜单添加子菜单项(图 2.42)

www.BOOKOO.com.cn

74


Visual C++6.0 编程实例与技巧

图 2.42 级联菜单 2. 定义加速键 可以为 0 菜单选项定义加速键 以便直接按加速键执行相应的命令 (1) 选择要定义加速键的菜单选项 按 Alt+Enter 键 系统弹出“Menu Item Properties” 对话框 (2) 在“Caption”文本框中将加速键加到菜单标题的后面 如果在菜单 标题后输入转义符

t

则所有加速键都按左对齐格式显示 例如 为

“Go”菜单选项定义加速键 F5

则“Cap tion”文本框中的内容为“Go

t

F5” (3) 在加速键编辑器中建立相应的加速键表条目 并赋给与菜单选项 相同的 ID 号 3. 定义状态栏提示 除了为菜单选项定义加速键 只要选中该项

还可以为其定义状态栏提示

这样

系统将在状态栏提示相应的描述性文本

(1) 选择要定义状态栏提示的菜单选项 按 Alt+Enter 键 系统弹出

www.BOOKOO.com.cn

75


Visual C++6.0 编程实例与技巧

“Menu Item Propertie s”对话框 (2) 在“Prompt”对话框输入描述性文字 图 2.43 是为菜单选项“Go”定义加速键和状态栏提示后的“Menu Item Properties”对话框

图 2.43 为菜单选项定义加速键和状态栏提示 4. 创建快捷菜单 我们知道

单击鼠标右按钮将弹出相应的快捷菜单

有与当前光标所指位置最为相关的命令 单 首先要创建菜单本身

快捷菜单包含

在应用程序中要使用快捷菜

然后将其与应用程序代码链接

(1) 创建带空标题的菜单栏 (2) 输入临时字符为菜单标题

以便在菜单栏创建菜单

(3) 在菜单下创建快捷菜单的菜单选项 (4) 再次使菜单栏为空

以便使快捷菜单显示在空的菜单栏下

(5) 保存菜单资源 (6) 添加以下代码到源文件中:

www.BOOKOO.com.cn

76


Visual C++6.0 编程实例与技巧

CMenu menu; 装载并验证菜单资源 VERIFY(menu.LoadMenu(IDRMENUI)); CMenu * pPopup = menu.GetSubMenu(0); ASSERT (pPopup! = NULL); 显示菜单内容 pPopup->Track Popup Menu(TPMLEFTALLIGN 1TPMRIGHTBUTTON,X,Y,AfxGet MainWnd()); 创建快捷菜单的菜单资源后

应用程序代码装载菜单资源并使用函

数 Track PopupMenu()来显示菜单内容 一旦用户在快捷菜单外单击鼠 标

就应让快捷菜单消失

如果用户选择某个 命令

则传递消息句柄

给窗口 建立快捷菜单后

可以在菜单编辑器中单击鼠标右按钮

从弹出的

快捷菜单选择“View As Popup”命令来查看或修改所建立的菜单 2.5.6 加速键编辑器 加速键表是一种 Windows 资源 它含应用程序用到的所有加速键及 相应的命令标识符 Visua l C++ 6.0 允许应用程序包含多个加速键表 加速键通常是菜单或工具栏上所用程 序命令 的键盘快捷键 定义加速 键后 可以使用 Class Wizard 为加速键命令编写要执行的代码 使用加速键编辑器

可以添加

删除

更改和浏览项目所用到的加

速键 可以查看和更改与加速键表中每个条目有关的资源标识符(资源标 识符用于在程序代码中引用加速键表中的每 个条目) 还可以为每个菜 单选项定义加速键 图 2.44 是打开某一加速键表资源后的加速键 编辑 器 www.BOOKOO.com.cn

77


Visual C++6.0 编程实例与技巧

图 2.44 加速键编辑器 如果要在加速键表中添加新的加速键 按 Ins 键或选中表尾的新项方 框输入加速键名 弹出 “Accel Properties”对话框(图 2.45) 在“Key”文本 框输入键名 在 ID 文本框输入加速 键标识符

图 2.45 “Accel Properties”对话框 “Key”文本框中的合法输入为: 0

255 间的某个整数

可以写成十进制

www.BOOKOO.com.cn

十六进制式八进制的

78


Visual C++6.0 编程实例与技巧

格式

“Type”框的设置用于确定该数是作为 ASCII 码或是虚拟键码值

通常 一位数(0 9)被直接解释成相应的键 要输入 0

9 间的 ASCII 码值

而不是 ASCII 码值 如果

则必须在数字前加两个 0(如 005)

单个键盘字符 大写的 A Z 或数字 0 9 既可解释成 ASCII 码 值 也可看成虚拟键值

其他字都看成 ASCII 字符

单个 A Z 间的字符(大写形式)前加符号^(如^D) 表示同时按下 Ctrl 和字母键所产生的组合键的 ASCII 码值 任何合法的虚拟键标识符

可以单击“Key”文本框右侧的下箭头

来选择标准的虚拟键标识符 注意 当输入 ASCII 值时 “Modifiers”框中的修改符 Ctrl 和 Shift 是 无效的 即不能通过符号^和控制键的组合来产生相应的虚拟键值 此外 可以先单击“Next Key Typed”按钮 再按键盘上的相应键来定 义加速键 定义加速键后

如果要从加速键表删除某一加速键

可以先指定要

删除的加速键再按 Del 键 还可以将加速键从一个资源文件移动或复制 到另一个资源文件中 2.5.7 串编辑器 串表是一种 Windows 资源 包含应用程序用到的所有串的 ID 号 值和标题 例如 状态栏提示可以放在串表中 每个应用程序只能有一 个串表 在串表中 串以 16 个为一组构成段或块 某一串属于哪一段 取决于该串的标识符值 例如 标识符值为 0 15 的串放在第一段 为 1 6 32 的串放在第二段 等等 要将串从某一段移到另一段 将串从 某一资源文件移到另一 资源文件

修改串及其标识符 等等

www.BOOKOO.com.cn

79


Visual C++6.0 编程实例与技巧

图 2.46 是为打开某一应用程序的串表资源后的串编辑器

图 2.46 串编辑器 在串编辑器中

串表中的每个段用水平线分开

如果要在串表中添

加新的串 在要添加串的串段中 选择新项方框输入串标识符或选择某 个串再按 Ins 键 弹出“String Properties” 对话框(图 2.47) 在“ID”文本 框输入串标识符和值 在“Caption”文本框输入串标题

图 2.47 “String Properties”对话框 如果要从串表删除某个串 可以先选择该串再按 Del 键 如果要修 改串及其标识符

则先指 定欲修改的串

然后按 Alt+Enter 键

www.BOOKOO.com.cn

弹出

80


Visual C++6.0 编程实例与技巧

从中修改串

“String Properties”对话框 2.5.8 版本信息编辑器

版本信息主要由公司名称 册等信息组成

产品标识

版权和商标注

版本信息 编辑器是用于编辑和维护版本信息的工具

尽管版本信息不是应用程序所必须的 段

产品版本号

但它是标 识应用程序的有效手

每个应用程序只能有一个版本信息资源

其 名 称 为 VSVER

SIONINFO 如果要在应用程序中访问版本信息

必须在应用程序中调用函数

GetFile VersionInfo 和 Ver Query Value 图 2.48 是打开某一应用程序的版本信息资源后的版本信息编辑器

图 2.48 版本信息编辑器 每个版本信息资源由多个串块组成;每个串块分别表示不同的语言 或字符集

用户所要做的 就是在版本信息编辑器中定义与产品有关的

字符集和语言 在版本信息资源的顶部有一个固定信息块 www.BOOKOO.com.cn

固定信息由可编辑的数 81


Visual C++6.0 编程实例与技巧

字框和可选择的下拉列表 组成

版本信息资源的底部含有一个或多个

可编辑的文本框 可以使用“Key”按钮或“Value”按钮来排序串块中的信息序列 如果要 编辑版本信息资源

可以在编辑器中双击所要编辑的项

然后在相应

的文本框输入文本或在下拉列表中选择某 一项 在版本信息编辑器中 如果要添加新的串块 则可以从“Insert”菜单 选择“New Versio n Info Block”命令 从弹出的“Block Header Properties” 对话框(图 2.49)中为新的串 块选择相应的语言和字符集

图 2.49 “Block Header Properties”对话框 如果要从版本信息资源中删除某一块

则先高亮要删除串块的

“Block Header”项 再从“ Insert”菜单中选择“Delete Version Info Block” 命令即可 2.5.9 图形编辑器 图形编辑器由一套功能强大的绘图工具组成

用于绘制位图

图标

和光标 图 2.50 是正在编 辑某一位图的图形编辑器窗口

www.BOOKOO.com.cn

82


Visual C++6.0 编程实例与技巧

图 2.50 图形编辑器 图形编辑器窗口用两个视图来显示图形 边的视图以实际尺寸显示 图形

视图间用分割线隔开

右边的视图则是放大后的图形(缺省放

大 6 倍) 在其中某一视图所做的改动会立即反 映到另一视图中 这是 由系统自动完成的 可以用分割条来调节两个视图的相对尺寸 如果 要 激活某一视图 请按 Tab 键或 F6 键 或者直接在其中单击任一处 视 图激活后

其四周会 出现选择边界

表明该视图是激活的

图形工具栏由两部分组成 选项选择器用于设置绘图选项(如画刷宽 度);工具栏由选择(Sele ct)

自由选择(Select Region)

Color) 橡皮擦(Erase) 填充(Fill)

颜色拾取(Select

放大器(Magnify) 铅笔(Peneil) 画

刷(Brush) 喷枪(Airbrush) 画线(Line) 曲线(C urve) 心矩形(Rectangle)

边界矩形(Outlined Reetangle)

www.BOOKOO.com.cn

文字(Text) 空

实心矩形(F illed

83


Visual C++6.0 编程实例与技巧

Rectangle) 空心圆角框(Round Reet) 边界圆角框(Outlined Round Rect) 实心 圆角框(Filled Rouncl Rect) 空心椭圆(Ellipse) 边界椭圆(Outlined Ellipse)和实心 椭圆(Filled Ellipse)等 21 个工具组成 供用户绘制图 输 入文本 擦除并管理视图 颜色调色板由两部分组成 色(对于图标和光标而言

颜色指示反映当前所用的前景色和背景

则反映屏幕色和反转色);调色板用于选择前

景色或背景色 除了擦除器 其余所有绘图工具都用当前前景色(绘制时单击鼠标左 按钮)和背景色(绘制时 单击鼠标右按钮)来绘制图形

可以在绘制过程

当中随时改变前景色和背景色 如果要改 变前 景色 将鼠标移到调色 板中指定的颜色后单击鼠标左按钮;如果要改变背景色 将鼠标移到 调 色中指定的颜色后单击鼠标右按钮 图形编辑器中的所有绘图工具都有相同的操作方法

即先选择相应

的工具 如有必要再指定 前景色与背景色或绘图选项(如画刷宽度) 然 后移鼠标到指定位置后再单击鼠标或拖动鼠标 来绘制或擦除图形 进入图形编辑状态后 菜单栏将出现“Image”菜单 可以使用“Image” 菜单的命令选项来 处理图像和管理颜色调色板 1. 设置位图属性 要改变位图属性 先打开要改变属性的位图 然后从“Edit”菜单选择 “Properties”命令 或按 Alt+Enter 键 弹出“Bitmap Properties”对话框(图 2.51)

www.BOOKOO.com.cn

84


Visual C++6.0 编程实例与技巧

图 2.51 “Bitmap Properties”对话框 对话框含两个选项卡 即“General”和“Palette”选项卡 其中 “General” 选项卡用 于设置位图资源的标识符

大小和颜色数等;“Palette”选项卡

用于改变位图中的所有颜色 属性 2. 创建图标和光标 图标和光标的编辑操作和位图基本上相同 图不同的属性

例如

对 于不同的显示设备

但图标和光标具有与位 每个图标或光标可以包

含不同的图像 此外 光标具有热点(hotstop) ——Windows 使用热点来 跟踪光标位置 在创建新的图标或光标时 图形编辑器首先创建 VGA 图像 图像开 始以屏幕色来填充(图 2.52 ) 对于光标而言 热点被初始化为图像的左 上角 坐标为(0,0) 在创建新的图标或光标时

必须指定好目标显示设备

当打开图标

或光标资源时 与当前显 示设备最为匹配的图形被自动打开 缺省时 图形编辑器支持如表 2.3 所列的显示设备

www.BOOKOO.com.cn

85


Visual C++6.0 编程实例与技巧

图 2.52 创建新图标时的图形编辑器 表 2.3 图形编辑器支持的显示设备 [BHDFG2,WK12ZQ2,K9

3ZQ2W]

显示设备颜色

宽度

高度

Monochrome

232

32

Small

1616

16

Normal

1632

32

Large

25664

64

选择目标显示设备的方法为:单击图形编辑器窗口控制栏的“New Device Image”按钮 弹 出“New Icon Image”对话框(图 2.53)

从对话框

的“Target device”列表框选取所要的 显示设备

www.BOOKOO.com.cn

86


Visual C++6.0 编程实例与技巧

图 2.53 “New Icon Image”对话框 如果要创建除标准设备外的其他定制显示设备 在“New Icon Image” 对话框单击“Custom ”按钮 从弹出的“Custom Image”对话框(图 2.54)输 入宽度 高度和颜色数

图 2.54 “Custom Image”对话框 赋给图标或光标屏幕色或反转色主要有两个用处 图像的轮廓并对其着色

另一是指出着反转色的区域

需要改变代表屏幕色和反转色属性的颜色

一是勾画出派生 用户可以根据

但这 种改变不会影响图标

或光标在应用程序中的外观 要在图标或光标中建立透明或反转的区域 方法为:在颜色调色板中 单击屏幕色或反转色选 择器 应用屏幕色或反转色到图像中

www.BOOKOO.com.cn

87


Visual C++6.0 编程实例与技巧

要改变代表屏幕色或反转色属性的颜色 方法为:在颜色调色板中单 击屏幕色或反转色选择 器

从颜色调色板中选取一种颜色为屏幕色或

反转色 系统自动将所选颜色的补色赋给另一 未选中的选择器 如果双击屏幕色或反转色指示器

系 统 将 弹 出 “Custom Color

Selector”对话框 从中可 以对屏幕色或反转色进行定制 在图形编辑器中创建光标时

控制栏将显示光标热点坐标和“Set

Hotspot”按钮 缺省时 热点光标为(0,0) 如果要改变热点坐标 方法为: 单击控件栏中的“Set Hotspot”按钮

然后单击要设为当前热点的位置即

可 2.5.10 工具栏编辑器 工具栏通常由多个工具按钮组成 最频繁的命令

通过工具按钮可以快速执行使用

系统将每 个工具栏保存为相应的位图

栏上每个工具按钮的图像

其中包括工具

工具按钮的图像具有 相同的尺寸

缺省时

是 16 15(以像素为单位) 工具按钮的图像在位图中依次排列 这种排 列 次序表明了屏幕上显示时工具按钮在工具栏上的排列次序 具按钮都有相应的状 态和风格(被按下的

每个工

面上的 面下的 无效的

向下无效的或不确定的) 工具栏编辑器用于创建工具栏资源并可以将已有位图转换为工具栏 资源

工具栏编辑器以图 形方式显示要处理的工具栏及正被选择的工

具栏按钮图像

图 2.55 是打开某一工具栏后的工 具栏编辑器

与图形编辑器类似

工具栏编辑器用两个视图显示被编辑的按钮图

像 视图间用分割条分 隔

被编辑按钮图像的上面是要处理的工具栏

工具栏中由模糊边界包围的是正被选择的按钮

www.BOOKOO.com.cn

88


Visual C++6.0 编程实例与技巧

创建新的工具栏有两种方法:一是直接创建 另一是将已有的位图转 换为工具栏 如果要直接创建

请从“Insert”菜单选择“Resource”命令

在弹出的

“Insert Resourc e”对话框中选择“Toolbar” 然后单击“New”按钮 进入工 具栏编辑器后直接编辑

图 2.55 工具栏编辑器 如果要将已有的位图转换为工具栏 方法为: (1) 在图形编辑器中打开已有的位图资源 (2) “Image” 菜 单 选 择 “Toolbar Editor” 命 令 Resource”对话框 度

然后

弹 出 “New Toolbar

在对话框中设置与位图匹配的图标图像的高度和宽

单击“OK”按钮进入工具栏 编辑器

(3) 完成转换后 从“Edit”菜单选择“Properties”命令 从弹出的属性 对话框设置工具 栏按钮的命令 ID

www.BOOKOO.com.cn

89


Visual C++6.0 编程实例与技巧

创建工具栏资源后 可以使用 Class Wizard 将工具栏按钮与源代码 连接 2.6 快速的应用程序实例 通过前边的学习 你已经了解了 Visual C++ 6.0 的工具及环境平台是 多么的方便 易用并且功 能强大 这里介绍一个快速的 Visual C++开发 实例 让你亲自感受一下使用 Visua l C++ 6.0 下开发应用程序的方便 快捷

使你在一开始便能领略到 Visual C++ 6.0 的神 奇风采

本节要创建一个基于对话框的小应用程序

具体步骤如下:

(1) 启动 Visual C++ 6.0 可以通过双击桌面图标或单击开始菜单相 应菜单项完 成 (2) 选择 File 下拉菜单的 New 选项 弹出 New 对话框

如图 2.56

(3) 在 New 对话框中选择 Project 选项卡(缺省即为此选项) 单击左 边列表框中的“MFC AppW izard exe ”选项 在“Project Name”文本域 中输入项目名字 ddd 在“location”文 本域输入保存位置 确保“Creat new workspace”选项被选中 选择 Win32 平台 单击 OK 按 钮 弹出“Step 1” 对话框 表示为开发过程第一步

如图 2.57

(4) 在 Step 1 对话框中 选 Dialog based 其他项不要改动 然后单 击 Finish 钮 弹出“Ne w Project Information”对话框 对所创建项目进行 总结

所选项配置不合适 可按 Can cel 按钮退回前述步骤重新配置

这里单击 OK 按钮 接受配置

如图 2.58

www.BOOKOO.com.cn

90


Visual C++6.0 编程实例与技巧

图 2.56 New 对话框 Projects 选项菜单

www.BOOKOO.com.cn

91


Visual C++6.0 编程实例与技巧

图 2.57 AppWizard 开发过程的 Step1 对话框

www.BOOKOO.com.cn

92


Visual C++6.0 编程实例与技巧

图 2.58 确认项目配置的 New Project Information 对话框 在经过上述操作之后 AppWizard 自动为用户构造应用程序 你可“坐 享其成”啦!经过 创建 后

项目建立起来了 此时项目工作区窗口显示

出 AppWizard 为你创建的项目内容 选择“C lass View” 如图 2.59 扩 展顶层文件夹后

显示该项目拥有三个类:CAboutApp

CDddApp

CDddDlg(AppWizard 自动根据项目名来构造类名) 扩展 Globals 文件夹 显示项目拥有一 个全局变量 theApp

www.BOOKOO.com.cn

93


Visual C++6.0 编程实例与技巧

图 2.59 “ddd.dseu”项目的 Class View 视图 好了 可以单击 Build 菜单下的“Build ddd.exe”选项来编译该项目了 之后选择“Ex cute ddd.exe”运行它(当然也可从 Build 工具条上相应按 钮来完成)

运行结果如图 2.60 显示一

图 2.60 ddd.exe 运行结果为显示一对话框 个对话框

上面含有两个按钮及一个静态文本

www.BOOKOO.com.cn

单击标题栏上的控

94


Visual C++6.0 编程实例与技巧

制菜单 弹出下拉菜单

有“移动” “关闭” “关于 ”三个常见选项如

图 2.61

单击“关于 ddd

弹出 Abo ut 对话框

图 2.61 最简单的应用程序就已具有控制菜单功能 如图 2.62 其上显示版权信息 关闭该“About”对话框 试着使用对 话框上的 “确定”或“取

图 2.62 单击控制菜单“About...” 选项显示信息对话框 消”钮 会发现这些按钮能作用于对话框使之消失 另外 你会惊奇 地发现

控件标题及静态文本都是中文 而在 Visual C++ 5.0 中都是英

文 当时还不能在 资源中直接支持中文 多么强大的功能!几乎未经任何手工编程

只是点几下鼠标

AppWizard 就已为你构造好了整 个应用程序 若想查看 AppWizard 为 你构造的源程序代码 可以选择项目工作区的“File Vi ew” 扩展顶层文 件夹 显示 AppWizard 为你创建的文件类型有:Source Files Header Fil es

Resource File 等 扩展 Source File 文件夹 则显示该项目拥有的全

部源文件(.cpp)

双击其中某项 则在源代码编辑窗口显示该文件的代

码 如图 2.63

www.BOOKOO.com.cn

95


Visual C++6.0 编程实例与技巧

图 2.63 单击控制菜单“About...”选项显示信息对话框 需要说明的是

上述代码看不懂不要紧

重要的是让你感受一下

Visual C++ 6.0 为你提供的 工具是如何功能强大 方便易用 本书后面 部分为给出许多编程实例 到那时 你会自然明 白上述代码的含义 对于已熟练了 C++语言编程的读者 可从此处直接阅读第四章:传统 Windows API 编 程”

第三章“C++基础”是为那些尚不十分了解 C++语

言的读者编写的

www.BOOKOO.com.cn

96


Visual C++6.0 编程实例与技巧

第三章 C++语言基础 语言基础 在 C 语言基础上发展起来的 C++语言是一种面向对象的程序设计语 言

由于 C++提出了把数据 和在数据之上的操作封装在一起的类 对 并通过派生

象和方法的机制

继承

重载和多态 性等特征

实现了

人们期待已久的软件重用和程序自动生成 使得软件 特别是大型复杂 软 件的构造和维护变的更加有序和容易

并使软件开发能更自然地反

映事物的本质 从而大 大提高了软件的开发效率和质量 本章对 C++语言的基本结构做了简要介绍 为了使读者在短时间内 掌握 C++语言的精华 我们 略去了一些深层次的细节描述 但这绝不 影响 C++语言的基本功能 如果已经熟练 掌握了 C++语言的基本结构 那么可以越过本章

直接开始下一章的学习

3.1 简单的 C++程序 C++程序 首先看下面这个简单的 C++程序(为了方便起见 程序的每一行都加 上了行号) 1.

这是一个简单的 C++程序

2. # include <iostrream.h> 3. void main (void) 4. { 5.

int i;

6.

cout<<

7.

cout

8.

cin >> i;

欢迎使用

Visual C++ 5.0 使用与开发 !

n

;

n

9. }

www.BOOKOO.com.cn

97


Visual C++6.0 编程实例与技巧

上面这个程序是一个典型的 C++程序 下面我们逐条语句进行解释 (1) 第 1 条语句是 C++语言的注释语句 其中“ 释 符号

表示以“

”是 C++语言的注

”开始的一行语句为注释行

(2) 第 2 条语句用 于将 定义输 入

输出 函数 (cout,cin)的 头文 件

iostream.h 包含到程序中来 (3) 第 3 条语句说明主程序的开始 表示主程序返回的是 void 类型 其传递值也是 voi d 类型

的数据

(4) 第 5 条语句声明了一个整型变量 i (5) 第 6 条和第 7 条语句是 C++语言的输出语句 可以将指定的字符 串输出到屏幕上

cout 函数在头文件 iostream.h 中定义

(6) 第 8 条语句是 C++语言的输入语句 表示从屏幕上向变量 i 输入 cin 函 数也是在头文件 iostresm.h 中定义的

一个值

从上面这个程序可以看出 C++语言继承了 C 语言的许多特点 同 时也增加了许多 新的功能 后面我们将逐一介绍 C++语言的特点和语 法结构 在一般的程序设计中 涉及的基本问题有两个 一个是数据的描述 一个是动作的描述 就毫无作用 下面

没 有数据

数据是动作的对象

程序就无法动作

而没有动作

程序

而动作的结 果会决定数据的内容

我们首先对 C++语言的数据结构和控制结构逐一进行 介绍

3.2 标识符 标识符(identifier)在程序中可以用作变量(variable) 对象(object) 类 (class) 结构( structure) 联合(union) 枚举类型(enumerated type) 类型 (type) 函数(function)和 标号(abel)等的名字 标识符必须是以大写字母

www.BOOKOO.com.cn

98


Visual C++6.0 编程实例与技巧

小写字母和直划线()开始

标 识符可以 由以下字符组成

小写字母 下划线()和数字 0 9 在 C++ 语言中

大写字母

大写字母和小写字

母分别代表不同的标识符 比如 Name 和 name 就表示两个不同的标识 符 C++语言还包含一些特殊的标识符 键字是预定义 的标识符

通常称为关键字(keyword)

在 C++编译程序中有特殊的含义 因此不能

用关键字作为变量 常量等的名称 在 Visual C++ 6.0 中

主要包含

关键字 asm auto badcadt badtyped bool bre ak case catch char cl ass const constcast conlinue defaut delete do double dynamiccast else enum exc ept extern txplicit false finally float for friend goto if inline int

long mutab le namespace new operator private

protected public register reinterpret cast ret um short signed sizeof static staticcast struct template this

throw try type into typedef

typeid union unsigned usting virtual void vloatie while 和 x alloc 3.3 基本数据类型 C++语言的基本数据类型可以分为三类 即整数类型(integral) 浮点 类型(float ing)和 voi d 类型 整数类型用于处理整数 Visual C++支持五 种整数类型 Char Short i nt

mtn 和 lo ng

浮点类型用于处理包含

小数部分的数值 Visual C++支持三种浮点类型 flo at doubl e 和 long double void 类型用于描述的值的空集 主要用于声明不返回值的函数 或指向任一 类型的指针

www.BOOKOO.com.cn

99


Visual C++6.0 编程实例与技巧

3.3.1 类型 Char 类型 Char 是包含 ASCII 字符(英文字母 数字 标点符号及某些特 殊符号)的整数类型 类型 为 Char 的变量可以声明为 Char signed Char 和 unsigned Char 编译器将其看成不同类型的 数据 缺省时 类型 Char 即为 signed Char 如果使用 J 编译选项 类型 Char 即为 unsigned Char 类型 Char 的长度及存储范围如表 3.1 所列 表 3.1 类型 Char 的长度及存储范围 类型

名称长度(字节)

取值范围

Char

1

-128 或者 0

255(使用 J 编译选择) signed char

1

-128

unsigned char

1

0

127

255

3.3.2 类型 Short 型 Short(或 Short int)是一种整数类型 其长度大于或等于类型 Char 小于或等于类型 int 类型为 Short 的对象可以声明为 signed short 和 unsigned short

signed short 即为 shor t

类型 short 的长度及存储范围如表 3.2 所列 表 3.2 类型 Short 的长度及存储范围 类型

名称长度(字节)

取值范围

Short (signed Short)

2

-32768 32768

unsigned Short

2

0

www.BOOKOO.com.cn

65535

100


Visual C++6.0 编程实例与技巧

3.3.3 类型 int 类型 int 是一种整数类型 其长度大于或等于类型 int 小于或等于 类型 long 类型为 int 的 对象可以声明为 signed int 和 unsigned int signed int 即为 int 类型 int 的长度及存储范围如表 3.3 所列 表 3.3 类型 int 的长度及存储范围 类型

名称长度(字节)

取值范围

int(signed int)

4

-2147483648

unsigned int

4

0

2147483648

4294967295

3.3.4 类型 long 类型 long(或 long int)是一种整数类型 其长度大于或等于类型 int 类型为 long 的对象可 以声明为 signed long 和 unsigned long signedlong 即为 long 类型 long 的长度及存储范围如表 3.4 所列 表 3.4 类型 long 的长度及存储范围 类型

名称长度(字节)

取值范围

4

-2147483648

long(signed long) 2147483648 unsigned ling

4

0

4294967295

3.3.5 类型 intn 类型 intn 用于表示整数变量的大小 其中 n 表示位数 可以是 8 16 32 或 64 使 用类型 intn 可以声明 8 位 16 位 32 位或 64 位的整 数变量 例如 www.BOOKOO.com.cn

101


Visual C++6.0 编程实例与技巧

int8 nSmall;

声明变量 nSmall 为 8 位整数 声明变量 nMedium 为 16 位整数

int16 nMedium; int32 nLarge;

声明变量 nLarge 为 32 位整数

int64 nHuge;

声明变量 nHage 为 64 位整数

类型 intn 通常用于编写可移植的代码 以使可以在多个不同平台上 使用 在 Visua l C++中数据类型 int8 与 Char 等价 int16 与 Short 等价 int3 2 与 int 等价 3.3.6 浮点类型 Visual C++支持三种浮点类型 float double 和 long double 浮点类 型的长度 存储范围如表 3.5 所列 表 3.5 浮点类型 类型

名称长度(字节)

取值范

围 float

4

1.172494351e-38

3.402823466e+38(正数) double

8

2.2250738585072014e-308 long

1.7976931348623158e+308(正数)

double

2.2250738585072014e-308

8 1.7976931348623158e+308(正数)

3.3.7 常量 C++的常量主要包括整型变量 浮点变量 字符变量及字符串常量 C++ 语言增加了操作符 Const 用于声明标识符 标识符值在程序运行 期间是不可更改的 const 的使用方式为 www.BOOKOO.com.cn

102


Visual C++6.0 编程实例与技巧

const 数据类型 标识符二值 例如 const int i = 10;

声明整型变量 i 等于 10

3.4 3.4 数据类型转换 在编写 C++程序过程中 经常会碰到类型转换问题 例如 将整数 类型数据和浮点 类型数据相加

这时 C++编译器就会自动进行类型转

换 为了避免不同的数类型在运算过程中出现混淆 型的数据

应尽量使用同种类

或采用 C++ 语言所提供的强制类型转换功能

Visual C++

中提交的强制类型转换包括以下两 种形式 (1) 在要转换的变量前加上括号

并在括号中指明欲转换的类型即

可 例如 float c; int a,b; c = (float) a

(float)b;

系统在运行时先将 a 和 b 强制转换成浮点类型再进行运算 这是传 统的 C 语言的表达方式 (2) 像函数调用一样 将欲转换的变量作为参数放在括号中 例如 c = float(a)

float(b);

这是 C++语言扩充的一种强制类型的转换方式 也是先将 a 和 b 强 制转换成浮点类型 再运算 3.5 C++存储类 C++存储类 存储类控制对象或变量的存在时间(生存期) Visual C++支持四种存 www.BOOKOO.com.cn

103


Visual C++6.0 编程实例与技巧

储类

auto

extern 和 static

register

3.5.1 auto 存储类 用 auto 存储类声明的变量都是局限于某个程序范围内的 只能在某 个程序范围内使用 内存空间

因此

从实 现技术上讲

auto 存储类采用堆栈方式分配

与程序执行超出该变量的作用 域时

就释放所占用

的内存空间 其值也随之消失 在 C++语言中 在程序段内声明 auto 变量时可以省略关键字 auto 例如

以下两条 语句都是声明一个整数变量的 Auto 变量 i auto int i; inti; (省略 auto)

3.5.2 register 存储类 使用 register 声明数据的主要目的是将所声明的变量放入寄存器内 这样可以加快程序的运 行速度

有时

在使用这种声明时

系统寄存

器已经被操作系统占据了 这时 register 变 量就自动作为 auto 变量使 用 register 变量的声明方式如下 register int i;

声明一个整数类型的 register 变量 i

3.5.3 extern 存储类 使用 extern 声明的变量为外部变量 一般是指定义在程序外部的变 量

当变量被定义为外部 变量时

个变量

所有其他函数或程序段都可引用这

这种变量的作用域是所有的函数或程序 段

之间传递数据

一般用于在函数

例如

www.BOOKOO.com.cn

104


Visual C++6.0 编程实例与技巧

文件 1 Extern 1.CPP # mclude <iostream.h> 声明外部变量 evalue

extern int evalue;

注意这里只是使该变量对 并不是实际的声明部分

main()可见 vois main() { evalue++;

使用外部变量 evalue

cout<<“外部变量”<<evalue<<endl; fun2(); } int evalue = 5;

外部变量 evalue 的实际声明处

void fun1(viod)

函数声明 fun()

{ evalue++;

使用外部变量 evalue

cout<<“外部变量”<<evalue<<endl; fun2() } 文件 2 Extern 1.CPP # mclude <iostream.h> extern int evalue;

引用外部变量 evalue(在文

件 1 中声明) www.BOOKOO.com.cn

105


Visual C++6.0 编程实例与技巧

函数声明 fun2()

void fun2() {

使用外部变量

evalue++;

cout<<“外部变量”<<evalue<<endl; } 在这个例子中 外部变量 evalue 用于在函数 fun1

fun2 和 main 之

间传递值 3.5.4 Static 存储类 静态变量也是一种局部变量 和 auto 的最大不同在于 Static 变量在 内存中是以固定地址存放 的 程序还在继续运行 失

而不是以堆栈方式存放的

因此

只要

静态变量就不会随着声明 它的程序段的结束而消

例如 Static.CPP # include <iostream.h> 函数声明

void fun() {

声明 auto 型变量 i

int 1 = 0; cout<<

i=

<<i<<endl;

cout<<

si =

<<endl;

i++;

变量 i 加 1

si++;

变量 si 加 1

} void main() www.BOOKOO.com.cn

106


Visual C++6.0 编程实例与技巧

{ fun(); fun();

fun()时

被保存下来 保存

静态变量 si 的值

而 auto 变量 i 的值则未被

总为 0

} 3.6 基本运算 C++语言中常用的基本运算包括算术运算 关系运算和逻辑运算 3.6.1 C++语言中的基本算术操作符如表 3.6 所列 在 C++语言中 可以用括号来改变算术运算的优先次序 例如 d = a*(b-c); 该语句就应先计算减法 再计算乘法 表 3.6 基本算术操作符 [BHDFG2,WK4,K3,K3,K12,K11,K6W] 操作名 示例

操作符

功能

运算优先级

双目运算 [BHDG3,WK3ZQ,K3ZQ,K12ZQ,K11ZQ,K6W] 加

c=a+b;若 a=4,b=6,则 c=10 减 c=a-b;若 a=10,b=3,则 c=7

将两个操作数的值相加

+

-

用第一个操作数减去第二个操作数 低

www.BOOKOO.com.cn

107


Visual C++6.0 编程实例与技巧

c=a*b;若 a=4,b=6,则 c=24

用第一个操作数的值除以第二个操

除 c=a

将两个操作数的值相乘

*

b;若 a=24,b=8,则 c=3

作数的值 用第一个操作数除以第二个操作数

求余 c=a

b;若 a=10,b=3, 则 c=1

中 并返回余数

[BHDG3,WK4ZQ,K3ZQ,K3ZQ,K12ZQ,K11ZQ,K6ZQW] 单目运算 负 数

将 操 作 数 取 负 值

c=-a;若 a=10,则 c=-10 3.6.2 关系运算

C++语言中的关系运算操作符如表 3.7 所示 表 3.7 关系操作符 操作符 == != >=

含义

操作符

等于

>

不等于

大于 小于

<

大于等于

含义

<=

小于等于

3.6.3 逻辑运算 C++语言中的逻辑运算操作符如表 3.8 所示 表 3.8 逻辑操作符 操作符

含义 www.BOOKOO.com.cn

108


Visual C++6.0 编程实例与技巧

!

非(NOT)

&&

与(AND) 或(OR)

3.7 自定义数据类型 除了提供基本数据类型外

C++语言还允许用户建立自定义的数据

类型 3.7.1 typedef typedef 用于某个标识符定义成数据类型 然后将这个标识符当作数 据类型使用 例如 将 notural 定义为整型

typedef int natural; natural i1,i2;

将 i1,i2 声明为 natural 类型

也即声明为整型 用 typedef 来重新定义数据类型可以提高程序的可靠性和可移植性 3.7.2 结构 C++语言中提供的结构数据类型是一种复合数据类型

用于将某些

相关的具有不同 类型的 数据组织到一个新的数据类型中 结构数据类 型的声明以关键字 struct 开始 语法形式为 struat structname{ mtype1 m1; mtypeN mN; };

www.BOOKOO.com.cn

109


Visual C++6.0 编程实例与技巧

mtypeN 可以是不同的数据类型 结构声明后 就可

其中 mtype1 以使 用

例如

struct square { int length; int width; Char name; };

声明结构 square 声明 square 类型的变量 S1

struare square S1;

声明 square 类型的变量 S2

square S2;

在声明结构类型的变量时 可以省略 struct S1.length = 100

使用时 在 S1 后面加一小数点

再加上域的名称 声明结构时

还可以直接声明变量 例如

struct square { int length; int width; Char name; } S1,S2; 声明了两个 square 类型的变量 S1 和 S2 在声明结构变量的同时

还可以设置初值

例如

前面声明的变量

S1 可以直接设置初值为 square S1 = {10,20,`A } 在 C++语言中 结构声明可以嵌套 即结构内的某个数据类型又是 一个结构 例如 struct S1 { www.BOOKOO.com.cn

110


Visual C++6.0 编程实例与技巧

}; S1 var1; Struct S2; }; 同样

在声明嵌套结构的变量时 也可以直接设置其初值

3.7.3 联合 C++语言中的联合(union)可使不同的数据类型共享同样的内存位 置 其定义方式 与结 构极为相似 但实质都不同 联合每次只能包含 一种数据类型的信息

联合的语法形式为

union unionname { type1 field1; type2 field2; typeN fieldN; }; 其中

type1,type2,

union 直接声明变量时

typeN 可以是不同的数据类型 其声明方式与结构类似

在使用关键字

例如

union variant { int i; Char ch; } U1;

声明 union 变量 U1

union variant U2

在 C++语言中 可以省略 union

在使用 union 变量时要记住 union 中的各种数据类型共享同一内存 位置 因此

union 的各 分量实际所用的是同一个值

www.BOOKOO.com.cn

111


Visual C++6.0 编程实例与技巧

3.7.4 枚举 枚举类型是用户自己定义的数据类型

枚举类型的语法形式为

为了提高程序的可读性

enum enumname {member1,member2 其中

采用这种数据类型的目的是

member1,member2

memberN}

memberN 分别表示要枚举的数据

在声明枚举类型的变量时 也有几种形式 例如 enum color { red,green,blue 直接声明枚举变量 mycolor

} mycolor;

声明变量 yourcolor

emumplor yourcolor

声明变量 hiscolor 可以

color hiscolor 省去 enum

既可以直接使用

使用枚举类型变量时 enum 数据类型的值

也可以用整数直接引用

例如

int i; mycolor = red;

变量 mycolor 的值为 red

相当于给 i 赋值 2

i = blue; 3.8 控制结构

编写程序时最重要的就是要了解应用程序的控制结构

控制结构是

通过控制语句来实现的 3.8.1 条件语句 C++语言支持四种基本的条件语句 if 语句 if swith 语句

条件语句用来判断程序的执行方式

www.BOOKOO.com.cn

else 语句 ?条件和 下面分别介绍

112


Visual C++6.0 编程实例与技巧

1. if 语句 在程序中 可使用 if 语句来有条件地执行某一语句序列 语法形式 为 if(expression) { statment 1; statementN } 其中 表达式 expression 必须用圆括号()括起来 其值为 TRUE 或 FALSE 当值为 TRUE 时 就 执行 statment 1, statementsN 当值为 FALSE 时 就执行 if 语句后面的语句 注意 若 if 语句中只有一条可 执行语句 可以省略花括号{} 例如 if (i>=0) i = 0; else 语句

2. if if

else 语句表示根据不同的条件分别执行不同的语句序列 语法形

式为 if (experssion) { statement 1; } else { statement 2; }

www.BOOKOO.com.cn

113


Visual C++6.0 编程实例与技巧

当表达式 expresion 的值为 TRUE 时 执行 statement 1 语句序列 当 值为 FALSE 时 执行 state ment 2 语句序列 当 if 序列只包含一条语句时

可以省略花括号{}

else 语句

3. if if

else 语句中的语句

elseif 语句用于进行多重判断 语法形式为

if(expression 1) { statement 1; } else if(expression 2) { statement 2; } elseif (expression 3) { statements 3 } else } statement 4; } 这个语句的含义是

当 expression 1 的值为 TRUE 时

就执行

expression 1 语句序列 否则(e xpression 1 的值为 FALSE) 当 expression 2 的值为 TRUE 时 就执行 expression 2 语句序列

否则(expression 2

的值为 FALSE) 当 expression 3 的值为 TRUE 时 就执行 expression 3 语

www.BOOKOO.com.cn

114


Visual C++6.0 编程实例与技巧

句序列 否则(所有条件都不成立) 就直接执行 if

elseif 语句后面的语

句 4. ?条件操作符 ?条件操作符可以简化条件表达式的表达形式 语法形式为 e1? e2

e3

例如

下面的语句

result = (a>b)? a:b; 与语句 if(a>b) result = a; else result = b; 是等价的 5. switch 语句 switch 用于测试某一变量具有多个值时所执行的不同动作

语法形

式为 switch (expression) { case constant 1: statement 1; break; case constan 2; statement 2; break; default: statement;

www.BOOKOO.com.cn

115


Visual C++6.0 编程实例与技巧

} 在执行 switch 语句时 首先在 case 条件中寻找相符的语句 找到后 就执行有关的语句序列

直到碰上 break 语句或 switch 语句的结束符号

才结束 defult 部分表示所有 case 条件都不符 合时才要执行的语句序 列 该部分可有可无 case 语句中的 break 语句也是可有可无 若未 加 break

那么就继续执行后面的 case 语句

3.8.2 循环语句 C++语言包含以下几种循环控制语句 for 循环

while 循环和 do

while 循环 1. for 循环 for 循环的语法形式为 for (exp1;exp2;exp3) { statement; } 其中 exp1 是赋值语句 用于设置循环控制变量 exp2 是关系表达式 用于测试是否退出控制 语句 exp3 是赋值语句 用于更新循环控制变 量 在 for 语句中 exp1 exp2 和 exp3 中的任 何一个都可以省略 但 它们之间的分号不能省略

例如

for (;i<=100;) sum ++; 2. while 循环 while 循环的语法形式为 while(expression) { statement;

www.BOOKOO.com.cn

116


Visual C++6.0 编程实例与技巧

} while 循环的功能与 for 循环完全一样

都是进行循环控制

expression 值为 TRUE 时 就继 续执行循环体内的语句序列 否则 就 退出 while 循环

while 循环也可以嵌套

while 循环

3. do

while 循环的语法形式为

do do {

statement; } while(expression); do while 循环与 for while 循环的不同之处在于 for 和 while 循环 都是将测试循环的语句 放在循环的起始位置 do 行完循环体后才测试循环是否结束 do

while 循环则是在执

whi le 循环也可嵌套

3.8.3 转移语句 C++语言包含以下几种转移语句 1. break 语句 break 语句用于强制退出循环语句及中断 case 语句 用 break 语句退 出循环后

将接着执行循 环体后面的语句

例如

for(i=0;i>10;i++) { if(i>5) break;

跳出循环执行 cout 语句

} cout << “退出循环” 2. continue 语句

www.BOOKOO.com.cn

117


Visual C++6.0 编程实例与技巧

continue 和 break 语句类似 不过执行 continue 语句时 并不完全退 出循环 而是使循环重 新回到测试位置 并忽略 continue 和循环结束 前的语句序列

例如

for (i=0;i>10;i++) { if(i>5) continue; cout <<“继续执行循环” } 执行以上程序时 当循环变量 i 大于 5 后 就不再执行 continue 后的 输出语句 而是直接返回 循环体的顶部开始执行 3. goto 语句 goto 语句是无条件转移语句 语法形式为 goto lablename 其中 lablename 是标号名 标识欲跳转的位置 若要使用 goto 语句 程序中必须 声明相应 的标号 标号的写法和变量一样 后面加上冒号 (

)

例如 lable: i++; if(i>10) goto lable;

使用 goto 时必须注意 goto 语句只限在同一程序段内转换 不能跳 到另一函数或程序内 3.9 数组

字符串和指针

在 C++语言中 数组

字符串和指针是相互关联的

www.BOOKOO.com.cn

118


Visual C++6.0 编程实例与技巧

3.9.1 数组 数组是具有相同数据类型的一组数据

用一个名称来表示

数组内的数据时 可使用 数组的下标值来说明

要访问

在 C++语言中 数组

是一个复合数值数据类型 其元素可以 是任何 数据类型 整型 实型 字符型

数组类型

是连续存放的

指针类型

结构等等

第一个元素的下标值为 0

数据类型 数组名称

数组的各个元素在内存 中 数组的声明格式为

长度

例如 int arrayint

100

Char arrayChar

声明一个整型数组 包含 100 个元素

; 50

;

声明一个字符数组 包含 50 个元素

同其他数据类型一样 数组在声明的同时也可以赋初值 例如 int arrayint

5

= {2,4,6,8,10};

int arrayint

= {1,3,5};

从上例可以看出

在设置数组初值时可以不指定数组长度

自动计算数组的长度

系统会

从 而为其分配相应的存储空间

除了一维数组 在 C++语言中还可以声明多维数组 声明数组时 若包含一个方括 号

就表示一维数组 若包含两个方括号

则表示二维数组

等等

例如

2

=

int arrayint

2

{ {1,1},{0,0}. };

声明一个二维数组并赋值

引用数组元素时 通过下标值来表明 例如 int arrayint

10

; www.BOOKOO.com.cn

119


Visual C++6.0 编程实例与技巧

给各数组元素赋值

arrayint

0

= 0;

arrayint

1

= 10;

3.9.2 字符串 但它们之间仍有区别

字符串和字符数组非常相似

一串字符组成的数组 但 字符串的末尾字符一定是

字符串是指由 0

而字符数

组则不一定 例如可以声明以下字符串 Char string

={ w , h

Char string

Why!

, y ,

0

};

;

在 C++语言中 有很多标准函数与字符串相关 比如 gets fgets fputs sprint f strcpy ctrcat

strcmp 和 strlen

gets 函数是从标准输入设备上读取字符串 使用该函数时 将以‘ n 换行符(即按回车键 )结束字符串 puts 函数是将字符串输出到屏幕上

语法形式为

puts(字符串) 用 puts 函数输出的字符串都自动加上

n

换行符

fgets 函数也是从输入设备上读取字符串 但与 gets 的区别是要限定 输入字符串的长度 fpu ts 函数是输出指定的字符串 不会自动为输出 的字符串加上换行符 sprintf 函数可以处理带有特殊字符的字符串 strcpy 函数是字符串拷贝函数 语法形式为 strcpy(str,str2); 换行时 将 str2 字符串拷贝到 str1 字符串内 strcat 函数用于字符串串接

语法形式为

www.BOOKOO.com.cn

120


Visual C++6.0 编程实例与技巧

strcat (str1,str2); 执行时

将 str1 串接在 str 后

strcmp 函数用于字符串比较 语法形式为 strcmp(str1,str2); 执行时 将 str1 和 str2 进行比较 如果二者相同 则返回零 如果 str1 小于 str2

则返回负 值

否则返回正值

strlen 函数用于计算字符串长度 语法形式为 strlen(str) 执行时 返回字符串 str 的长度 注意该长度不包括字符串的结束字 符

0 3.9.3 指针 指针在 C++语言中使用最灵活 但也是最难掌握的概念之一 所谓

指针包含另一变 量内存地址的变量 指针在使用前需进行声明 声明格式为 数据类型 * 变量 或: 数据类型 * 变量; 其中 后一种声明方式是 C++语言新增加的 数据类型是 C++语言 中任何 一种数据类型

声明中的*是“指向”的意思

指针和数据是息息相关的 际就是一个地址 int arr 2

数组指针的另一表示法

即数组 的起始地址 =

数组的名字实

例如

{0,1}

这里的 arr 即为数组的起始地址 其元素的地址分别为

www.BOOKOO.com.cn

121


Visual C++6.0 编程实例与技巧

arr

1

=arr 地址+(1*4)

1

=arr 地址+(2*4)

整数的长度为 4

和 arr

这样 在使用数组 arr 时 *(arr+1)和 arr

是完全一致的

C++语言提供有 new 操作符 可以很方便地为指针分配内存空间 语法形式为 pointervar = new datatype; 其中 pointervar 是指针变量名

datatype 是数据类型名

C++语言还提供另外一种操作符 delete 可以很方便地释放指针所占 有的内存空间

语法形式为

delete pointervar; 这样

就可以将指针变量是 pointervar 所占用的内存空间释放给系

统 3.10 函数 函数是由功能相关的语句序列所组成的独立模块 基本结构

是结构化程序的

函数的定义为

函数类型 函数名(数据类型参数 1

数据类型 参数 2)

{ 函数体(语句序列) } 其中

函数类型是指函数的返回值类型 可以是 C++语言中任何一

种数据类型 若 未设置函 数类型 系统会自动将其假设为整数类型 如果在设计函数时不希望返回任何值 那么就将 函数类型设置成 void

www.BOOKOO.com.cn

122


Visual C++6.0 编程实例与技巧

型 参数 1 参数 2

是函数的形式参数 在调用函数时 必须用相应

的实参与其匹配 若希望 在 调用函数时更改传入参数的值 可以采用 传送地址的方法

也就是说

通过传送参数地址

函数可以向调用函

数返回任意多个值 在 C++语言中 函数声明有一个新的值 即允许程序内含有多名称 相同的函数

这 就是函数重载

例如

可以构造以下三个名称相同但

功能不同的函数 put (int i);

输出一个整数

put(float f);

输出一个浮点数

put(Char Ch);

输出一个字符

在调用 put 函数时 只需直接使用 不必理会其参数是什么 函数可以调用自身 这叫做递归调用 例如 int fun(int i) { int x; fun(x); } 在设计递归函数时

一定要注意函数的出口设置

否则极易产生无

限递归循环 3.11 类与对象 我们知道 传统的结构化语言(如 C Pascal 向过程的方法来解决问题 程

PL 1 等)都是采用面

结构化程序通常包含一个过程和若干个过

由其中每个子过程来处理某个小问题

再由 主过程自顶向下调用

www.BOOKOO.com.cn

123


Visual C++6.0 编程实例与技巧

各子过程逐步解决整个问题 在结构化程序设计方法中 代码和数据是 分离的 由此带来了很多缺陷 其中最主要的就是程序的可维护性差 当对某段程序进行修 改或删除时 要进行相应的修改

这样

整个过程中所有与之相关的部分都

在开发和调试时 就会花费大量的时间

此我们就需要一种更好的方法来解决这类问题 面向对象程序设计方法为我们提出了一个全新的概念

它的主要思

想是将数据(数据成员)及 处理这些数据的相应函数(成员函数)封装到 Class (C++的一种新的数据类型)

使用类的变量则称为对象(Object)

如图 3.1 所示 在对象内 只有属于该对象的成员函数 才可以存取该对 象的数据成员 这样 其他函数就不会无意中破坏其内容 从而达到保 护和 隐藏数据的 效果

图 3.1 类与对象关系示意图 与传统的面向过程的程序设计方法相比

面向对象程序设计方法有

三个优点 第一 程序的 可维护性好 面向对象程序易于阅读和理解 程序员只需了解必要的细节

因此

降低了的 复杂性

第二

程序的

易修改性好 即程序员可以很容易地修改 添加或删除程序的属性 是通过增加删除对象(Object)来完成的

第三

www.BOOKOO.com.cn

对象(Object)可以使用多

124


Visual C++6.0 编程实例与技巧

即可重用性 好

程序员可以根据需要将类和对象保存起来

随时

插入到应用程序中 无需做什么修改 面向对象程序设计方法提出了一些新的概念

如类(Class)

对象

(Object) (encapsu-lati on) 继承(inheritance)和多态性(polymorphism)等 以下我们将分别加以详细介绍 3.11.1 类的定义 类(Class)是一种用户自定义的数据类型 Class 开始

接着是类的 名字

声明一个类时

以关键字

其语法结构为

Class 类名称 { type vars; number functions; public; type vars; number functions; } 在类中可以包含以下三种类型 (1) 私有类型(private)

私有类型包含数据(又称为数据成员)和函数

(又称为成员函数) 在关键字 private 后面声明 如果省略关键字 private 则必须紧跟在类名称的后面声明 这 样都 被视为私有类型 这种类型 的数据只许类本身声明的函数对其进行存取

而该类外部的任何 函数

都不能访问这种类型数据 (2) 公有类型(public) 类与外部的接口

公有类型在关键字 public 后面声明 它们是

任何 函数都可以访问公有类型数据和成员函数

www.BOOKOO.com.cn

125


Visual C++6.0 编程实例与技巧

有类型与私有类型的关系

如图 3.2 所示

图 3.2 公有类型与私有类型关系示意图 如果将类看成是一座冰山 部分

对外部可见

那么公有类型数据就是冰山浮出水面的

而私 有类型数据则是冰山隐藏在水下的部分

外部不可见 (3) 保护类型(protected):保护类型用于类的继承 后面将详细介绍 类是面向对象程序最基本的单元 以类的方式设计实际待解 决的问题

在设计面向对象程序时

首先要

也就是将问题所要处理的数据定

义成类的私有类型数据或公有类型数据

同时将 处理问题的方法定义

成类的公有或私有成员函数 以下是类的简单例子: 简单的类例子) # include <iostream.h> # include <math.h> class counter { 声明一个私有类型数据

double datavalue; public: void setvalue(double);

声明一个公有类型的成员函数

double getvalue(void);

声明一个公有类型的成员函数

www.BOOKOO.com.cn

126


Visual C++6.0 编程实例与技巧

声明一个公有类型的成员函数

int getnum(void) }sam;

声明类对象 sam 成员函数的具体声明

void counter:: setvalue(double V) { datavalue = v; } double counter::getvalue(void)

成员函数的具体声明

{ double dd; dd = sin(10.0*datavalue); return (dd); } int counter::getnum(void)

成员函数的具体声明

{ int ii; ii = int (datavalue); return (ii); } main()

主过程

{ sam.setvalue(50.0);

设置初值

cout<<“The value is:“<<sam.getvalue()<<endl; cout<<“The num is:“<<sam.getnum()<<endl; return (0); } 在这个例子中 声明了一个简单的类 counter 此外 类还可以嵌套 www.BOOKOO.com.cn

127


Visual C++6.0 编程实例与技巧

声明

例如: Class employee { dass ename { char firstname

20

;

char lastname

20

;

char middlename

20

;

}myname; class esalary { double basesal; double overtimesal; } mysalary; public; void empinput(void); void empoutput(void); }; 在这个例子中 类 employee 嵌套声明了两个类 ename 和 esalary 3.11.2 对象的定义 与结构一样

类也是一种数据类型

(object)

对象是类的实 例

的声明

即在声明类时直接定义对象

要想使用类

在前面的例子中

还必须声明对象

实际上已涉及到了对象

此外 还可以在声明类之后再单独声明类对象 例如: Class example

声明类 example www.BOOKOO.com.cn

128


Visual C++6.0 编程实例与技巧

{ int;; public: float f; void fun(); } 声明类对象 A

example A;

对象声明后即可使用

通过对象可以使用类中的公有类型数据或成

员函数 其使用方式为: 对象名.成员函数名 或 对象名.数据 例如 可以按以下方式调用类 example 中的成员函数 fun: A.fun(); 在本例中

对象 A 所能使用的数据和函数只有变量 f 和成员函数

变量 i 为 example 的私 有数据 不能通过 A.i 使用

fun()

由于不同类间允许有相同名称的成员函数 数时

需要使用作用域操 作符(::)

的 例如

因此在定义类的成员函

以指明该成员函数是属于哪一个类

可以按以下方式在类外声明类 exam ple 的成员函数 fun:

void example::fun() { i = 10; }

www.BOOKOO.com.cn

129


Visual C++6.0 编程实例与技巧

3.11.3 构造函数和析构函数 构造函数(constructor)是一个特殊的类成员函数 或分配内存空间 构 造函数与类具有相同的名字

用于初始化类变量 例如:

声明类 example

class example { int i; public:

声明构造函数 与类同名

example(); void fun(); };

构造函数的设计方法与普通成员函数类似

不过不需要函数类型

构造函数有两个重要特征 :可以接受参数并能够重载 当创建对象时 首先自动执行构造函数 译器为其自动生成

如果类中未显式定义 构造函数

那么就由编

上例中的构造函数可以如下定义:

example::example() { i = 0; } 与构造函数功能相反的函数为析构函数(destructor) 析构函数也是一 个类成员函数

通常 用于释放已分配的内存空间

数具有相同的名字 但必须在前面加上“ 时或某个对象已执行完时

析构函数与构造函

”符号 当使用 delete 操作符

系统都会自动调用析构函数

析构 函数不

能带有参数 而且不可以重载 如果类中未显示定义析构函数 那么编 译器也会为其 自动生成一个 下面是一个完整的带有构造函数和析构函数的例子: www.BOOKOO.com.cn

130


Visual C++6.0 编程实例与技巧

# include <iostream.h> const int QUARTER = 25; const int DIME = 10; 声明类 coins

Class coins { int number; public

声明构造

coins(){cout<<“类的初始化 n”;} 函数 coins coins(){cout<<“类的终止 n”;}

声明析构函数 coins

声明三个公有类型的成员函数

void getcents(int);

int convertquarter(void); int convertdirne(int); }; 成员函数 getcents

void coins::getcents(int cents) { number = cents; cout<<number<<“cents

n”;

} int coins::convertquarter()

成员函数 convertquarter

{ cout<<number QUARTER<<“quarters return(d

n”;

DIME);

} void main()

主程序开始

www.BOOKOO.com.cn

131


Visual C++6.0 编程实例与技巧

{ int cc,qq; cout<<“Enter the cash in cents:”; cin>>cc;

输入 cents 数

coins coins1;

声明 coins 类的对象 coins1

coins1.getcents(cc); qq = coins1.convertquarter(); dd = coins1.convertdime (qq); cout<<dd<<“dimes

n”;

} 在这个例子中 分别定义了构造函数 coins 和析构函数 coins 本例 中的构造函数和析构函 数都是隐式定义的 中

即函数体包含在声明部分

还可以显式定义构造函数和析构函数 例 如: class name

定义类 name

{ char *namestr; int namelen; public: name(int); name();

构造函数 name 析构函数

带有一个参数

name

void display(); }; name::name(int len)

显示定义构造函数 name

{ namestr = new char

lent1

;

www.BOOKOO.com.cn

132


Visual C++6.0 编程实例与技巧

namelen = len; } name::

显示定义析构函数 name

name()

{ delete namestr; } void name::display()

定义成员函数 display

{ cout<<“Length”<<namelen<<“ n”; cout<<“Name”<<namestr<<“ n”; } 从这个例子可以看出 利用 new 操作符和 delete 操作符 可以在构 造函数和析构函数中分配和释放内存空间 此外 在构造函数中还可以 直接传递参数

使用时

只需在主程序中使用下面的语句:

void main() { name n(10);

声明类 name 的对象 n 并将

参数 10 直接输

入 n.display(); } 3.11.4 内联成员函数 成员函数在类的体内定义(隐式定义)或体外定义(显式定义)是有重 大区别的 在类中定义的函数被看作内联函数 而在类外定义的函数缺 省情况下都是非内联函数 因此 通常把较短的函数定义在类的体内

www.BOOKOO.com.cn

133


Visual C++6.0 编程实例与技巧

而较长的函数定义在类的体外 对于内联函数 编译时在所有调用该函 数的地方将装入实际的函数代码 使用 inline 关键字 可以把体外定义的函数看成是内联函数 例如: Class Rectangle

定义类 Rectagle

{ int left; int top; int right; int bottom; public void getcoord(int * l,int *t,int *r,int * b); }; inline void Rectangle::getcoord(int * l,int * t, int * r, int * b)

将成员函数 getcoord 定义成内联函数

{ *l = left; *l = top; *r = right; *b = bottom; } 在这个例子中 使用关键字 inline 将成员函数 getcoord 定义成内联 函数 3.11.5 对象与成员函数 前面介绍了成员函数与对象的声明方式 和返回值不仅可以是整型

字符型

事实上

指针类型等

www.BOOKOO.com.cn

成员函数的参数

也可以把对象作为

134


Visual C++6.0 编程实例与技巧

参数和返回值

我们通过下面的例子来说明:

# include<iostream.h> 声明类 distance

Class distance { int mile,yard; public: distance();

声明构造函数

distance(int,int);

声明重载的构造函数

void getvalue();

声明成员函数

distance addvalue(distance); 声明成员函数 addvalue 类的对象 void display();

参数为 distance

返回值也是 distance 类的对象 声明成员函数

}; 定义构造函数

distance::distance() { mile = 0; yard = 0; }

distnace::distance(int v1,int v2)

定义重载的构造函数

{ mile = v1; yard = v2; } void distance::getvalue()

定义成员函数体

www.BOOKOO.com.cn

135


Visual C++6.0 编程实例与技巧

{ cout <<“Enter mile:”; cin >> mile; cout << “Enter yard:”; cin >> yard; } distance distance::addvalue(distance A) 定义成员函数体 { distance B; B.yard = yard + A.yard; B.mile = 0; if(B.yard >= 1760) { B.mile = 1; B.yard-=1760; } B.mile+=mile + A.mile; return B; } void distance::display()

定义成员函数体

{ cout << mile << “miles”<< yard <<“yards”<<endl; } void main() {

www.BOOKOO.com.cn

136


Visual C++6.0 编程实例与技巧

distance D(10,100); H 定义对象 D 并传入初值 distance F,G;

定义对象 F 和 G

F.getvalue(); G=D+F

G = D.addvalue(F);

cout<<“The first distance:”; D.display(); cout<<“The second distance:”; F.display(); cout<<“The third distance:”; G.display(); } 在这个例子中 成员函数 addvalue 的参数为一个对象 返回值也是 一个对象 在主程序 mai n 中 通过对象 D 来调用该成员函数 此外 本例中还声明了重载的构造函数 distance 和 dist ance(int,int) 这是一个 新概念 将在后面详细介绍 3.11.6 this 指针 this 指针是一个隐含的指针 每个成员函数都有一个 this 指针变量 this 指针用以指向以该成员函数所属类定义的对象 当某个对象调用成 员函数时 成员函数的 this 指针便自动指问 该对象

因此 不同的对

象调用同一成员函数时 编译器便依成员函数 this 指针所指向的不 同 对象来确定应该引用哪一个对象的数据成员 3.11.7 静态数据成员和成员函数 类可以包含静态的数据成员和成员函数 如果用关键字 static 来声明

www.BOOKOO.com.cn

137


Visual C++6.0 编程实例与技巧

数据成员

则只有该数据成员的一个拷贝被类的对象所维护 例如:

class Test

声明类 Test

{ public: 静态数据成员

static int count; }

在这个例子中 无论类 Test 创建多少了实例 count 只保存一个拷贝 如果在类中声明了静态数据成员

则必须像全局变量一样

在类外

定义或初始化 由于静态数据成员的定义在类外出现 因此必须用作用 域操作符(::)来指明数据成员所属的类

例如

可以按如下方法定义和

初始化 count: int Test :: count = 0; 和正常的数据成员一样

静态数据成员必须在类中声明

而且其作

用域也仅限于所属的类 此外 也可以将成员函数声明为静态的 例如: class Test

声明类 Test

{ public 静态成员函数

static int getcount(); {

} } 静态成员函数有以下特点: 类外代码可以使用类名和作用域操作符来调用静态成员函数 无 www.BOOKOO.com.cn

138


Visual C++6.0 编程实例与技巧

需引用类的实例

甚至类的实例可以不存在 例如:

int count = Test::getcount(); 静态成员函数只能引用属于该类的静态数据成员或静态成员函 数 这是因为静态成员函数没有 this 指针来存放对象的地址 因此 如 果要访问非静态数据成员

编译器无法判定所 访问的数据成员是哪个

对象 3.11.8 友元函数 只有类中的成员函数才可以访问本类中的私有类型数据

前面说过

和保护类型数据 为了方便使用 C++语言提供有一种特殊的函数 允 许访问类的私有类型数据和保护类型 数据

这就是友元函数(friend

function) 友元函数的声中方式为: Class 类名称 { type vars;

public friend 函数类型 函数名();

友元函数

}; 友元函数的体在类的外部声明 护类型数据

并可以访问类的私有类型数据和保

以下是使用友元函数的例子:

# include<iostream.h> Class iscore;

预声明类 iscore

Class uscore;

声明类 uscore www.BOOKOO.com.cn

139


Visual C++6.0 编程实例与技巧

{ int scorevalue; public 声明成员函数

void getscore();

friend void total(iscoreX,uscore Y); 声明友元函数 total }; void uscore:: getscore()

声明类 uscore 的

成 员函数体

{ cout<<“Enter score in University:”; Cin>>scorevalue; } 正式声明类 iscore

class iscore { int scorevalue; public: void getscore();

声明成员函数

friend void total (iscore X, uscore Y); 声明友元函数 total }; void iscore::getscore()

声明类 iscore 的成员函数 体

{ cout<<“Enter score in Institute:”; cin>>scorevalue; } void total (iscore X, uscore Y) www.BOOKOO.com.cn

140


Visual C++6.0 编程实例与技巧

声明友元函数 total 的函数体 在类的外部声明 该友元函数使用了类 iscore 和 uscore 中的私有数据 { cout<<“The total score is:“<<X.scorevalue +Y.scorevalue<<endl; } void main() { iscore A;

声明类 iscore 的对象 A

uscore B;

声明类 uscore 的对象 B

A.getscore(); B.getscore(); total(A,B);

调用友元函数 total

} 尽管使用友元函数可以访问类中的私有数据

但为了确保数据的完

整性及数据封装与隐藏的原则 建议尽量不使用或少使用友元函数 友 元函数与结构化程序设计中的 Goto 语句一样 增加了一种灵活的手段 但不提倡使用 3.12 类继承 面向对象程序设计中

允许某个类继承其他类的属性

被继承的类

称为基类(base class)或父类(parent class) 继承的类称为派生类(derived class)或子类(child class)

www.BOOKOO.com.cn

141


Visual C++6.0 编程实例与技巧

3.12.1 派生类与基类 声明一个派生类的语法形式为: Class 派生类名称:访问权限 基类名称 {

}; 其中 访问权限有三种:public private 和 protected 缺省权限为 public 在类的定义中 所有公有类型数据都允许其他函数访问 而私有类型数 据只能供成员函数访问

但是

无法访问基类中的私有数据

当 派生类继承基类的特性时

派生类

若想让基类中的某些数据不 仅可供成员

函数访问 而且可将其特性传给派生类 那么就可以将其声明成保护类 三种 数据类型的意义分别为:

公有类型数据(public):可供程序中任何函数访问 私有类型数据(private):仅供基类中的成员函数使用 这类数据不 能传递给继承者 保护类型数据(protected):供成员函数使用 但可以传递给派生类 使用 如果将派生类的声明权限声明为 public

那么所有基类的公有数据

在派生类中仍是公有数据 所有基类的保护类型数据在派生类中仍是保 护数据 如果将派生类的访问权限声明为 priv ate 那么所有基类的公有 数据和保护数据在派生类中都变成私有数据 以下是一个简单的类继承的例子: class parent

声明基类

{

www.BOOKOO.com.cn

142


Visual C++6.0 编程实例与技巧

私有数据 i 仅供成员函数

int i; protected:

使用

不能传递给派生类

保护数据仅供成员函数使用 可供派生类使用

int x; 公有数据

public:

可供任何函数使用

parent(); void change(); void display(); }; parent::parent()

定义构造函数体

{ x = 0; i = 0; } 定义成员函数体

void parent::change() { x++; i++; }

定义成员函数体

void parent::display() { cout<<“x=“<<x<<endl; } Class son:public parent

声明 son 为 parent 的派生类 访问权限为 public 即 parent 类的公有数据在 son 中仍为公有数据 parent 类的 保护数据在 son 中仍为保护数据 www.BOOKOO.com.cn

143


Visual C++6.0 编程实例与技巧

{ public: 声明成员函数

void modify(); };

定义成员函数体

void son::modify(); { x++; }

经过类继承 派生类 son 具有以下特征: 保护数据 x 公有成员函数 parent 公有成员函数 change 公有成员函数 modify 公有成员函数 display 从这个例子可以看出 尽管派生类 son 中只声明了一个函数 但却 继承了基类的很多特性 此外 派生类可以使用基类中的公有数据和保 护数据 但基类却不可以使用派生类的函数 3.12.2 派生类的对象和构造函数 为某个基类声明一个派生类后

仍可以利用基类来声明对象

而且

所声明的对象和派生类所声明的对象不会冲突 在前面的例子中 利用 基类 parent 声明一个派生类 son 这样 在主 程序中就可以声明如下的 对象: void main()

主过程开始

{ www.BOOKOO.com.cn

144


Visual C++6.0 编程实例与技巧

用派生类 son 来定义一个对象 A

son A;

用基类 parent 来定义一个对象 B

parent B; cout<<

Display derived class object A ;

A.display();

对象 A 的显示结果

A.change();

调用从基类中继承的成员函数

A.modify();

调用派生类中声明的成员函数

cout<<

Display bass class object B ;

B. change(); B. display(); } 在这个例子中 派生类的对象 A 可以调用从基类中继承下来的成员 函数 而基类的对象 B 却不可以调用派生类中新声明的成员函数 例如 B.modify 就是非法的 前面已说过

在声明类对象时

可以利用构造函数来设置类对象的

初始值 同样 在派生类中也可以利用构造函数来进行初始化工作 例 如 将派生类 son 修改一下: class son:public parent { public: son():parent()

声明派生类的构造函数

{

} void modify(); }; www.BOOKOO.com.cn

145


Visual C++6.0 编程实例与技巧

主程序开始

void main() {

声明派生类的对象

son A;

此时

调用相应的构造函数 son()来进行初始化

} 在主程序中 声明 son 类的对象 A 时 就会调用相应的 son()构造函 数进行初始化操作 而调用 son()时 又会间接调用 parent 类的构造函数 parent()来设置初始值 3.12.3 多重派生类 前面介绍的类继承都是基类——派生类的一对一关系

事实上

类的派生类之间可以有复杂的多对多关系 这里将要介绍的多重派生类 就是从一个基类派生出多个不同派生类的一对 多关系 如图 3.3 所示

图 3.3 继承与派生示意图 各个派生类除了具有基类的特性外 于自己的相应特性

还可以针对各自的需要加入属

以下是一个多重派生类的例子:

# include<iostream.h> const int LEN = 80; class scoreclass

定义基类 scoreclass

{ www.BOOKOO.com.cn

146


Visual C++6.0 编程实例与技巧

protected:

定义保护类型的数据

char stuname

LEN ;

int escore,mscore; public: void getscore()

声明公有成员函数

{ cout<<“Enter student name:”; cin>>stuname; cout<<“Enter English score:”; Cin>>escore; cout<<“Enter Mathmatic score:”; Cin>>mscore; } void display()

声明公有成员函数

{ cout<<“Student name:“<<stuname<<endl; cout<<“English score:“<< e-score<<endl; cout<<“Mathmatic score:“<<mscore<<endl; } }; Class CS:public scoreclass

声明派生类 CS

{ int cscore; public: void getscore()

声明派生类的成员函数

{

www.BOOKOO.com.cn

147


Visual C++6.0 编程实例与技巧

scoreclass::getscore();

调用基类的成员函数

cout<<“Computer score:”; cin>>cscore; } void display()

声明该派生类的成员函数

{ scoreclass::display();

调用基类的成员函数

cout<<“Computer score:“<<cscore<<endl; } }; Class PS:public scoreclass

声明派生类 ps

{ int Pscore; public: void getscore()

声明该派生类的成员函数

{ scoreclass::getscore();

调用基类的成员函数

cout<<“Physics score:”; Cin>>Pscore; } void display()

声明该派生类的成员函数

{ scoreclass::display();

调用基类的成员函数

cout<<“Physics score:”<<pscore<<endl; } }; www.BOOKOO.com.cn

148


Visual C++6.0 编程实例与技巧

class CHS:public scoreclass

声明派生类 CHS

{ int chscore; public: void getscore()

声明该派生类的成员函数

{ scoreclass::getscore();

调用基类的成员函数

cout<<“Chemistry score:”; Cin>>chscore; } void display()

声明该派生类的成员函数

{ scoreclass::display();

调用基类的成员函数

cout<<“Chemistry score:“<<chscore<<endl; } }; 在这个例子中 通过基类 scoreclass 派生了三个派生类 CS PS 和 CHS 三个派生类除了都继承基类的特性之外 还各自定义了属于自己 的私有数据和成员函数 3.12.4 类的多继承 在类继承中

不但允许从一个基类派生出多个派生类

个类同时继承多个基类

而且允许某

如图 3.4 所示

www.BOOKOO.com.cn

149


Visual C++6.0 编程实例与技巧

图 3.4 多继承关系示意图 通过多继承

派生类将继承多个基类的特性

此时基类和派生类的

声明方式为: Class N1

声明基类 N1

{

};

class Nm

声明基类 Nm

{

}; Class S:public N1, ,publicNm 声明派生类 S 同时继承基类 N1, ,基类 Nm {

}; 以下是一个多继承的例子: # include<iostream.h> # include<string.h> const int LEN = 50; www.BOOKOO.com.cn

150


Visual C++6.0 编程实例与技巧

声明基类 Station

class Station { protected: Char fromstation Char tostation

LEN ;

LEN ;

public: ,charts

Station (char fs

)

基类 Station 的构造函数

{ strcpy(fromstation,fs); strcpy(tostation,fs); } void inputvalue()

声明基类 Station 的成员函数

{ cout<<“Enter from station:”; Cin>>fromstation; cout<<“Enter to station:”; Cin>>tostation; } void display()

声明基类 Station 的成员函数

{ cout<<“Going from”<< fromstation <<“station to”<< tostation <<“station”; } }; class Mile

声明基类 Mile www.BOOKOO.com.cn

151


Visual C++6.0 编程实例与技巧

{ protected: int mile; public: Mile(int m)

声明基类 Mile 的构造函数

{ mile = m; } 声明基类 Mile 的成员函数

void inputmile() { cout<<“Enter mile:”; cin >> mile; } void display()

声明基类 mile 的成员函数

{ cout<<“is”<<mile<<“miles”; } }; class Price:public Station 声明派生类 Price

public Mile 同时继承基类 Station 和 Mile 的特性

{ int price; public; Price(char ff

,char tt

,int mm, int pp):

Station(ff,tt),Mile(mm) 声明派生类 Price 的构造函数 带四个参数 www.BOOKOO.com.cn

152


Visual C++6.0 编程实例与技巧

{ price = p; } void getprice()

声明派生类 Price 的成员函数

{ Station::inputvalue(); Mile::inputmile(); cout<<

Enter price:

;

cin>>price; } void display()

声明派生类 Price 的成员函数

{ Station::display(); Mile::display(); cout<<

, The price is

<<price<<enell;

} }; void main() { Price A (

Beijing

,

Xia men

, 1400, 50);

A.display(); } 3.12.5 多层派生类 前面介绍的几种类继承

无论是多重派生或是多继承

都只是单层

次的 即从基类到派生类只有一层 在 C++语言中 还有另外一种类继

www.BOOKOO.com.cn

153


Visual C++6.0 编程实例与技巧

承 即多层派生类

如图 3.5 所示

图 3.5 多层派生类示意图 以下是一个多层派生类的例子: #include<iostream.h> #include<stdlib.h> const int LEN = 50; 声明基类 Stack

class Stack { protected: int head; int stack

LEN ;

public: Stack(){head=0;};

声明基类的构造函数

void push (int val)

声明基类的成员函数

{ head++; stack

head

=val;

www.BOOKOO.com.cn

154


Visual C++6.0 编程实例与技巧

} 声明基类的成员函数

void POP () { int temp; temp = stack

head

;

head--; return temp; } }; 声明第一层派生类 op

class op-stack:public Stack

stack

{ public: void push(int val)

声明第一层派生类的成员函数

{ if (head>LEN) { cout<<

Stack underflow! <<endll;

exit(1); } return Stack::POP(); } }; class usestack:public opstack

声明第二层派生类 op

stack

{ public: void Produce Stack()

声明第二层派生类的成员函数

www.BOOKOO.com.cn

155


Visual C++6.0 编程实例与技巧

{ int val; cout<<

Input data:

;

Cin>>val; while((val<0)

(val>100)) {

cout<<

Input Error!

<<endl;

cout<<

Please input again:

;

cin>>val; } opstack::push(val); } }; void main() { usestack A;

声明第二次派生类的对象 A

A.ProdouceStack(); A.ProdouceStack(); cout<<

<<A.POP()<<endl;

cout<<

<<A.POP()<<endl;

} 3.13 重载 重载包含函数重载和操作符重载

是 C++语言所提供的一个重要特

www.BOOKOO.com.cn

156


Visual C++6.0 编程实例与技巧

3.13.1 函数重载 重载包含函数重载和操作符重载 是 C++语言所提供的一个重要特 性 3.13.2 函数重载 函数重载允许一个程序内声明多个名称相同的函数

这些函数可以

完成不同的功能 并可以带有不同类型 不同数目的参数及返回值 使 用函数重载可以减轻用户的记忆负担

并使程 序结构简单

易懂

下是一个函数重载的例子: #include<iostream.h> 预定义重载函数

int double Fun(int ii); float doubleFun (float ff); 主程序开始

main() { int aa=1;

float bb=1.0; int isum; float fsum; isum = doubleFum(aa); fsum = doubleFun(bb); cout<<

The integer:

cout<<

The float:

<<isum<<endl; <<fsum<<endl;

return(0); } int doubleFun (int ii)

定义重载函数 doubleFun

www.BOOKOO.com.cn

157


Visual C++6.0 编程实例与技巧

{ return(2*ii); } 定义重载函数 doubleFun

float doubleFun(float ff)); { return (2*ff); } 在类的成员函数和构造函数上

也可以直接使用函数重载的概念

例如: Class sample { int i; public: sample();

定义重载的构造函数

samPle(int); void display(); }; sample::sample()

定义构造函数 sample

{ i=0; } 定义构造函数 sample(int)

sample::sample(int) { i=X; } 定义重载的构造函数后

声明对象时就可以根据不同的参数来分别

www.BOOKOO.com.cn

158


Visual C++6.0 编程实例与技巧

调用不同的构造函数

例如:

void main() { sample A;

自动调用构造函数 sample

sample B(5);

自动调用构造函数 sample(int)

} 在类继承中

也可以出现基类的成员函数与派生类的成员函数名字

相同的现象 这时 应按作用域操作符(::)的指引来调用相应的成员函数 例如: class BaseClass

声明基类

{ public: BaseClass(); void display();

基类的构造函数 基类的成员函数

}; class SubClass:public BaseClass

声明派生类

{ public: SubClass(); void display();

派生类的构造函数 派生类的成员函数 与基类的成员函数同名

}; void BaseClass::display()

作用域操作符表示该成员函数属于

基类的 { www.BOOKOO.com.cn

159


Visual C++6.0 编程实例与技巧

} 3.13.3 操作符重载 操作符重载与函数重载的功能一致 操作符重载是将 C++语言中的 已有操作符赋予 新的功能 但与该操作符的本来含义不冲突 使用时 只需根据操作符出现的位置来判别其具体执行哪一种运算 使用操作符 重载时

必须用以下的方式来声明成员函数:

函数类型 operator #(形参表) 其中 operator 是关键字 #号表示欲定义的操作符 函数类型指明 返回值类型 通常与类型一致或为 void 型 以下是一个操作符重载的例 子: #include<iostream.h> class OperClass

声明一个类

{ int x; public; OperClass();

构造函数

OperClass Operator ++ (); 声明重载的操作符++

返回值类型为 OperClass 类

这里的++为前置运算 void display();

成员函数

}; OperClass::OperClass()

声明构造函数

{

www.BOOKOO.com.cn

160


Visual C++6.0 编程实例与技巧

x=0; } void OperClass::display()

声明成员函数

{ X=

cout<<

<<x<<endl;

} OperClass OperClass::Operator++(int)

声明重载后置操作符++ 的

具体操作 { OperClass A; x++;

进行正常的整数加 1 操作

A.x=X; return A; } void main()

主程序开始

{ OperClass X, Y; X.display(); ++X; Y=++X;

声明两个对象

对象 X 调用成员函数 display()

对象 X 调用前置操作符++ 对象 X 调用前置操作符++

Y++X.display();

并将返回值符给对象 Y

对象 Y 调用后置操作符++

并调用成员函数

dis play } 本例中介绍的是单目操作符重载 因此有前置运算和后置运算之分 若使用双目操作符重载

则没有这个问题 例如 可将上例修改一下:

www.BOOKOO.com.cn

161


Visual C++6.0 编程实例与技巧

class OperClass { int x; public: OperClass(); OperClass Operator + (OperClass); 声明重载的操作符+

具有两个 OperClass 类类型的操作数

返回值也是 OperClass 类类型 void display(); };

OperClass OperClass::OperClass + (OperClassA) 声明重载操作符+的具体运算操作 { OperClass B; B.x=x+A.x; return B; } void main()

主程序开始

{ OperClass X,Y,Z; Z=X+Y;

声明三个对象

对象 X 调用重载操作符+与对象 Y 相加 并将返回值

赋给 Z } 本例中的重载操作符+是双目运算符 使用时与普通的“加”操作符没

www.BOOKOO.com.cn

162


Visual C++6.0 编程实例与技巧

有什么差别 操作符重载除了具有上述功能之外 换

还可以进行不同数据类型的转

在 C++语言中 可以通过以下语句进行强制类型转换: int i; float f = 5.0; i = int(f); 为了扩充这种功能

型转换函数

可以进行操作符重载的概念

设计新的强制类

例如:

#include<iostream.h> 声明类 Price

class Price { int sum; public: Price();

构造函数

Price(int);

构造函数 带有一个参数

Operator flat();

声明重载函数 float

Operator int();

声明重载函数 int

转换成浮点数

转换成整数

}; Price::Price()

声明构造函数的体

{ sum = 1; } Price::Price (int S)

声明构造函数的体

{ sum = S;

www.BOOKOO.com.cn

163


Visual C++6.0 编程实例与技巧

} 声明重载函数 float 的体

Price::Operator float() { float pp; pp = sum * 1.5; return pp; } Price::Operator int()

声明重载函数 int 的体

{ int pp; pp = sum * 1.5; return pp; } 主程序开始

void main() {

声明对象 A

Price A(10); Price B;

声明对象 B

float fprice; int iprice; fprice = float(A); iprice = int(B); fprice = B;

将对象 A 强制转换成浮点数 将对象 B 强制转换成整数

未显式调用转换函数 但根据等号左边的变量

类型(浮点数)自动确定所要调用的转换函数 float iprice = A;

与上个语句类似 系统自动调用转换函数 int

}

www.BOOKOO.com.cn

164


Visual C++6.0 编程实例与技巧

3.14 多态性 多态性(playmorphism)指的是一个接口名称具有多种功能 是面向对 象程序设计的重要特性 前面介绍的函数重载和操作符重载都是用来表 示名称相同而功能不同的 因此属于程序编 译时的多态性 在 C++语 言中 还有一种运行时的多态性 又称为虚拟函数(virtu al function) 以 下介绍的就是这种多态性 3.14.1 虚似函数 虚拟函数指的是某个函数在某类中被声明成 virtual

而在派生类中

又 重新定义了此函数 声明虚拟函数的语法形式为 virtual 函数类型 函数名称() { } 例如

以下是一个声明虚拟函数的简单例子

Class BaseC

定义基类 BaseC

{ p8blic: virtual void display() 声明虚拟函数

在基类中必须带有关键字 virtual

{ cout<<

Virtual Function

.<<endl;

{ }; Class DerivedC:public BaseC

DerivedC

{

www.BOOKOO.com.cn

165


Visual C++6.0 编程实例与技巧

public: 声明虚拟函数

void display()

在派生类中可以省略关键字

virtual { cout<<

DerivedC displaty. <<endl;

} } 使用虚拟函数的主要目的是让程序在运行时能执行多态性功能

针是执行多态性功能的主要工具 当声明一个指向基类的指针时 该指 针也适用于相应的派生类

此时

若基类和派 生类中已有了同样的成

员函数 那么 无论指针指向哪个类 所调用的成员函数都是基类的 例如 Class BC

声明基类 BC

{ public; void display()

声明成员函数

{ } }; Class DC:public BC

声明派生类 DC

{ public: void display() { }

www.BOOKOO.com.cn

166


Visual C++6.0 编程实例与技巧

}; 主程序开始

void main() {

声明指向基类 BC 的指针

BC * PP BC X

声明 BC 类的对象 X

DC Y

声明 DC 类的对象 Y

PP = &X; PP display(); PP = &Y; PP display();

指针 PP 指向基类 BC 该语句将调用基类 BC 中的成员函数 指针 PP 指向派生类 DC 该语句也调用基类 BC 中的成员函数

display 而不是派生 DC 类中的成员函数 display } 声明虚拟函数后

这种问题就可以得到解决

例如

将上例中的成

员函数声明成虚拟函数 那么无论类指针指向基类 BC 或是派生类 DC 都可以执行相应的成员函数 display 而不会再 执行基类的 display 了 此外

指向基类的指针用于派生类时

只适用于派生类中从基类继

承而来的函数 现在 若将函数声明或虚拟函数 那么 指向基类的指 针也适用于派生类中自行定义的虚拟函数 例如 将上面的例子修改一 下 Class BC

声明基类 BC

{ public: virtual void display()

声明函数 display 声明成虚拟函数

{

www.BOOKOO.com.cn

167


Visual C++6.0 编程实例与技巧

} }; 声明派生类 DC

Class DC:public BC { public: void display()

该成员函数也是虚拟函数

{ } }; void main()

主程序开始

{ BC * PP

指针 PP 指向基类 BC

BC X

声明 BC 类的对象 X

DC Y

声明 DC 类的对象 Y 该语句将调用基类 BC 中的成员函数 display

PP display() PP = &Y;

基类指针 PP 指向派生类 DC 该语句调用派生类 DC 中的面员函数 display

PP display() }

虚拟函数实现的多态性是运行时的多态性 重载类似

但仍有一定的 区别

在函数重载中

与编译时的多态性函数 尽管函数名相同

相同名函数的返回值类型及所传递的参数是不一 样的

而在虚拟函数

中 各函数的类型和所传递的参数类型一定要一样 否则 就不能称为 虚拟 函数

而只能叫重载

虚拟函数就是通过这种完全一致来实现运

行时的多态性 函数被定义成虚拟函数后

不论类声明中定义了多少层派生类

www.BOOKOO.com.cn

这 168


Visual C++6.0 编程实例与技巧

种虚拟特性都可以一直继承下来 直到最深层 例如 图 3.6 所示的类 继承 类A Virtual Void display()基 类 继 承 类B Void display()第一层派生类 继 承 类C Void display()第二层派生类

图 3.6 多层派生类中的虚拟函数 在基类 A 中声明了虚拟函数 display 这种虚拟特性被继承到类 B 和 类C中

实用时就可以实现多态性 例如

void main() { A * PP

声明一个指向类 A 的指针 PP

www.BOOKOO.com.cn

169


Visual C++6.0 编程实例与技巧

AM

声明类 A 的对象 M

BN

声明类 B 的对象 N

CP

声明类 C 的对象 P

PP = &M;

指针 PP 指向 A 类 调用基类 A 中的成员函数 display

PP display() PP = &N;

指针 PP 指向基类 B 调用派生类 B 的成员函数 display

PP display(); PP = &P

指针 PP 指向派生类 C 调用派生类 C 中的成员函数 display

PP display(); } 3.14.2 纯虚拟函数 在实际应用中

C++语言设计虚拟函数通常都是用基类定义某事件

的接口和数据定义 而在派生类中定义处理该事件的方法 这样就可以 实现一接口

多种功能

由于在基类中定义的 虚拟函数通常只是完成

初始化或定义接口的目的 一般不执行具体功能 所以 为了减小程序 的复杂程度并节省内存空间 可以让此函数等于零 以称为纯虚拟函数 以下是一个具体应用虚拟函数及纯虚拟函数的例子 # include <iostream.h> Class BaseC

BaseC

{ protected: int x; public: virtual void initvalue(int i) www.BOOKOO.com.cn

170


Visual C++6.0 编程实例与技巧

{ x = i; } virtual void display() = 0; 该函数被声明成纯虚拟函数 当各派生类中均未定义 display()虚拟函数时

则执行此虚拟函数

}; class SquareC:public BaseC

声明派生类 SquareC

{ public: void display()

派生类中的虚拟函数 display

{ cout<<

X=

<<X<<endl;

cout<<

X Square =

<<**x<<endl;

} }; Class CubeC:public BaseC

声明派生类 CubeC

{ public: void display()

派生类中的虚拟函数 display

{ cout <<

X=

<<X<<endl;

cout <<

X Cube =

<<x**&x<<endl;

} };

www.BOOKOO.com.cn

171


Visual C++6.0 编程实例与技巧

主程序开始

void main() { BaseC * PP

声明指向类 BaseC 的指针

SquareC A;

声明类 SquareC 的对象 A

CubeC B;

声明 CubeC 的对象 B

PP = &A;

指针 PP 指向派生类 SquareC

PP initvalue(2); 尽管派生类 SquareC 中未声明虚拟函数 initvalue 但程序执行时

将自动寻找所属基类 BaseC 中的虚拟函数

initvalue 并执行它 PP display(); PP &B;

调用派生类 SquareC 中的虚拟函数 display

指针 PP 指向派生类 CubeC

PP initvalue(4); 指针 PP 自动寻找所属的基类 BaseC 执行所声明的虚拟函 数 initvalue PP display();

调用派生类 CubeC 中的虚拟函数 display

} 注意

如果某个类拥有纯虚拟函数

那么这个类就称为抽象类

象类不能声明属于此类的对象 但可以声明指向该类的指针 因为指针 是支持多态性的主要工具 同时抽象类一定要有其他派生类存在 否则 程序在编译时会出错 3.15 C++语言的输入 C++语言的输入

输出

传统 C 语言的输入 输出函数中最常见的是 printf 函数和 scanf 函

www.BOOKOO.com.cn

172


Visual C++6.0 编程实例与技巧

数 其中 printf 函数是输出函数 scanf 函数是输入函数 在 C++语言中 仍然可以使用这种方式进行输入 输出 但是 C ++语言提供了更为方便 的输入 输出处理功能 即输入 输出流(I

O stream)

这是为了适应

面向对象程序设计方法而推出的 3.15.1 I

O 结构

在 Visual C++ I

O 流的类结构如图 3.7 所示

其中 ios 是基类 所有的 I

O 流类都是 ios 的派生类

第一层派生类包含 istream 类和 ostream 类 istream 类主要用于处理 输入功能 包含成员函数 get getline read 和>>等;ostream 类主要用于 处理输出功能

包含成员函数 put

write 和<<等

第二层派生类包含 istrstream 类:istreamwithassign 类 if stream 类 iostream 类 ofst ream 类 ostreamwithassign 类和 ostrstream 类 这些类 是由 istream 类和 ostream 类派生的 因此继承了相应的输入或输出功 能 第三层派生类包含 fstream 类 strstream 类和 stdiostream 类 fstream 支持碰盘文件的输入 与输出 输出

stdiostream 类是标准 I

strstream 类支持内存中字符串的输入与 O 文件的输入

输出类

在前面的例子中 已多次用到 C++语言的输入和输出操作符:cout 和 cin 事实上 cout 是派生类 ostreamwithassign 类的一个对象 必须结合 操作符<<使用 <<操作符是从 o stream 类继 承下来的 称为插入操作 符

引导待输出的数据输出到屏幕上

cout 最大的特点是输出的数 据

可以是各种类型数据 包括整型 实型 字符型等 使用该语句时根本 不用考虑待输出数 据的类型 也不必有相应的格式控制符(如 printf 函

www.BOOKOO.com.cn

173


Visual C++6.0 编程实例与技巧

数中的 d 和

c 等) 这样 无论输出变 量的类型是否做了修改 cout

语句都完全一致 这在设计大型程序时是十分有用的 Cin 是派生类 istreamwithassign 类的一个对象 必须结合操作符>> 使用 >>操作 符是从 is tream 类继承下来的 称为提取操作符 表示从 标准输入设备上读取数据 Cin 与 cout 一样

在输入数据时 论哪种

数据类型 使用的格式都相同 因此在设计 C++程序时

大多数使用

这种方式进行输入

图 3.7 I 3.15.2 其他输入

O 流的类结构示意图

输出函数

在设计 C++程序时 除了 cout 和 cin 函数 输出函数 在 派生类 ostr eam 类中

还可以使用其他的输入

包含许多用于处理输出功能的

成员函数 由于 cout 继承了 ostream 类的特性 因此 可以使用这些成 员函数

例如:

# include<iostream.h> # include<string.h>

www.BOOKOO.com.cn

174


Visual C++6.0 编程实例与技巧

主程序开始

void main() {

=“输出”;

char str

int len = strlen (str); cout.write(str,lent1); 调用成员函数 write 将 len+1 个字符输出 其中包括字符 串的结果字符 endl 为换行输出

cout<<endl;

for(int i = 0; i<len;i++> cout.put (str i

);

调用成员函数 put

输出单个字符

} 在派生类 istream 类中 包含许多用于处理输出功能的成员函数 由 于 cin 继承了 istream 类 的特性 因此可以使用这些成员函数 例如: # include <iostream.h> const int LEN = 100; 主程序开始

void main() { char str1

LEN ,str2

LEN ;

char ch; cin.get(ch);

调用成员函数 get 读取一个字符 并放到 ch 中

cin.get(str1.LEN); 调用成员函数 get 读取 LEN 长度的字符串或读到换行符为 止 并放到 str1 中 cin.get(str1,LEN, $

); www.BOOKOO.com.cn

175


Visual C++6.0 编程实例与技巧

调用成员函数 get 读取 LEN 长度的字符串或读到 $ 为 止 并放到 str1 中 cin.getline(str2,LEN); 调用成员函数 getline 读取 LEN 长度的字符串或读到换行 符为止 该 函数将换行符也读到字符串内 cin.getline(str2,LEN, $

并放到 str2 中

);

调用成员函数 getline 读取 LEN 长度的字符串或读到 $ 为止

并放到 s tr2 中

cin.read(st2,10); 项用成员函数 read 读取 10 个字符 若字符串长未到 10 该函数会一直等待 继续输入 }

www.BOOKOO.com.cn

176


Visual C++6.0 编程实例与技巧

第四章 Windows API 程序的组织结构 本章介绍 Windows 程序的动态组织结构 包含:非优先权式多任务 消息

消息队列

数 楚

消息处理函数(或称窗口函数)

CALL-BACK 函

这些都是 Windows 程序的必备知识 务必在一开始就了解清 往后的路才会走得顺利

4.1 单工与多工作操作系统 在介绍 Windows 程序的基本构架之前 我们必需先了解单任务与多 任务的不同 4.1.1 单任务 以往的 MS-DOS 是一种单一任务类型的操作系统 也就是说 计算 机中同时只允许一件任务进行 必须等到第一个任务结束或者主动让出 使用权时

才能进行下一个任务

就好像在电话 亭打公用电话

一部

话机只供一个人使用 必须到第一个人打完电话离开后 下一个人才能 进入电话亭使用电话 这种单用户的系统

运行中的程序完全掌握了计算机的控制权

位可以回想一下 当您在设计程序时 所画的框图 是不是一步步都在 自己的掌控之下呢?您是否还考虑到其他程序的状态呢?当然不会 4.1.2 多任务 所谓多任务是指同一时间有好几个任务一起进行

一般中大型的计

算机操作系统 都是采用多用户多任务的方式 就好像一间办公室中各 忙各的工作 但人们相互之间并不干扰一样

www.BOOKOO.com.cn

177


Visual C++6.0 编程实例与技巧

1. 优先级式的多任务 单任务时 CPU 的时间几乎完全被应用程序所占用 操作系统只是 被动等候程序要求 提供必要的服务 如文件的存取等 多任务时 系 统就必须掌握主动 由它来分配时间与资源给每一个运行中的程序 例 如 在某一段时间中 CPU 用来运行一个字处理程序 当时间一到 系 统就必须立刻将 CPU 时间分配给另一个工程计算程序 或是交给绘图 一切的资源

程序 制

如内存

以达到数个程序同时工作

印表

屏幕显示

一样都必须由系统管

却又不致 于互相干扰的目的

这种由一个凌驾所有程序之上的系统 来操纵生杀大权的控制方式 称为“优先级”式的多任务(Preemptive Multitasking)

普遍见于各类主机

系统上 2. 非优先级式的多任务 另外还有一种多任务作业的方法

系统的权力并没有那么大

仅仅

只运行一些分配的工作 其余全靠各应用程序间相互合作 做完一件事 后立刻让出使用权给其他程序 如此达成的多 任务方式 称为“非优先 级”式的多任务(Ninpreemptive Multitasking) 意思是说 大家 要共同生 就必须遵守游戏规则

彼此互相礼让

Windows 的作业方式 便是采用后者“非优先权级的多任务” 局工作过程是这样的:程序从系统取得控制权

其全

然后进行程序本身的工

工作完毕后

Windows 的编程者就得随时注意适时地让出使用权 否则系统 会

再将控制权交还系统

以便 分配给下一个程序

因为某一程序的干扰而无法顺利的运作 如果有某个程序硬要独占 CPU 时间

占用内存资源

则系统对它也无可奈何

除了控制权之外 各种输出 输入设备 内存等资源也是依循“取得 www.BOOKOO.com.cn

178


Visual C++6.0 编程实例与技巧

—使用—归还”的三部 曲节奏与韵律来进行的 系统只负责将资源分配 给各程序

当资源不再使用时

程序应该立 刻交出资源的使用权

样才能维持多任务作业的顺利进行 记:“有借有还

再借不难”的基本原则

所以编 Windows 程序一定要谨 才不会出错

4.2 消息 Message 在 Windows 多任务环境下 同时会有许多程序交织着进行 这样复 杂的工作是如何管理的呢? Windows 凭借的就是“消息传送(Message Passing)”这个法宝! 在 Windows 下 所有外部输入如:按键 鼠标按钮 移动 计时等动 作都是由系统先拦截 转 换成消息(Message)之后 输入 Windows

再传给各个程序:

程序 B

所以在 Windows 程序中 我们要以 GetMessage()这个函数来读取信 息 而不能用 getch()或 sc anf()直接来读取输入 这样它们不但无法取 得消息 而且会霸占 CPU 时间 使得未按键之前

其他 所有程序都

不能动了 Windows 拦截输入的目的之一 是为了将不同外设输入的数 据

转 换成一致的格式

以方便程序处理

这个一致的格式就是消息

(Message) 4.2.1 Message 的结构 消息是一个结构 它的组成如下: typedef struct tagMSG{ HWND

hWnd;

UINTmessage;消息

所欲送达的窗口代码(handle) 为-Unsign 整数(int)

WPARAMwParam;相关参数 后文说明 www.BOOKOO.com.cn

179


Visual C++6.0 编程实例与技巧

LPARAMtParam;相关参数 后文说明 DWORDtime 时间 POINTpt 鼠标光标位置 }MSG; 我们可以看到消息结构包含了按键 据

其中很重要的是第一 项 hWnd

(handle)

鼠标时间等不同输入设备的数

这是消息所欲送到的窗口的代码

也就是说“消息传送的最终目的地是窗 口而不是程序”

点很重要 请各位记住

这一

我们后文会再说明

4.2.2 消息队列 Message Queue 传送消息时 消息将会漏失掉

如果程序正在忙碌

来不及接收源源而来的消息

所以 Wi ndows 的做法是先把消息放入消息队列

(Message Queue)内 等有空闲时再由程序主动从队列 中读取消息 如 下图所示: 每个程序运行之初 Windows 就会为它创建一个存放消息的队列 称为应用程序消息队列(Ap plication message queue) 时

当外部输入发生

输入设备的驱动程序会将输入转换成消息 的格式收集到一个系统

的队列(System queue) 然后再由 Windows 来分派到各应用的程序队 列 里 采用系统列的理由 和应用程序队列的理由是一样的 因为 Windows 是非优先级是多任务

系统本身的优先性比一般程序也高不到哪里

所以 Windows 也可能无法及时接收输入消息 统队列中

因此才先将消息放入系

等 Windows 有空时再来分配消息

www.BOOKOO.com.cn

180


Visual C++6.0 编程实例与技巧

4.2.3 消息的来源 除了外部输入

程序与程序

程序与系统之间也都是靠消息来通信

的 所以消息的来源有: 外部输入的消息 其他程序发出的消息和系统 送来的消息三种来源

这些消息又可以分为两 类

一类是经过消息队

列的 queued message 例如前述的输入消息便是 一类是不经过队列 的 unqueued message 大部分由系统传来的消息都是 unqueued 的 我们在 适当的时候再予以 说明

消息的来源如图 4.3 所示

图 4.3 消息来源示意图 4.2.4 读取消息的循环:Message Loop 因为消息不会直接送给程序

必须由程序主动从队列中读取

所以

WinMain()中通常会以一个循环来读取信息: while (GetMessage(

msg

NULL 0,0))读取消息的循环

{

} 此循环用 GetMessage()函数往该程序的 message queue 读取消息 并 www.BOOKOO.com.cn

181


Visual C++6.0 编程实例与技巧

存入 msg 结构变量 然后再由循环内部来处理消息 GetMessage():多任务的基础 当 GetMessage()从消息队列读消息时 序做

则表示目前没有工作给该程

此时 GetMessage()会自动将程序的控制权交给 Windows

以便

Windows 将控制权转移给下一个程序 这正是 Wind ows 多任务的基础 所以 Windows 程序中一定要用 GetMessage()来取消息 多任务才能进行 4.3 窗口函数: 窗口函数:消息所要送达的对象 当我们操作窗口时(如按钮)

窗口必须对该操作有所反应

Windows 下 每个窗口背后都有一个窗口函数 负责窗口对操作(输入) 的反应 也就是负责窗口的行为 如图 4.4 所示 反应”动作是通过消息来传送的

因为窗口的“输入—

所以窗口函数的工作

就是按所收到

的消息种类来决 定反应的动作 因此窗口函数有时也称为“消息处理函 数”

因为窗口函数对消息反应之不 同

每个窗口才有不同的行为

www.BOOKOO.com.cn

182


Visual C++6.0 编程实例与技巧

图 4.4 每个窗口背后

都有一个窗口函数

典型的窗口函数的格式为: Switch (message){case MSGI: 依据消息的种类 (procedure for MSGI);处理 MSGI 消息的程序 做不同的行为 break; case MSG2: (procedure for MSG2); 处理 MSG2 消息的程序 break; : : default:不想处理的消息 就交给系统处理! return(DefWindowPro((hwnd

message

www.BOOKOO.com.cn

183


Visual C++6.0 编程实例与技巧

wParam lParam)); } return (NULL); Windows 会将各类消息输入它们所应属于的窗口函数 例如您在某 个窗口按下鼠标或选择某个菜单命令 系统当然要指明是在哪一个窗口 进行的 还记得消息结构中的 hWnd 吗?在 msg.h Wnd 中就标示着消息 所应送达的窗口代码 窗口函数在收到消息后 就以 switch-case 的方式 来拾取它所关心的消息 并加以处理 至于不是该窗口所要处理的消息 则 在 default 处 交

给 一 个 叫 DefWindowProc() 的 函 数 处 理

Def:WndowProc() 是 一 个 标 准 窗 口 处 理 函 数 (Default

Window

Procedure) 它会以 Windows 的方式来处理消息 例如窗口的放大 缩 小

移动等标 准的处理动作 这种处理方式 也有人称为“事件驱动方式”(event-driven) 即事件(消

息)才会引发动作

程序并不会主动做任何事

事件驱动的观念 在 Windows 编程中十分重要 等你学会 Windows 程序的基本结构之后

方能 体会它的基本精神!

回调函数 4.3.1 CALL-BACK FUNCTION:回调函数 CALL-BACK FUNCTION(回调函数)是 Windows 程序的一大特色 什么是回调函数?为什么会有回 调函数呢?首先我们来看这样一个问 题 假设目前屏幕上有许多程序在运行 同时打开了许 多窗口;如图 4.5 所示

www.BOOKOO.com.cn

184


Visual C++6.0 编程实例与技巧

图 4.5 同时打开多个窗口 如果 USER 将 A 窗口拖动到它处 这些活动的消息就如前述的会被 存入消息队列 然后程序用 G etMessage()读取 WinMain()读到消息后 接下去就是想办法把消息送给 A 窗口的处理程序

当 A 窗口的处理程

序由消息得知原来 USER 在拖动窗口 所以便使 A 窗口随着鼠标移动的 方向 移动

一切尽如预计的一样进行顺利

但是

A 窗口移开后

原本被 A 窗口遮住一半的 B 窗 口呢?B 窗口是否应该将原本遮住的那一 半显示出来呢?要如何显示呢? 首先我们会想到的是 Windows 应该会将掩盖的部分保存起来 等遮 盖物移开时再予显示 内存

但 是这样做有一个问题

如果窗口重叠有十层

九层

可就不得了

www.BOOKOO.com.cn

那就是占用了太多的 如果不保存被遮盖

185


Visual C++6.0 编程实例与技巧

的窗口 另外一个方法就是当遮盖物移去时 重新画出被遮盖的部分 提到重画 一个窗口画面是由应用程序和 Windows 共同画成的 Windows 画的是窗口的基本架 构

如外框

放大钮

的是工作区的内容(窗口扣除边框 菜单 工作区(Client Region)

缩小钮

控制盒而程序画

拉动抽条等的四方形 称作

所以说 重画到底由谁来画呢?是 Wind ows?A

窗口函数?还是 B 窗口函数呢? 目前三者的情形是: A 窗口函数:知道 A 窗口发生移动 但不知道谁在它的底层 也 不知道该如何重画? B 窗口函数:知道如何重画 但不知道何时该画(即上层窗口是否 已移走)?并且此时也无控制权以做重画的动作 Windows:知道 A 窗口发生移动 也知道 B 窗口在它的底层 但 不知道该如何画? 仔细观察上列条件 我们发现 B 窗口函数和 Windows 正好可以合作 完成重画的工作!但是 B 窗口函数没有控制权 如何能和 Windows 协力 画出 B 窗口被遮盖的部分呢?唯一的办法就是把 B 窗口 函数交由 Windows 调用!(仔细想想!这是关键!)这就是 CALL-BACK 函数(回调函 数)的由来 这 样做 使得 Windows 在 A 窗口移走后 能够调用 B 窗 口函数来画出 B 窗口被遮盖的部分 所以 在 Windows 的编程规则中 在程序初始化的阶段 便由程序 将窗口函数的“入口”提 供给系统 谓登记下来

Windows 会将此入口登记下来

其实就是在系统内创建一个指针 指向此回调函数

所 如图

4.6 所示

www.BOOKOO.com.cn

186


Visual C++6.0 编程实例与技巧

图 4.6 创建指针指向回调函数 这等于是将函数“嵌入”系统之中 再由系统随时视需要来调用

成为系统的一部分

待会儿

嵌入之后

我们会以实例来演示

这个“嵌

入”动作是如何完成的 凡是 Windows 的程 序都会把它们的窗口函数登 记到 Windows 系统上 再由 Windows 调用 因为一般操作系统(如 D OS Unix)都是由系统提供函数 由程序来调用 但用回调函数则是由程序提 供函数

由系 统调用

方向刚好相反

所以称之为 CALL-BACK

FUNCTION:回调函数 4.3.2 窗口函数的登记 窗口函数是在 Register 窗口类时 在 wc.tpfn-WndProc 这个对象上设 置的 例如: wc.lpfnWndProc = WndProc;

*将窗口函数 WndProc“嵌入”系统*

这样 当运行 Register Class ( WC)时 就把 WndProc()这个函数的

www.BOOKOO.com.cn

187


Visual C++6.0 编程实例与技巧

地址登记到 Windows 也就是“嵌入”系统了!我们将在后面的程序中以实 例说明 在 Windows 下 像窗口函数这种回调式的函数都必须声明成 far 才 行 这是因为回调函数是由 Windows 系统来调用的 而 Windows 系统 和窗口函数是存放在不同的内存区的 因此窗口函数 必须声明成 far 系 统才调用得到 值得说明的是 WinMain()都不必声明为 far

这是因为 Windows 系

统并不直接调用 WinMain() 系统调用的 是起始模块(如 CWS.OBJ) 编 译器会自动安排使系统能调用到起始组 我们不用担心 至于 W inMain() 编译器会将它和起始模块联合在同一个内存区内 调用即可

因此使用近程(near)

WinMain()就不用声明为 far 了 如图 4.7 所示

图 4.7 起始模块近程调用 Winmain() 4.3.3 用 Dispatch Message 来分配消息 接着我们来看一看消息被程序读取之后的情形 当程序取得消息后

www.BOOKOO.com.cn

188


Visual C++6.0 编程实例与技巧

接着就必须将消息分配给所属的窗口函数处理 但是 这项分配工作 系统本来就在做了 Windows

怎么说呢?因为所有的 窗口函数地址都已登记到

Windows 清楚地知道每个窗口函数的入口(不然如何调用

所以分派(传送)消息给窗口函数的工作交给 Windows 做最恰当不

呢?)

过了 怎么交呢 Win dows 提供一个叫 Dispatch Message()的函数 应 用程序可用此函数将消息交由 Windows 来传 给所属的窗口函数 因此 Windows 的消息循环就大体简化成: While(Get Message(

msg

NULL,0,0))

{ DispatchMessage(

msg)

分派消息的工作就交给 Windows

吧 } 当程序从队列读到消息后 就由 dispatch(配发)给 Windows 转交到所 属的窗口函数 程序连分配工作都省了 有人说 CALL-BACK 函数不能 直接调用 事实上 它们可以调用 只是用 Dis patchMassage( msg)更 方便 用不着自己调用 如此而已 这也就是为什么在一般的 Windo ws 程序中 我们虽设计了窗口函数 却从未使用过 原来它都由 Windows 代劳了 4.4 Windows 程序的流程 现在全部细节已经说明完毕

我 们 将 Windows 程 序 的 两 大 过

程:WinMain()和 WndProc()整理说明

www.BOOKOO.com.cn

189


Visual C++6.0 编程实例与技巧

4.4.1 WinMain() WinMain()是 Windows 程序的起始函数 通常它会做窗口注册 创 建窗口 显示窗口的动作 然后进入消息循环 此循环会不断地往消息 队列中读取消息 一旦取得消息立刻用 Dispatch Massage()通过系统来调 用消息处理函数

如此一直循环

直到 GetMassage()找不到消息

出控制权 才暂告休息 4.4.2 WndProc() WndProc() 是 处 理 消 息 的 函 数 或 称 窗 口 函 数 Windows 来调用

为一回调型的函数

是控制窗口行为的过程

WndProc() 统 一 由

WndProc()按消息来做出响应

整体的消息通信流程如图 4.8 所示

图 4.8 消息通信流程 4.4.3 CHECK POINT 至于 Windows 的程序流程则整理如下: 外部输入动作发生

被转换成消息

www.BOOKOO.com.cn

190


Visual C++6.0 编程实例与技巧

放入消息 queue 由 Get Message()读消息 Dispatch 消息给 Windows 由 Windows 回调(CALL BACK)消息处理函数(窗口函数) 读一下消息

若无消息则将控制权交回 Windows

由 Windows

将控制权交给下一程序 问题: 为什么 WinMain()用 Get Message()取得消息后

要将消息再用

Dispatch 给 Windows 呢? 既然消息要用 Dispatch 给 Windows 那为何不由 Windows 直接 从消息队里取消息 而要由程序先 Get Message()再使用 Dispatch 呢? 如果您能毫无阻碍的读懂上列运行流程并回答上列问题

表示您已

上路了 4.4.4 写个正规的 Windows 程序 现在我们已准备好相关的知识 所以就来看看正规的 Windows 程序 是怎样一幅面貌吧 # include <Windows.h> long FAR PASCAL WndProc(HWND WORK WORK LONG); int

PASCAL

hPre-vInstance

WinMain(HANDLE

LPSTR lps2CmdParam

hInstance

HANDLE

intnComShow)

{ HWND

hwnd;

MSG

msg;

www.BOOKOO.com.cn

191


Visual C++6.0 编程实例与技巧

if(!hPrevInstance){

当本 Instance 第一次运行时 需作窗口注册

设置 wc.style = NULL; wc.lpfnWndProc = WndProc; 将消息处理函数 WndProc“嵌入”系统 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL,IDIAPPLICATION); 装入库存图标图 wc.hCursor = LoadCursor(NULL,IDCARROW); 装入箭头光标 wc.hbrBackground = GetStock Object(WHITEBRUSH); 背景用白色笔刷 无菜单

wc.lps2MenuName = NULL; wc.lps2ClassName = RegisterClass( wc);

ClassW1

指定类别名称

;

注册窗口类别

} 建立窗口

hwnd = Creat Window( “ClassW1”

窗口类型名

World-Windows style”

窗口标题字符串

WSOVERLAPPEDWINDOW,

可重叠窗口

“Hello

CWUSEDEFAULT,

x:缺省左上角的水平位置

CWUSEDEFAULT,

y:缺省左上角的垂直位置

CWUSEDEFAULT,

w:缺省高度 www.BOOKOO.com.cn

192


Visual C++6.0 编程实例与技巧

CWUSEDEFAULT,

n:缺省高度

NULL,

无父窗口

NULL,

无菜单

hInstance()

程序 Instance 模块

NULL);

额外参数

Show Window(hwnd,nComdShow); 用 nCmdShow 的方式显示窗口 更新窗口工作区

Update Window(hwnd); While(GetMessage( Dispatch Message(

msg,NULL,NULL,NULL)) msg);

将消息送至队列

return msg.wParam; } long FAR PASCAL WndProc (HWND hwnd,WORD message, 消息处理程序

WORD wParam,LONG lParam) {

开始处理消息

switch(messag) { case WMDESTROY:

破坏窗中消息

PostQuit Message(0); break; 将其他消息送往系统作缺省处理

default:

return(DefWindowProc(hwnd,message, }

wParam,lParam));

return NULL; } 这正是学者害怕而又很著名的 Windows 的第一程序

www.BOOKOO.com.cn

有五十多种

193


Visual C++6.0 编程实例与技巧

呢 许多程序都是以这么一个程序做为 Windows 初学者的示范程序 如 果我们也是一开始就给您这个程序 感想如 何呢?但通过学习 此程序 就不是那么难以理解

深奥莫测了 就让我们一起来分析一下吧

4.5 程序分析( 程序分析(一):WinMain() winhello 这个程序主要由两个函数组成 WinMain()与 WndProc() 它们的意义前文已有详细说明

相信大家已十分清楚

的构造 4.5.1 WinMain()的构造 WinMain()的内容看来虽长 但若略去设置及参数不看 则其结构是 非常简单的: if(!hPrevInstance) 若是第一次运行

则须做初始工作

RegisterClass( wc); hwnd = Creat Window( ); Show Window( ); Update Window( ); 建立窗口显示窗口更新窗口 while(GetMessage(

msg,NULL,NULL,NULL)——取得消息的循环

Dispatch Message (

msg);——分派消息

return msg.wParam;——结束本程序 WinMain()的内容 我们已很熟悉 它先判断是否为第一个 Instance 只有当 hPrevInstance = 0 才需要登记窗口类 其次便是创建 显示窗 程序中多了一个 Update

口等必要的步骤

在 ShowWindow()之后

Window()的动作

这是为了保证窗口的内容能更新成最新状态

本 书

后文会再给予说明

www.BOOKOO.com.cn

194


Visual C++6.0 编程实例与技巧

4.5.2 WMQUIT 消息 做完显示

更新窗口的工作

程序便进入消息循环

这样程序才能

和外界通信 才不会成为断线的风筝 由消息队列取得的若为 WMQUIT 消息

则 GetMessage()会返回零 使 循环停止

结束程序

当消息不为 WMQUIT 时 GetMessage 会返回一个非零值 使循环 得以继续 并且由 D ispatchMessage ( msg)将消息交由系统来分派给所 属的窗口函数去处理 当 GetMessage()在消息队列中取不到消息时

则表示目前没有工作

给程序做 此时 GetMes sage()会将程序的控制权交给 Windows 就是这 样分工合作 完成了整个 Windows 的多任务操 作 4.6 程序分析( 程序分析(二):窗口函数 ):窗口函数 WndProc() 我们知道为了使窗口不致成为断线的风筝

除了由程序队列读消息

之外 很重要的就是要有一个消息处理函数(又称窗口函数)来处理消息 本程序的消息处理函数就是 WndProc(): long FAR PASCAL WndProc (HWND hwnd,WORD message,

息处理程序 WORD wParam,LONG lParam) { 开始处理消息

switch(message) { case WMDESTROY:

破坏窗口消息

PostQuitMessage(0); break; default:

将其他消息送往系统作缺省处理

return (DefWindowProc(hwnd,message, www.BOOKOO.com.cn

195


Visual C++6.0 编程实例与技巧

wParam,lParam); } return NULL; } 首先我们来看 WndProc()这个消息处理函数: long FAR PASCAL WndProc(HWND hwnd,WORD message, WORD wParam,LONG lParam) WndProc()是个回调函数 有四个函数 这里要再告诉大家一个观念: 所有“嵌入”到系统中 由系统负责调用的消息处理函数 都是固定会输 入以上四个参数 这四个参数 第一个是 HWND 类型

代表的是窗口

的 handle 也就是说 代表消息所属的窗口 第二个参数 message 就是 消息本身

也就是函数中 switch 判断的依据

最后两个参数

一个是

16 位的短变量

一个是 32 位(4bytes)的长变量 代表了伴随消息而来

的必要参数数据

以后我们会看到

随着消息种类的不同

此二参数

代表的意义也都不一样 注意: DispatchMessage( msg)所输入的 msg 是一个 MSG 类型的结构 而 窗口函数取到的 message 是一个 WORD 类型的简单变量 二者不同 其实 message 是系统取自 msg 结构第二数据项来输入 Wn dProc()的 WndProc()以 switch-case 来判别消息的种类(消息 message 是系统由 WndProc()第 2 个参数输入的)

在这里

default 两种状况 WMDESTROY

只处理了 WMDESTROY 和

这是窗口程序重要的消息之一 它

关系着窗口的结束过程

www.BOOKOO.com.cn

196


Visual C++6.0 编程实例与技巧

4.6.1 WMDESTROY 毁减消息 当用鼠标双击窗口 Control Box(即最左上角那个方框)时 系统会送 出 一 个 WMDEST ROY 消 息 给 消 息 处 理 函 数

当 WndProc() 收 到

WMDESTROY 时标准的响应动作是调用 Po stQuitMessage()(见前面程 序)

送出一个 WMQUIT 消息到自己的消息队列

WMQUIT 消息会使得 GetMessage()返回 0

前面提 到过

因而结束 while 循环正常地

结束程 序 为何结束一个程序要这样麻烦的分段传送两个消息呢(其实更多)?为 何系统在得知用鼠标双击 Control Box 动作时 不直接将 WMQUIT 消息 送入程序的消息队列呢?以目前读者 对 Windows 程序的理解 应该能自 己揣摹出原因吧

请将您的答案记录在本页的空白处

等 日后再回来

比较 此处的答案可显示出您目前对 Windows 这种消息传送 非优先权 级多任务 操作方式体会的深度有多少 4.6.2 default:DefWindowProc() 前 面 已 提 到 过 DefWindowProc() 是 缺 省 的 消 息 处 理 函 数

所有

WindProc()未予处理的消息最后都要交给 DefWindowProc()来处理 这样 窗口才能正常运行

所以程序 default:处要写成:

return(DefWindowProc(hwnd,message,wParam,lParam));这样

我们已

将 winhello 的细节动作大致交待清楚 您是否对 Windows 的设计观念多 少了解了一些呢?再一次强调 一定要先弄 清楚整个运行机制后 再往 前推进

否则您可能只有愈来愈糊涂呢

4.7 模块定义文件 现在 我们是不是马上可以开始编译 连接 并运行这个程序呢? www.BOOKOO.com.cn

197


Visual C++6.0 编程实例与技巧

还不行 一般来说 发展一个 Windows 应用程序 除了 C 程序文件 外 还需要相当多的辅助文件 写一个略为像样的应用程序 需要十几 个各式各样的文件 个文件

是很平常的事

在这里

我 们最起码还需要另一

就是所谓“模块定义文件”

模块定义文件的扩展文件名是.DEF 的一些性质 以帮助连接器

这个文件的内容是描述该程序

连接成需要的.EXE 可执行文件

这个文件的写法是以一个大写的字段名为开头

其后是该字段的内

容 以空白符隔开 我们来看看 Winhello 这个程序的模块定义文件是什 么样子: NAME

WINHELLO

DESCRIPTION C Windows Application EXETYPEWindows STUB

Winstub.exe

CODEPRELOAD MOVEABLE DATAPRELOAD MOVEABLE MULTIPLE HEAPSIZE1024 STACKSIZE4096 EXPORTSWndProc 以下我们简介各字段的内容和意义: 1. NAME 名称栏 这是指可运行文件.EXE 的文件名 通常与主程序文件(WinMain 所 在的文件)同名 2. DESCRIPTION 描述栏 此字段的内容将以字符串类型放在.EXE 文件中 但不会被运行 这

www.BOOKOO.com.cn

198


Visual C++6.0 编程实例与技巧

个字段的目的是让编程者可以放一些让自己或别人看的消息于程序中 例如版权或版本说明等 3. EXETYPE 运行文件种类栏 这一栏通常的内容就是 Windows 代表这是一个必须在 Windows 下 运行的.EXE 文件 4. STUB 标号栏 这一栏的字符串 是指要一起连接在.EXE 开头的小程序 如果您试 着在 DOS 下直接运行一个 W indows 应用程序 会看到这么一行字:The Program requires Microsoft Windows 就是由 Win stub.exe 这个小程序所完 成的

这个小程序的作用是在程序最开头的地方

判别现在系统是 否

处于 Windows 环境下 如果是 便运行程序;如果不是 就显示上列文 字而停止运行

因此

如果您改变了这个标号文件

改以自己编写的

判断程序 下次运行时(在 DOS 下)便会看到 您自己加入的消息或画面 了 5. CODE 代码字段 这个字段的参数是用来描述代码段的性质 PRELOAD 是指 Windows 必须在运行前将代码装入至 内存中;MOVEABLE 代表此段程序是可移 动地址的

这样窗口系统可将此代码搬移

存;DISCARDABLE 则是用于通知系统

以获得较大的连续可用内

当内存不够用时

可以将此程

序段抛弃 6. DATA 数据文件段 DATA 定段是指数据段的性质

其中前两个参数 PRELOAD 和

MOVEABLE 代表的意义和 CODE 字段 中相同 最后一个 MULTIPLE 是指程序的每一个 instance 都要有一个自己的数据段 正如前面说过的 www.BOOKOO.com.cn

199


Visual C++6.0 编程实例与技巧

同一个程序有好几个同时运行的 instance 则 DATA 声明成 MULTIPLE 的作用就是在 使数据段不要相混 7. HEAPSIZE 调整内存空间字段 当程序中要求动态分配内存时 系统就会将 heap 中的内存分配给该 程序 因此 这个字段决定要保留多少内存给动态分配使用 尽管我们 在程序中并没有任何分配内存的要求 我们还要必须保留 1K 的空间给 heap 8. STACKSIZE 堆栈区大小字段 堆栈是 C 程序中很重要的区域 所有调用函数 传递参数的动作 都必须要有足够的堆栈区 我们的程序中并没有作很多层的调用 也没 有太多的消息

所以要求的堆栈区并不大

递归调用 或是使用相当多的 local 变量

如 果您的程序中

有用到

就必须声明较大的堆栈区

9. EXPORTS 外加嵌入字段 前面提到过 消息处理函数 WndProc 必须被嵌入到系统中 才能由 系统来调用 所有在程序中必须被嵌入而由系统来调用的函数 都必须 在此字段中声明 此处只有一个 WndProc 将 来我们会看到许多:定时 处理 对话框处理 4.8 编译运行 现在 我们终于可以开始来编译并运行我们的第一个 Windows 程序 了 我们将以 Borland 或 TURBOC++3.1 版来编译这个程序 如果您是 MSC 的用户

请先设置好必要的环境变量(可以使用

setvars.bat 来设置)

BIN 目录下的

再创建一个工程文件如下:

www.BOOKOO.com.cn

200


Visual C++6.0 编程实例与技巧

winhello.exe:winhello.obj winhello.def link winhello, align:16

NULL

nod slibcew libw,winhello

rc.winhello.exe winhello.obj:winhello.C d-C-Gsw-Ow-Wz-Ip winhello.C 使用 Borland 或 TURBOC++ 不管您是在 Windows 版本或 IDE 版本 之下 请先使用鼠标 或按 Alt+P

选择 Project Open Project 选项 指

定.PRJ 文件名为 winhello;再选择 Project

Add Item 项目 移动滚动条

将 winhello.C 及 winhello.def(不一定需要)两个文件加入(Add)至 winhe llo.prj 中 最后再按 Done 接着 您只须选择 Run Run 选项(或按 Ctrl+F9 键) 系统就会编 译 连接 并将运行结果显示出来(如果程序键入及操 作过程无误的话) 如何?试着用鼠标放大 缩小 移动这个窗口 欣赏一下自己的成果 吧! 说明: 使用 Borland

TURBOC++时 若未指定.def 文件 则 Complier 会以

缺省的 def 文件 格式来安排程序 因此初学者不需要自己编写 def 文件 def 文件的用途是在设置调试程序的运行特性(如果是 MOVE DISCARD) 但对于小型的程序而言 大半无用武之地 所以用 Complier 缺省的 def 就可以了 因此

只要观念清楚

造出一个可以任意移动

winhello 这个程序一点也不复杂 缩小

放大的窗口

而且

五十行的程序一点也不

嫌多 您要是在 DOS 中写出一个类似的程序 恐怕要数百行呢 但无论如何

如果每次写一个程序

都要键入那么多内容

www.BOOKOO.com.cn

恐怕您

201


Visual C++6.0 编程实例与技巧

早就大喊吃不消了 其实 每一个 Windows 的内容 都有许多大同小异 的地方 尤其是 WinMain()主程序 几乎只要更改 几个字符串或数字就 行了 因此我们就将 winhello.C 保留成一个样板程序 每次写新程序时 就将样板拷贝一份 更改必要内容 如 Creat Window 的标题字符串 WndProc 处 理 各 类 消 息 的 语 句

模 块 定 义 文 件 中 NAME 和

DESCRIPTION 的字段以及其他需要更改的设置 便成为另 一份完整的 Windows 程序了 4.9 Hunfarian 命名规则 在本章结束前 我们来谈一谈 Windows 的命名规则和所谓的匈牙利 命名法 在前面的程序中 我们看过 WMDESTROY hWnd 等常量和 变量 它们的名字好像都是一些规则 是的

在 Windows 程序中 所

有常量和数据类型定义皆是大写字母 并且常量名以类型缩写开头 加 一个下划线字符 再跟着类型代表意义的字符串 如表 4.1 所列 表 4.1 常用类型缩写列表 [BHDFG2,WK8ZQ1,K12ZQ1,K19ZQ1W] 类型缩写

说明

举例

CS

class

style

CSHREDRAW CW

creat

windowcw

USERDEFAULT DT

draw

text

cursor

id

DTVCENTER IDC IDCARROW

www.BOOKOO.com.cn

202


Visual C++6.0 编程实例与技巧

IDI

icon

id

IDIAPPLICATION WM

window

message

WMDESTROY WS

window

style

WSOVERLAPPEDWINDOW Windows 本身的常量和数据类型都定义于 Windows.h 头文件中 我 们可在 SDK 或者是 BC++的 INCLUDE 目录中找到 windows.h 头文件 4.9.1 Hungarian 命名法 至于 Windows 程序的变量采用 Hungarian 命名法 这是由微软一位 匈牙利籍(Hungarian)程序员 Charles Simonyi 先生首先采用的 在 Hungarian 法则中 变量名称必须用一个或多个小写英文字母的缩 写

来代表其数据类型 如表 4.2 所列 例如用 h 开头代表这个变量是

一个 HANDLE.lpsz 代表的一指向 ASCII 字符串的 远指针(Long Pointer to String asciiz) 其后则是变量的代表名称 字与字相连 但每个有意义 的英文字以大 写开头 好处是一目了然

以示区隔

不久我们就会熟悉这种命名法

一眼就能看出这个 变量在做什么

对于程序日后的

维修非常有利 表 4.2 Hungarian 法则 [BHDFG2,WK10,K29W] 缩写

代表的类型

[BHDG2,WK10ZQ3,K29ZQ1W] b

BOOL or Boolean integer

by

byte or undigned Char

www.BOOKOO.com.cn

203


Visual C++6.0 编程实例与技巧

c

char

cx,cyshort int

通常做为 x 或 y 方面的长度变量

dw

DWORDdouble word 或 unsigned long

fn

function

h

handle

I

integer

L

LONG

N

short or integer sstring

Sz

ASCII(null-terminated) string

W

WORD (unsigned Int)

x,yshort int

通常作为 x,y 坐标变量

如果读者键入的速度不慢的话 我建议在程序书写时采用 Hungarian 命名法 学者

因为日后修改 起来会方便许多

节省不少麻烦

但如果是初

就不需花那么多时间在录入上了

现在 你已具备 Windows 编程必须的知识了 准备进入多彩多姿的 Windows 世界吧

www.BOOKOO.com.cn

204


Visual C++6.0 编程实例与技巧

第五章 应用程序框架结构 第五章 Visual C++应用程序框架结构 在 Visual C++6.0 中 编写 Windows 应用程序主要有以下几种方法 第一种方法

直接调用 W indows 环境提供的 Win32 API 应用程序编

程接口 函数来编写 Windows 应用程序 使用这种 方法 大量的程序 代码必须由用户自己编写 第二种方法 使用 MFC 类库和活动模板库 ATL

直接编写 Windows 应用程序 MFC 类库和 ATL 提供有大量预

先编写好的类及支持代码 用于 处理多数标准的 Windows 编程任务 例如

创建窗口

处理消息

添加工具栏和对话框

等 等

因此

使

用 MFC 类库和 ATL 可以简化 Windows 应用程序的编写工作 第三种方 法 既使用 M FC 类库和 ATL 也使用向导 Wizards 来编写 Windows 应用程序 在这种情况下 首先 要 用 MFC AppWizard MFC ActiveX ControlWizard ISAPI Extension Wizard 和 ATL COM AppW izard 来生成 Windows 应用程序的基本源文件 然后用 Class Wizard 来建立应用程序 的类

消 息处理和数据处理或者定义控件的属性

事件和方法

最后

把各应用程序所要求的功能添加 到类中 本书以后的内容主要针对第三种方法进行讨论

本章首先介绍如何

用 MFC AppWizard 来生成 并建立应用程序的基本框架 然后 讨论应 用程序基本框架是如何构成的 以及基于 MFC 类 库的应用程序又是如 何执行的 最后 本章对文档模板 窗口类和窗口对象 消息和命令的 处理 以及 Class Wizard 和 WizardBar 的使用作了介绍 5.1 创建 MDI 示例应用程序 为方便 Windows 应用程序执行流程的讲解 这里创建一个基于 MFC

www.BOOKOO.com.cn

205


Visual C++6.0 编程实例与技巧

的 MDI 应用程序作为案例 按照前述方法 启动 Visual C++6.0,使用 AppWizard 创建名为 MyApp 的 MDI 应用程序

AppWizard 各步骤中的选项均使用缺省值

程序创建成功后 源程序清单如下 //////////// /////////// // MyApp.h : main header file for the MYAPP application #if

!defined(AFXMYAPPHB4F35DE8D56F

11D2B2A1CC50F8CF2D6EINCLUDED) #define AFXMYAPPHB4F35DE8D56F11D2B2A1CC50F8CF2D6EINCLUDED #if MSCVER > 1000 #pragma once #endif // MSCVER > 1000 #ifndef AFXWINH #error include

stdafx.h

before including this file for PCH

#endif #include

resource.h

// main symbols

///////////////////////////////////////////////////////////////////////////// // CMyAppApp: // See MyApp.cpp for the implementation of this class // class CMyAppApp : public CWinApp { public: CMyAppApp();

www.BOOKOO.com.cn

206


Visual C++6.0 编程实例与技巧

// Overrides // ClassWizard generated virtual function overrides //{{AFXVIRTUAL(CMyAppApp) public: virtual BOOL InitInstance(); //}}AFXVIRTUAL // Implementation //{{AFXMSG(CMyAppApp) afxmsg void OnAppAbout(); // NOTE - the ClassWizard will add and remove member functions here. //

DO NOT EDIT what you see in these blocks of generated

code ! //}}AFXMSG DECLAREMESSAGEMAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFXINSERTLOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFXMYAPPHB4F35DE8D56F11D2B2A1CC50F8CF2D6EINC LUDED ) ////////////// ////////////// // MyApp.cpp : Defines the class behaviors for the application. // www.BOOKOO.com.cn

207


Visual C++6.0 编程实例与技巧

#include

stdafx.h

#include

MyApp.h

#include

MainFrm.h

#include

ChildFrm.h

#include

MyAppDoc.h

#include

MyAppView.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif ///////////////////////////////////////////////////////////////////////////// // CMyAppApp BEGINMESSAGEMAP(CMyAppApp, CWinApp) //{{AFXMSGMAP(CMyAppApp) ONCOMMAND(IDAPPABOUT, OnAppAbout) // NOTE - the ClassWizard will add and remove mapping macros here. //

DO NOT EDIT what you see in these blocks of generated

code! //}}AFXMSGMAP // Standard file based document commands ONCOMMAND(IDFILENEW, CWinApp::OnFileNew) ONCOMMAND(IDFILEOPEN, CWinApp::OnFileOpen) // Standard print setup command ONCOMMAND(IDFILEPRINTSETUP, www.BOOKOO.com.cn

CWinApp::O

208


Visual C++6.0 编程实例与技巧

nFilePrintSetup) ENDMESSAGEMAP() ///////////////////////////////////////////////////////////////////////////// // CMyAppApp construction CMyAppApp::CMyAppApp() { // TODO: add construction code here, // Place all significant initialization in InitInstance } ///////////////////////////////////////////////////////////////////////////// // The one and only CMyAppApp object CMyAppApp theApp; ///////////////////////////////////////////////////////////////////////////// // CMyAppApp initialization BOOL CMyAppApp::InitInstance() { AfxEnableControlContainer(); // Standard initialization // If you are not using these features and wish to reduce the size // of your final executable, you should remove from the following // the specific initialization routines you do not need. #ifdef AFXDLL Enable3dControls();

// Call this when using MFC in a share

d DLL #else Enable3dControlsStatic();

// Call this when linking to MFC

statically www.BOOKOO.com.cn

209


Visual C++6.0 编程实例与技巧

#endif // Change the registry key under which our settings are stored. // TODO: You should modify this string to be something appropriate // such as the name of your company or organization. SetRegistryKey(T(

Local AppWizard-Generated Applications

LoadStdProfileSettings();

));

// Load standard INI file options

(including MR U) // Register the application

s document templates.

Document

templates // serve as the connection between documents, frame windows and views. CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDRMYAPPTYPE, RUNTIMECLASS(CMyAppDoc), RUNTIMECLASS(CChildFrame), // custom MDI child frame RUNTIMECLASS(CMyAppView)); AddDocTemplate(pDocTemplate); // create main MDI Frame window CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDRMAINFRAME)) return FALSE; mpMainWnd = pMainFrame; // Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo);

www.BOOKOO.com.cn

210


Visual C++6.0 编程实例与技巧

// Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // The main window has been initialized, so show and update it. pMainFrame->ShowWindow(mnCmdShow); pMainFrame->UpdateWindow(); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); // Dialog Data //{{AFXDATA(CAboutDlg) enum { IDD = IDDABOUTBOX }; //}}AFXDATA // ClassWizard generated virtual function overrides //{{AFXVIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX);

//

DDX/DDV support //}}AFXVIRTUAL // Implementation protected: //{{AFXMSG(CAboutDlg) www.BOOKOO.com.cn

211


Visual C++6.0 编程实例与技巧

// No message handlers //}}AFXMSG DECLAREMESSAGEMAP() }; CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { //{{AFXDATAINIT(CAboutDlg) //}}AFXDATAINIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFXDATAMAP(CAboutDlg) //}}AFXDATAMAP } BEGINMESSAGEMAP(CAboutDlg, CDialog) //{{AFXMSGMAP(CAboutDlg) // No message handlers //}}AFXMSGMAP ENDMESSAGEMAP() // App command to run the dialog void CMyAppApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); } ///////////////////////////////////////////////////////////////////////////// www.BOOKOO.com.cn

212


Visual C++6.0 编程实例与技巧

// CMyAppApp message handlers /////////////// /////////////// // MyAppView.h : interface of the CMyAppView class // ///////////////////////////////////////////////////////////////////////////// #if

!defined(AFXMYAPPVIEWHB4F35DF2D

56F11D2B2A1CC50F8CF2D6EINCLUDED) #define AFXMYAPPVIEWHB4F35DF2D56F11D2B2A1CC50F8CF2D6EINCLUD ED #if MSCVER > 1000 #pragma once #endif // MSCVER > 1000 class CMyAppView : public CView { protected: // create from serialization only CMyAppView(); DECLAREDYNCREATE(CMyAppView) // Attributes public: CMyAppDoc* GetDocument(); // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFXVIRTUAL(CMyAppView) www.BOOKOO.com.cn

213


Visual C++6.0 编程实例与技巧

public: virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); //}}AFXVIRTUAL // Implementation public: virtual ~CMyAppView(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFXMSG(CMyAppView) // NOTE - the ClassWizard will add and remove member functions here. //

DO NOT EDIT what you see in these blocks of generated

code ! //}}AFXMSG DECLAREMESSAGEMAP() }; #ifndef DEBUG // debug version in MyAppView.cpp www.BOOKOO.com.cn

214


Visual C++6.0 编程实例与技巧

inline CMyAppDoc* CMyAppView::GetDocument() { return (CMyAppDoc*)mpDocument; } #endif ///////////////////////////////////////////////////////////////////////////// //{{AFXINSERTLOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFXMYAPPVIEWHB4F35DF2D56F11D2B2A1CC50F8CF2D6 EINCLUD ED) ///////////// ///////////// // MyAppView.cpp : implementation of the CMyAppView class // #include

stdafx.h

#include

MyApp.h

#include

MyAppDoc.h

#include

MyAppView.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif ///////////////////////////////////////////////////////////////////////////// // CMyAppView IMPLEMENTDYNCREATE(CMyAppView, CView)

www.BOOKOO.com.cn

215


Visual C++6.0 编程实例与技巧

BEGINMESSAGEMAP(CMyAppView, CView) //{{AFXMSGMAP(CMyAppView) // NOTE - the ClassWizard will add and remove mapping macros here. //

DO NOT EDIT what you see in these blocks of generated

code! //}}AFXMSGMAP // Standard printing commands ONCOMMAND(IDFILEPRINT, CView::OnFilePrint) ONCOMMAND(IDFILEPRINTDIRECT, CView::On FilePrint) ONCOMMAND(IDFILEPRINTPREVIEW,

CView::O

nFilePrintPreview) ENDMESSAGEMAP() ///////////////////////////////////////////////////////////////////////////// // CMyAppView construction/destruction CMyAppView::CMyAppView() { // TODO: add construction code here } CMyAppView::~CMyAppView() { } BOOL CMyAppView::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return CView::PreCreateWindow(cs); www.BOOKOO.com.cn

216


Visual C++6.0 编程实例与技巧

} ///////////////////////////////////////////////////////////////////////////// // CMyAppView drawing void CMyAppView::OnDraw(CDC* pDC) { CMyAppDoc* pDoc = GetDocument(); ASSERTVALID(pDoc); // TODO: add draw code for native data here } ///////////////////////////////////////////////////////////////////////////// // CMyAppView printing BOOL CMyAppView::OnPreparePrinting(CPrintInfo* pInfo) { // default preparation return DoPreparePrinting(pInfo); } void CMyAppView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { // TODO: add extra initialization before printing } void

CMyAppView::OnEndPrinting(CDC*

/*pDC*/,

CPrintInfo*

/*pInfo*/) { // TODO: add cleanup after printing } ///////////////////////////////////////////////////////////////////////////// www.BOOKOO.com.cn

217


Visual C++6.0 编程实例与技巧

// CMyAppView diagnostics #ifdef DEBUG void CMyAppView::AssertValid() const { CView::AssertValid(); } void CMyAppView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CMyAppDoc* CMyAppView::GetDocument() // non-debug version is inline {

ASSERT(mpDocument->IsKindOf(RUNTIMECLASS(CMyAppDoc))); return (CMyAppDoc*)mpDocument; } #endif //DEBUG ///////////////////////////////////////////////////////////////////////////// // CMyAppView message handlers ////////////// ////////////// // MyAppDoc.h : interface of the CMyAppDoc class // ///////////////////////////////////////////////////////////////////////////// #if

!defined(AFXMYAPPDOCHB4F35DF0D5

6F11D2B2A1CC50F8CF2D6EINCLUDED) www.BOOKOO.com.cn

218


Visual C++6.0 编程实例与技巧

#define AFXMYAPPDOCHB4F35DF0D56F11D2B2A1CC50F8CF2D6EINCLUDE D #if MSCVER > 1000 #pragma once #endif // MSCVER > 1000 class CMyAppDoc : public CDocument { protected: // create from serialization only CMyAppDoc(); DECLAREDYNCREATE(CMyAppDoc) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFXVIRTUAL(CMyAppDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); //}}AFXVIRTUAL // Implementation public: virtual ~CMyAppDoc(); #ifdef DEBUG virtual void AssertValid() const; www.BOOKOO.com.cn

219


Visual C++6.0 编程实例与技巧

virtual void Dump(CDumpContext& dc) const; #endif protected: // Generated message map functions protected: //{{AFXMSG(CMyAppDoc) // NOTE - the ClassWizard will add and remove member functions here. //

DO NOT EDIT what you see in these blocks of generated

code ! //}}AFXMSG DECLAREMESSAGEMAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFXINSERTLOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFXMYAPPDOCHB4F35DF0D56F11D2B2A1CC50F8CF2D6 EINCLUDE D) //////////// //////////// // MyAppDoc.cpp : implementation of the CMyAppDoc class // #include

stdafx.h

#include

MyApp.h

www.BOOKOO.com.cn

220


Visual C++6.0 编程实例与技巧

#include

MyAppDoc.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif ///////////////////////////////////////////////////////////////////////////// // CMyAppDoc IMPLEMENTDYNCREATE(CMyAppDoc, CDocument) BEGINMESSAGEMAP(CMyAppDoc, CDocument) //{{AFXMSGMAP(CMyAppDoc) // NOTE - the ClassWizard will add and remove mapping macros here. //

DO NOT EDIT what you see in these blocks of generated

code! //}}AFXMSGMAP ENDMESSAGEMAP() ///////////////////////////////////////////////////////////////////////////// // CMyAppDoc construction/destruction CMyAppDoc::CMyAppDoc() { // TODO: add one-time construction code here } CMyAppDoc::~CMyAppDoc() { }

www.BOOKOO.com.cn

221


Visual C++6.0 编程实例与技巧

BOOL CMyAppDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization code here // (SDI documents will reuse this document) return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CMyAppDoc serialization void CMyAppDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: add storing code here } else { // TODO: add loading code here } } ///////////////////////////////////////////////////////////////////////////// // CMyAppDoc diagnostics #ifdef DEBUG void CMyAppDoc::AssertValid() const { CDocument::AssertValid(); www.BOOKOO.com.cn

222


Visual C++6.0 编程实例与技巧

} void CMyAppDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif //DEBUG ///////////////////////////////////////////////////////////////////////////// // CMyAppDoc commands //////////// //////////// // MainFrm.h : interface of the CMainFrame class // ///////////////////////////////////////////////////////////////////////////// #if

!defined(AFXMAINFRMHB4F35DECD56

F11D2B2A1CC50F8CF2D6EINCLUDED) #define AFXMAINFRMHB4F35DECD56F11D2B2A1CC50F8CF2D6EINCLUDE D #if MSCVER > 1000 #pragma once #endif // MSCVER > 1000 class CMainFrame : public CMDIFrameWnd { DECLAREDYNAMIC(CMainFrame) public: CMainFrame(); // Attributes www.BOOKOO.com.cn

223


Visual C++6.0 编程实例与技巧

public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFXVIRTUAL(CMainFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFXVIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: // control bar embedded members CStatusBar mwndStatusBar; CToolBar

mwndToolBar;

// Generated message map functions protected: //{{AFXMSG(CMainFrame) afxmsg int OnCreate(LPCREATESTRUCT lpCreateStruct); // NOTE - the ClassWizard will add and remove member functions here. //

DO NOT EDIT what you see in these blocks of generated

code! //}}AFXMSG www.BOOKOO.com.cn

224


Visual C++6.0 编程实例与技巧

DECLAREMESSAGEMAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFXINSERTLOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFXMAINFRMHB4F35DECD56F11D2B2A1CC50F8CF2D6EI NCLUDED ) /////////// /////////// // MainFrm.cpp : implementation of the CMainFrame class // #include

stdafx.h

#include

MyApp.h

#include

MainFrm.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif ///////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENTDYNAMIC(CMainFrame, CMDIFrameWnd) BEGINMESSAGEMAP(CMainFrame, CMDIFrameWnd) //{{AFXMSGMAP(CMainFrame)

www.BOOKOO.com.cn

225


Visual C++6.0 编程实例与技巧

// NOTE - the ClassWizard will add and remove mapping macros here. //

DO NOT EDIT what you see in these blocks of generated

code ! ONWMCREATE() //}}AFXMSGMAP ENDMESSAGEMAP() static UINT indicators

=

{ IDSEPARATOR,

// status line indicator

IDINDICATORCAPS, IDINDICATORNUM, IDINDICATORSCRL, }; ///////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { // TODO: add member initialization code here } CMainFrame::~CMainFrame() { } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; www.BOOKOO.com.cn

226


Visual C++6.0 编程实例与技巧

if (!mwndToolBar.CreateEx(this, TBSTYLEFLAT, WS CHILD | WSVISIBLE | CBRSTOP |

CBRSGRIPPER

|

CBRSTOOLTIPS

|

CBRSFLYBY

|

CBRSSIZEDYNAMIC) || !mwndToolBar.LoadToolBar(IDRMAINFRAME)) { TRACE0(

Failed to create toolbar

return -1;

// fail to create

@n

);

} if (!mwndStatusBar.Create(this) || !mwndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0(

Failed to create status bar

return -1;

// fail to create

n

);

} // TODO: Delete these three lines if you don

t want the toolbar to

// be dockable mwndToolBar.EnableDocking(CBRSALIGNANY); EnableDocking(CBRSALIGNANY); DockControlBar(&mwndToolBar); return 0; } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CMDIFrameWnd::PreCreateWindow(cs) )

www.BOOKOO.com.cn

227


Visual C++6.0 编程实例与技巧

return FALSE; // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef DEBUG void CMainFrame::AssertValid() const { CMDIFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CMDIFrameWnd::Dump(dc); } #endif //DEBUG ///////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers /////////////// /////////////// // ChildFrm.h : interface of the CChildFrame class // ///////////////////////////////////////////////////////////////////////////// #if

!defined(AFXCHILDFRMHB4F35DEED5

6F11D2B2A1CC50F8CF2D6EINCLUDED) #define www.BOOKOO.com.cn

228


Visual C++6.0 编程实例与技巧

AFXCHILDFRMHB4F35DEED56F11D2B2A1CC50F8CF2D6EINCLUDE D #if MSCVER > 1000 #pragma once #endif // MSCVER > 1000 class CChildFrame : public CMDIChildWnd { DECLAREDYNCREATE(CChildFrame) public: CChildFrame(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //{{AFXVIRTUAL(CChildFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); //}}AFXVIRTUAL // Implementation public: virtual ~CChildFrame(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif // Generated message map functions www.BOOKOO.com.cn

229


Visual C++6.0 编程实例与技巧

protected: //{{AFXMSG(CChildFrame) // NOTE - the ClassWizard will add and remove member functions here. //

DO NOT EDIT what you see in these blocks of generated

code! //}}AFXMSG DECLAREMESSAGEMAP() }; ///////////////////////////////////////////////////////////////////////////// //{{AFXINSERTLOCATION}} // Microsoft Visual C++ will insert additional declarations immediately before the previous line. #endif // !defined(AFXCHILDFRMHB4F35DEED56F11D2B2A1CC50F8CF2D6E INCLUDE D) /////////// /////////// // ChildFrm.cpp : implementation of the CChildFrame class // #include

stdafx.h

#include

MyApp.h

#include

ChildFrm.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE

www.BOOKOO.com.cn

230


Visual C++6.0 编程实例与技巧

static char THISFILE

= FILE;

#endif ///////////////////////////////////////////////////////////////////////////// // CChildFrame IMPLEMENTDYNCREATE(CChildFrame, CMDIChildWnd) BEGINMESSAGEMAP(CChildFrame, CMDIChildWnd) //{{AFXMSGMAP(CChildFrame) // NOTE - the ClassWizard will add and remove mapping macros here. //

DO NOT EDIT what you see in these blocks of generated

code ! //}}AFXMSGMAP ENDMESSAGEMAP() ///////////////////////////////////////////////////////////////////////////// // CChildFrame construction/destruction CChildFrame::CChildFrame() { // TODO: add member initialization code here } CChildFrame::~CChildFrame() { } BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Modify the Window class or styles here by modifying // the CREATESTRUCT cs if( !CMDIChildWnd::PreCreateWindow(cs) ) www.BOOKOO.com.cn

231


Visual C++6.0 编程实例与技巧

return FALSE; return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CChildFrame diagnostics #ifdef DEBUG void CChildFrame::AssertValid() const { CMDIChildWnd::AssertValid(); } void CChildFrame::Dump(CDumpContext& dc) const { CMDIChildWnd::Dump(dc); } #endif //DEBUG ///////////////////////////////////////////////////////////////////////////// // CChildFrame message handlers 5.2 应用程序类和源文件 使用 MFC AppWizard 生成 MDI 应用程序的基本框架时 将派生五 个主要的类 程序类

文档类

视图类

主边框窗口类

子边框窗口类和应用

主要 的程 序 任务 均分 布在 这 些类 中实 现

此外

MFC

AppWizard 为每个类生成各自的源文件 并从程序名中获取类名和类源 文件 名作为缺省名

类之间通过调用公有成员函数并传递消息来进行

相互间的通信和交换数据 假设创建了名为 MyApp 的 MDI 应用程序的基本框架 则 MyApp

www.BOOKOO.com.cn

232


Visual C++6.0 编程实例与技巧

程序的文档类称为 CMyAppDoc

该 类是从 CDocument 类派生的

CMvAppDoc 类的头文件为 MyAppDoc.h 实现文件为 MyAppDoc.cpp 文档类用于存放应用程序的数据并实现文件保存和装载功能

通过

MyAppDoc::Serialize 实现 MyApp 程序的视图类称为 CMyAppView 该类是从 CView 类派生 的

CMyAppView 类 的 头 文 件 为 My AppView.h

实现文件为

CMyAppView.cpp 视图类指定用户以什么方式见到文档数据 以及 如 何与其进行交互 MyApp 程 序 的 主 边 框 窗 口 类 称 为 CMainFrame

该类是从

CMDIFrameWnd 类派生的 CMainFrame 类的头文件为 MainFrm.h 实 现文件为 MainFrm.cpp 主边框窗口类用于管理应用程序窗口

显示标

题栏 菜单栏 工具栏 状态栏 控制菜单和控制按钮 主边框窗口是 所有 MDI 子边 框窗口的包容器 MyApp 程 序 的 子 边 框 窗 口 类 称 为 CChildFrame

该类是从

CMDIChildWnd 类派生的 CChildFram e 类的头文件为 ChildFrm.h 现文件为 ChildFrm.cpp

子边框窗口类用于管理在 MDI 主边框 窗口中

打开的各个文档 每个文档及其视图都有一个 MDI 子窗口 MDI 子窗 口包含在主边框窗 口中

而且没有自己的菜单栏

工具栏和状态栏

必须与包含它的主边框窗口共享 MyApp 程序的应用程序类称为 CMyAppApp

该类是从 CWinApp

类派生的 CMyAppApp 的头文件为 MyApp.h 实现文件为 MyApp.cpp 应用程序类控制应用程序的所有对象

文档

视图和边框 窗口

并完

成应用程序的初始化工作和最后的清除工作 每个基于 MFC 类库的应 用程序都必须有一个从 CWinApp 类派生的对象 应用程序的一个且是 www.BOOKOO.com.cn

233


Visual C++6.0 编程实例与技巧

唯一的一个对象将创建并管理应用 程序所支持的文档模板

文档模板

使得所创建的文档 视图和边框窗口有机地结合起来 除了生成主要类的源文件外 MFC AppWizard 还生成为建立应用程 序所必须的其他文件 1

Resource.h 是标准的头文件

包含所有资源符号的定义

2 StdAfx.h 和 StdAfx.cpp 用于生成预编译的头文件 MyApp.pch 和预编译类型文件

Si dAfx.obj

3 MyApp.clw 为 ClassWizard 数据库文件 存放由 ClassWizard 使用的信息 Class Wizard 使用这些信息来编辑已有的类或者添加新的 类 此外 ClassWizard 还使用这个文件来存储 信息 这些信息可用于 创建和编辑消息映射 4

对话数据映射 并创建成员函数的原型

MyApp.rc 是包含资源描述信息的资源文件

有的应用程序资源

包括存储在子目录

res 中的图标

资源文件列有所 位图和光标

这个文件可以由 Developer Studio 直接编辑 5

res MyApp.rc2 包含不是由 Developer Studio 编辑的资源 可

以将所有不能由资源编辑器编辑的资源放置到这个文件中 6 res MyAppDoc.ico 是包含 MDI 子窗口图标的图标文件 这个 图标包含在资源文件 MyApp .rc 中 7 res

MyApp.ico 是包含应用程序图标的图标文件 应用程序图

标包含在资源文件 MyApp .rc 中 8

res Toolbar.bmp 是用于创建工具栏按钮的位图文件 初始工

具栏和状态栏是在主边框窗口类中构造 此外 MFC AppWizard 还生成 ReadMe.txt 文件 用于描述为应用程 序生成的所有源文件 www.BOOKOO.com.cn

234


Visual C++6.0 编程实例与技巧

5.3 程序的控制流程 Windows 应用程序的初始化 运行和结束工作都是由应用程序类完 成的 应用程序类构成了 应用程序的主执行线程 每个基于 MFC 类库 而建立的应用程序都必须有一个且只有一个从 CWi nApp 类派生的类对 象

该对象在窗口创建之前构造 与所有 Windows 应用程序一样 基于 MFC 类库而建立的应用程序

也有一个 WinMain 函数 但是 在应用程序中不用编写 WinMain 代码 它是由 MFC 类库提供的 在应用程序启动时调用这个 函数 WinMain 函数执行注册窗口类等标准服务 然后再调用应用程序对象中的成员函 数来 初始化和运行应用程序 在 MyApp 程 序 中

CMyAppApp 类 对 象 的 定 义 在 实 现 文 件

MyApp.cpp 中 CMyAppApp theApp 由于 CMyAppApp 类对象是全局定义的

因此在程序入口函数

WinMain 接收控制之前将调用构造函数 初始时 由 MFC AppWizard 生成的 CMyAppApp 构造函数是空的构造函数 什么也不做

因此

编译器将调用基类 CWinApp 的缺省构造函数 CWinApp 构造函数把应 用程序对象 CMyApp App 的地址保存在一个全局指针中 通过全局指针 就可以调用 CmyAppApp 的成员函数 在所有全局对象创建之后 WinMain 函数接收控制 在初始化应用 程序时 WinMain 函数调用应用程序对象的 InitApplication 和 InitInstance 成员函数 在运行应用程序的消息循环时 成员函数

在程序结束时

tInstance 成员函数

WinMain 函数将调用 Run

WinMain 函数将调用应用程序对象的 Exi

图 5.1 说明了应用程序的执行顺序 www.BOOKOO.com.cn

235


Visual C++6.0 编程实例与技巧

图 5.1 应用程序的执行顺序 成员函数 InitInstance Run ExitInstance 和 0nIdle 都是可以覆盖的 其中

InitInstanc e 是唯一一个必须覆盖的 CWinApp 成员函数

5.3.1 成员函数 InitInstance Windows 允许用户运行同一应用程序的多个副本或实例 每当启动 新的应用程序实例时 Win Main 函数都要调用 InitInstance InitInstance 是应用程序类 CMyAppApp 的一个成员函数 定义 缺省时

在源文件 MyApp.cpp 中

由 AppWizard 生成的 InitInstance 主要完成以下工作

(1)创建并注册文档模板 文档模板用于存放与应用程序文档 视图 和边框窗口有关的信息

创建或打开文档时

应用 程序使用文档模板

创建文档类对象来存放文档 创建视图类对象来显示文档 创建边框窗 口类对象来画出视图窗口 成员函数 InitInstance 中的以下代码用于创建 并注册文档模板 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(

www.BOOKOO.com.cn

236


Visual C++6.0 编程实例与技巧

IDRMYAPPTYPE, RUNTIMECLASS(CMyAppDoc), RUNTIMECLASS(CChildFrame), // custom MDI child frame RUNTIMECLASS(CMyAppView)); AddDocTemplate(pDocTemplate); 其中

传 给 CMultiDocTemplate 构 造 函 数 的 第 一 个 参 数

IDRMYAPPTYPE 为资源符号 该资源符号用于标识与文档类型的菜单 和加速键有关的资源 传给 CMultiDocTemplate 构造函数的其余三个参 数是对 RUNTIMECLASS 宏的调用 对 RUNTIMECLASS 宏的每次调用 都返回指定类 边框窗口 文档和视图 的信息 从而使应用程序可以 动态地创建该类的一 个实例 即对 RUNTIMECLASS 宏的每次调用都 将返回一个指向 CRuntimeClass 对象的 指针 文档模板对象创建后

必 须 调 用 CWinApp 的 成 员 函 数

AddDocTemplate 来注册文档模板对象 址

调用参数为文档模板对象的地

AddDocTemplate 函数将文档模板存放在应用程序对象中 2 从.INI 文件中装载标准文件选项或 Windows 注册信息 包含最

近使用过的文件名

实现代码为

LoadStdProfileSettings

//装载标准的 INI 文件选项 包含 MRU

3 由于 MDI 主边框窗口与某个打开的文档是无关的 MDI 主边 框窗口类未包含在文档模板中 因此 当打开第一个文档时 MDI 主边 框窗口不会自动被创建

必须用以下代码创建 MDI 主边框窗口

CMainFrame* pMainFrame = new CMainFrame; if (!pMainFrame->LoadFrame(IDRMAINFRAME)) return FALSE; mpMainWnd = pMainFrame; www.BOOKOO.com.cn

237


Visual C++6.0 编程实例与技巧

// Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; // The main window has been initialized, so show and update it. pMainFrame->ShowWindow(mnCmdShow); pMainFrame->UpdateWindow(); 首先

创建主边框窗口类对象 然后 调用成员函数 LoadFrame 创

建主边框窗口 参数为 IDR MAINFRAME 主边框窗口的资源符号 接着

使用应用程序对象的 mpM ainWnd 成员变量存 放指向主边框窗

口的指针

最后

调 用 ShowWindow 使 主 边 框 窗 口 可 见 并 调 用

UpdateWindow 绘 制窗口客户区 4 处理命令行 以便打开命令行中指定的文档或打开新的空文档 实现代码为 CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); // Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; 此外 可以根据需要在成员函数 InitInstance 中添加专门的初始化代 码 5.3.2 成员函数 Run 初始化完成后 WinMain 函数将调用 CWinApp 的成员函数 Run 来 www.BOOKOO.com.cn

238


Visual C++6.0 编程实例与技巧

处理消息循环 成员函数 Run 不断执行消息循环 检查消息队列中有没 有消息 息

如果有消息

将消息发送下去

以便采取动 作

如果没有消

则调用消息 CWinApp 的成员函数 OnIdle 来做空闲时的处理工作

如果既 没有消息要处理

也没有空闲时的处理工作要做

将一直等待 直到消息出现

则应用程序

当应用程序结束时 成员函数 Run 将调

用 ExitInstance 来做最后的清理工作 图 5.2 说明了消 息循环的执行顺 序

图 5.2 消息循环的执行顺序 5.3.3 成员函数 ExitInstance 每当要终止应用程序的某一副本时 都要调用 CwinApp 的成员函数 ExitInstance 如果要做专门的清理工作 如释放 GDI 资源或释放执行期 间所占用的内存

则可以覆盖成员函数 Exi tInstance

5.3.4 成员函数 OnIdle 如果没有消息要处理

则应用程序将调用 CwinApp 的成员函数

www.BOOKOO.com.cn

239


Visual C++6.0 编程实例与技巧

OnIdle 来做空闲处理 缺省时 该成员函数将更新用户界面对象 如工 具栏按钮

的状态

并完成在运行过程中所创 建的临时对象的清理工

作 5.3.5 CWinApp 的服务功能 除了运行消息循环 初始化应用程序并在程序结束后做清理工作外 CWinApp 还提供有以下两种功能服务 1. Shell 注册 缺省时

可以在文件管理器

或资源管理器

中通过双击来打开应

用程序创建的数据文件 对于 MDI 应用程序 如果在创建文件时指定 了文件扩展名 MFC AppWizard 将在 InitInstance 中增加以下代码 EnableShellOpen RegisterShellFileTypes 其中

成员函数 RegisterShellFileTypes 将向文件管理器 或资源管

理器 注册应用程序的文档类型 该函数往 Windows 注册数据库中增加 一些条目

用于将扩展名与文档类型关联起 来

指定打开应用程序所

用的命令并指定打开某类文档所用的动态数据交换

DDE

命令

员函数 EnableShellOpen 允许应用程序接收来自文件管理器 或资源管 理器 的 DDE 命令

以便打开用户选中的文件

2. 拖放功能 Windows 允许用户从文件管理器 或资源管理器 窗口中拖放文件 到应用程序窗口中 例如 把多个文件名拖到 MDI 主边框窗口中 应 用程序搜索文件名并在 MDI 子窗口打开这些文件 现文件拖放功能

要在应用程序中实

可以在成员函数 InitInstance 中增加以下代码

www.BOOKOO.com.cn

240


Visual C++6.0 编程实例与技巧

mpMainWnd->DragAcceptFiles 如果不想实现拖放功能

可以去掉这个函数调用

注意

还可以用

OLE 对象链接与嵌入 实现通用的拖放功能 即在文档内部或文档之 间拖放数据 5.4 文档模板 文档模板用于存放与应用程序文档 MFC 类 库 提 供 有 两 种 文 档 模 板 类

视图和边框窗口有关的信息 即 用 于 SDI 应 用 程 序 的

CSingleDocTemplate 和用于 MDI 应用程序的 CMultiDocTemplat e 对于 每种文档类型 CSingleDocTemplate 每次只能创建并管理一个文档 而 CMultiDocTe mplate 可以创建并管理多个文档 有些应用程序还可以同时支持多种文档类型

例如

文本文档和图

形文档 在这种情况下 每个应用程序对象管理多个文档模板 每个文 档模板创建并管理一个 对于 SDI 应用程序

或多个文档 对于 MDI

应用程序 文档模板由应用程序对象创建和维护 在 WinMain 函数调用成员函 数 InitInstance 进行初始 化时 必须创建一个或多个适当类型的文档模 板

应用程序对象保存指向模板列表中每个文 档模板的指针

并提供

添加文档模板的接口 如果应用程序支持多种文档类型 则对每种文 档 类型都要调用一次 CwinApp 的成员函数 AddDocTemplate. 此外 在响应“File”菜单中的“New”或“Open”命令时 文档模板将创 建新的边框窗口 以便查看文档 新的文档及其相关视图和边框窗口的 创建是应用程序对象 共同合作而产生的

文档模板

新 创建的文档和新创建的边框窗口

参阅图 5.3 所示的创建关系

www.BOOKOO.com.cn

241


Visual C++6.0 编程实例与技巧

图 5.3 对象间的创建关系 5.5 窗口类与窗口对象 在 MFC 类库中 所有窗口最终都是从 CWnd 类派生的 5.5.1 窗口对象 窗口对象 不管是边框窗口还是其他类型的窗口 与 Windows 窗口 HWND 是有区别的 窗口对象是可以由应用程序直接创建的 CWnd 类

或派生类

的对象

它随着对应用程序的构造 函数和析构函数的

调用而出现和消失 而 Windows 窗口是 Windows 内部数据结构的句柄 在显 示时要消耗系统资源 Windows 窗口由窗口句柄 HWND 标识 并由 CWnd 类的成员函数 Creat e 在创建 CWnd 对象之后创建 窗口可 以被应用程序调用销毁

也可以被用户动作销毁

窗口 句柄保存在窗

口对象的 mhWnd 成员变量中

www.BOOKOO.com.cn

242


Visual C++6.0 编程实例与技巧

CWnd 及其派生类提供有构造函数

析构函数和成员函数

用于初

始化对象 创建 Windows 基本结构和访问被封装的窗口句柄 HWND CWnd 还提供用于封装 Windows API 的成员函数 使用这些函数可以发 送消息

访问窗口状态

转换坐标 更新和滚动窗口等

CWnd 的主要目的在于提供处理标准 Windows 消息 如 WMPAINT 或 WMMOUSEM OVE

的接口

CWn d 的多数成员函数都是标准

Windows 消息 以标识符 afxmsg 及以前缀“On”开始的 消息 如 OnPaint 和 OnMouseMove 等 的处理函数 5.5.2 CWnd 派生的窗口类 可以从 CWnd 直接创建窗口 也可以从 CWnd 派生新的窗口类 在 由 MFC AppWizard 生成的应用程序中 多数窗口都是由 CWnd 派生的 窗口类而创建的 CframeWnd 用于 SDI 主边框窗口 形成单个文档及其视图的边 框 CMDIFrameWnd 用于 MDI 应用程序的主边框窗口 主边框窗口 是所有 MDI 子窗口的包容器 并与所有 MDI 子窗口共享菜单栏 MDI 主边框窗口是出现在桌面上的顶层窗口 CMDIChildWnd 每个文档及其视图都有一个 MDI 子窗口 MDI 子窗口包含在 MDI 主边框窗口中 MDI 子窗口没有菜单栏 必须与 MDI 主边框窗口共享菜单栏 视图 视图是从 CWnd 派生的 CView 类 或派生类之一 创建的 视图是放在 SDI 主边框窗口或 MDI 子窗口的客户区中 它是文档与用 户间沟通的桥梁

www.BOOKOO.com.cn

243


Visual C++6.0 编程实例与技巧

对话框 对话框是用从 CWnd 派生的 CDialog 类创建的 表单 视图与对话框一样

表单视图是基于对话模板资源的

用 CFormView CRecor dView 或 CDaoRecordView 类创建的 控件 控件 如按钮 列表框和组合框等 是由 CWnd 派生的其他 类创建的 控件栏 控件栏是包含控件的子窗口

例如工具栏和状态栏

除了 MFC 类库提供的窗口类以外 有时还需要某些特殊用途的子窗 口 要创建这样的窗口 必须自己编写从 CWnd 派生的窗口类 并使其 成为边框窗口或视图的一个子窗口 5.5.3 注册窗口类 传统的 Windows 程序开始执行时 首先要定义和注册窗口类 定义 窗口类实际上就是定义窗口的属性 窗口的形式和功能

但是

而注册窗口类就是告诉 Windows

在框架应用程序 中

多数窗口类都是自动

完成注册的 如果要注册自己的 Windows 窗口类 则可以调用全局 函 数 AfxRegisterWndClass 然后把已注册的类传给 CWnd 的 Create 成员 函数 5.5.4 窗口的创建过程 MFC 类库所提供的所有窗口类都采用分两次构造的方法 即在引用 C++的 new 操作符时 构造函数将分配并初始化一个 C++对象 但不创 建相应的 Windows 窗口 要等 到调用 该窗口对象的 Create 成员函数 时才创建窗口 Create 成员函数把 Windows 窗口做出来 并将 窗口句 柄 HWND 保存在窗口对象的公有数据成员 mhWnd 中 在调用 Create

www.BOOKOO.com.cn

244


Visual C++6.0 编程实例与技巧

成员函 数前 可能要调用 AfxRegisterWndClass 全局函数来注册一个窗 口类 以便设置边框的图标 和类风格 对于边框窗口 可以用 LoadFrame 成员函数代替 Create 成员函数 LoadFrame 成员函数绘 制 Windows 窗口时使用的参数更少 它从资源 中获取许多缺省值 如边框标题 图标 图标

加速键表和菜单等

注意

加速键表和菜单等资源必须有一个公共的资源符号

IDRMAINFRAME 5.5.5 销毁窗口 在框架应用程序中 边框窗口关闭后 缺省处理函数 OnClose 将调 用 DestroyWindow 函数来销毁窗口 销毁 Windows 窗口时最后调用的 成员函数是 OnNcDestroy 该函数做一些清 理工作 调用 Default 成员 函数完成 Windows 清理工作 最后调用虚拟成员函数 PostNcDe stroy 将 窗口对象删除 在销毁边框窗口或视图时 必须调用 CWnd 的成员函数 DestroyWindow

而不要使用 C++的 delete 操作符

此外 可以用 CWnd 成员函数 Detach 将窗口对象与 Windows 窗口 脱离 这样 在窗口对象被销毁时 析构函数就不会销毁 Windows 窗口 5.6 设备文本与图形对象 窗口的使用主要有两种 一是处理 Windows 消息 另一是在窗口中 进行绘制 对于 Windows 消息的处理 可以使用 ClassWizard 把这些消 息映射到窗口类中 然后在该类中编写消息处理 函数 在框架应用程序中

主要的绘制工作都出现在视图中

每当窗口内

容必须绘制时 都要调用 视图的 OnDraw 成员函数 如果窗口是视图

www.BOOKOO.com.cn

245


Visual C++6.0 编程实例与技巧

的一个子窗口

则可以把视图的某些绘制工作交给 子窗口处理

而让

OnDraw 成员函数调用该窗口的一个成员函数即可 但是 无论哪种情 窗口绘制都要用到设备文本

device context

5.6.1 设备文本 设备文本也可称为设备描述表

DC

设备上下文或设备场境

Windows 应用程序与设备驱动程序和输出设备 如打印机或显示器 之 间的连接桥梁

也可以说设备文本实际上就是 一个输出路径

Windows 应用程序开始 经过适当的设备驱动程序 最后达到窗口客户 区 此外 设备文本还完全定义了设备驱动程序的状态 在应用程序向 窗口客户区输出信息前

必须先获得一个设备文本

如果没有获得设

备文本 那么应用程序和相应窗口之间就没有 任何通道 在 MFC 应用程序中 所有的绘制调用都必须通过设备文本对象来实 设备文本对象封装了用于绘制线段

形状和文本的 Windows API

函数 在 MFC 类库中 CDC 类是定义设备文本对象的类 所有的绘图函 数都在 CDC 类中定义 因此 CDC 类是所有其他 MFC 设备文本的基类 任何类型的设备文本对象都可以调用这些绘图函数 CPaintDC 类是从 CDC 类派生的设备文本类

我们知道

传统的

Windows 绘制过程是 先调用 Be ginPaint 函数获取设备文本 然后在设 备文本中绘制 接着再调用 EndPaint 函数释放设备文 本 CPaintDC 类 对象将这一过程封装起来 其构造函数将调用 BeginPaint

而析构函数

将调 用 EndPaint 这样 绘制过程就可以简化为创建 CDC 对象 绘制 和销毁 CDC 对象 在框架应用 程序中 这个过程大部分是自动完成的

www.BOOKOO.com.cn

246


Visual C++6.0 编程实例与技巧

当从 OnDraw 成员函数调用返回时 CPaintDC 对象将 被销毁并且基本 的设备文本将释放给 Windows 要注意的是 CPaintDC 对象只在响应 WMPAINT 消息时 在 OnPaint 消息处理函数中使用 此外 从 CDC 类派生的类还有 CClientDC 类

CWindowsDC 类和

CMetaFileDC 类 与 CClientDC 对象有关的设备文本是窗口客户区 其 构造函数将调用 GetDC 函数

析构函数将调用 Re1easeDC 函数

CWindowsDC 对象有关的设备文本是整个窗口区域 包括边框

与 其构

造函数将调用 GetWindowDC 函数 析构函数将调用 ReleaseDC 函数 CMetaFileDC 类对象封装的是 Windows 元 文件 可以使用这些设备文 本对象直接在视图中进行绘制 5.6.2 图形对象 Windows 提供有许多在设备文本中使用的绘图工具 例如 可以使 用画笔 Pen 来画线 使用画刷 Brush 来填充图形 使用字体 Font 来绘制文本 同样 MFC 类库提供有与 Wind ows 绘图工具等价的图形 对象类

表 5.1 列出了可用的图形对象类

这些图形对象类的基类是

CGdiObject 表 5.1 Windows 图形对象类 图形对象类 Cpen

说明 封装了 GDI 的画笔对象 Windows 句柄为

HPEN Cbrush

封装了 GDI 的画刷对象 Windows 句柄为

HBRUSH Cfont

封装了 GDI 的字体对象

www.BOOKOO.com.cn

Windows 句柄为

247


Visual C++6.0 编程实例与技巧

HFONT 封装了 GDI 的位图对象 Windows 句柄为

Cbitmap HBITMAP

封装了 Windows 调色板 Windows 句柄为

Cpalette HPALETTE

封装了 GDI 的区域对象 Windows 句柄为

CRgn HRGN 要改变当前的图形对象 以创建定制的图形对象

既可以使用库存

stock

图形对象

也可

然后将其选入设备文本

5.7 消息与命令 所有 Windows 应用程序都是消息驱动的 消息处理是所有 Windows 应用程序的核心部分 当用户单击鼠标或改变窗口大小时 都将给适当 的窗口发送消息

每个消息都对应于某个特定的 事件

与所有其他

Windows 应用程序一样 框架应用程序也要处理 Windows 消息 但框架 应用 程序使消息处理更容易

更易于维护并且封装得更好

5.7.1 消息与消息处理 前面说过 应用程序初始化完成后 将调用 CwndApp 成员函数 Run 来处理消息循环 消息循环不断检索由各种事件所产生的消息 并将消 息分发给适当的窗口 窗口接受到消息后 将调用专门的处理函数来处 理各种消息

消息处理函数通常是某一类的成员函数

编写消息处 理

是编写框架应用程序的主要任务 可以使用 ClassWizard 创建消息处理函数 然后从 Class Wizard 直接 跳到源文件中消息处理函数的定义部分

从中编写消息处理函数的代

www.BOOKOO.com.cn

248


Visual C++6.0 编程实例与技巧

码 事实上 在 Windows 应用程序中编写消息处理函数 十分类似于在 汇编语言编程中编写中断处理程序 那里的内部中断 外部中断与这里 的内部事件 外部事件一一对应

原理相同

5.7.2 消息映射 可以接收消息和命令的所有框架类都有自己的消息映射

框架利用

消息映射把消息和对象及其处理函数链接起来 从 CCmdTarget 类派生 的任何类都可以有消息映射

消息映射既可以处 理消息

也可以处理

命令 5.7.3 消息的种类 消息主要有三种类型 即标准 Windows 消息 控件通知和命令消息 5.7.3.1 标准 Windows 消息 除 WMCOMMAND 外

所 有 以 “WM” 为 前 缀 的 消 息 都 是 标 准

Windows 消息 标准 Windows 消息由窗口和视图处理 这类消息通常含 有用于确定如何对消息进行处理的一些参数 标准 Window s 消息都有 缺省的处理函数 这些函数在 CWnd 类中进行了预定义 MFC 类库以消息名为基础形成这些处理函数的名称 这些处理函数 的名称都以前缀“On”开始 有的处理函数不带参数 而有的则有几个参 数

有的还有除 void 以外的返回值类型 CW nd 中消息处理函数的说

明都有 afxmsg 例如 消息 WMPAINT 的处理函数 在 CWnd 中被声明 成 afxmsg void OnPaint

www.BOOKOO.com.cn

249


Visual C++6.0 编程实例与技巧

关键字 afxmsg 用于把处理函数与其他 CWnd 成员函数区分开来 这 些函数并不真正是虚拟的 而是通过消息映射实现的 消息映射仅仅依 赖于标准的预处理宏 而不是依赖于 C++语言的任何扩展 经过预处理 以后 关键字 afxmsg 的位置就变成空白 如果要覆盖 CWnd 类中已定 义的某一消息处理函数 只需用 ClassWizard 在派生类中用同样的原型 定义一个函数并为该 函数做一个消息映射条目即可 当 ClassWizard 为 某一给定的消息编写处理函数的轮廓时 将使用被覆盖成员函数推荐使 用的形式 例如 WMCREATE 消息的 OnCreate 处理函 数将首先调用 基类的处理函数 int

只有在不返回-1 的条件下才继续执行: LPCREATESTRUCT

CMyAppView::OnCreate

lpCreateStruct { if

Cview::OnCreate lpCreateStruct

==-1

return -1 //TODO 在此处添加专门的创建代码 return 0 } 标准 Windows 消息常见的有 WMCHAR 消息 鼠标消息 WMPAINT 消息 WMHSCROLL 消息 WMVSCROLL 消息和 WMTIMER 消息等 1. WMCHAR 消息 当用户按下键盘上的某一键时 WMCHAR 消息的处理函数为 OnChar

都会产生 WMCHAR 消息 函数原型为

afxmsg void OnChar UINTnChar,UINT nRepCni,UINT nFlags 其中 参数 nChar 是所按键的字符代码值 nRepCnt 表示重复次数

www.BOOKOO.com.cn

250


Visual C++6.0 编程实例与技巧

该值是用户按键时重复击键的次数 nFlags 表示扫描码 先前键状态和 键转换状态等 其含义参阅有关资料 2. 鼠标消息 在 Windows 中 鼠标输入是不可避免的 几乎所有的应用程序都要 进行鼠标操作 由于鼠标使用相当频繁 而且相当重要 因此鼠标消息 的类型也较多

主要有 WMMOUSEMOVE

移鼠标 到新的位置

WMLBUTTONDOWN

单击鼠标左按钮

WMRBUTTONDOWN

击鼠标右按钮

WMLBUTTONUP

释放鼠标左按钮

WMRBUTTONUP 击鼠标左按钮

释放鼠标右按钮

和 WMLBUTTONDBLCLK

所有鼠标消息的处理函数都有类似的原型 WMLBUTTONDOWN 消息

例如

对于

其处 理函数的原型为

afxmsg void OnLButtonDown(UINT nFlags,Cpoint point) 所有鼠标消息的处理函数都有两个参数 nFlags 和 point nFlags 参 数表示鼠标按钮的状态及鼠标事件发生时键盘上某些键的状态 每一状 态都由 nFlags 的某一位表示

详细资料请参 阅有关书籍

参数 point 是鼠标事件发生时鼠标光标的位置 位置是相对于窗口左 上角的水平 X 坐标和垂直 Y 坐标而言的 3. WMPAINT 消息 当调用成员函数 UpdateWindow 或 RedrawWindow 要求重新绘制窗 口内容时

应用程序将收到 WM PAINT 消息

Windows 之所以提供

WMPAINT 消息 是因为当窗口最小化后 再还原或被另一窗 口遮盖然 后又移开后 当前窗口的某些内容必须重新绘制 通常 Windows 并不 记录窗口中 的具体内容

因为维护窗口内容是编程人员而不是系统的 www.BOOKOO.com.cn

251


Visual C++6.0 编程实例与技巧

责任

多数情况下

应用程序比系 统更了解窗口内容

也更易于维护

窗口内容 当然系统也能帮助维护窗口内容 其方式是向 Windows 应 用程序发送 WMPAINT 消息 应用程序检索到该消息后 就需要重新显 示 窗口内容 WMPAINT 消息的处理函数为 OnPaint 该函数无参数 4. WMHSCROLL 消息 WMHSCROLL 消息是用户单击窗口的水平滚动条时产生的消息 WMHSCROLL 消息的处理函数为 OnHScroll afxmsg void OnHScroll UINT nPos

函数原型为

UINT nSBCode

CScrollBar* pScrollBar

其中 参数 nSBCode 是指示用户滚动请求的滚动条代码 其取值可 以查阅有关资料 当滚动条代码为 SBTHUMBPOSITION 和 SBTHUMBTRACK 时 nPos 用于指定滚动框的位置 否则 无意义 如果滚动消息来自滚动条 控件 则 pScrollBar 为指向控件的指 针 如果单击窗口滚动条 则该值 为 NULL 5. WMVSCROLL 消息 WMVSCROLL 消息是用户单击窗口的垂直滚动条时产生的消息 WMVSCROLL 消息的处理函数为 OnVScrol1 afxmsg void OnVScroll UINT nPos

函数原型为

UINT nSBCode

CScrollBar* pScrollBar

其中 参数 nSBCode 是指示用户滚动请求的滚动条代码 其取值可 以查阅有关资料 当滚动条代码为 SBTHUMBPOSITION 和 SBTHUMBTRACK 时 nPos 用于指定滚动框的位置 否则 无意义 如果滚动消息来自滚动条 www.BOOKOO.com.cn

252


Visual C++6.0 编程实例与技巧

控件 则 pScrollBar 为指向控件的指针 如果单击窗口滚动条 则该值 为 NULL 处 理 WMHSCROLL 和 WMVSCROLL 消 息 要 调 用 成 员 函 数 SetScrollPos 来重新设置滚动框的位置 6. WMTIMER 消息 在 Windows 中 安装有计时器时 可以使用成员函数 SetTimer 定期 向应用程序发送消息

这种消息就是 WMTIMER.

每当计时器周期被

触发时 系统就发送消息 WMTI ME R 可以使用计时器来激活某个程 序

尤其是当应用程序被作为一个任务在后台运行时 WMTIMER 消息的处理函数 OnTimer

函数原型为

afxmsg void OnTimer(UINT nIDEvent); 其中

参数 nIDEvent 用于指示计时器的标示符

5.7.3.2 控件通知 包含从控件和其他子窗口传送给父窗口的 WMCOMMAND 通知消 息 例如 当用户改变编辑控件中的文本时 编辑控件将发送给父窗口 一条含有 ENCHANGE 控件通知码的 WMCOMMAND 消息 窗口的消 息处理函数将以某种适当的方式对通知消息作出响应 象其他标准 Windows 消息一样 控件通知消息由窗口和视图处理 但是

如果用户单击按钮 控件时 发出的 BNCLICKED 控件通知消息

将作为命令消息来处理 5.7.3.3 命令消息 命令消息包含来自用户界面对象

如菜单项

工具栏按钮和加速键

等 的 WMCOMMA ND 通知消息 命令消息的处理与其他消息的处理 www.BOOKOO.com.cn

253


Visual C++6.0 编程实例与技巧

不同

命令消息可以被更广泛的对象

如文档

文档 模板

应用程序

对象 窗口和视图等 处理 如果某条命令直接影响某个特定的对象 则应 该让该对象来处理这条命令 例如 当应用程序接收到“File”菜单 中的“New”命令时

将调用 CWinApp 类的成员函数 OnFileNew 打开新

的空文档 1. 用户界面对象 菜单项

工具栏按钮和加速键都是可以产生命令的用户界面对象

每个这样的对象都有一个 ID 通过给对象和命令分配同一 ID 可以把用 户界面对象与命令联系起来

命令是被当作特殊 的消息来处理的

用户界面对象产生一条命令后

应用程序的某个对象就将处理这条命

令 2. 命令 ID 命令完全是由命令 ID 来描述的 命令 ID 分配给产生该命令的用户 界面对象 通常 命令 ID 是以其所表示的用户界面对象的功能来命名 的 例如 “Eidt”菜单中的“Copy”命令就可以 用 IDEDITCOPY 来命名 MFC 类库预定义了某些命令 ID 而其他命令 ID 则 要编程人员自己定 义

所有预定义命令 ID 的列表

可参见 AfxRes.h 文件

3. 命令目标 当用户界面对象被单击后

将调用处理函数执行所产生的命令

Windows 把不是命令消息的消息直接发送给窗口 该窗口中用于处理这 条消息的处理函数将被调用 多个候选对象

称为命令目标

但是

对于命令消 息

则把命令发送给

其中通常总有一个要引用该命令的处

理函数 处理函数处理命令的方法与处理标准 Windows 消息的方法是一 样的 但调用机制不 一样 www.BOOKOO.com.cn

254


Visual C++6.0 编程实例与技巧

4. 命令和控件通知的处理函数 命令或控件通知都没有缺省的处理函数

把命令或控件通知映射成

处理函数时 ClassWizar d 以命令 ID 或控件通知码来命名处理函数 可 以接受 修改或替换推荐使用的名字 例如

“Edit”菜单的“Cut”命令

其命令 ID 被预定义成 IDEDITCUT 处理函 数被命名为 afxmsg void OnEditCut 此外

对于缺省按钮的 BNCLICKED 通知消息 其处理函数可以命

名成 afxmsg void OnClickedUseAsDefault 命令和控件通知的消息处理函数都没有参数

也不返回值

5.7.4 发递和接收消息 多数消息都是用户与应用程序的相互作用而产生的 当产生消息时 CWinApp 的成员函 数 Run 用于检索消息 并将消息发送给适当的窗口 接收消息的必须是一个窗口对象 标准 W indows 消息通常都由窗口对 象直接处理

而命令消息

通常由应用程序的主边框窗口产生

则被

发送给命令目标链 每个可以接收消息或命令的对象都有自己的消息映射 或命令与其处理函数 名联系在一起

用于将消息

当命令目标对象接收到消息或命

令后 将搜索消息映射 寻找匹配的处理函 数 如果找到了处理函数 就调用该处理函数 1. 非命令消息与处理函数 与命令不同 标准 Windows 消息不是通过命令目标链发送 而是由 Windows 给其发送消息的那个窗口处理

该窗口可能是主边框窗口

www.BOOKOO.com.cn

255


Visual C++6.0 编程实例与技巧

MDI 子窗口 标准控件 对话框 视图或其他类型 的子窗口 在运行 时 每个 Windows 窗口都与窗口对象 从 CWnd 直接或间接派生 联 系在一起 每个窗口对象都有自己的消息映射和处理函数 框架利用消 息映射把接收到的消息与处 理函数进行匹配 2. 命令消息的发送 编程人员除了要建立命令与处理函数间的消息映

处理命令消息时

射关系外 还必须编写大部分的命令处理函数 对于命令消息

框架把命令发送给标准命令目标对象链

其中之一

会有该命令的处理函数 每个命令目标对象都将检查自己的消息映射 看看能否处理相应的消息 不同的命令目标类检查消息映射的时机是不同的

通常

每个命令

目标类先把命令发送给某 些其他对象 给予其他对象先行处理的机会 如果这些对象都不能处理该命令 果也不能提供处理函数

则起始类检 查自己的消息映射

则把该命令发送给更多的命令目标

命令目标链发送命令的一般顺序为 标对象

然后发送给自己

先发送给当前活动的子命令目

最后再发送给其他命令目标

例如 在 MDI 应用程序中 选择“Edit”菜单的“Clear All”命令时 将 产生一条命令消息 假定命令处理函数是应用程序文档类的成员函数 那么该命令的发送顺序为 首先边框窗 口收到命令消息 接着 MDI 主 边框窗口给当前活动的 MDI 子窗口处理该命令的机会 MDI 子窗 口在 检查自己的消息映射前 会

按标准的发送顺序给其视图处理该命令的机

视图检查其消 息映射

给与其相连的文档

如果没有找到处理函数

文档检查其消息映射

再把该命令发送

找到该命令的处理函数并

调用 发送过程结束 www.BOOKOO.com.cn

256


Visual C++6.0 编程实例与技巧

如果文档没有处理函数

就再把该命令发送给文档模板

然后返回

到视图 再返回边框窗口 最后边框窗口检查其消息映射 如果还找不 到处理函数 则该命令将被回送到 MDI 主边框 窗口 再到应用程序对 象 在命令发送过程中

每个命令目标都要调用序列中下一命令目标的

OnCmdMsg 成员函数 命令 目标通过 OnCmdMsg 成员函数确定是否可 以处理某条命令

如果不处理就将其发送给另一命令 目标

每个命令目标类都可以覆盖 OnCmdMsg 成员函数 这样就可以让每 个命令目标类都能把命令发 送给下一特殊的目标 CCmdTarget 类的成 员函数 OnCmdMsg 的缺省实现是 使用命令目标类的 消息映射为其所 收到的每条命令搜索处理函数 如果找到匹配的 就调用该处理函数 5.7.5 如何搜索消息映射 用 MFC AppWizard 创建新的应用程序框架后 MFC AppWizard 将为 创建的每个命令目标类 包括派生的应用程序对象 文档 视图和边框 窗口等

编写一个消息映射

每个命令目标类的 消息映射存放在相应

的.CPP 文件中 可以在 AppWizard 创建的基本消息映射的基础上 使 用 C lassWizard 为每个类将要处理的消息和命令添加一些条目 例如 对于应用程序类

MFC Ap pWizard 创建的基本消息映射为

BEGINMESSAGEMAP(CMyAppApp, CWinApp) //{{AFXMSGMAP(CMyAppApp) ONCOMMAND(IDAPPABOUT, OnAppAbout) // NOTE - the ClassWizard will add and remove mapping macros here. //DO NOT EDIT what you see in these blocks of generated code! www.BOOKOO.com.cn

257


Visual C++6.0 编程实例与技巧

//}}AFXMSGMAP // Standard file based document commands ONCOMMAND(IDFILENEW, CWinApp::OnFileNew) ONCOMMAND(IDFILEOPEN, CWinApp::OnFileOpen) // Standard print setup command ONCOMMAND(IDFILEPRINTSETUP,

CWinApp::O

nFilePrintSetup) ENDMESSAGEMAP() 又如

以下是视图类的消息映射在添加某些条目后的结果

BEGINMESSAGEMAP(CMyAppView, CView) //{{AFXMSGMAP(CMyAppView) // NOTE - the ClassWizard will add and remove mapping macros here. //DO NOT EDIT what you see in these blocks of generated code! //}}AFXMSGMAP // Standard printing commands ONCOMMAND(IDEDITCLEARALL, OnEditClearA ll) ONWMMOUSEACTIVATE() ONUPDATECOMMANDUI(IDEDITCLEAR

ALL,

OnUpdateEditClearAll) ENDMESSAGEMAP() 每个命令目标类的消息映射都由一组宏组成

其中

BEGINMESSAGEMA P 和 ENDM ESSAGEMAP 用于将消息映射括起 来

其他宏

如 ONCOMMAND 则包含有消 息映射的内容

应注意的是消息映射宏后面不能带有分号

此外

消息映射还包含

以下形式的注释

www.BOOKOO.com.cn

258


Visual C++6.0 编程实例与技巧

//{{AFXMSGMAP(CMyAppView) //}}AFXMSGMAP 这些注释与普通编程中的注释性质不同

它们用于把许多映射条目

括起来 ClassWizard 在编写映射条目时将使用这些注释,所以千万不要 删 掉它们

所有 ClassWizard 映射条 目都位于 注释行 之间

当用

C1assWizard 创建新类时 将为该类提供一个消息映射 1. 消息映射的继承关系 在消息处理过程中

检查每个命令目标类自己的消息映射并不是消

息映射的最后一步 例如

CMyAppView 类的基类是 CView 类

该类

是从 CWnd 类派生的 这样 CMyAppView 是一个 CView ,也是一个 CWnd 从而每个 CMyAppView 对象都是将拥有所有这些类的消息映射 因此 如果消息在 CMyAppView 类的消息映射中找不到匹配 则还 消息映射开始的

要搜索该类的直接基类的消息映射 BEGINMESSAGEMAP 宏 其参数是两个类名

BEGINMESSAGEMAP(CmyAppView Cview) 第一个参数指出该消息映射所属类的名称

第二个参数指出所属类

的直接基类 因此 基类中的消息处理函数就被派生类继承了 如果在 所有基类的消息映射中都找不到处理函数

则执行缺省的消息处理函

数 如果消息是命令消息 就将其发送给下一命令目标 如果是标准 的 Windows 消息 就将其传给适当的缺省窗口函数 2.消息映射的结构 在源文件中

每个消息映射都由一组预定义的宏组成

这些在消息

映射内部的宏称为映射宏 消息映射中使用的映射宏依所处理消息的类 型而定

表 5.2 总结了各种类型的映射宏 每个映射条目都由一个映射

www.BOOKOO.com.cn

259


Visual C++6.0 编程实例与技巧

宏组成

映射宏中可以没有参数或者可以有多个参数 表 5.2 消息映射的预定义映射宏

消 息 类 型

宏 格 式

参数 预定义 Windows 消息

ONWMXXXX

命令

ON0000COMMAND

无参

数 命

令 ID 处理函数名 更新命令

ONUPDATECOMMANDUI

命令

ID 处理函数名 控件通知

ONXXXX

ONMESSAGE

自定

ONREGISTEREDMESSAGE

消息

ONCOMMANDRANGE

连续

件 ID 处理函数名 用户自定义消息 义消息 ID 处理函数名 已注册 Windows 消息 ID 处理函数名 命令 ID 范围 范围内命令 ID 的开始和结束 将更新的命令 ID 范围

ONUPDATECOMMANDUIRAN GE 连续

范围内命令 ID 的开始和结束 控件 ID 的范围

ONCONTROLRANGE

控件通

知码和连续范围内命令 ID 的开始和结束 其中 XXXX 表示的名称是以 Windows 标准消息名称或控件通知码 为基础的

例如

ONW MPAINT

ONWMLBUTTONDOWN 和

www.BOOKOO.com.cn

260


Visual C++6.0 编程实例与技巧

ONENCHANGE 等 尽管 ONWMXXXX 宏没有参数 但对应的处理函 数通常是有参数的 3.消息映射的范围 可以把某一范围内的消息映射成单个的消息处理函数

例如

把某

一范围内的命令 ID 映射成一个命令更新处理函数 使得所有命令一起 有效或一起无效 在 ClassWizard 中 不支持消息映射范围 必须自己 编写这些消息映射条目 5.7.6 用户界面对象的更新 通常

菜单项和工具栏按钮都有不止一种状态

是不可用的

显示为灰色

被选中的或未被选中的

例如

菜单项可以

工具栏按钮可以

是不可用的或被选中的 如果应用程序的条件发生变化 呢

那么如何更新这些用户界面对象

例如 当用户首次打开包含“ClearAll”命令的弹出式菜单 “Edit”菜

单 时 将发出一条更新命令 ONUPDATECOMMANDUI,由于该消息是 在弹出式菜单可见之前被发送的

因此要调用更新命令处理函数

OnUpdateEditClearAll 来初始化命令 以便根据应用程序的当前 状态使 命令有效或无效

www.BOOKOO.com.cn

261


Visual C++6.0 编程实例与技巧

第六章 对话框 对话框用于显示和获取用户输入

控件和控件栏 对话框通过一个或多个控件

按钮 列表框 组合框和编辑框等 与用户进行交互 控件是一种特定 类型的输入或输入窗口 通常为其父窗口 如对话框 边框窗口 视图 窗口或控件栏等 控件栏

所拥有

如工具栏

状态栏和对话栏

是包含按钮

编辑框

复选

框或其他控件的窗口 控 件栏通常放置在边框窗口的顶部或底部 6.1 对话框 在 Windows 中 对话框是应用程序与用户交互的主要途径 Developer Studio 的对话编辑器使得对话框的设计以及对话模板资源的创建更为容 易 而 C1assWizard 则简化了对话框控件 的初始化和验证以及获取用户 输入的过程 在 MFC 类库中 对话框由 CDialog 类管理 6.1.1 对话框的组成 对话框主要由以下两部分组成 (1) 对话模板资源 对话模板资源用于定义对话框控件及其分布 对 话资源中保存有用于创建并显示对话窗口的对话模板 对话模板定义对 话框的特性 如大小 位置和风格等 以及对话框控件的类型和位置 除了直接使用作为资源保存的对话模板外

还可以根据需要 在内存中

创建对话模板 (2) 对话类 从 CDia1og 派生的对话类提供编程接口来管理对话框 每个对话框是一个窗口

对话窗口创建后

将自动根据对话模板资

源来创建对话框控件 www.BOOKOO.com.cn

262


Visual C++6.0 编程实例与技巧

6.1.2 模态和非模态对话框 由 CDialog 类管理的对话框有两种类型 即模态对话框和非模态对 话框 模态对话框是最常用的对话框 要求用户在应用程序继续执行之 前作出响应

即模态对话框不允许在未收到 响应之前就进入应用程序

的其他部分

非模态对话框允许用户为对话框提供信息

而无需删 除

该对话框就可以回到前面的任务 即非模态对话框不阻止应用程序的运 行

在输入转到应 用程序的其他部分之前

并不需要作出响应

创建

对话模板时的资源编辑和 ClassWizad 过程 对模态对话框与非模态对话 框都是一样的 创建应用程序对话框的主要过程为 1

用对话编辑器设计对话框并创建相应的对话模板资源

2

用 ClassWizard 创建对话类

3

把对话资源的控件与对话类中的消息处理函数链接起来

4 用 ClassWizard 添加与对话控件有关的成员变量并为控件指定 对话数据交换

DDX

和对话数据验证 DDV

6.1.3 创建对话资源 可以使用 Developer Studio 的对话编辑器来设计对话框并创建对话 资源 在对话编辑器中 可以调整对话框显示时的大小和位置 从控件 工具栏拖放各种类型的控件到对话框中 置

测试对话框的外观和行为

单击按钮等来操纵对话框中的控件

用对话工具栏调整控件的位

测试对话框时

对话模板资源保存在应用程序的资源文件中

可以在文 本框输入文

如果需要

可以对其

进行编辑修改

www.BOOKOO.com.cn

263


Visual C++6.0 编程实例与技巧

用同样的方法也可以创建 CFormView 和 CRecordView 类的对话模板 资源 6.1.4 用 ClassWizard 创建对话类 创建对话模板资源后 接着可以用 ClassWizard 创建对话类并映射消 息

C1assWizard 可以帮助用户管理以下与对话框有关的任务 1

从 CDialog 类派生新的对话类来管理对话框

2

映射 Windows 消息到对话类

3

声明对话类的成员变量来表示对话框中的控件

4

指定控件和成员变量之间如何交换数据

对于应用程序中的每个对话框

都要创建新的对话类来管理对话资

源 用 ClassWizard 创建对话类时 将在应用程序指定的.H 和.CPP 文件 中添加以下内容 1 在.H 文件中添加对话类的类声明 该类是从 CDialog 类派生的 2

在.CPP 文件中添加类的消息映射

对话框的标准构造函数和

成员函数 Do DataExch ange 的覆盖函数 成员函数 DoDataExchange 用 于实现对话数据交换和验证功能 6.1.5 创建并显示对话框 对话框的生命周期可以概括为

通过用户界面对象启动对话框

命令处理函数内部创建和初始化对话对象 与对话框交互 关闭对话框 对话对象的创建分为两步

首先构造对话对象

然后再创建对话窗

口 模态对话框和非模态对话框在创建和显示过程中略微有所不同 1. 创建模态对话框

www.BOOKOO.com.cn

264


Visual C++6.0 编程实例与技巧

创建模态对话框时 首先要调用两个 CDiaLog 公有构造函数中的任 何一个来构造对话对象 然后再调用对话对象的成员函数 DoModal 装载 对话资源 显示对话框 并管理与对话对 象的交互直到用户选择“OK” 或“Cancel”按钮 2. 创建非模态对话框 对于非模态对话框

必须在对话类中编写自己的公有构造函数

将调 用自己的公有构造函数

建非模态对话框时

然后再调用对话对

象的成员函数 Create 装载对话资源 可以在构造 函数的调用期间或调 用之后调用 Create 如果对话资源具有 WSVISIBLE 属性 对话 框将立 即显示

如果没有

必须调用成员函数 ShowWindow 来显示对话框

3.使用内存中的对话模板 此外

还可以使用内存中的对话模板间接创建对话框 方法为

1 使用 DLGTEMPLATE 结构定义对话框的大小和风格 如果对 话框包含控件 还要使用 DLGI TEMTEMPLATE 结构定义对话框中每 个控件的大小和风格 2 在构造 CDialog 对象后 调用 CDialog 成员函数 CreateIndirect 创建非模态对话框或调用 InitModalIndirect 与 DoModal 创建模态对话 框 6.1.6 设置对话框的背景颜色 在应用程序类的成员函数 InitInstance 中调用 CWinApp 的成员函数 SetDialogBkColor 来设置对话框的背景颜色 所设置的颜色将适用于所 有的对话框和消息框 SetDialogBkColor 函数 原型为 void SetDialogBkColor COLORREF clrCtlBk=RGB 192 192 192

www.BOOKOO.com.cn

265


Visual C++6.0 编程实例与技巧

COLORREF clrCtlText=RCB 0

0

0

其中 参数 clrCtlBk 用于指定对话框的背景颜色 clrCtlText 用于指 定对话框中控件的颜色 6.1.7 初始化对话框 在创建对话框及其所有的控件后

就在对话框即将显示之前将调用

对话对象的成员函数 OnIn itDialog 来初始化对话框 例如 设置编辑框 的初始文本等 对于模态对话框 在调用 Do Modal 时调用 OnInitDialog 而对于非模态对话框

在调用 Create 时调用 OnInitDia1og

通常必须在 CDialog 派生类中覆盖成员函数 OnInitDia1og 此外 必 须从覆盖函数中调用基 类 CDialog 的成员函数 OnInitDia1og 该函数返 回 TRUE 表示焦点被设置在对话框的第一个控 件中 6.1.8 处理 Windows 消息 对话框是一种窗口 可以处理各种 Windows 消息 如果要对话框处 理 Windows 消息

就必须覆 盖适当的消息处理函数

首先

使用

C1assWizard 把消息映射到对话类中 以便为每条消息 编写消息映射条 目并给对话类添加相应的消息处理函数 然后 再编写消息处理函数中 的代 码

此外 在 CDialog 派生类中 通常必须覆盖以下虚拟成员函

数 1

响应消息 WMINITDIALOG 初始化对话框时 覆盖成员函数

OnInitDialog 2 单击“OK”按钮响应控件通知消息 BNCLICKED 时

覆盖成员

函数 OnOK

www.BOOKOO.com.cn

266


Visual C++6.0 编程实例与技巧

3 单击“Cancel”按钮响应控件通知消息 BNCLICKED 时 覆盖成 员函数 OnCance l 如果对话框中的按钮不仅仅是“OK”或“Cancel”按钮

则在对话类中

还必须编写消息处理 函数 以便响应由其他按钮产生的控件通知消息 6.1.9 对话数据交换和验证 对话数据交换 DDX 用于初始化对话框中的控件并获取用户的数 据输入 而对话数据验证 DDV 则用于自动验证对话框中的数据输入 要在对话框中使用 DDX 和 DDV 必须用 ClassWi zard 创建数据成员 设置数据类型并指定验证规则 1.数据交换 如果使用 DDX 机制 通常在 OnInitDia1og 函数或对话对象的构造函 数中设置对话对象的数据成员的初值

在即将显示对话框之前

机制把成员变量的值传给对话框中的控件

DDX

当对话框在响应 DoModal

或 Create 而被显示时 将在对话控件中显示这些数据 CDialog 的成员 函 数 OnInitDia1og 缺省时调用 CWnd 类的成员函数 UpdateData 来初始 化对话框控件 当用户单击“OK”按钮或以 TRUE 为参数调用成员函数 UpdateData 时

DDX 机制将把值从对话框控件中传给数据成员 DDV 机制将对指

定有验证规则的所有数据项进行验证 函数 U pdateData 的原型为 BOOL UpdateData BOOL bSaveAndValidate 如果参数 bSaveAndValidate 为 FALSE

TRUE

则初始化对话框

如果为

TRUE 则获取并验证对话数 据 CDialog:: OnInitDia1og 的缺省实现是 以 FALSE 为参数调用 UpdateData 而 Cdialog::On Ok 的缺省实现是以

www.BOOKOO.com.cn

267


Visual C++6.0 编程实例与技巧

TRUE 为参数调用 UpdateData 执行交换任务时

UpdateData 建立 CDataExchange 对象并调用

CDia1og 派 生 类 的 覆 盖 成 员 函 数

DoDataExchange

函 数

DoDataExchange 的参数为指向 CDataExchange 的指针 覆盖成员函数 D oDataExchange 时 要为每个数据成员 控件 指定对 DDX 函数的一次 调用 根据 UpdateData 传给 DoDataExchange 的 CDataExchange 参数 每个 DDX 函数都知道如何交换数据 以下是在对话类中覆盖成员函数 DoDataExchange 的一个例子 void CpenDialog::DoDataExchange(CDataExchange* pDX) { Cdialog::DoDataExchange(pDX); {{AFXDATAMAP(CpenDialg) DDXControl(pDX,IDCWIDTH,mpWidthEdit); DDXText(pDX,IDCWIDTH,mpWidth); DDVMinMaxInt(pDX,mpWidth,1,6); DDXRadio(pDX,IDCSOLID,mpStyle); }}AFXDATAMAP } 在

{{AFXDATAMAP 与

AFXDATAMAP 定界符之间 的

DDX 和 DDV 行是对话框的数据映射 2.数据验证 除了指定数据交换外 还可以调用 DDV 函数指定数据验证 前面例 子中的 DDVMinMa xInt 调用将验证在编辑框控件中所输入的整数大于 0 小于 7 如果验证失败 DDV 函数将显示消息框提示用户 并把焦点

www.BOOKOO.com.cn

268


Visual C++6.0 编程实例与技巧

放在违反规则的控件中以便用户重新输入数据 给定控件的 DDV 函数 调用 必须紧接在同一控件的 DDX 函数调用之后 必须使用 ClassWizard 编写数据映射中的所有 DDX 和 DDV 调用 不要人工编辑定界符之间的数 据映射行 6.1.10 对话框控件的类型无关访问 对话框中的控件可以使用 MFC 控件类 如 CListBox 和 CEdit 接口 进行访问 可以创建控件对象并将其链接到对话控件上 然后通过控件 类接口对该控件进行访问 调用成员函数对其进 行操作 链接对话框中的控件和 CDialog 派生类中的控件成员变量主要有以 下两种方法 1. 使用内联成员函数 第一种方法是使用内联成员函数

调 用 CWnd 的 成 员 函 数

GetDlgItem 返回一个指向给定控件的临时对象的指针 并将返回值类型 强制转换成适当的 C++控件类型 例如 在 CDialog 派生类中声明内联成员函数 Cbutton* GetMyCheckbox return 接着

Cbutton*

GetDlgItem IDCCHECKBOXI

调用内联成员函数对控件进行类型无关的访问 例如

GetMyCheckbox

—>SetState TRUE

2. 使用 ClassWizard 如果只是简单地访问某一控件的值 则使用 I DX 机制即可 如果 不仅仅是访问某一控件的值 则可以用 ClassWizard 给对话类添加适当 的成员变量 并把该变量链接到 Control 属性即 可

www.BOOKOO.com.cn

269


Visual C++6.0 编程实例与技巧

对话类的成员变量可以拥有 Control 属性 而不是 Value 属性 Value 属性指的是从控件所返回数据的类型 如 CString 或 int 允许通过某一数据成员对控件进行直接访 问 MFC 控件类 如 CButton 或 CEdit

Control 属性

数据成员的类型必须是

之一 对于某一给定的控件 可以

有多个具有 Value 属性的成员变量 但最多只能有一个具有 Control 属性 的成员变量 具有 Control 属性的成员变量也称为成员对象 可以使用成员对象调 用控件的任何成员变量 例如

对于由 mCheckboxDefault 变量表示的

CButton 类型的复选框控件

可以调 用 mChe ckboxDefault.SetState

TRUE 来设置控件的状态 成员对象 mCheckboxDefault 的作用与 前 面 GetMyCheckbox 内联成员函数的作用一样 如果复选框不是自动复 选框

则对话类中还 需要一个消息处理函数

用于在按钮被单击时处

理 BNCLICKED 控件通知消息 6.1.11 关闭对话框 对于模态对话框

选择“OK”按钮或“Cancel”按钮关闭对话框时

Windows 将给对话对象发送 BNCLICKED 控件通知消息和按钮 ID 即 IDOK 或 IDCANCEL

CDialog 类提供有 缺省处理 函数 OnOK 和

OnCance1 缺省处理函数将调用 CDialog 的成员函数 EndDia1og 关闭对 话窗口

此外 还可以直接从代码中调用成员函数 EndDialog 在对话

类中覆盖成员函数 OnOk 时 要从 覆盖函数中调用基类的缺省处理函 数以便调用 EndDialog 或者直接调用 EndDialog 对于非模态对话框

关闭和删除时缺省处理函数 OnClose 将调用

DestroyWindow 删除对话窗口 如果对话框是独立的 则应该覆盖函数

www.BOOKOO.com.cn

270


Visual C++6.0 编程实例与技巧

PostNcDestroy 并针对 this 指针调用 delete 操作符 以销毁对话对象 还 应该覆盖函数 OnCancel 并从中调用 DestroyWindows 如果对话框不是 独 立的

则对话框的所有者将销毁对话对象

6.1.12 管理对话框的 MFC 函数 对话类是从 CDialog 类派生的 CDialog 类提供有管理对话框的成员 函数

可以在对话类的消息处理函数中调用这些成员函数 1

函数 EndDialog 用于关闭模态对话框 函数原型为

void EndDialog

int nResult

函数返回 nResult 作为 DoModal 的返回值 2 函数 GetDefID 返回对话框中缺省按钮的控件 ID 通常为“OK” 按钮

函数原型为 DWORD GetDefID

如果缺省按钮有 ID 值

则返回值的高位字节为 DCHASDEFID 低

位字节为按钮的控 件 ID 如果缺省按钮没有 ID 值

则返回 0

3 函数 GotoDlgCtrl 移输入焦点到对话框的指定控件中 函数原 型为 void GotoDlgCtrl

CWnd* pWndCtrl

参数 pWndCtrl 标识接收输入焦点的控件 可以通过调用 CWnd 的成 员函数 GetDlgItem 得到指向指定控件的指针 4 函数 MapDialogRect 转换对话框单位为屏幕单位 函数原型为 void MapDia1ogRect

LPRECT 1pRect

参数 1pRect 指向包含要转换的对话框坐标的 RECT 结构或 CRect 对 象

www.BOOKOO.com.cn

271


Visual C++6.0 编程实例与技巧

5 函数 NextDlgCtrl 移输入焦点到对话框中的下一控件上 如果 输入焦点在对话框的最后一个控件上 则移到第一个控件 函数原型为 void NextDlgCtrl 6 函数 PrevDlgCtrl 移输入焦点到对话框中的上一控件上 如果 输入焦点在对话框的第一个控件上 则移到最后一个控件 函数原型为 void PrevDlgCtrl 7

函数 SetDefID 更改对话框的缺省按钮控件 函数原型为

void SetDefID UINT nID 参数 nID 指定要设置为缺省按钮的控件 6.2 通用对话类 通用对话框是系统定义的对话框 标准操作(如选择文件名

指定字体

可以使用通用对话框来执行各种 选择颜色等)

几个从 CDialog 派生的通用对话类(如表 6.1 所列) Windows 通用对话框

MFC 类库提供有 这些类封装了

相应的对话模板资源和代码在通用对话框中提

供 表 6.1 通用对话类 通用对话类

说明

CcolorDialog

选择颜色 CfontDialog 指定字体

CfileDialog

选择要打开或保存的文件 CprintDialog

指定与打印有关的信息 CfindReplaceDialog

在文本文件中查找和替换

www.BOOKOO.com.cn

272


Visual C++6.0 编程实例与技巧

6.2.1 CFontDialog 类 CFontDialog 类 允 许 用 户 在 应 用 程 序 中 嵌 入 字 体 对 话 框 CFontDialog 类对象是一个对话框

每个

可以从系统安装的字体列表中选择

某一特定的字体 CFontDialog 类的使用过程为 (1)构造 CFontDialog 类对象 例如 CFontDialog fdFontDialog 此外

声明 CFontDialog 对象

还可以从 CFontDialog 派生子类并使用子类的构造函数

(2)设置或修改对象的数据成员 mcf 以便初始化对话框中控件的值或 状态

mcf 结构类型为 CHOOSEFONT

(3)调用成员函数 DoModal 显示对话框并让用户从中指定字体 如果 选择“OK”按钮 DoMo dal 返回 IDOK 如果选择“Cancel”按钮 DoModal 返回 IDCANCEL 取消用户输入 例如 显示字体对话框让用户指定 如果返回值不是 IDOK 则退出 if(fdFontDialog.DoModal()!=IDOK ) return; (4)如果 DoModal 返回的是 IDOK 系统将用户输入信息保存在数据 成员 mcf 中 使用以下 CFontDialog 的成员函数可以获取用户输入信息 函数 GetColor 得到选择字体的颜色 函数 GetCurrentFont 得到当前选择的字体 函数 GetFaceName 得到选择字体的字体名字 函数 GetStyleName 得到选择字体的风格名字 函数 GetSize 返回选择字体的点大小 函数 GeiWeight 返回选择字体的磅数 www.BOOKOO.com.cn

273


Visual C++6.0 编程实例与技巧

函数 IsStrikeOut 确定字体是否带删除线 函数 IsUnderLine 确定字体是否带下划线 函数 IsBold 确定字体是否为粗体 函数 IsItalic 确定字体是否为斜体 6.2.2 CColorDialog 类 CCo1orDialog 类允许用户在应用程序中嵌入颜色对话框

每个

CColorDialog 类对象是一个对话框 可以从系统定义的颜色列表中选择 某一特定的颜色

CColorDialog 类的使用过程为

(1)构造 CColorDialog 类对象 (2)设置或修改对象的数据成员 mcc 以便初始化对话框中控件的值 mc c 结构类型为 CHOOSECOLOR (3)调用成员函数 DoModal 显示对话框并让用户从中选择颜色 如果 选择“OK”按钮 DoMo dal 返回 IDOK 如果选择“Cancel”按钮 DoModal 返回 IDCANCEL 取消用户输入 (4)如果 DoModal 返回的是 IDOK 系统将用户输入信息保存在数据 成员 mcc 中 使用 CColorDialog 的成员函数可以获取用户输入信息 函数 GetColor 返回包含选择颜色值的 COLORREF 结构 函数 GetSavedCustomColors 获取用户创建的定制颜色 函数 SetCurrentColor 设置当前选择颜色为参数指定的颜色 函数 GetStyleName 得到选择字体的风格名字 函数 OnColorOk 用于验证输入到对话框中的颜色 可以在派生的 子类中覆盖此函数

www.BOOKOO.com.cn

274


Visual C++6.0 编程实例与技巧

6.2.3 CFileDialog 类 CFileDialog 类允许用户在应用程序中嵌入通用文件对话框 使用通 用文件对话框可以实现与 Windows 标准一致的“Open

对话框”和“Save

As”对话框 CFileDialog 类的使用 过程为 (1)构造 CFileDialog 类对象

如果传给构造函数的第一个参数设为

TRUE 则构造 “Op en”对话框 如果设为 FALSE 则构造“Save As” 对话框 (2)设置或修改对象的数据成员 mofn 以便初始化对话框中控件的值 m ofn 结构类型为 OPENFILENAME (3)调用成员函数 DoModal 显示对话框并允许用户输入路径和文件 如果选择“OK”按钮

DoModal 返回 IDOK

如果选择“Cancel”按钮

DoModal 返回 IDCANCEL 取消用户输入 (4)如果 DoModal 返回的是 IDOK 系统将用户输入信息保存在数据 成员 mofn 中 使用 CFileDialog 的成员函数可以获取用户输入信息 函数 GetFileName 返回选择文件的文件名 函数 GetPathNanle 返回选择文件的全路径 函数 GetFileExt 返回选择文件的扩展名 函数 GetNextPathName 返回下一选择文件的全路径 函数 GetReadOn1ypref 返回选择文件的只读状态 函数 GetStartPosition 返回文件列表的第一个元素的位置 6.3 控件 控件是一种特定类型的输入或输入窗口

通常为其父窗口(如对话

框 边框窗口 视图窗口或控件栏等)所拥有 可以在对话框或窗口中直

www.BOOKOO.com.cn

275


Visual C++6.0 编程实例与技巧

接创建控件对象

还可以从对话模板资源 中创建对话框中的控件

通过 Visual C++6.0 可以创建或者使用的控件有 Windows 标准控件 (如编辑框 按 钮 列表框

组合框等) Windows 公用控件 MFC 附

加的控件类(如位图按钮)和 ActiveX 控件等 6.3.1 标准控件 MFC 类库中提供有与 Windows 标准控件相对应的一组控件类 表 6.2 列出了这些及对应的标 准控件 表 6.2 标准控件类 控件类

Windows 标准控件

Cstate

静态文本

Cbutton

按钮控件(如按钮

复选框

单选

钮或成组框控件) ClistBox

列表框控件

CcomboBox

组合框控件

Cedit

编辑控件

CscrollBar

滚动条控件

每个控件类封装一个 Windows 标准控件并提供有相应的成员函数来 管理控件 使用控件对象的成员函数可以获取和设置控件的值或状态 并响应由该控件传送给父窗口的各种消息

所 有的控件类对象部分两

步创建 首先 调用构造函数构造控件类对象 然后 调用控件类的 成 员函数 Create 创建控件并将其与控件类对象链接 调用成员函数 Create 时 可以指定控件 类对象的风格 1. 静态文本控件

www.BOOKOO.com.cn

276


Visual C++6.0 编程实例与技巧

静态文本控件用于显示文本串 文件

矩形

图标

光标

位图或元

静态文本控件通常不接收输入也不提供输出

但是当以

SSNOTIFY 风格创建时将传送鼠标单击消息给父窗口 用于操作静态文本控件的 CStatic 成员函数主要有 函数 SetBitmap 指定静态文本控件中要显示的位图 函数 GetBitmap 获取由 SetBitmap 设置的位图句柄 函数 SetIcon 指定静态文本控件中要显示的图标 函数 GetIcon 获取由 SetIcon 设置的图标句柄 函数 SetCursor 指定静态文本控件中要显示的光标 函数 GetCursor 获取由 SetCursor 设置的光标句柄 函数 SetEnhMetaFile 指定静态文本控件中要显示的元文件 函数 GetEnhMetaFile 获取由 SetEnhMetaFi1e 设置的元文件句柄 2. 按钮控件 按钮控件是一种矩形子窗口

通过单击或双击可以执行某一任务

按钮可以单独出现 也可以成组出现 典型的按钮控件有复选框 单选 钮和按钮(pushbutton) 在调用成员函数 Cr eate 初始化按钮对象时 通 过指定按钮风格可以确定要创建的按钮类型 如果要处理按钮控件传送给父窗口的控件通知消息

则必须为每条

消息添加消息映射条目和消息处理函数 单击按钮时 将产生控件通知 消 息 BNCLICKED

双击按钮时

将产

生控件通知消息

BNDOUBLECLICKED 用于操作按钮控件的 CButton 成员函数主要有 函数 GetState 获取单选钮或复选框的状态 函数 SetState 设置按钮控件的高亮状态 高亮状态影响按钮控件 www.BOOKOO.com.cn

277


Visual C++6.0 编程实例与技巧

的外观

不影响单选钮或复选框的选中状态 函数 SetCheck 设置单选钮或复选框的选中状态 参数为 0 设置

状态为未选中 为 1 设置状态为选中 为 2

设置状态为不确定(按钮

有 BS3STATE 或 BSAUTO3ST ATE 风格) 函数 GetCheck 获取按钮控件的选中状态 返回值为 0 未选中 为 1

状态为

状态为选中 为 2,状态为来确定(按钮有 BS3STATE 或

BSAUTO3STATE 风格) 函数 GetButtonStyle 返回按钮对象的 BS 风格值 函数 SetButtonStyle 更改按钮对象的风格值 函数 SetIcon 指定按钮上显示的图标 函数 GetIcon 返回由 SetIcon 设置的图标句柄 函数 SetBitmap 指定按钮上显示的位图 函数 GetBitmap 返回由 SetBitmap 设置的位图句柄 函数 SetCursor 指定按钮上显示的光标 函数 GetCursor 返回由 SetCursor 设置的光标句柄 3. 列表框控件 列表框控件用于显示项目列表(如文件名) 用户可以查看和选择 在 单选择列表框中只 能选择一项

在多选择列表框中

围 列表项选中时将被高亮并传送控件

可以选择项目范

通知消息给父窗口

如果要处理列表框控件传送给父窗口的控件通知消息 条消息添加消息映射条目 和消息处理函数

则必须为每

与列表框有关的常见消息

有 LBNDBLCLK 双击列表项 仅当列表框有 LBSNOTIFY 风格时 传送此消息 www.BOOKOO.com.cn

278


Visual C++6.0 编程实例与技巧

LBNERRSPACE 列表框不能分配足够的内存 LBNKILLFOCUS

列表框失去输入焦点

LBNSETFOCUS

列表框接收输入焦点

LBNSETCANCEL

取消当前列表框选择

仅当列表框有

LBSNOTIFY 风格 时传送此消息 LBNSELCHANGE

列表框选择即将更改 如果列表框选择是

由 CListBox 的成员 函数 SetCu rSel 更改的 则不传送此消息 仅当列 表框有 LBSNOTIFY 风格时传送此消息 用于操作列表框控件的 CListBox 成员函数主要有 函数 GetCount 返回列表框中列表项的数目 函数 GetHorizontaIExtent 返回列表框的可滚动宽度 函数 SetHorizontalExtent 设置列表框的水平滚动宽度 函数 GetTopIndex 返回列表框中第一个可见项的索引 初始时为 0

表示列表框的第一项

列表框滚动后 第一个可见项为其他项

函数 SetTopIndex 指定特定的列表项为可见的 函数 GetItemData 返回与指定列表项有关的 32 位值 函数 SetItemDataPtr 设置指向列表项的指针 函数 GetItemRect 返回列表框中当前显示列表项的边界矩形区 域 函数 ItemFromPoint 确定与指定点最近的列表项 函数 SetItemHeight 设置列表项的高度 函数 GettemHeight 确定列表项的高度 函数 GetSel 返回指定列表项的选择状态 函数 GetText 复制列表项到缓冲区 www.BOOKOO.com.cn

279


Visual C++6.0 编程实例与技巧

函数 GetTextLen 返回列表项的字节数 函数 SetColumnWidth 设置多栏列表框的栏宽 函数 SetTabStops 设置列表框中制表符的位置 函数 AddString 添加字符串到列表框中 函数 DeleteString 从列表框中删除字符串 函数 InsertString 在列表框的指定位置插入字符串 函数 ResetContent 从列表框中清除所有入口 函数 Dir 从当前目录添加文件名到列表框 函数 FindString 搜索列表框中的指定字符串 函数 FindStringExact 搜索列表框中第一个与指定字符串匹配的 字符串 函数 SelectString 搜索并选择单选择列表框中的指定字符串 此外 与单选择列表框有关的函数有 GetCurSet 和 SetCurSe1 与多 选 择 列 表 框 有 关 的 函 数 Se tSe1 GetSelCount

GetCaretIndex

SetCaretIndex

GetSelItems 和 SetItemRange 等

4. 编辑控件 编辑控件是一个矩形子窗口

从中可以输入文本信息如果要处理编

辑控件传送给父窗口的控件通知消息 则必须为每条消息添加消息映射 条目和消息处理函数 与编辑控件有关的 常见消息有 ENCHANGE 改变编辑控件中的文本 与消息 ENUPDATE 不同 此消息 是在 Windows 更新显示后传送的 ENERRSPACE 编辑控件不能分配足够的内存 ENHSCROLL

单击编辑控件的水平滚动条

在屏幕更新前

父窗口被通知 www.BOOKOO.com.cn

280


Visual C++6.0 编程实例与技巧

ENVSCROLL

单击编辑控件的垂直滚动条

在屏幕更新前

父窗口被通知 ENKILLFOCUS

编辑控件失去输入焦点

ENSETFOCUS

编辑控件接收输入焦点

ENMAXTFXT

当前插入超过编辑控件指定的字符数并已被截

断 或者编辑控件没有 ESAUT OHSCROLL 风格而要插入的字符数超过 编辑控件的宽度 或者编辑控件没有 ESAUTOVSCROLL 风格而插入文 本后导致总行数超过编辑控件的高度 ENUPDATE 编辑控件即将显示被更改的文本 可以使 CWnd 目的成员函数 SetWindowText 和 GetWindowText 设置 或获取编辑控件的整个内容 此外 与编辑控件有关的 CEdit 成员函数 主要有 函数 GetSel 得到编辑控件中当前选择的起始和结束字符位置 函数 ReplaceSel 用指定的文本替换编辑控件中的当前选择 函数 SetSel 选择编辑控件中的字符范围 函数 Clear 清除编辑控件中的当前选择 函数 Copy 以 CFTEXT 格式复制编辑控件中的当前选择到剪贴板 中 函数 Cut 以 CFTEXT 格式删除编辑控件中的当前选择并将其复制 到剪贴板中 函数 Paste 以 CFTEXT 格式从剪贴板复制数据到编辑控件的当前 位置 函数 Undo 撤消最后的编辑控件操作 函数 CanUndo 判断编辑控件操作是否可以 Undo www.BOOKOO.com.cn

281


Visual C++6.0 编程实例与技巧

函数 EmptyUndoBuffer 重设或清除编辑控件的 Undo 标记 函数 GetModify 判断编辑控件的内容是否已修改 函数 SetModify 设置或清除编辑控件的修改标记 函数 SetReadOnJy 设置编辑控件为只读状态 函数 GetPasswordChar 得到编辑控件中显示的口令字符(当用户 输入文本时) 函数 SetPasswordChar 设置编辑控件中显示的口令字符(当用户输 入文本时) 函数 GetFirstVisibleLine 判断编辑控件中最顶端的可见行 函数 LineLength 得到编辑控件的行长度 函数 LineScroll 滚动多行编辑控件中的文本 函数 LineFromChars 得到包含指定字符索引的行号 函数 GetRect 得到编辑控件的格式化区域 函数 LimitText 限制可以输入到编辑控件中的文本长度 函数 GetLineCount 得到多行编辑控件的行数 函数 GetLine 从多行编辑控件中得到指定的文本行 5. 组合框控件 组合框是由列表框和静态文本控件或编辑控件组成的

控件的列表

框部分可能一直显示或可能在用户选择下拉箭头时下拉显示 列表框中 当前选择项显示在静态文本控件或编 辑控件中 编辑控件和一直显示的列表框组成

简单的组合框由一个

下拉组合框由编辑控

件和列表

框组成 仅当用户选择下拉箭头时才显示列表框 下拉列表框由静态文 本控件和 列表框组成

仅当用户选择下拉箭头时才显示列表框

如果要处理组合框控件传送给父窗口的控件通知消息 www.BOOKOO.com.cn

则必须为每 282


Visual C++6.0 编程实例与技巧

条消息添加消息映射条目和消息处理函数 与组合框控件有关的常见消 息有 组合框的列表框已关闭

CBNCLOSEUP

如果组合框有

CBSSIMPLE 风格 则不传送此消息 双击组合框中的列表项

CBNDBLCLK

仅当组合框有

CBSSIMPLE 风 格 时 传 送 此 消 息

对 于 具 有 CBSDROPDOWN 或

CBSDROPDOWNLIST 风格的组合框

由于单 击已将组合框隐藏

此不会发生双击事件 CBNDROPDOWN 组合框的列表框即将下拉 仅当组合框具有 CBSDROP DOWN 或 CBSDROPDOWLIST 风格时传送 CBNEDITCHANGE

更改组合框中编辑控件部分的文本

CBNEDITUPDATE 消息不同 此消息是在 Windows 更新显示后传送 如果组合框有 CBSDROPDOW NLLST 风格 则不传送此消息 CBNEDITUPDATE 编辑控件部分即将显示被更改的文本 如果 组合框有 CBSDROPDOWNLIST 风格 则不传送此消息 CBNERRSPACE 组合框不能分配足够的内存 CBNSELENDCANCEL 用户选择被取消 用户单击某一项 然 后单击另一窗口或控件隐藏组合框的列表框 CBNSELENDOK 用户选择一项并按回车键 CBNKILLFOCUS

组合框失去输入焦点

CBNSETFOCUS

组合框接收输入焦点

CBNSELCHANGE

用户单击列表框或使用箭头键导致组合框的列

表框选择即将被更改 要处理这个消息 可以通过 GetLBText 或其他类 似的成员函数获取组合框的编辑控件中 的文本 www.BOOKOO.com.cn

注意

不能使用函数 283


Visual C++6.0 编程实例与技巧

GetWindowText 与组合框控件有关的 CComboBox 成员函数基本上与列表框控件 静态文本控件和编辑控件类似

详细信息请读者参见有关资料

6. 滚动条控件 滚动条在 Windows 系列软件中使用相当普遍 滚动条有两种存在方 式

一是窗口滚动条

另一是滚动条控件

与滚动条控件有关的

CScrollBar 成员函数主要有 函数 GetScrollPos 得到滚动框的当前位置 函数 SetScrollPos 设置滚动框的当前位置 函数 GetScrollRange 得到指定滚动条的滚动范围 函数 SetScrollRange 设置指定滚动条的滚动范围 函数 ShowScrollBar 显示或隐藏滚动条 函数 EnableScrollBar 使能或禁能一个或两个滚动箭头 函数 SetScrollInfo 设置滚动条有关的信息 函数 GetScrollInfo 得到滚动条有关的信息 函数 GetScrollLimit 得到滚动条的最大滚动位置 6.3.2 附加的控件类 除了 Windows 标准控件类外 MFC 类库还提供有其他几个控件类(表 6.3) 表 6.3 附加控件类 控件类 CbitmapButton

说明 以位图而不是文本作标签的按钮控

www.BOOKOO.com.cn

284


Visual C++6.0 编程实例与技巧

复选列表框 列表中的每一项是一

CcheckListBox 个复选框 CdragListBox

拖放列表框 允许用户移动列表框

CtoolBar

含有其他控件的工具栏

CstatusBar

含有面板或指示符的状态栏

CdialogBar

从对话模板资源创建的对话框

这里先讨论一下位图按钮 复选列表框和拖放列表框 控件栏(如工 具栏

状态栏和对话栏 )将在后面讨论

1. 位图按钮 位图按钮是以位图而下是以文本作标签的按钮控件 CBitmapButton 类对象包含有四个 位图

表示按钮的各种状态

每个 上(正

常的) 下(被选择的) 拥有焦点和无效的 仅仅第 一个位图是必须的 其他位图为可选的

CBitmapButton 的基类为 CButton

创建位图按钮时

必须设置 BSOWNERDRAW 风格指定按钮是

drawn 按 钮

Wi ndows 将 传 送 WMMEASUREITEM 和

Owner

WMDRAWITEM 消息给按钮

框架将处理这些 消息并管理按钮的外

观 要在窗口客户区中创建位图按钮 必须遵循以下步骤 (1)为位图按钮创建 1

4 个位图图像

(2)构造 CbitmapButton 对象 (3) 用 成 员 函 数 Create 创 建 Windows 按 钮 控 件 并 将 其 与 CBitmapButton 对象链接 (4)在构造位图按钮后调用成员函数 LoadBitmaps 装载位图资源 要在对话框中包含位图按钮控件 必须遵循以下步骤 www.BOOKOO.com.cn

285


Visual C++6.0 编程实例与技巧

(1)为位图按钮创建 1 (2)创建带 Ower

4 个位图图像

drawn 按钮的对话模板

(3) 设 置 按 钮 标 题 ( 如 “BMPIMAGE”) 并 定 义 按 钮 符 号 ( 如 “IDCBMPIMAGE”) (4)在应用程序资源文件中 为每个位图图像构造资源符号 钮标题为“BMPIMAGE ” “BMPIMAGED”

如果按

则位图图像的标题分别为“BMPIMAGEU”

“BMPIMAGEF”和“BMPIMAG EX”

(5)在应用程序的对话类中添加 CBitmapButton 成员对象 (6)在对话类对象的成员函数 OnInitDialog 中调用 CBitmapButton 对 象的成员函数 AutoLoad

参数为按钮控件 ID 和对话对象的 this 指针

2. 复选列表框 复选列表框中的每一项是一个复选框

要创建复选列表框

必须从

CCheckListBox 类派生下个类 为派生类编写构造函数 然后调用成员 函数 Create 如果是缺省复选列表框(每一项包含文本串和缺省大小的复选框) 则 可以调用缺省的 Cche ckListBox::DrawItem 来绘制复选列表框 否则 必须覆盖成员函数 CListBox::CompareItem

CcheckListBox::DrawItem

和 CcheckListBox::MeasureItem 来绘制 与复选列表框有关的 CCheckListBox 成员函数有 函数 SetCheckStyle 设置复选框部分的风格 函数 GetCheckStyle 获取复选框部分的风格设置 函数 SetCheck 设置列表项中复选框的状态 函数 GetCheck 获取列表项中复选框的状态设置 函数 Enable 使列表项有效或者无效 www.BOOKOO.com.cn

286


Visual C++6.0 编程实例与技巧

函数 IsEnabled 确定列表项是否有效 函数 OnGetCheckPosition 获取列表框中复选框的位置 函数 DrawItem 在更改列表框的可见部分时被调用(可覆盖) 函数 MeasureItem 在创建列表框时被调用(可覆盖) 3. 拖放列表框 拖放列表框允许用户移动列表项 与 CDragListBox 类有关的列表框 控件不能具有 LBSSORT 或者 LBSMULTIPLESELECT 风格 要在已有的对话框中创建拖放列表框

必须首先在对话编辑器中添

加列表框控件到对话模板资源中 然后使用 ClassWizard 赋成员变量给 对应的列表框控件

成员变量必须拥有 Control 属性

类型必须是

CDragListBox 与拖放列表框有关的 CDragListBox 成员函数有 函数 ItemFromPt 返回正被拖放列表项的坐标 函数 BeginDrag 在拖放操作开始时被调用(可覆盖) 函数 CancelDrag 在取消拖放操作时被调用(可覆盖) 函数 Dragging 在拖动列表项时被调用(可覆盖) 函数 Dropped 在放下列表项后被调用(可覆盖) 6.3.3 控件与对话框 对话框中的控件通常在创建对话框时从对话模板中创建

可以用

ClassWizard 来管理对话框中的控件 1. 使用对话编辑器添加控件 用对话编辑器创建对话模板资源时

可以从控制工具栏中拖动控件

再将其放到对话框中 这时 将在对话模板资源中添加相应类型的控件

www.BOOKOO.com.cn

287


Visual C++6.0 编程实例与技巧

当构造对话对象并调用成员函数 Create 或 Do Modal 时 将创建相应的 Windows 控件并将其放人对话窗口中 拖放控件到对话框后 可以用相应的属性对话框设置控件的风格 2. 调用构造函数和成员函级 Create 添加控件 要自己创建控件对象

必须在对话对象或边框窗口对象中嵌入控件

对象 并在成员函数 OnIn itDialog(对于对话框)或 OnCreate(对于边框窗 口)中调用该控件对象的成员函数 Create

以下例子说明如何在对话框

中添加编辑控件 (1)在派生的对话类声明中嵌入 CEdit 对象的声明: class CderivedDialog: public Cdialog { protected CEdit medit

嵌入到对话框中的编辑控件

public virtual BOOL OnInitDialog() } 由于 CEdit 对象的声明嵌入到了对话类的声明中 因此在构造对话 类对象时将自动构造编辑控件对象 (2)在成员函数 OnInitDialog 中调用编辑控件对象的成员函数 Create: BOOL CDerivedDialog::OnInitDialog() { Cdialog::OnInitDialog() CRect rect(80

100

160

调用基类的 OnInitDialog 200)

建立矩形区域

调用成员函数 Create 创建编辑控件

www.BOOKOO.com.cn

288


Visual C++6.0 编程实例与技巧

medit.Create(WSCHILD|WSVISIBLE|WSTABSTO P|ESAUTOHSCROLL|WSBORDER

rect

this

medit.SetFocus()

设置输入焦点

return FALSE

说明已经设置焦点

IDEXTRAEDIT)

6.3.4 管理对话框控件的 MFC 函数 对话类是从 CDialog 类派生的 而 CDialog 类又是从 CWnd 类派生 的

CWnd 类提供有管理对话框和对话框控件的成员函数 可以在对话

类的消息处理函数中调用这些成员函数 用于管 理对话框控件的 CWnd 成员函数主要有 (1)函数 CheckDlgButton 用于选择(放选中标记)或清除(删除选中标记) 按钮 或者更改三状态按钮的状态

函数原型为

void CheckDlgButton(int nIDButton

UINT nCbeck)

其中 参数 nIDButton 为指定要修改的按钮 参数 nCheck 指定要采 取的动作 如果非 0

则放置选中标记到按钮中 如果为 0

中标记 对于三状态按钮

如果为 2

则删除选

则按钮状态 为不确定

函数 CheckDlgButton 传送 BMSETCHECK 消息给指定的按钮 (2)函数 CheckRadioButton 选择给定的单选钮(添加选中标记)并清除 组内其他所有单选 钮(删除选中标记)

函数原型为

void CheckRadioButton(int nIDFirstButton

int nIDLastButton

int

nIDCheckButt on) 其中

参数 nIDFirstButton 和 nIDLastButton 分别指定组内第一个和

最后一个单选钮 而参数 nIDCheckButton 指定被选中的单选钮 函数 CheckRadioButton 传送 BMSETCHECK 消息给指定的单选钮

www.BOOKOO.com.cn

289


Visual C++6.0 编程实例与技巧

(3)函数 GetCheckedRadioButton 返回指定组中当前被选中的单选钮 函数原型为 int GetCheckedRadioButton(int nIDFirstButton int nIDLastButton) 其中 参数 nIDFirstButton 和 nIDLastButton 分别为组中第一个和最 后一个单选钮 (4)函数 DlgDirList 往列表框中填充文件或目录列表 函数原型为 int DlgDirList(LPTSTR lpPathSpec,int nIDListBox int nIDStaticPath 其中

UINT nFileType)

参数 LpPaihSpec 指向包含路径或文件名的字符串

参数

nIDListBox 指定列表框标识符 nIDStaticPath 指定用于显示当前驱动器 或目录的静态文本控件 nFileType 指定要显示文 件的属性 函数 DlgDirList 传送消息 LBRESETCONTENT 和 LBDIR 给列表框 (5)函数 DlgDirListComboBox 往组合框的列表框中填充文件或目录 列表 函数原型为 int DlgDirListComboBox(LPTSTR lpPathSpec int nIDComboBox int nIDStaticPath

UINT nFileType)

参数含义与 DlgDirList 类似 函数 DlgDirListComboBox 传送消息 CBRESETCONTENT 和 CBDIR 给组合框 (6)函数 DlgDirSelect 从列表框中获取当前选择的文件或目录 函数 原型为 BOOL DlgDirSelect(LPTSTR lpString 其中

int nIDListBox)

参数 lpString 为列表框中的当前选择

(7)函数 DlgDirSelectComboBox 从组合框的列表框中获取当前选择 www.BOOKOO.com.cn

290


Visual C++6.0 编程实例与技巧

的文件或目录 BOOL

函数原 型为 DlgDirSelectComboBox(LPTSTR

lpString

int

nIDComboBox) 其中

参数 lpString 为组合框的列表框中的当前选择

(8)函数 GetDlgItem 返回指向对话框中指定控件的指针 函数原型为 CWnd* GetDlgItem(int nID) 其中

参数 nID 为指定的控件 ID

(9)函数 GetDlgItemInt 返回指定控件中由文本表示的整数值 函数原 型为 UINT GetDlgIemInt(int nID bSigned 其中

BOOL* lpTrans

NULL

BOOL

TRUE) 参数 nID 为指定的控件 ID

(10)函数 GetDlgItemText 获得在控件内显示的标题或文本 函数原型 为 int GetDlgItemText(int nID LPTSTR lpStr int nMaxCount) int GetDlgItemText(int nID Cstring

rString)

其中 参数 nID 为指定的控件 ID 控件内显示的标题或文本由参数 LpStr 和 nMax 以及 Count 或者 rString 返回 (11)函数 GetNextDlgGroupItem 返回指向一组控件中下一个(或上一 个)控件的指针

函数原型为

Cwnd* GetNextDlgGroupItem(Cwnd* pWndCtl BOOL bPrevious FALSE) 其中

参数 pWndCtl 为指向当前控件的指针

参数 bPrevious 为

FALSE 表示返回下一个控件的指针 否则为上一个控件的指针 www.BOOKOO.com.cn

291


Visual C++6.0 编程实例与技巧

(12) 函 数 GetNextDlgTabItem 返 回 指 向 下 一 个 或 ( 上 一 个 ) 具 有 WSTABSTOP 风格 的控件的指针 Cwnd*

函数原型为

GetNextDlgTabItem(Cwnd*

BOOL

pWndCtl

bPrevious=FALSE) 其中

参数 pWndCtl 为指向当前控件的指针 参数 bPrevious 为

FALSE 表示返回下一个控件的指针 否则为上一个控件的指针 (13)函数 IsDlgButtonChecked 判断按钮控件是否选中 函数原型为 UINT IsDlgButtonChecked(int nIDButton) 其中

nIDButton 为按钮的控件 ID

(14)函数 IsDialogMessage 判断指定的消息是否为非模态对话框的消 息

函数原型为 BOOL IsDialogMessage(LPMSG lpMsg) 其中 参数 lpMsg 指向包含要检查消息的 MSG 结构 (15)函数 SendDlgItemMessage 传送消息给指定的控件 函数原型为 LRESULT SendDlgItemMessage(int nID UINT message WPARAM wParam=0

LPARAM lparam=0)

其中 参数 nID 为指定的控件 ID 参数 message 为要传送的消息 参数 wParam 和 lParam 为与消息有关的附加消息 (16)函数 SetDlgItemInt 将整数转换为字符串并将其赋给控件 函数 原型为 void SetDlgItemInt (int nID UINT nValue BOOL bSigned=TRUE) 其中 参数 nID 为指定的控件 ID 参数 nValue 为要转换的整数值 bSigned 指定整数值是否为有符号的 (17)函数 SetDlgItemText 设置由对话框控件显示的文本 www.BOOKOO.com.cn

函数原型 292


Visual C++6.0 编程实例与技巧

为 void SetDlgItemText(int nID LPCTSTR lpszString) 其中 参数 nID 为要设置文本的控件 ID 参数 lpszString 为要显示 的文本 6.3.5 公用控件类 公用控件补充了标准控件的不足

使得应用程序编程更为生动

趣和形象 公用控件只能在 Windows 95 Windows NT 3.51 及以后版本 中使用 1. 公用控件类 在 MFC 类库中提供有相应的公用控件类用于封装 Windows 公用控 件 表 6.4 列出了主要的公用控件类 表 6.4 公用控件类 公用控件类 CanimateCtrl

Windows 公用控件说明 动画控件以 Windows 标准视频/音频格式

显示 AVI 剪样 CheaderCtrl

标题控件显示列标题

ChotKeyCtrl

热键控件显示用户创建的热键

CimageList

图像列表控件管理大小相同的图标或位图

ClistCtrl

列表控件(或列表查看控件)管理由图标和

标签组成的列表项 并 4 种不同方式(图 标 小图标 列表和详细资料)显示列表项 内容

www.BOOKOO.com.cn

293


Visual C++6.0 编程实例与技巧

CprogressCtrl

进展条控件指示某一长任务完成的进展程

CsliderCtrl

滑动条控件(或轨道条控件)包含滑动条和

可选 tick 标记的窗口 SpinButtonCtrl 上 下控件(或旋转控件)向上和向下的箭头

与某个编辑控件相连 时称为旋转控件 CstatusBarCtrl

状态栏控件显示应用程序的有关信息

CtabCtrl

制表控件一次显示多页信息或控件

CtoolBarCtrl

工具栏控件包含按钮和可选控件的窗口

CtreeCtrl

树形控件(或树形查看控件)显示项的层次

列表结构 2.公用控件的使用 公用控件可以在基于对话模板的对话框

表单视图

记录视图和任

何其他窗口中使用 还可以作为其他任何窗口的子窗口使用 公用控件 作为子窗口可以传送控件通知消息给父窗口

多数公用控件传送控件

通知消息 WMNOTIFY 该消息的缺省处理函数是 CWnd::OnNot ify 消 息映射条目为 ONNOTIFY

此外

还可以在公用控件类的派生类中处

理自己 的控件通知消息 下面以动画控件为例说明公用控件的使用方法

其他公用控件的使

用请读者参见有关资料 3.动画控件的使用 动画控件以标准 Windows 视频 音频格式(AVI)显示剪样(Clip) 类 似于电影

每个 AVI 剪样是由一系列位图帧组成的

动画控件只能播

放简单的 AVI 剪样 不支持声音 如果要支 持多媒体播放和记录功能 www.BOOKOO.com.cn

294


Visual C++6.0 编程实例与技巧

必须使用 MCIWnd 窗口类 动画控件的使用通常必须遵循以下步骤 (1)创建动画控件 如果动画控件在对话模板中指定 则在对话框创 建时自动创建动画控件 否则 可以使用成员函数 Create 作为子窗口创 建动画控件 (2)通过调用成员函数 Open 装载 AVI 剪样到动画控件中 如果动画 控件是在对话框中 则在 对话类的成员函数 OnInitDialog 中调用 Open Open 函数原型为 BOOL Open(LPCTSTR lpszFileName) BOOL Open(UINT nID) 其中

参数 lpszFileName 包含 AVI 文件名字或 AVI 资源名字 nID

是 AVI 资源符号 (3)通过调用成员函数 Play 播放 AVI 剪样 如果动画控件是在对话框 中 则在对话类的成员函数 OnInitDialog 中调用 Play Play 函数原型为 BOOL Play(UINT nFrom UINT nID UINT nRep) 其中 参数 nFrom 为要播放的起始帧 nID 为要播放的结束帧 nRep 为重复播放的次数 如果动画控件具有 ACSAUTOPLAY 风格 则可以不用调用成员函数 Play (4)调用成员函数 Seek 显示部分 AVI 剪佯或逐帧播放 Seek 函数原 型为 BOOL seek(UINT nID) Seek 函数静态播放 AVI 剪样中的某一帧 参数 nID 指定要播放的 帧 www.BOOKOO.com.cn

295


Visual C++6.0 编程实例与技巧

(5)调用成员函数 Stop 停止 AVI 剪样的播放 (6)调用成员函数 Close 关闭动画控件中打开的 AVI 剪样 (7)销毁动画控件 动画控件传送两种类型的控件通知消息 当动画控件开始播放 AVI 剪样时 传送 ACNSTART 消息 当完成或停止播放时 传送 ACNSTOP 消息 6.4 控件栏 控件栏(如工具栏 状态栏和对话栏等)可以快速单步执行命令动作 它极大地改进了程序的可用性

所有控件栏的基类都是 CControlBar

基类 CControlBar 提供了在父边框窗口中定 位控件栏的功能 控件栏是 主边框窗口的一个子窗口 控件栏对象(工具栏对象 状态栏对 象和对 话栏对象)是作为主边框窗口类的数据成员被声明的 在创建主边框窗口 时创建控件 栏对象

在销毁主边框窗口时销毁控件栏对象

6.4.1 工具栏 工具栏包含一组用于激活命令的位图按钮 窗口的顶部 他边上

此外

工具栏通常放在父边框

可以 将工具栏拖动并停泊在父边框窗口的任何其

并可以使其成为浮动的

即放在浮动的 窗口中

随着用户在

工具栏按钮上移动鼠标 工具栏还可以显示工具提示 工具提示是一个 小型弹出式窗口 显示工具栏按钮用途的简短描述 工具栏按钮与菜单选项是类似的 选项

将产生相应的命令

生的命令

单击工具栏按钮相当于选择菜单

应用程序通过提供消息处理函数来处理产

如 果 工 具 栏 的 某 个 按 钮 没 有 COMMAND 或

www.BOOKOO.com.cn

296


Visual C++6.0 编程实例与技巧

UPDATECOMMANDUI 处理函数 则框架自动使该按钮无效 Visual C++提供有两种方法用于创建工具栏 一是使用资源编辑器 方法为 (1)创建工具栏资源 (2)构造 CToolBar 对象 (3)调用成员函数 Create 创建 Windows 工具栏并将其与 CToolBar 对 象链接 (4)调用成员函数 LoadToolBar 装载工具栏资源 另一方法为 (1)构造 CToolBar 对象 (2)调用成员函数 Create 创建 Windows 工具栏并将其与 CToolBar 对 象链接 (3)调用成员函数 LoadBitmap 装载包含工具按钮图像的位图 (4)调用成员函数 SetButtons 设置按钮风格并使每个按钮与位图图像 相关 所有工具栏按钮图像都保存在一个位图中

每个图像都有相同的大

小 缺省为 16 像素宽 15 像素高 工具栏对象根据被单击按钮在工具 栏中的位置来处理工具栏中的鼠标单击事件

并 产生适当的命令

钮通过控件 ID 数组与按钮所产生的命令相关 控件 ID 在数组中的位置 与 按钮图像在工具栏位图中的位置是一样的

如 果 在 MFC

AppWizard 中选择“Initial Toolba r”选项 则在主边框窗口类的源文件中 将增加一个按钮数组 数组中含有分隔符(IDSEPARATOR) 用于将按钮 分组

在确定按钮位置时

分隔符是被忽略的

工具栏按钮可以以按钮

复选框或单选钮的形式出现和起作用

www.BOOKOO.com.cn

创 297


Visual C++6.0 编程实例与技巧

建复选框按钮时 应赋风格 TBBSCHECKBOX 给按钮或在命令更新处 理函数中调用 CCmdUI 对象的成员函数 S etCheck 创建单选钮按钮时 应在命令更新处理函数中调用 CCmdUI 对象的成员函数 SetRadi o 与工具栏有关的其他 CToolBar 成员函数有 函数 SetSizes 设置按钮及位图的大小 函数 SetHeight 设置工具栏的高度 函数 SetBitmap 设置位图图像 函数 CommandToIndex 返回给定命令 ID 的按钮索引 函数 GetItemID 返回指定索引的按钮或分隔符的命令 ID 函数 GetItemRect 获取给定索引项的显示区域 函数 GetButtonStyle 获取按钮风格 函数 SetButtonStyle 设置按钮风格 函数 GetButtonInfo 获取按钮的 ID 风格和图像号 函数 SetButtonInfo 设置按钮的 ID 风格和图像号 函数 GetButtonText 获取显示在按钮上的文本 函数 SetButtonText 设置显示在按钮上的文本 6.4.2 状态栏 状态栏由一行输出面板或指示符组成

输出面板通常用作消息行和

状态指示符 例如 简要描述被选中菜单命令或工具命令的命令帮助消 息行以及指示 SCROLL LOCK NUMLOCK 和其他键 的状态的指示符 状态栏通常位于边框窗口的底部 状态指示符的标识符存放在一个数组中 如果在 MFC AppWizard 中 选择“Initial ToolBar” 选项 则在主边框窗口类的源文件中将创建指示符

www.BOOKOO.com.cn

298


Visual C++6.0 编程实例与技巧

数组

例如 static UINT indicators

=

{ 状态行指示符

IDSEPARATOR IDINDICAIORCAPS LDINDICAIORNUM IDINDICATORSCRL }

指示符从左到右水平排列在状态栏中 在数组中添加更多的标识符 就可以增加更多的指示符 可以根据需要改变指示符的大小 通过增加 IDSEPARATOR 元素还可以增加分隔符 用作消息区

最左边的指示符(位置 0)通常

可以在其中显示命令提示之类的文本字符串

要创建状态栏 应遵循以下步骤 (1) 构造 CStatusBar 对象 (2) 调用成员函数 Create 创建状态栏窗口并将其与 CStatusBar 对象 链接 (3)调用成员函数 SetIndicators 设置指示符的标识符为数组中相应元 素指定的值

装载每 个标识符指定的字符串资源并将字符串设为指示

符文本 此外 还可以按以下方法更新指示符文本 调用 CWnd 成员函数 SetWindowsText 更新最左边的指示符的文 本 在状态栏的命令更新处理函数中调用 CCmdUI 的成员函数 SetText 设置支本

www.BOOKOO.com.cn

299


Visual C++6.0 编程实例与技巧

调用 CStatusBar 的成员函数 SetPaneText 设置任一指示符的文本 调用 CStatusBar 的成员函数 SetPaneStyle 设置指示符的风格 与状态栏有关的其他 CStatusBar 成员函数有 函数 CommandToIndex 获取给定指示符 ID 的索引 函数 GetItemID 获取给定索引的指示符 ID 函数 GetItemRect 获取给定索引的显示区域 函数 GetPaneInfo 获取给定索引的 ID 风格和宽度 函数 GetPaneStyle 获取给定索引的指示符风格 函数 GetPaneText 获取给定索引的指示符文本 函数 SetPaneInfo 设置给定索引的 ID 风格和宽度 6.4.3 对话栏 对话栏是具有非模态对话框特性的工具栏

工具栏和对话栏主要不

同在于 对话栏是从对话 模板创建的 可以包含任何 Windows 控件 对话栏支持用 Tab 键在各控件之间移动 可以指定 对话栏风格以便将 其放在主边框窗口的顶部 底部 左边或右边 在其他方面 使用对话 栏 与使用非模态对话框是一样的

对话栏是主边框窗口的扩展部分

对话栏的任何控件通知消 息(如 BN CLICK 或 EN CHANGE)都将被传 送给主边框窗口 创建对话栏的基本过程为 (1) 在对话编辑器中创建对话模板资源 (2) 从“Edit”菜单选择“Properties”命令 (3) 从属性对话框 选择“Styles”选项卡并设置以下属性 在“Style”框选择“Child”

www.BOOKOO.com.cn

300


Visual C++6.0 编程实例与技巧

在“Border”框选择“None” (4) 选择“More Styles”选项卡并清除“Visible”复选框 (5) 选择“General”选项卡并清除“Caption”框 (6) 构造 CDialogBar 对象 (7) 调用成员函数 Create 创建对话栏窗口并将其与 CDialogBar 对象 链接 6.4.4 CControlBar 成员函数 与控件栏有关的 CControlBar 成员函数有 函数 GetBarStyle 获取控件栏风格设置 函数 SetBarStyle 设置控件栏风格设置 函数 GetCount 返回控件栏中非 HWND 元素的数目 函数 GetDockingFrame 返回指向控件栏为船坞(dock)的边框的指 针 函数 IsFloating 判断控件栏是否为浮动控件栏 函数 CalcFlxedLayout 返回控件栏的大小 函数 CalcDynamicLayout 返回动态控件栏的大小 函数 EnableDocking 使控件栏为船坞或浮动 6.5 编程范例 作为练习 这里介绍一个简单的程序例子 MyApp 该应用程序是 SDI 单文档类型 功能是当用户鼠标单击窗口客户区时 在单击位置处画一 个圆

圆的颜色和线型粗细可以通过菜单命令 改变

其中要用到自己

定义的两个对话框类 CcolorDlg 和 CLineTypeDlg 下面让我们开始吧

www.BOOKOO.com.cn

301


Visual C++6.0 编程实例与技巧

1. 创建应用程序框架 (1) 在 Visual C++6.0 开发环境中 选 File-New(即 File 菜单下的 New 命令 其他类 同)

在弹出的 New 对话框的 Projects 选项卡中 输入

项目名 MyApp 选择“create new workspace ”项和“Win32”平台 在左边 的项目类型列表中选择“MFC AppWizard (Exe)” 如图 6. 1

单击“OK”

钮 进入下一步 (2) 在弹出的 MFC AppWizard Step 1 对话框中 选择 SDI(单文档) 如图 6.2

按“Finis h”钮弹出“New Project Information”对话框 按“OK”

钮结束 AppWizard 创建过程 2. 添加菜单项 (1) 在“Resouce View”视图中 展开 Menu 项 双击 IDRMAINFRAME 项 菜单编 辑窗口打开 如图 6.3 所示添加菜单项“设置” 并在其下拉 菜单中添加“颜色”和“线型 ”项

www.BOOKOO.com.cn

302


Visual C++6.0 编程实例与技巧

图 6.1 New 对话框

图 6.2 MFC 应用程序向导第一步

图 6.3 添加“设置”菜单项 (2) 用鼠标单击“颜色”菜单项

按键盘 Enter 键

弹出“Property”

属性对话框 如 图 6.4 所示 在其中设置 ID 码和标题 Caption 用同样 的方法设置“线型”项 两者的设置结果为 ID

Caption

“颜色”项

IDSETUPCOLOR

“线型”项

IDSETUPLINETYPE 线型

颜色

www.BOOKOO.com.cn

303


Visual C++6.0 编程实例与技巧

图 6.4 属性窗口 (3) 给这两个菜单项添加消息处理函数 在 Class Wizard 对话框中 选择 Message Map 页 给 IDSETUPCOLOR IDSETUPLINETYPE 分别 建立消 息处理函数 方法为在 Object IDs 列表框选 ID 码 在 Messages 列表框中选 COMMAND 按“Add Function”钮 在弹出的“Add Member Function”对话框中按“OK”钮确认接受缺省函数 名 OnSetupColor()和 OnSetupLineType() (4) 利用 Class Wizard 建立响应消息 WMLBUTTONDOWN 的处理函 数 OnLButtonDown( ) 覆盖虚拟成员函数 OnInitialUpdate() 3. 创建对话框资源及对话框类 (1) 创建颜色对话框及 CColorDlg 类 选择 Insert-Resouce... 弹出“Insert Resouce”对话框 选择 Dialog 并 按 New 按钮 对话框资源编辑器打开一个对话模板 如图 6.5 用鼠标 右单击对话框窗体非控件区域

在 弹出菜单上选择 Properties 属性对

话框打开 设置窗体 ID 为 IDDCOLORDLG 标题 为“设置颜色” 关 闭之

www.BOOKOO.com.cn

304


Visual C++6.0 编程实例与技巧

图 6.5 对话模板 在窗体上放置三个单选按钮

通过属性对话框设置其标题分别为

红 绿 蓝 ID 码分 别为 IDCRED IDCGREEN IDCBLUE 结果 如图 6.6

图 6.6 添加三个单选按钮 把该对话框定义成 CColorDlg 类 双击窗体空白区域 出现的“Adding a class”对话框如图 6.7 单击 OK 确认 弹出“New Class”对话框 在保 证“Base Class”栏为 CDialog 情况 下 在 Class Name 栏输入 CColorDlg, 如图 6.8

单击 OK,系统自动打开 Class Wizard 对话框 www.BOOKOO.com.cn

305


Visual C++6.0 编程实例与技巧

图 6.7 “Adding a class”对话框 在 Class Wizard 对话框中 选择 Message Map 页 IDCGREEN

给 IDCRED

IDCBLUE 分别建立消息处理函数 方法为在 Object IDs

列表框选 ID 码

在 Messag es 列表框中选 BNCLICKED

按“Add

Function”钮 在弹出的“Add Member Funct ion”对话框中按“OK”钮确认 接受缺省函数名

如图 6.9

覆盖虚拟对话框成员函数 OnInitDialog() CColorDlg

在 Object IDs 列表框选

在 Messages 列 表 框 中 选 WMINITDIALOG

按 “Add

Function”钮系统即完成添加任务

www.BOOKOO.com.cn

306


Visual C++6.0 编程实例与技巧

图 6.8 “New Class”对话框

www.BOOKOO.com.cn

307


Visual C++6.0 编程实例与技巧

图 6.9 Class Wizard 对话框 从 Class View 视图中 右击 CColorDlg 项 在弹出菜单中选“Add Member Variable... ” 在弹出的对话框中输入欲添加变量的类型 int 及变 量名 mnColor

如图 6.10

图 6.10 添加成员变量

www.BOOKOO.com.cn

308


Visual C++6.0 编程实例与技巧

给上述的四个消息处理函数添加代码如下(程序中黑色部分) void CColorDlg::OnBlue() { // TODO: Add your control notification handler code here mnColor=3; } void CColorDlg::OnGreen() { // TODO: Add your control notification handler code here mnColor=2; } void CColorDlg::OnRed() { // TODO: Add your control notification handler code here mnColor=1; } BOOL CColorDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here mnColor=0; return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } (2) 创建线型对话框及 CLineTypeDlg 类 仿照上述方法创建线型对

www.BOOKOO.com.cn

309


Visual C++6.0 编程实例与技巧

话框及 CLineTypeDlg 类 创建的线型对话框如图 6.11 其上编辑控件 的 ID 码为 IDCLINETYPE 对话框窗体 的 ID 码为 IDDLINETYPEDLG 标题为“设置线型” 给控件 IDCLINETYPE 增加一个连接变量 在把对 话框定义成 CLineTypeDlg 类后 Class Wizard 已自动打开 选择 Member Variables 页 在 Control IDs 列表框中选择 IDCLINETYPE,按 Add Variable 钮 在弹出的对话框中输入欲添加变量的类型 int 及变量名 mnLineType 如图 6.12

图 6.11 “设置线型”对话框

www.BOOKOO.com.cn

310


Visual C++6.0 编程实例与技巧

图 6.12 添加成员变量 按照上述方法给 CLineTypeDlg 类添加消息处理函数 OnInitDialog() 并用 Class Wizard 的 Me ssage Map 页创建响应控件 IDCLINETYPE 内容 变化的消息处理函数

OnChangeLineT ype()

与上述方法不同的是在

Message 列表框中选择的消息是 ENCHANGE 然后给上述两个消息处理函数添加代码如下 void CLineTypeDlg::OnChangeLinetype() { // TODO: If this is a RICHEDIT control, the control will not //

send

this

notification

unless

you

override

the

CDialog::OnInitDialog( ) // function and call CRichEditCtrl().SetEventMask() // with the ENMCHANGE flag ORed into the mask. // TODO: Add your control notification handler code here www.BOOKOO.com.cn

311


Visual C++6.0 编程实例与技巧

UpdateData(TRUE); } BOOL CLineTypeDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here mnLineType=3; return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } (3) 在 CMyAppView.cpp 文件头部添加如下两行 #include

ColorDlg.h

#include

LineTypeDlg.h

4. 给 CMyAppView 类添加成员变量 在 Class View 视图中 右击 CMyAppView 弹出如图 6.10 所示的对 话框 利用上述方法给 CMyA ppView 类增加如下变量 bool

mbEnable

intmnColor intmnLineType intmnX intmnY 5. 初始化视图类成员变量 视图类成员变量的初始化一般在 OnInitialUpdate()函数中完成 按如 下所示给 OnInitialUp date()增加代码 void CMyAppView::OnInitialUpdate() www.BOOKOO.com.cn

312


Visual C++6.0 编程实例与技巧

{ CView::OnInitialUpdate(); // TODO: Add your specialized code here and/or call the base class mbEnable=false; mnColor=0; mnLineType=3; mnX=mnY=NULL; } 6. 编写其余函数代码 给剩下三个函数添加代码如下 void CMyAppView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default CRect rect; GetClientRect(&rect); if(rect.PtInRect(point)) { mbEnable=true; mnX=point.x; mnY=point.y; Invalidate(); } CView::OnLButtonDown(nFlags, point); } void CMyAppView::OnSetupColor() {

www.BOOKOO.com.cn

313


Visual C++6.0 编程实例与技巧

// TODO: Add your command handler code here CColorDlg dlg; if(dlg.DoModal()==IDOK) { mnColor=dlg.mnColor; Invalidate(); } } void CMyAppView::OnSetupLinetype() { // TODO: Add your command handler code here CLineTypeDlg dlg; dlg.mnLineType=3; if(dlg.DoModal()==IDOK) { mnLineType=dlg.mnLineType; Invalidate(); } } 7. 编译运行 选择 Build-Build MyApp.exe 项

系统开始编译

创建可执行程序

MyApp.exe 之后选择 Exec ute MyApp.exe 运行该程序 程序出现如下 窗口界面 如图 6.13

www.BOOKOO.com.cn

314


Visual C++6.0 编程实例与技巧

图 6.13 应用程序运行界面 在窗口客户区单击

则系统自动在鼠标单击处绘制一个圆

如图

6.14 选择“设置”菜单项下的“颜色”菜单项 弹出如图 6.15 所示对话框 在其中更改颜色后

系统自动以新的颜色绘制圆 如图 6.16

选择“设置”菜单项下的“线型”菜单项 弹出如图 6.17 所示对话框 在其中更改线型后

系统自动以新的线型绘制圆

www.BOOKOO.com.cn

315


Visual C++6.0 编程实例与技巧

图 6.14 在窗口中绘制出一个圆

图 6.15 “设置颜色”对话框

www.BOOKOO.com.cn

316


Visual C++6.0 编程实例与技巧

图 6.16 用新设置的颜色所绘制 的圆

图 6.17 “设置线型”对话框

www.BOOKOO.com.cn

317


Visual C++6.0 编程实例与技巧

第七章 如何创建一个应用程序 在前面一章

举了一个创建对话框应用程序的简单例子

通过这个

例子 你应该对如何用 Vi sual C++6.0 编写和设计应用程序有一个简单 的认识

知道了创建应用程序的基本 方法和步骤

在这一章中

将详

细地介绍每一步以及相关的内容 7.1 工程文件(Project) 工程文件(Project) 在 Visual C++6.0 中创建一个应用程序 Wizards

是通过使用 Microsoft

Microso ft Foundation Classes(MFC) 和 Active Template

Library(ATL)完成 Wizards, 如 MFC AppWizard

MFC ActiveX ControlWizard,ISAPI

Extension Wizard 和 ATL C OM AppWizard 等 能帮助你生成各种不同 类型的 Windows 程序的基本源代码文件 MFC 和 ATL 库提供一些基本的类和支持代码 通过把它们加到你的 源代码文件

来完成你的应 用程序的一些特殊功能

创建应用程序首先要选择建立它的工程文件(Project)和工程工作区 文件(Project Works pace) 那么

什么是“工程(Project)”呢

“工程”

更经常地称做“项目”

是一些相互关联的源文件的集合

这些源文件被编译 链接后 组合在一起形成可执行的 Windows 应用程 序 Visual C++中 你的应用程序框架被当作包含所使用的所有文件的一 个工程 在工程工作区窗口中可以看到这个工程的具体内容 工程工作 区窗口中有三个制表框 www.BOOKOO.com.cn

318


Visual C++6.0 编程实例与技巧

(1) ClassView 显示工程中的所有类及其成员函数 (2) FileView 显示工程中的所有文件 (3) ResourceView 显示工程中的所有资源 在 Visual C++6.0 中 把你的文件 工程和工程配置组织起来都是在 工程工作区完 成的

可以使用工程工作区窗口去查看和访问工程的各

种组件 工程工作区的内容和设置是通过工程 工作区文件(.dsw)来描述 的 建立一个工程工作区文件的同时 还产生了工程文件(.dsp )和工作 区选项文件(.opt) 用于保存工作区的设置 工作区选项文件是描述组织 和显示 你的工程工作区的当前硬件配置

一个工程工作区中可以包含

有几个不同语言的 Vlsual C+ +工程 在这个工作区中 一个工程的文件 可以被该工作区的其他工程共享 在前面的例子中 从 Visual C++的 File 菜单中选择 New 后 出现一 个制表对话框窗口 如图 7.1 所示 在 Projects 制表页中显示了若干种 工程文件的类型 这些类型有: (1) ATL COM AppWizard(ATL 指 Active Template Library COM 指 Component Object Mo del) (2) Cluster Resource Type Wizard (3) CustomAppwizard (4) Database Project (5) DevStudio Add-in Wizard (6) DirectDraw AppWizard(从第三方软件安装) (7) Extended Stored Proc Wizard (8) ISAPI Extension Wizard(ISAPI 指 Internet Support API) (9) Makefile

www.BOOKOO.com.cn

319


Visual C++6.0 编程实例与技巧

(10) MFC ActiveX ControlWizard (11) MFC AppWizard(DLL) (12) MFC AppWizard(exe) (13) New Database Wizard (14) Utility Project (15) Win32 Application (16) Win32 Console Application (17) Win32 Dynamic-Link Library(动态链接库) (18) Win32 Static Library(静态库) 在本书中 较常用到 MFC AppWizard(exe)这一项 如图 7.1 所示 因为编写普通的可执行应用程序 只需使用 Visual C++的 AppWizard 来 创建一个工程文件

图 7.1 新建工程文件窗口

www.BOOKOO.com.cn

320


Visual C++6.0 编程实例与技巧

如图 7.1 所示 在窗口 Project 页的右上角的 Project name 编辑框中 输入你给应用程序的工程文件取的名称 必须给工程文件取个名字 才 能继续下一步 在下面的 Location 编辑框中输入工程文件放置的目录 Visual C++ 为你选放了一个缺省的路 径

你可以更改这个路径

在这个子目录下

将存放 AppWizard 为你生成的所有文件 以及你 以后要增加的资源文 件

生成的可执行文件等

所以一般要给这个子目录预留比较大的空

间 Platform 编辑框中是该工程文件的操作系统环境

一般根据计算机

的硬件配置决定 通常选 用 Visual C++给出的缺省环境就行了 确定了工程文件的类型 按钮进入 AppWizard

名称

路径和操作系统环境后

单击 OK

产生应用程序的框架

7.2 AppWizard AppWizard 能为你产生应用程序的一个基本框架

这是一个通用的

基本框架 在此基础上 可以根据应用程序的具体要求来添加一些特征 和资源 AppWizard 通过人机对话的形式

一步一步地指导你产生一个什么

样的应用程序框架 首先是 AppWizard 的第一步 如图 7.2 所示

www.BOOKOO.com.cn

321


Visual C++6.0 编程实例与技巧

图 7.2 AppWizard 的第一步 What type of application would you like to create 这是询问你要产生 一个什么类型 的应用程序

下面有三种选择

选择不同的项时

左边

会显示出不同类型的窗口模型 (1) Single document 选择这一项是允许你产生一个单文本界面(SDI) 结构的应用程序 单文本界面 就像 Windows 中的写字板 不允许用户 同时使用多个文本 只能使用一个文本 (2) Multiple documents 选择这一项是允许你产生一个多文本界面 (MDI)结构的应用程 序

多文本界面 就像 Windows 的文件管理器

允许用户在主窗口中一次打开多个文本 缺 省选项为该项(图 7.2 显示 的就是选择该项时的窗口) www.BOOKOO.com.cn

322


Visual C++6.0 编程实例与技巧

(3) Dialog based 选择这一项是允许你产生一个基于对话框结构的应 用程序 基于对话框结构的应用程序 就像 AppWizard 在每一步所显示 的对话框 如图 7.3 所示 主要用于人机 对话 让用户传送一些消息给 计算机 我们这一章的各种控件的制作 选择 Dialog based 这一项

都是在对话框窗口中进行的

所以

选择了这一项后 将显示如图 7.3 所示的窗

图 7.3 选择 Dialog based 项后的窗口 What languange would you like your resources in?这是询问在你的资 源中选择哪种语言 按右边的箭头 弹出一个下拉列表框 列出了系统 中的各种可使用的语言

如果你想要选 择的不是英语 www.BOOKOO.com.cn

那么系统中必

323


Visual C++6.0 编程实例与技巧

须 有 关 于 这 种 语 言 的 动 态 链 接 库 (. DLL) 文 件

文件的名称 是

APPWZxxx.DLL,其中 xxx 这三个字母表示的是该语言的简称 例如 选 择汉语的话

则.DLL 文件名为 APPWZCHS.DLL

选择英语的话

为 APPWZENU.DLL 确定了应用程序的类型和使用资源的语言后 单击底部的 NEXT 按 钮 进行 AppWizard 的第二 步 进入第二步后

显示一个如图 7.4 所示的窗口

图 7.4 AppWizard 生成对话框的第二步 在这一步的窗口中 询问了好几个问题 What features would you like include 这是询问你希望你的对话框窗 口拥有哪些特征

下面有供选择的三项

www.BOOKOO.com.cn

324


Visual C++6.0 编程实例与技巧

(1) About box 如果你想要 AppWizard 产生关于一个消息窗口的代 码 那么可以选择这项

这个消息窗口称为 About box 显示的是关于

你的应用程序的软件版本的信息 缺省的情况 是选择这项 (2) Context sensitive Help 如果你希望 AppWizard 产生一系列关于上 下文帮助的帮助文件 就选择这一项 选择这一项必须有 Help 编译器 若没有的话

可以重新安装上 Help 编译 器

(3) 3D controls 选择这一项可以使你的控件具有三维立体效果 缺省 的情况是选择这项 What other support Would you like to include 这是询问你的应用程 序还需要包括哪些 支持

有 Automation 和 ActiveX Controls

(1) Automation 选择这项可以使你的应用程序能利用其他程序中实 现的对象 或者让你的 应用程序的对象能被 Automation 客户所使用 (2) ActiveX Controls 如果你希望应用程序中使用 ActiveX Controls 就可以选择这一项

若你没有选择此项

ActiveX Controls 那么 中加上一句

但是后来又想在工程中插入

你可以在你的 程序的 InitiaInstance 成员函数

AfxEnableControlContainer 缺省状况是选 择此项

Would you like to include WOSA support

这是询问你是否需要

WOSA 支持 如果选择 Wind ows Sockets 就可以通过 Internet 的 TCP IP 协议与外界通信 Please enter a title for your dialog 是提示你输入你要创建的对话框的 标题名

缺省的情况是与你的工程文件的名称一样

确定了这几项后 单击底部的 NEXT 按钮 进入第三步 进入第三步后

显示一个如图 7.5 所示的窗口

www.BOOKOO.com.cn

325


Visual C++6.0 编程实例与技巧

图 7.5 AppWizard 生成对话框的第三步 在这个窗口中询问了两个问题 Would you like to generate source file comments

如果选择了这一项

AppWizard 生成的源代码文件中会产生并插入注释 这些注释中包含有 告诉用户在哪里加入自己增加的代码 缺省情况是选择此项 How would you like in use the MFC library 这一项是询问选择哪一 种 MFC 库 (1) As a shared DLL 选择这一项是把 MFC 类库作为共享的动态链接 库(DLL)连接到你的应用程序中 链接库

当运行应用程序时可以调用这个动态

如果你的程序中包含多个可执行 文件

www.BOOKOO.com.cn

那么动态链接库可以

326


Visual C++6.0 编程实例与技巧

节省很多硬盘和内存空间

缺省情况时选择这一项

(2) As a statically linked library 选择这一项是连接一个静态的 MFC 类库到你的应用程序 确定以上几项后 单击底部的 NEXT 按钮 进入 Wizard 生成对话框 应用程序的第四步 即最后一步 进入第四步后 显示一个如图 7.6 所示的窗口 在这个窗口中显示 AppWizard 为你的应用程序生成的派生类的一些 情况 AppWizard creates the following classes for you 在这个编辑框中显示 的是 AppWizard 生成的派生类的名称

单击某一项使之高亮化

则在

下面的几个编辑框中将显示这个派生类 的一些情况 (1) Class name

派生类名

(2) Base class

基类名

(3) Header file

声明派生类的头文件名

(4) Implementation fi1e

定义派生类的源代码文件名

由于我们在前面选择的是基于对话框的应用程序 所以 Appw1zard 自动生成两个派生类 基类 CWinapp 的派生类和基类 CDialog 的派生类 如果你选择的是 SDI 或 MDI 应用程序 则会生成 更多的派生类 在后 面我们将会讲到这两种类型的应用程序

www.BOOKOO.com.cn

327


Visual C++6.0 编程实例与技巧

图 7.6 AppWizard 生成对话框的最后一步 在 Class name 编 辑 框 中 你 可 以 自 定 义 派 生 类 名 AppWizard 给出的缺省名

其他 几项是不可编辑的

但一般使用

但可以看到给出

的缺省状况 单击底部的 Finish 按钮 弹出一个 New Project Infomation 窗口 如 图 7.7 所示 显示出前

www.BOOKOO.com.cn

328


Visual C++6.0 编程实例与技巧

图 7.7 AppWizard 的信息对话框 面四步中你为新的工程文件所定义的所有特性

现在你可以最后确

认一下 你要建立的应用程序框架是否具有这些特性 单击 OK 按钮 AppWizard 就开始按照这些特征为你生成应用程 序框架

生成以后就

不能再更改了 单击 Cancel 按钮退回到前一步 并且可以依次退回到任何一步 更 改某一项特性 确认这些特性后 单击 OK 按钮就完成了 AppWizard 的框架编程工 作 接下去该是可视化编辑界面并编写代码与之相连的过程

www.BOOKOO.com.cn

329


Visual C++6.0 编程实例与技巧

7.3 单文档界面(SDI) 单文档界面(SDI)编程 (SDI)编程 在前面的章节中

我们制作的应用程序是基于对话框窗口的应用程

序 目的是学习控件的制作和用法 在这一章中我们将要介绍如何制作 一个单文档界面(SDI)的应用程序 例子

通 过一个简单的单文档应用程序的

主要理解可视化和文档的概念以及它们之间的关系

7.3.1 Person 应用程序 在编写 Perm 应用程序之前 我们先看看该应用程序要完成一些什么 功能

运行应用程序后的主窗口如图 7.8 所示

图 7.8 Person 应用程序主窗口 在应用程序的主窗口中有四个编辑框

Name

Age

Sex 和 Job

我们还可以看到在窗口的左上角有两个菜单项 File 和 Help 这是 两个弹出式菜单 图 7.9 和图 7.10 显示了这两个弹出式菜单

www.BOOKOO.com.cn

330


Visual C++6.0 编程实例与技巧

图 7.9 Person 应用程序的 File 弹出式菜单

图 7.10 Person 应用程序的 He1p 弹出式菜单 (1)在这些编辑框中输入一些内容 如图 7.11 所示

图 7.11 在 Person 的主窗口中输入内容 (2)在应用程序的 File 菜单中选择 Save as 项

保存输入的内容

显示一个 Save as 对话框 如图 7.12 所示

www.BOOKOO.com.cn

331


Visual C++6.0 编程实例与技巧

图 7.12 Person 应用程序的 Save as 对话框 (3)在文件名编辑框中输入要保存的文件名 例如 PERSON1 细心的读者可能会发现

在文件类型的编辑框中显示的是 PER

File(*.per) 所以输入文 件名后 文件的后缀名自动设为.per 这个后缀 名是可以制作的 在这一章的后面将会讲到 如何制作这个后缀名 (4)单击 Save as 的“保存”按钮 这样输入的内容保存在文件 PERSON1.per 中 同时窗口的标题也改 为 PERSON1.perPerson 下面检测一下是否已经将数据存入到 PERSORN1.per 中 (5) 选择 File 菜单中的 Exit 项 退出 Person 应用程序 (6)再执行一次该应用程序 (7)选择 Person 应用程序 File 菜单中的 Open 项

显示一个 Open 对

话框窗口 (8)在 Open 对话框窗口中选择文件 PERSON1.per 这时 Person 应用 程序在屏幕上 显示出 PE RSON1.per 的内容 说明刚才我们正确地存入 了输入的数据

并成功地从文件中调 出来

显示在屏幕上

知道了 Person 应用程序的功能 下面我们开始制作该应用程序

www.BOOKOO.com.cn

332


Visual C++6.0 编程实例与技巧

7.3.2 生成工程文件 首先是生成 Person 应用程序的工程文件 按下面的步骤生成 PersOn 应用程序的工程文件 (1)在 Visual C++中选择 File 菜单中的 New 选项 出现一个对话框窗 口

该窗口中有四个制表页

File

Projects

Workspaces 和 Other

Documents (2) 选 择 Projects 这 一 页

在 该 页 左 边 的 编 辑 框 中 选 择 MFC

AppWizard(exe)项 (3)在 Projects name 编辑框中输入要创建的工程文件的名称 我们给 这个应用程序取名为 person (4)Location 编辑框中描述的是放置该工程文件的路径(位置) 这可以 根据用户需要

输入自己希望放置该文件的位置

(5)确定工程文件的类型 名称和路径选项后 按 OK 按钮

就可以

开始制作该工程文件了 (6)Visual C++显示一个 MFC AppWizard-Step 1 的窗口(如图 7.13) 这是生成工程 文件的第一步

在这一步中

将让你选择创建什么类型

的应用程序以及资源文件使用什么窗口 (7)在 What type of application would you like to create 中 选择 Sing1e documen t 选项 注意 这里与以前不一样 在前面的例子中 我们选择的都是 Dialog based 选项 是基于对话框的应用程序 而现在是制作一个单文档的应 用程序 所以选择的是 single document 选 项 (8)在 What language would you like your resources in 中 选择“英语 美国

” 项 www.BOOKOO.com.cn

333


Visual C++6.0 编程实例与技巧

(9)单击 Next 按钮 进入第二步 Visual C++显示一个 MFC AppWizard Step 2 of 6 的窗口(如图 7.14) 该窗口是生 成工程文件的第二步 (10)在 What database support would you like to include 中 选择 None

表示不需 要数据库的支持

(11)单击 Next 按钮 进入第三步

图 7.13 Person 应用程序的 Wizard 的第一步

www.BOOKOO.com.cn

334


Visual C++6.0 编程实例与技巧

图 7.14 Person 应用程序 AppWizard 的第二步 Visual C++显示一个 MFC AppWizard Step 3 of 6 的窗口(如图 7.15) 该窗口是生 成工程文件的第三步 (12)在 What compound document support wou1d you like to include 中 选择 None (13)单击 Next 按钮 进入第四步 Visual C++显示一个 MFC AppWizard step 4 of 6 的窗口(如图 7.16) 该窗口是生 成工程文件的第四步

www.BOOKOO.com.cn

335


Visual C++6.0 编程实例与技巧

图 7.15 Person 应用程序 AppWizard 的第三步

www.BOOKOO.com.cn

336


Visual C++6.0 编程实例与技巧

图 7.16 person 应用程序 AppWizard 的第四步 (14)在 What features would you like to include

问题中 选择 3D

controls(三维 图形控件)选项 (15)在 How filelist

n1any

fi1es

would

you

like

on

your

recent

使用缺省值 4 表示在 File 菜单中将显示 4 个最后打开过

的文件 (16)单击 Next 按钮 进入第五步 Visua1 C++显示一个 MFC AppWizard 一 Step 5 of 6 的窗口(如图 7.17)

该窗口是 生成工程文件的第五步

www.BOOKOO.com.cn

337


Visual C++6.0 编程实例与技巧

图 7.17 Person 应用程序 AppWizard 的第五步 (17)在 Would you like to generate source file comments

中 询问是否

在生成源代 码文件中加注释 应选择 Yes 因为在 AppWizard 生成的应 用程序源代码文件(.cpp 文件)中 加上代码的注释 有助于阅读和理解源 代码的含义 (18)在 How would you like to use the MFC library 问题中 选择使用 MFC 的动态链接 库(DLL)

而不是静态链接库

(19)单击 Next 按钮 进入最后一步 Visual C++显示出生成工程文件的最后一步(如图 7.18) 出现 MFC AppWizard Ste p 6 of 6 的窗口 类名和文件名

可以看到 AppWizard 给出的所创建的

在这一步中我们将修改应 用程序视类的基类

www.BOOKOO.com.cn

338


Visual C++6.0 编程实例与技巧

图 7.18 Person 应用程序 AppWizard 的最后一步 (21)单击列表框中的 CPersonView 项 在下面的列表框显示这个派生类的名称 基类 源代码文件名称等 (22)单击 base class 组合框的箭头按钮

在弹出的列表框中选择

CFormView 项 因为只有 从 CFormView 基类中派生出来的视类对象中 才能放置控件 (23)单击 Finish 按钮 显示一个 New Project Information 的窗口 该 窗口显示了前面几 步所选择的全部设置 的类以及应用程序的全部特征 径之中 正确

通过这些

新创建

还显示了 该应用程序将生成在哪个路

你可以最后检查一下

如果发现不对的地方

包括应用程序类型

应用程序的设置是否 完全

可以按 Cancel 键取消前面的操作

www.BOOKOO.com.cn

339


Visual C++6.0 编程实例与技巧

AppWizard 重新制作

检查后若完全正确 则执行后面的步骤

(24)单击 OK 按钮 AppWizard 完成应用程序的自动生成工作

在指定的目录下生成应

用程序框架所必须的全部 文件 7.3.3 文档和视 什么是视 对于初次接触 Visual C++6.0 的读者来说 “视”是一个难以理解的概 念 因为在 汉语中这 个词是一个动词 是“看”的意思 而在这里是作 为一个名词来用

“视”是英语 中的单词“View”翻译过来的

实际上

在 MFC 类库中 有一个很重要的基类 CView 这就是通常所说 的“视 类” 也许理解“视”的英语意思“View”更加容易一些

它可以理解为“显

示” 也就是说 将 数据显示出来 可以显示在屏幕上 也可以从打印 机中输出 所以视用于连接文档和输出设备 对于用户来说

它实际上是一个普通的窗口

可以被关闭

移动和

改变它的的尺寸 就像 Wi ndows 的其他窗口一样 而对于程序员来说 它是从类库中的类 CView 派生出来的一个对象

视对象的行为由

CView 类及其派生类的成员函数和数据成员所决定 文档的概念很好理解

它就是存储数据的载体

可以在文档中对数

据进行存盘和调出数据 那么为什么要使用“文档

视”结构呢

使用这种结构的一个明显的

好处就是 一个文档的数据可以有多个视的显示

www.BOOKOO.com.cn

340


Visual C++6.0 编程实例与技巧

7.3.4 界面的可视化编程 1. 应用程序类窗口的可视化实现 AppWizard 已经为 Person 应用程序生成了工程文件和工程工作区文 件

在 Person 的工作区中选择 ResourceView 制表页 如果 Person 的工

作区还没有打开 在 File 主对话框中选择 Open W orkspace 项 打开 Person 工作区文件 在 AppWizard 中 我 们 已 经 把 应 用 程 序 视 类 的 基 类 定 义 为 CFormView 这个类与对话框类很相似 可以在窗口中放入各种控件 这些控件是基于对话框模板的资源 所以我们可以在 ResourceView 制表页中发现 IDDPERSONFORM 这 一项

说明 我们可以像编辑对话框一样

在对话框编辑器中对

CFormView 类的对象进行可视化设计 我们现在要做的工作就是在这个视类窗体中进行界面设计 (1) 在 ResourceView 制 表 页 中 打 开 Dialog 资 源 组

然后双击

IDDPERSONFORM Visual C++6.0 在右边的工作台中显示可以进行可视化编辑的 IDDPERS OWFORM 对话框

并打开一个控件工具窗口

(2)删除 AppWizard 在对话框窗口中生成文本 contro1s on this dial og,删除这段文本后

TODO

Place form

我们才能在对话框中制作

Person 应用程序的界面 (3)在控件工具窗口中选择相应控件 将它们放在对话框中的适当的 位置

并通过鼠标拖 动控件边框的小黑方块

调整控件的大小

用前面几章中介绍的方法设置所有对象的属性

各个对象的属性设

置如表 7.1 所列 www.BOOKOO.com.cn

341


Visual C++6.0 编程实例与技巧

表 7.1 Person 应用程序主对话框窗口中各个对象的属性 对象

ID

Caption

分组框

IDCSTATIC

静态文本框

IDCSTATIC

Name

静态文本框

IDCSTATIC

Age

静态文本框

IDCSTATIC

Sex

静态文本框

IDCNAMEEDIT

Job

编辑框

IDCAGEEDIT

编辑框

IDCSEXEDIT

编辑框

IDCJOBEDIT

注意 在设置编辑框的属性时 需要对编辑框的 sty1es 属性进行一 些修改 在 Multiline 复选框中设置选中标志 在 Want Return 复选框中设置选中标志 设置这两项是为了在编辑框中允许按 Enter 键 若没有设置这两项 则按 Enter 键时就会退出该应用程序 按表 7.1 中的内容在对话框中制作控件 完成后的对话框如图 7.19 所示

www.BOOKOO.com.cn

342


Visual C++6.0 编程实例与技巧

图 7.19 设计完成后的 Person 应用程序对话框 (4)选择 File 菜单中 Save 项 保存所做的工作 2. 菜单条的可视化实现 在图 7.8 中我们可以看到

Person 应用程序的主菜单只有两个 的

AppWizard 创建的菜单条中有三项 除了 File 和 Help 外 还有一项 Edit 所以要实现应用 程序的菜单条是很简单的 只要删除掉 AppWizard 提 供的菜单项 Edit (1) 在 ResourceView 制 表 页 中 打 开 Menu 资 源 组

然后双击

IDDMAINFRAME 显示一个编辑状态的菜单条 菜单条中有三项 File Edit 和 Help 其中的 Edit 项在 Person 应用程序中是不需要的 应删除

www.BOOKOO.com.cn

343


Visual C++6.0 编程实例与技巧

(2)单击菜单条中的 Edit 项 (3)按键盘上的 Delect 键

屏幕上将显示打开的 Edit 弹出式菜单

Edit 菜单项被删除

(4)选择 File 菜单中 Save 项 保存所做的工作 3. 给对话框 IDDPERSONFORM 中的控件连接变量 对话框 IDDPERSONFORM 中有四个编辑框控件 在后面编写代码 时

需要 使用它们的对象名

所以要给它们连接变量

作为这些控件

的对象名 (1)将鼠标移到对话框 IDDPERSONFORM 的 Name 编辑框上

单击

鼠标右键 在弹出的列表框中选中 Classw1zard 项 Visual C++6.0 显示 ClassWizard 窗口 (2) 选择 Member Variables 制表页 (3)选择 Class Name 为 CPersonView 因为是在应用程序的视类中进 行界面设计 (4)在 Control IDs 中单击 IDCNAMEEDIT 项 使之高亮化 (5)按右边的 Add Variable 按钮 Variable 窗口

Visual C++显示一个 Add Member

设置成员变量名为 mName 类别为 Value 变量类型为

CString (6)按 OK 按钮 Visual C++把 mName 变量加到了 CPersonView 类中 Add M ember Variable 窗口消失 (7)按 ClassWizard 中的 OK 按钮 这样编辑框 IDNAMEEDIT 就连接 了一个 变量 mName 根据表 7.2 中的定义 用同样的方法连接变量到其他的编辑框中 表 7.2 IDDPERSONFORM 对话框的变量表 ID

变量名 www.BOOKOO.com.cn

类别变 344


Visual C++6.0 编程实例与技巧

量类型 IDCNAMEEDIT

mName

Value

mAge

Value

mSex

Value

mJob

Value

CString IDCAGEEDIT CString IDCSEXEDIT CString IDCJOBEDIT CString 下面我们运行一下可视化编写后的应用程序 (1)从 Build 主对话框中选择 Build Person.exe 项 Visual C++ 编译和链接 Person 应用程序 (2)从 Build 主对话框中选择 Execute Person.exe 项 Visual C++执行 Person 应用程序 应用程序的界面菜单条上的菜单项也正如我们所希望的 注意

虽然现在我们还没有给应用程序添加任何代码

但是它已经

具有了一些功能 如 File 菜单中的 Save 和 Save as 项已经可以往文件中 存数据 在编辑框中按 Enter 键 没有任何反应 因为现在已经允许在编辑框 中插入回车符 (3)选择 File 莱单中的 Exit 项 退出 Person 应用程序 7.3.5 添加代码 1. 声明文档类中的数据成员 前面在视类中声明了几个数据成员 即给对话框 IDDPERSONFORM

www.BOOKOO.com.cn

345


Visual C++6.0 编程实例与技巧

中的编 辑框连接的变量 在对话框 IDDPERSONFORM 的设计过程中 已经创建了四个变量 mName

mAge

mSex 和 m Job 因为 IDDPERSONFORM 是与应用程

序的视类 CFormView 相连 接的 所有这三个变量是应用程序视类的数 据成员 下面我们要在文档类中声明几个数据成员

分别与视类中的这几个

数据成员相对应 虽然没有必要使文档类的数据成员与视类的数据成员有相同的名 字 但是 这些变量使用与视类中的数据成员相同的名称 便于阅读源 代码程序 数据成员的声明应该在类的声明中

而类的声明放在头文件中

此 Person 应用程序中文档类数据成员的声明放在头文件 PersonDoc.h 中 打开头文件 PersonDoc.h

在文件中输入以下代码

PersonDoc.h : interface of the CPersonDoc class

#if

!defined(AFXPERSONDOCH40CDCE8D8

F9E11D2B2A0F73190299F97INCLUDED) #define AFXPERSONDOCH40CDCE8D8F9E11D2B2A0F73190299F97INCLUDE D

www.BOOKOO.com.cn

346


Visual C++6.0 编程实例与技巧

#if MSCVER > 1000 #pragma once #endif

MSCVER > 1000

class CPersonDoc : public CDocument { protected:

create from serialization only

CPersonDoc(); DECLAREDYNCREATE(CPersonDoc) Attributes public:

编写代码处 CString mAge; CString mName; CString mJob; CString mSex;

Operations public: Overrides ClassWizard generated virtual function overrides {{AFXVIRTUAL(CPersonDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar);

www.BOOKOO.com.cn

347


Visual C++6.0 编程实例与技巧

}}AFXVIRTUAL Implementation public: virtual ~CPersonDoc(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: Generated message map functions protected: {{AFXMSG(CPersonDoc) NOTE - the ClassWizard will add and remove member functions here. DO NOT EDIT what you see in these blocks of generated code ! }}AFXMSG DECLAREMESSAGEMAP() };

{{AFXINSERTLOCATION}} Microsoft Visual C++ will insert additional declarations immediate ly before the previous line.

www.BOOKOO.com.cn

348


Visual C++6.0 编程实例与技巧

#endif !defined(AFXPERSONDOCH40CDCE8D8F9E11D2B2A0F73190299F 97INCLUD ED) 声明了这几个变量以后 下一步就是初始化变量 2. 初始化文档类的数据成员 初始化文档类的数据成员的代码要编写在文档类的成员函数 OnNewbeument()中 因为该函数在下列情况之一时被执行 用户启动应用程序时 选择 File 菜单中的 New 选项时 (1)在菜单 New 中选择 Classwizard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 C1ass name 中选择 CPersonDoc 项 (3)在 Object IDs 中选择 CPersonsDoc 项 (4)在 Messages 中选择 OnNewDocument 项 (5)按右边的 Add Function 按钮 这样 在 C1assWizard 窗口的 Member functions 中增加了一个成员函 数 OnNewDocument() (6)单击 ClassWizard 窗口中的 Edit Code 按钮 Visual C++ 显示源代码文件 PersonDoc.cpp OnNewDocument() 处

并将光标停在函数

等待你定义函数的内容

(7)在函数中输入以下代码 BOOL CPersonDoc::OnNewDocument() { if (!CDocument::OnNewDocument())

www.BOOKOO.com.cn

349


Visual C++6.0 编程实例与技巧

return FALSE; TODO: add reinitialization code here (SDI documents will reuse this document)

编写代码处 mAge=

;

mName=

;

mJob=

;

mSex=

;

return TRUE; } 初始化文档类数据成员的代码很简单

就是给每一个编辑框变量赋

一个空值 当应用程序第一次运行时 编辑框中什么也不显示 当然 如果你希望显示一些数据的话

可以给这些数据成员赋其他的值

3. 初始化视类的数据成员 我们知道对于类中的数据成员

首先应该声明

然后才能初始化

那么为什么这里不用先声明视类的数据成员就初始化呢 其实我们在前面的可视化设计时就已经声明了视类的数据成员

可视化设计时 有一步是给编辑框连接变量 实际上是在视类中把这些 编辑框声明为它的数据成员

所以就不需要编 写代码来声明视类的数

据成员了 视的作用是显示文档中的数据 员相对应的文档类的数据 成员

我们已经初始化了与视类的数据成

所以初始化视类的数据成员也就是把

www.BOOKOO.com.cn

350


Visual C++6.0 编程实例与技巧

文档类的初始化值传递到视类中

显示在屏幕上

初始化视类的数据成员的代码要编写在视类的成员函数 OnInitialUpdate()中 因为该函数在下列情况之一时被执行 (1)用户启动应用程序时 (2)选择 File 菜单中的 New 选项时 (3)选择 File 菜单中的 Open 选项时 我们要在函数 OnInitialUpdate()中编写代码 用文档类数据成员的值 来修改视类的数据成员 所以当用户启动应用程序或者打开和新建一个 文件时 都会用文档类数据成员的值来修改视类的数据成员 这样就完 成了视类的数据成员的初始化工作 (1)在菜单 View 中选择 ClassWizard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 Class name 中选择 CPersonsView 项 (3)在 Object IDs 中选择 CPersonsView 项 (4)在 Messages 中选择 OnInitialUpdate 项 (5)按右边的 Add Function 按钮 这样 在 ClassWizard 窗口的 Member Functions 中增加了一个成员函 数 OnInitialUpdate () (6)单击 ClassWizard 窗口中的 Edit Code 按钮 Visual C++显示源代码文件 PersonView. cpp OnInitialUpdat e()处

并将光标停在函数

等待你定义函数的内容

(7)在函数中输入以下代码 void CPersonView::OnInitialUpdate() { www.BOOKOO.com.cn

351


Visual C++6.0 编程实例与技巧

CFormView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit();

编写代码处 CPersonDoc* pDoc=GetDocument(); mAge=pDoc->mAge; mName=pDoc->mName; mJob=pDoc->mJob; mSex=pDoc->mJob; UpdateData(FALSE);

} 我们分析一下编写的代码 第一行语句是用函数 GetDocument()来取得文档的指针 pDoc: CPersonDoc* pDoc=GetDocument(); 然后用指针 pDoc 获得文档中数据成员的值来代替视类的数据成员: mAge=pDoc->mAge; mName=pDoc->mName; mJob=pDoc->mJob; mSex=pDoc->mJob; 注意

在这几条语句中

虽然等号两边的变量名是一样的

但实际

上并不是同一个变量 左边的变量表示的是视类的数据成员 而右边的 变量因为有一个指向文档的指针

所以表示的 是文档类的数据成员

最后一行语句把视类的数据成员显示在屏幕上

www.BOOKOO.com.cn

352


Visual C++6.0 编程实例与技巧

UedateDeta(FALSE) 这里我们用到视类的一个很重要的成员函数 GetDocument() 它用 于在视类中访问文档中的数据成员 可以参看本章后面关于视类的成员 函数的介绍

了解该函数以及其他一些成员 函数的用法

这对编程很

有帮助 4. 连接文档类和视类的数据成员 我们在运行应用程序时

往编辑框中输入数据或修改数据

都只是

修改编辑框所对应的视类的数据成员的值 而它所对应的文档类的值并 没有随之变化

所以我们还必须把文档类的数 据成员和视类的数据成

员连接起来 使得当视类中的数据发生变化时 文档类中的相应数据 成 员也发生变化 无论用户什么时候更改这些变量

即在编辑框中输入内容时

应用

程序都应该更改相应的文档中的数据成员 (1)在菜单 View 中选择 ClassWizard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 Class name 中选择 CPersonView 项 (3)在 Object IDs 中选择 IDCNAMEEDIT 项 (4)在 Message 中选择 ENCHANGE 项 ENCHANGE 消息表示编辑框的内容发生改变 编辑框进行操作

在任何时候用户对

都将发送该消息

(5)按右边的 Add Function 按钮 (6)显示一个对话框窗口

建议函数名为 OnChangeNameEdit()

(7)按 OK 按钮 这样 在 ClassWizard 窗口的 Member functions 中增加了一个成员函 www.BOOKOO.com.cn

353


Visual C++6.0 编程实例与技巧

数 OnChangeNameEdit( ) (8)单击 ClassWizard 窗口中的 Edit Code 按钮 Visual C++显示源代码文件 PersonView. cpp

并将光标停在函数

OnChangeNameEd it()处 等待你定义函数的内容 (9)在函数中输入以下代码 void CPersonView::OnChangeNameEdit() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask() with the ENMCHANGE flag ORed into the mask. TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CPersonDoc* pDoc=GetDocument(); pDoc->mName=mName; pDoc->SetModifiedFlag();

} 编写的第一行语句 UpdateData(TRUE) 用于将编辑框的当前值来代替编辑框在视类中相对应的变量 要注 意这里的变量不是文档类中相对应的变量 所以下面我们还要用这个视

www.BOOKOO.com.cn

354


Visual C++6.0 编程实例与技巧

类的变量值来修改文档类中的变量值 下面两条语句 CPersonDoc* pDoc=GetDocument(); pDoc->mName=mName; 获取文档中的一个指针 然后用视类的成员变量的值替换指针所指 的文档类的成员变量的值 最后一条语句 pDoc->SetModifiedFlag(); 函数 SetMedifiedFlag()是文档类中的成员函数

它设置文档类的

Modified 标志为 TRUE 当用户保存了文档后

Medified 标志被 Visual C++自动设置为

FALSE 指示数据已 经保存 如果 Modified 标志设置为 TRUE 时

用户要退出应用程序 Visual

C++就会显示一个警告信息 提示用户还没有保存文档 问是否保存被 修改的文档 现在我们已经把编辑框队 Name 在文档类中的数据成员和在视类中 的数据成员连接起来了 也就是说 当视类中该数据成员发生改变时 与之相对应的文档类的数据成员也发生改变 下面我们按照同样的方法

把其他几个编辑框的文档类和视类的数

据成员连接起来 分别在它们的 EN 一 CHANGE 消息所对应的消息处 理函数中编写代码 其他三个编辑框所对应的 ENCHANGE 消息处理函数分别是 IDCAGEEDIT

OnChangeAgeEdit()

IDCSEXEDIT

OnChangeSexEdit()

www.BOOKOO.com.cn

355


Visual C++6.0 编程实例与技巧

IDCJOBEDIT

OnChangeJobEdit()

在这几个函数中编写代码如下 void CPersonView::OnChangeAgeEdit() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask() with the ENMCHANGE flag ORed into the mask. TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CPersonDoc* pDoc=GetDocument(); pDoc->mAge=mAge; pDoc->SetModifiedFlag();

} void CPersonView::OnChangeJobEdit() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask() with the ENMCHANGE flag ORed into the mask.

www.BOOKOO.com.cn

356


Visual C++6.0 编程实例与技巧

TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CPersonDoc* pDoc=GetDocument(); pDoc->mJob=mJob; pDoc->SetModifiedFlag();

} void CPersonView::OnChangeSexEdit() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask() with the ENMCHANGE flag ORed into the mask. TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CPersonDoc* pDoc=GetDocument(); pDoc->mSex=mSex; pDoc->SetModifiedFlag();

}

www.BOOKOO.com.cn

357


Visual C++6.0 编程实例与技巧

5. 向文件中写数据和读数据 在 Persons 应用程序中 当我们从 Rle 菜单中选择 hve 或 Save as 时 要把文档类数据成 员的内容写到文件中 而当我们从 File 菜单中 选择 qrn 时 要把文件中的数据读出来 并把 这些数据赋给文档类的数 据成员 在 Visual C++6.0 中 有一个专门的函数来完成对这些事件的响应 这就是文档类 的 成员函数 Serialize()函数 当用户选择 File 菜单中的 Save Save as 或 Open 选项时 自动调 用该函数 我们要做的工作就是在这个函数中添加一些代码

来实现对文件数

据的读和写: void CPersonDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { TODO: add storing code here

编写代码处 ar<<mAge; ar<<mName; ar<<mJob; ar<<mSex;

} else { www.BOOKOO.com.cn

358


Visual C++6.0 编程实例与技巧

TODO: add loading code here

编写代码处 ar>>mAge; ar>>mName; ar>>mJob; ar>>mSex;

} } Serialize()函数的参数 ar 表示程序将要读和写的文件 6. if 语句的判断条件(ar.IsStoring()) 当往文件中写数据时为 TRUE 也就是说 当从 File 菜单中选择 Save 或 Save as 时 满足条件 而选择 File 菜单中 Open 时 则执行 else 语句后面的代码 所以 if 语句后面的代码 ar<<mAge ar<<mName ar<<mJob ar<<mSex 是把数据存入到文件中 ar 具体指的是哪一个文件 取决于用户在 save as 对话框中选择的文件 而 else 语句后面的代码 ar>>mAge ar>>mName

www.BOOKOO.com.cn

359


Visual C++6.0 编程实例与技巧

ar>>mJob ar>>mSex 是把文件中存的数据调出来 流操作符“>>”表示将文件 ar 中的数 据装载到文档类的数据成员中 注意

从文件中读数据和写数据的顺序应该是一致的

即写数据的

语句 ar<<mAge 放在 if 语句后面的第一句

那么相应的读该数据的语句

ar>>mAge 也应该放在 else 语句后面的第一句 其他语句以此类推

按顺序排放

7.3.6 进一步完善应用程序 我们在运行 Person 应用程序时 在应用程序的 File 菜单中选择 Save 项 显示一个 Save as 对 话框 如图 7.20 所示 在图 7.20 中我们会发现

在文件类型的编辑框中显示的是 PER

File(*.per) 所以输入文件名后 文件的后缀名自动设为.per 了 这个后 缀名是怎么定制的呢 这一节我们要做一些完善 Person 应用程序的工作 使用户在从 File 菜单中选择 Save as 项或 Open 项时 文件的缺省后缀名为.per 表示这 些文件是 Person 应用程序的文件 实现步骤如下 (1)在 Person 工作区中选择 Resource View 制表页 (2)双击该制表页中的 String Table 项

www.BOOKOO.com.cn

360


Visual C++6.0 编程实例与技巧

显示一个 String Table 对话框 我们要修改的是 IDRMAINFRAME 项 (3)双击 IDRMAINFRAME 项 显 示 一 个 String Properties 对 话 框 窗 口 IDRMAINFRAME 的字符 Person

n

nPerson

在该窗口中显示

串的内容 n

n

nPerson.Document

nPerson Document

字符串中的符号“ n ”是分隔符 若两个分隔符之间什么也没有 则表 示为 NULL 所以实际上这个字符串是由下面几个字 符串组成的 Person NULL Person NULL NULL Person.Document Person.Document (4)我们要更改的是第四个和第五个字符串 把第四个字符串改为 PER File(*.per) (5)把第五个字符串改为 .per 修改后的 String Properties 窗口显示字符串如下 Person

n nPerson

nPER File(*.per)

n.per nPerson.Document

nPerson Document (6)选择 File 菜单中 Save 项 保存所做的工作 现在我们编译并运行一下 Person 应用程序的话 会发现缺省的文件 名已经改变成为如图 7.20 所示了 www.BOOKOO.com.cn

361


Visual C++6.0 编程实例与技巧

图 7.20 Person 应用程序的 Save as 对话框 7.3.7 执行 Person 应用程序 到现在为止 我们已经编写完了 Person 应用程序的所有代码 下面 执行一下该应用程序 (1)从 Build 主菜单中选择 Build Person.exe 项 Visual C++编译和连接 Person 应用程序 (2)从 Build 主菜单中选择 Execute Person.exe 项 Visual C++执行 Person 应用程序 (3)按照本章开头的步骤测试该应用程序的功能 如果你是一步一步按前面的步骤来做的话 会与预期的结果一样 (4)选择 File 菜单的 Exit 项 退出 Person 应用程序

www.BOOKOO.com.cn

362


Visual C++6.0 编程实例与技巧

第八章 动态链接库(DLL) 动态链接库 本章将介绍什么是动态链接库 如何用 Visual C++ 6.0 创建一个动态 链接库 以 及如何使用动态链接库等 8.1 动态链接库的概念 动态链接库是一个包含函数的库文件

程序员可以很容易地分配新

的函数和资源 动态链接 库和其他的 C++库不一样 它们是在运行时 和应用程序连接

而不是在编译

链接 过程中被应用

动态链接库的英语拼写是 Dynamic Link Library 简称 DLL 与之 相对应的是静态链接库 (Static Link Library) 它们之间的区别在于 静 态链接库是在应用程序的编译过程中 与应用程序相连接的 接库是在应用程序的执行过程中与应用程序相连接的

而动态链 如果与

应用程序连接的是静态链接库 每一个应用程序在编译过程中都必须拷 贝一份库的 代码 码开销很大

这样造成了资源的浪费

而且使应用程序本身的代

而动态链接库在编译 过程中并不连接应用程序

而是当

应用程序运行时连接 可以和其他的应用程序共享库中的函数和资源 减少了因重复拷贝而造成的应用程序的冗长以及计算机资源的占用 当用户创建使用动态链接库的应用程序时

必须将动态链接库文件

和应用程序的可执行文件 一同分发 为了使应用程序正常地使用动态链接库 这些.DLL 文件必须存放在 下列任何一个子

目录 之中

(1)Windows 的 SYSTEM 子目录 (2)应用程序所在的子目录 (3)配置文件中定义的自动搜索的子目录 www.BOOKOO.com.cn

363


Visual C++6.0 编程实例与技巧

Visual C++6.0 的强大功能使我们编写动态链接库的工作变得非常简 单 下面我们 编写一个简单的.DLL 文件 学习怎样制作动态链接库文 件 8.2 创建一个动态链接库文件 首先使用 Appwizard 建立一个动态链接库的工程文件 然后在源代 码文件中输入应用程序所 需的函数代码 最后编译生成一个.DLL 文件 实际上创建动态链接库的工作很简单

下面我们一步一步地创建一

个.DLL 文件 8.2.1 建立工程文件 我们将要创建的动态链按库取名为 OneDLL 首先是生成 OneDLL 动态链接库工程文件 按下面 的步骤生成 OneDLL 动态链接库的工程 文件 (1)在 Visual C++中选择 File 菜单中的 New 选项 出现一个对话框窗 口

该窗口中 有四个制表页

File

Projects

Workspaces 和 Other

Documents (2) 选 择 Projects 这 一 页

在 该 页 左 边 的 编 辑 框 中 选 择 MFC

Appwizard(dll)项 (3)在 Projects name 编辑框中输入要创建的上程文件的名称 我们给 这个应用程序取名为 OneDLL 如图 8.1 所示

www.BOOKOO.com.cn

364


Visual C++6.0 编程实例与技巧

图 8.1 新建工程文件的对话框窗口 (4)Location 编辑框中描述的是放该工程文件的路径(位置) 这可以根 据用户需要

输 入自己希望放置该文件的位置

(5)确定工程文件的类型 名称和路径选项后 单击 OK 按钮

就可

以制作该工程文件了 (6)Visual C++显示一个 MFC AppWizard-Step 1 of l 的窗口 如图 8.2 所示

这 是生成工程文件的第一步

在这一步中

将让你选择创建什

么类型的链接库应用程序 (7)在 What type of DLL would you like to create?中

有三个选项

Regular DLL with MFC statically linked Regular DLL using shared MFC DLL MFC Extension DLL(using shared MFC DLL)

www.BOOKOO.com.cn

365


Visual C++6.0 编程实例与技巧

第一项是建立一个静态链接库 第二项是建立一个 Win32 应用程序 和 MFC 应用程序都可调用的 动态链接库 第三项是建立一个只有 MFC 应用程序能调用的动态链接库 选择第二项 即 Regular DLL using shared MFC DLL 如图 8.2 所示

图 8.2 OneDLL 动态链接库应用程序 AppWizard 的第一步 (8)在 Would you like to generate source file comments

中 询问是否

在生成源代码 文件中加注释 应选择 Yes 因为在 AppWizard 生成的动 态链接库应用程序源代码文件(.cpp 文件)中加上源代码的注释 有助于 阅读和理解源代码的含义 (9)单击“Finish”按钮 显示一个 New Project Information 的窗口 如 图 8.3 所示 www.BOOKOO.com.cn

366


Visual C++6.0 编程实例与技巧

图 8.3 OneDLL 应用程序 AppWizard 的 New Project Information 窗口 该窗口显示了在前面所选择的全部设置 包括 AppW1zard 生成的动 态链接库程序的源代码文 件名和头文件名 成在哪个路径之中

通过这些

还显示了该应用程序将生

你可以最后检查 一下

应用程序的设

置是否完全正确 如果发现不对的地方 可以按 Cancel 键取消前面的操 作

用 AppWizard 重新制作 检查后若完全正确 则执行后面的步骤 (10)单击“OK”按钮

AppWizard 完成动态链接库程序的自动生成工

在指定的目录下 生成了工程文件所必须的全部文件

www.BOOKOO.com.cn

367


Visual C++6.0 编程实例与技巧

8.2.2 定制 CPP 文件 我们在工程文件的工作区可以看到

生成了这么一些文件

(1)源代码文件: OneDLL.cpp OneDLL.def OneDLL.rc StdAfx.cpp (2)头文件: OneDLL.h Resource.h StdAfx.h (3)资源文件: OneDLL.rc2 其中我们感兴趣的是 OneDLL.cpp 文件和 OneDLL. def 文件 OneDLL.cpp 文件是 DLL 的主要的源代码文件 它包含了 COneDLLApp 类的定义 OneDLL.def 文件包含了 DLL 提供的关于 DLL 在 Wind ows 下运行的一些信息 在这个文件中定义了一些参数 如 DLL 的名称和 属性等

还声明了从 DLL 中输出的函数

下面我们将要定制 OneDLL.cpp 文件和 OneDLL.def 文件 (1) 打 开 OneDLL.cpp 文 件

在该文件后面加上一个自定义的

Message()函数 具体形式 如下 int Message(void) { MessageBox(NULL, This is the example of testing DLL.

,NULL,

MBICONEXCLAMATION); www.BOOKOO.com.cn

368


Visual C++6.0 编程实例与技巧

return 1; } 这个函数非常简单

就是显示一个消息框窗口

我们使用这个函

数的唯一目的就是介绍 如何 向动态链接库中添加函数 中

不可能使用这么简单的函数

在实际应用

但是在动态链接库 中添加函数的基

本方法是一样的 有了函数体后 还必须在文件的头部声明该函数 OneDLL.cpp : Defines the initialization routines for the DLL.

#include

stdafx.h

#include

OneDLL.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

编写代码处 int Message(void);

(2)选择 File 菜单中 Save 项 保存所做的工作 这样 OneDLL.cpp 文件的代码应如下所示 OneDLL.cpp : Defines the initialization routines for the DLL.

www.BOOKOO.com.cn

369


Visual C++6.0 编程实例与技巧

#include

stdafx.h

#include

OneDLL.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

编写代码处 int Message(void);

Note!

If this DLL is dynamically linked against the MFC DLLs, any functions exported from this DLL which call into MFC must have the AFXMANAGESTATE macro added at the very beginning of the function.

For example:

extern

C

BOOL PASCAL EXPORT

ExportedFunction()

www.BOOKOO.com.cn

370


Visual C++6.0 编程实例与技巧

{

AFXMANAGESTATE(AfxGetStaticModuleState()); normal function body here }

It is very important that this macro appear in each function, prior to any calls into MFC. This means that it must appear as the first statement within the function, even before any object variable declarations as their constructors may generate calls into the MFC DLL.

Please see MFC Technical Notes 33 and 58 for additional details.

COneDLLApp BEGINMESSAGEMAP(COneDLLApp, CWinApp) {{AFXMSGMAP(COneDLLApp)

www.BOOKOO.com.cn

371


Visual C++6.0 编程实例与技巧

NOTE - the ClassWizard will add and remove mapping macros here. DO NOT EDIT what you see in these blocks of generated code! }}AFXMSGMAP ENDMESSAGEMAP()

COneDLLApp construction COneDLLApp::COneDLLApp() { TODO: add construction code here, Place all significant initialization in InitInstance }

The one and only COneDLLApp object COneDLLApp theApp;

编写代码处 int Message(void) { MessageBox(NULL, This is the example of testing DLL. www.BOOKOO.com.cn

,NULL, 372


Visual C++6.0 编程实例与技巧

MBICONEXCLAMATION); return 1; }

8.2.3 定制 DEF 文件 (1) 打开 OneDLL.def 文件 将其修改如下 ; OneDLL.def : Declares the module parameters for the DLL. LIBRARY

OneDLL

DESCRIPTION

OneDLL Windows Dynamic Link Library

EXPORTS ; Explicit exports can go here ; ;编写代码处 Message ; 动态链接库的 DEF 文件定义了 DLL 的各种特点 DEF 文件中注释 的标识符和 CPP 文件不一样 注释行用 表示 而不是用

表示

第一行语句 LIBRARY

OneDLL

表示要建立的动态链接库的名称为 OneDLL.DLL 语句 EXPORTS 后面加上在 OneDLL.cpp 文件中编写的函数: Message 表示与该动态链接库连接的 EXE 应用程序都可以调用该函数 现在 我们已经编写了创建 OneDLL.DLL 文件所有的代码 下一步

www.BOOKOO.com.cn

373


Visual C++6.0 编程实例与技巧

是创建该动态链接库文件 (2)选择 Project 菜单中的 Build OneDLL.DLL 项 Visual C++将编译并建立 OneDLL.DLL 文件 这样 我们就已经创建了一个动态链接库文件 OneDLL.DLL 8.3 编写使用动态链接库的应用程序 下面编写一个名叫 TestDLL 的应用程序来测试 OneDLL.DLL 动态链 接库

该应用程序将载入 One DLL.DLL 并调用 Message()函数

8.3.1 TestDLL 应用程序 在编写 TestDLL 应用程序之前 我们先看看该应用程序要完成一些 什么功能 运行应用程序后的主窗口如图 8.4 所示 在应用程序的主窗口中有三个菜单项 File DLL 和 Help DLL 菜单中有两项

Load.dll 和 Test.dll

如图 8.5 所示

www.BOOKOO.com.cn

374


Visual C++6.0 编程实例与技巧

图 8.4 TestDLL 应用程序主窗口

图 8.5 TestDLL 应用程序的 DLL 菜单 (1)选择 Load.dll 项 载入 OneDLL 动态链接库 (2)再选择 Test.dll 项 则弹出一个消息对话框窗口 如图 8.6 所示 显示一句话

“ This is the example of testing DLL

www.BOOKOO.com.cn

375


Visual C++6.0 编程实例与技巧

图 8.6 选择 Test.dll 后弹出的消息对话框 (3)如果你在选择 Test.dll 之前 没有先选择 Load.dll 那么将显示另 一个消息对话框 如图 8.7 所示

图 8.7 不先载入动态链接库而选择 Test.dll 项 这个消息对话框提示要先载入动态链接库 才能使用该动态链接库 www.BOOKOO.com.cn

376


Visual C++6.0 编程实例与技巧

(4)选择 File 菜单的 Exit 选项 退出 TestDLL 应用程序 知道了 TestDLL 应用程序的功能 下面我们开始制作该应用程序 8.3.2 创建应用程序的工程文件 首先是生成 TestDLL 应用程序的工程文件

按下面的步骤生成

TestDLL 应用程序的工程文 件 (1)在 Visual C++中选择 File 菜单中的 New 选项 出现一个对话框窗 口

该窗口中 有四个制表页

Documents

File

Projects

Workspaces 和 Other

如图 8.1 所示

(2) 选 择 Projects 这 一 页

在 该 页 左 边 的 编 辑 框 中 选 择 MFC

AppWizard(exe)项 (3)在 Projects name 编辑框中输入要创建的工程文件的名称 我们给 这个应用程序取名为 TextDLL (4)Location 编辑框中描述的是放该工程文件的路径(位置) 这可以根 据用户需要

输入自己希望放置该文件的位置

(5)确定工程文件的类型 名称和路径选项后 单击 OK 按钮

就可

以开始制作该工程文件 了 (6)Visual C++显示一个 MFC AppWizard Stepl 的窗口 这是生成工程 文件的第一 步

在这一步中

将让你选择创建什么类型的应用程序以

及资源文件使用什么语言 (7)在 What type of application would you like to create 中 选择 Single Documen t 选项 (8)在 What language would you like your resources in 中 选择“英语 美国

” 项

www.BOOKOO.com.cn

377


Visual C++6.0 编程实例与技巧

(9)单击 Next 按钮 进入第二步 Visual C++显示一个 MFC AppWizard Step 2 of 6 的窗口 该窗口是生 成工程文件的第二步 (10)在 What database support would you like to include 中 选择 None

表示不需 要数据库的支持

(11)单击 Next 按钮 进入第三步 Visual C++显示一个 MFC AppWizard Step 3 of 6 的窗口 该窗口是生 成工程文件的第三步 (12)在 What compound document support would you like to include 中 选择 None (13)单击 Next 按钮 进入第四步 Visual C++显示一个 MFC AppWizard Step 4 of 6 的窗口 该窗口是生 成工程文件的第四步 (14)在 What features would you like to include controls(三维图形控件)选项

问题中 只选择 3D

其余各项都不需要

(15)在 What WOSA support would you like to include 中 不选择任 何一项 (16)在 How many files would you like on your recent file list 中 使用 缺省值 4

表示在 File 菜单中将显示 4 个最后打开过的文件

(17)单击 Next 按钮 进入第五步 Visual C++显示一个 MFC AppWizard Step 5 of 6 的窗口 该窗口是生 成工程文件的第五步 (18)在 Would you like to generate source file comments

中 询问是否

在生成源代 码文件中加注释 应选择 Yes 因为在 AppWizard 生成的应 www.BOOKOO.com.cn

378


Visual C++6.0 编程实例与技巧

用程序源代码文件(.cpp 文件)中 加上代码的注释 有助于阅读和理解源 代码的含义 (19)在 How would you like to use the MFC library 问题中 选择使用 MFC 的动态链接 库(DLL)

而不是静态链接库

(20)单击 Next 按钮 进入最后一步 Visual C++ 显 示 出 生 成 工 程 文 件 的 最 后 一 步

出 现 一 个 MFC

AppWizard step 6 of 6 的窗口 可以看到 AppWizard 给出的所创建的类 名和文件名

在这一步中我们将修改应用程序视类 的基类

(21)单击 Base class 组合框的箭头按钮

在弹出的列表框中选择

CFormView 项 (22)单击 Finish 按钮 显示一个 New Project Information 的窗口 该窗口显示了前面几步所 选择的全部设置 包括应用程序类型 新创建的类以及应用程序的全部 特征

还显示了该应用程序将生成在哪个 路径之中

以最后检查一下 地方

应用程序的设置是否完全正确

通过这些

你可

如果发现有不 对的

可以按 Cancel 键取消前面的操作 用 AppWizard 重新制作 检

查后若完全正确

则执行后面的步骤

(23)单击 OK 按钮 AppWizard 完成应用程序的自动生成工作

在指定的目录下生成应

用程序框架所必须的全部文件 8.3.3 菜单条的可视化实现 在图 8.1 中我们可以看到 TestDLL 应用程序的主菜单有三项 File DLL 和 Help 而 Visual C++的 AppWizard 创建的菜单条中的三项是

www.BOOKOO.com.cn

379


Visual C++6.0 编程实例与技巧

File Edit 和 Help 所以要实现应用程 序的菜单 条是很简单的 只要 删除掉 AppWizard 提供的莱单项 Edit 再加上 DLL 项 并修改一下 File 菜 单项就行了 (1) 在 Resource View 制 表 页 中 打 开 Menu 资 源 组

然后双击

IDMAINFRAME 显示一个编辑状态的菜单条 菜单条中有三项 File Edit 和 Help 其中的 Edit 项在该应用程序中是不需要的

应删除

(2)单击菜单条中的 Edit 项 屏幕上将显示打开的 Edit 弹出式菜单 (3)按键盘上的 Delect 键 Edit 菜单项被删除 (4)增加一项菜单 DLL 输入它的子菜单项 Load.dll 和 Test.dll 并设 置子菜单项的 ID 为

IDDLLLOAD 和 IDDLLTEST

(5)删除 File 菜单中的其他子菜单 只留下 Exit 项 (6)选择 File 菜单中 Save 项 保存所做的工作 8.3.4 代码编写 1.声明变量 打开文件 TestDLLView.cpp #include

在文件的前面增加以下代码

TextDLLView.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

www.BOOKOO.com.cn

380


Visual C++6.0 编程实例与技巧

#endif

增加代码处 HINSTANCE handlerDLL=NULL; typedef int(* MESSAGE)(void); MESSAGE Message;

第一条语句 HINSTANCE handlerDLL=NULL; 声明一个全局变量 handlerDLL 并设置初始值为 NULL 该变量用 于存储动态链接库的句柄 下面一条语句 typedef int(* MESSAGE)(void); 声明一个 MESSAGE 的变量类型 用来保存一个不带参数 返回值 为整数的函数指针 语句 MESSAGE Message; 声明一个 MESSAGE 类型的变量 Message 这样 该变量可以被认 为是一个不带参数

返回值为整数的函数

2. 给菜单项 Load.dll 添加代码 在使用动态链接库的函数之前

必须先加载该动态链接库

在正常

的情况下 用户希望应用程序能自动加载动态链接库 而不是通过进行 某个操作后再加载

所以

通常是在应用程序 的入口处编写代码加载

动态链接库 例如在 TestDLL 应用程序中 就应该把加载动态链接库 的

www.BOOKOO.com.cn

381


Visual C++6.0 编程实例与技巧

代码放在 CTestDLLAPP 类的 InitInstance()成员函数中 但是在本示例中

我们是演示如何使用动态链接库

所以不是自动

加载动态链接库 而是通过选择 DLL 菜单中的 Load.dll 项后

才加载

OneDLL 动态链接库 因此 这些代码应放在 Lcod. dll 菜单项的消息处 理函数中 (1)在菜单 DLL 上单击鼠标右键

在弹出的列表对话框中选择

ClassWizard 选项 Visual C++显示一个 CiassWizard 的对话框窗口 (2)在 Object IDs 中选择 IDDLLLOAD 项 (3)在 Class name 中选择 CTestDLLView 项 (4)在 Messages 中选择 COMMAND 项 (5)单击右边的 Add Function 按钮 (6)在弹出的窗口中接受建议的函数名 OnDLLLoad() 这样 在 ClassWizard 窗口的 Member functions 中增加了一个成员函 数 OnDLLLoad() (7)单击 ClassWizard 窗口中的 Edit Code 按钮 Visua1 C++显示源代码文件(.cpp) 并将光标停在函数 OnDLLLoad() 处 等待你定义函数的内容 (8)在函数中输入以下代码 void CTextDLLView::OnDllLoad() { TODO: Add your command handler code here

编写代码处

www.BOOKOO.com.cn

382


Visual C++6.0 编程实例与技巧

if(handlerDLL!=NULL) { MessageBox( The OneDLL.DLL has been loaded.

);

return; } handlerDLL=LoadLibrary(

OneDLL.DLL );

if(handlerDLL==NULL) { MessageBox( Cannot Load the OneDLL.DLL ); } Message=(MESSAGE)GetProcAddress(handlerDLL,

Message );

} 下面我们分析这些代码 第一条语句是一个 if 语句 if(handlerDLL!=NULL) { MessageBox( The OneDLL.DLL has been loaded.

);

return; } 判断 OneDLL 动态链接库是否加载 若全局变量 handlerDLL 不为 NULL

则表示 OneDLL 已经加载

此时显示一个消息对话框“The

OneDLL.DLL has been loaded” 若动态链接库没有被加载

则执行下面一条语句:

handlerDLL=LoadLibrary(

OneDLL.DLL );

www.BOOKOO.com.cn

383


Visual C++6.0 编程实例与技巧

该语句使用 LoadLibrary 函数加载 OneDLL.DLL 并将动态链接库 的句柄赋给变量 handlerDLL 下一条 if 语句: if(handlerDLL==NULL) { MessageBox( Cannot Load the OneDLL.DLL ); } 说明若调用了 LoadLibrary 函数以后

handlerDLL 的值仍然为

则表现动态链接库加载不成功

也显示一个消息对话框

NULL

此时

“Cannot Load the OneDLL.DLL”告诉用户不能 加载 OneDLL 下一条语句: Message=(MESSAGE)GetProcAddress(handlerDLL,

Message );

使用 GetProcAddress 函数将 OneDLL 库中的 Message()函数地址赋 给变量 Message 函数 GetPro cAddress 的第一个参数是动态链接库的句 柄

第二个参数是要提取地址的函数名 此时 全局变量 Message 中包含的是 Message()函数的地址 这就是

说 用户可以把变量 Me ssage 当作函数 Message()来使用 可以在应用 程序中像调用其他函数一样来调用 Message() 函数 3. 给菜单项 Test.dll 添加代码 (1)在菜单 DLL 上单击鼠标右键

在弹出的列表对话框中选择

CassWizard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 Object IDs 中选择 IDDLLTEST 项 (3)在 Class name 中选择 CTestDLLView 项

www.BOOKOO.com.cn

384


Visual C++6.0 编程实例与技巧

(4)在 Messages 中选择 COMMAND 项 (5)单击右边的 Add Function 按钮 (6)在弹出的窗口中接受建议的函数名 OnDLLTest() 这样 在 ClassWizard 窗口的 Member functions 中增加了一个成员函 数 OnDLLTest() (7)单击 ClassWizard 窗口中的 Edit Code 按钮 Visual C++显示源代码文件(.cpp)

并将光标停在函数 OnDLLTest()

处 等待你定义函数的内容 (8)在函数中输入以下代码 void CTextDLLView::OnDllTest() { TODO: Add your command handler code here

编写代码处 if(handlerDLL==NULL) { MessageBox( Please Load the OneDLL.DLL first.

);

return; } Message();

} 该函数的第一条语句 if(handlerDLL==NULL) { www.BOOKOO.com.cn

385


Visual C++6.0 编程实例与技巧

MessageBox( Please Load the OneDLL.DLL first.

);

return; } 检查变量 handlerDLL 是否为 NULL 若是 则还没有加载动态链接 库 显示一个消息对话框提示用户必须先加载 OneDLL 动态链接库 如果该变量不为 NULL 表示已经加载了 OneDLL 动态链接库

执行下一条语句: Message() 这个语句只是简单地调用 OneDLL 库中的 Message()函数 (9)选择 File 菜单中 Save 项 保存所做的工作 8.3.5 执行 TestDLL 应用程序 到现在为止 我们已经编写完了 TestDLL 应用程序的所有代码 下 面执行一下该应用程序: (1)从 Build 主菜单中选择 Build TestDLL.exe 项 Visual C++编译和连接 TestDLL 应用程序 (2)从 Build 主菜单中选择 Execute TestDLL.exe 项 Visual C++执行 TestDLL 应用程序 (3)按照本章开头的步骤测试该应用程序的功能 如果你是一步一步按前面的步骤来做的话 会与预期的结果一样 (4)选择 File 菜单的 Exit 项 退出 TestDLL 应用程序 注意

如果应用程序 TestDLL 不能加载 OneDLL 动态链接库 那是

因为用户没有把该动态链接 库拷贝到下面三个目录的任何一个目录 下

www.BOOKOO.com.cn

386


Visual C++6.0 编程实例与技巧

Windows 的 SYSTEM 子目录 应用程序所在的子目录 配置文件中定义的自动搜索的子目录 这样就无法将动态链接库加载到应用程序中

www.BOOKOO.com.cn

387


Visual C++6.0 编程实例与技巧

第九章 多文档界面(MDI)编程 编程 多文档界面 本章我们将介绍如何制作一个多文档界面(MDI)的应用程序 通过创 建一个简单的多 文档应用程序的例子

理解视的概念以及视和文档之

间的相互关系 9.1 什么是多文档界面(MDI) 什么是多文档界面(MDI) 多文档界面就是指在一个主窗口中可以同时对多个文档进行浏览 编辑和维护 用程序

在 Windows 风格的应用程序中 有许多多文档界面的应

例如

Microsoft Word 就是最经典的多 文档应用程序

用过 Word 的读者都知道 在 Word 中可以同时打开多个文本文件 仅仅通过窗口的切换就能分 别对它们进行编辑等 只能对当前窗口的那一个文本进行编辑

但是在同一时刻

每一个窗 口对应一个文本

但一个文本可以在不同的窗口中打开 在前面的章节中 我们介绍了单文档界面(SDI) SDI 和 MDI 的最大 区别就是

SDI 一次只能 对一个文档进行操作

打开多个子窗口

在主窗口中不能同时

而多文档界面就可以做到这一 点

比单文档界面的效率更高

所以多文档界面

更灵活 同时也更复杂

下面我们要编写的 MDI 应用程序是支持一个文档的多个视 也就是 说 用户可以在多个窗口中打开同一个文档 9.2 Books 应用程序 在编写 Books 应用程序之前 我们先看看该应用程序要完成一些什 么功能 运行应用程序后的主窗口如图 9.1 所示 www.BOOKOO.com.cn

388


Visual C++6.0 编程实例与技巧

图 9.1 Books 应用程序主窗口 从图 9.1 中我们可以看到 在应用程序的主窗口中还有一个窗口 这个窗口称为子窗口 主窗口的标题栏显示为 Books Booksl 在主窗口的上端有一个菜单条

子窗口的标题为 Booksl

莱单条中有三个主菜单项

File

Window 和 Help 在 Booksl 子窗口中有三个编辑框 可以在这三个编辑框中输入数据 三个编辑框分别是 Book

Writer 和 Content 用于输入书名 作者和内

容 (1)在这三个编辑框中任意输入一些数据 (2)选择 File 菜单中的 Save 出现一个 Save As 对话窗口 缺省文件名的后缀为.bks 保存数据到

www.BOOKOO.com.cn

389


Visual C++6.0 编程实例与技巧

文件 Booksl.bds 中

显示的界面如图 9.2 所示

图 9.2 保存文档到 Books1.bks 后的 Books 应用程序 下面我们检测一下菜单中各项的作用 (1)选择 File 中的 New 选项 在 Books 主窗口中又打开一个命名为 Books2 窗口 而原来的子窗口 仍然还在 如图 9.3 所 示

www.BOOKOO.com.cn

390


Visual C++6.0 编程实例与技巧

图 9.3 打开第二个子窗口后的 Books 应用程序 这就是多文档界面(MDI)和单文档界面(SDI)的不同之处

多文档界

面的应用程序可以在一个主窗口中打开多个不同的子窗口 (2)在这个子窗口中任意输入一些数据 然后保存到 Books2.bks 文件 中 (3)选择 File 菜单中的 Close 关闭当前窗口 即 Books2.bks 窗口 而 Book1.bks 窗口仍然打开在 主窗口中 (4)选择 File 菜单中的 Save As 选项 在弹出的 Save As 对话框中 输入 Books2 将 Booksl 另存于 Books2 中 这时会出现一个对话框询问是否替换原来的 Books2 文件 如图 9.4 所示

www.BOOKOO.com.cn

391


Visual C++6.0 编程实例与技巧

图 9.4 Save As 询问对话框 (5)单击“是”按钮 这时 Books2 文件中的内容已经被 Bookl 文件代替了 (6)选择 Window 菜单中的 New Window 选项 在主窗口中又打开一个与 Books1.bks 子窗口一样的子窗口 但两个 子窗口的标题不一样

原子窗口的标题变为 Books1.bks

窗口的标题设为 Books2.bks

1

新建的子

2

如图 9.5 所示 两个窗口中编辑框的内容也是相同的

图 9.5 打开一个与原子窗口相同的子窗口 www.BOOKOO.com.cn

392


Visual C++6.0 编程实例与技巧

在任意一个子窗口中输入或修改数据 改变 实际上

这就是一个文档中的多个视

这两个于窗口对应的文档是一个文档 文档发生改变

另一个窗口中也发生相应的

更新其中一个视时

对应的

与这个文档相关联的其他视也跟着更新

(7)选择 Window 菜单中的 Tile 选项 应用程序以平铺的格式排放 Books1 和 Books2 子窗口 如图 9.6 所 示

图 9.6 平铺格式放置子窗口 (8)选择 Window 中的 Cascade 选项 应用程序以级联的格式将两个子窗口排放在主窗口中 如图 9.7 所 示

www.BOOKOO.com.cn

393


Visual C++6.0 编程实例与技巧

(9)单击两个子窗口右上端的最小化按钮 两个窗口缩小成两个图标 显示在主窗口的左下端 (10)用鼠标拖动最小化图标 放在主窗口的任意位置 如图 9.8 所示

图 9.7 级联格式放置子窗口

www.BOOKOO.com.cn

394


Visual C++6.0 编程实例与技巧

图 9.8 最小化子窗口在主窗口中的随机分布 (11)选择 Window 菜单的 Arrange Icons 选项 两个图标按顺序整齐地排列在主窗口的左下端

www.BOOKOO.com.cn

如图 9.9 所示

395


Visual C++6.0 编程实例与技巧

图 9.9 最小化子窗口的顺序排列 (12)单击 Books1 子窗口的最大化按钮 (13)选择 Window 菜单的 Split 选项 发现在子窗口中出现一根横线和一根竖线

而且此时移动鼠标

条线的交叉点也随着移动 (14)将鼠标移到适当的位置 单击一下鼠标左键 此时子窗口被这一根横线和一根竖线分成四个窗口 如图 9.10 所示 窗口中的内容是一样的 更新其中任何一个窗口 其他的窗口也跟着改 变

www.BOOKOO.com.cn

396


Visual C++6.0 编程实例与技巧

图 9.10 被分割后的子窗口 (15)选择 Help 菜单中 About 选项 弹出一个 About 对话框窗口 现在我们已经完成了 Books 应用程序的各个菜单项的实验 其实这 个应用程序还有一个 特性我们没有看到 还原子窗口的分割状态

使之为一个窗口

仔细观察这个子窗口

发现在水平滚动条的最左 端和垂直滚动条的最右端 各有一个分隔条 (16)用鼠标拖动着两个分隔条 能实现 Window 菜单中 Split 选项的功能 如图 9.10 所示 (17)选择 File 菜单的 Exit 选项 退出 Books 应用程序 现在

我们已经了解了 Books 应用程序的功能 下面将根据这些功

能开始我们的编程

www.BOOKOO.com.cn

397


Visual C++6.0 编程实例与技巧

9.3 生成应用程序的工程文件 首先是生成 Books 应用程序的工程文件 按下面的步骤生成 Books 应用程序的工程文件 (1)在 Visual C++中选择 File 菜单中的 New 选项 出现一个对话框窗 口

该窗口中有四个制表页

File

Projects

Workspaces 和 Other

Documents (2) 选 择 Projects 这 一 页

在 该 页 左 边 的 编 辑 框 中 选 择 MFC

AppWizard(exe)项 (3)在 Projects name 编辑框中输入要创建的工程文件的名称 我们给 这个应用程序取名为 Books (4)Location 编辑框中描述的是放该工程文件的路径(位置) 这可以根 据用户需要

输入自己希望放置该文件的位置

(5)确定工程文件的类型 名称和路径选项后 按 OK 按钮

开始制

作该工程文件 (6)Visual C++显示一个 MFC AppWizard Step 1 的窗口 如图 9.11 所 示

这是生成 工程文件的第一步

在这一步中

将让你选择创建什么

类型的应用程序以及资源文件使用什么语言 (7)在 What type of application would you like to create 中 选择 Multiple Docum ent 选项 (8)在 What would you like your resources in 中 选择“英语 美国 ” 项

习惯于 使用汉语者也可以选择“中文 中国

”项

(9)单击 Next 按钮 进入第二步 Visual C++显示一个 MFC AppWizard Step 2 of 6 的窗口 如图 9.12 所示 该窗口是生成工程文件的第二步 www.BOOKOO.com.cn

398


Visual C++6.0 编程实例与技巧

(10)在 What database support would you like to include 中选择 None 表示不需要数据库的支持 (11)单击 Next 按钮 进入第三步

图 9.11 Books 应用程序 AppWizard 的第一步

www.BOOKOO.com.cn

399


Visual C++6.0 编程实例与技巧

图 9.12 Books 应用程序 AppWizard 的第二步 Visual C++显示一个 MFC AppWizard Step 3 of 6 的窗口 如图 9.13 所示 该窗口是生成工程文件的第三步

www.BOOKOO.com.cn

400


Visual C++6.0 编程实例与技巧

图 9.13 Books 应用程序 AppWizard 的第三步 (12)在 What Compound document support would you like to include 中 选择 None (13)单击 Next 按钮 进入第四步 Visual C++显示一个 MFC AppWizard step 4 of 6 的窗口 如图 9.14 所示 该窗口 是生成工程文件的第四步 (14)在 What features would you like to include

问题中 选择 3D

Controls(三维 图形控件)选项 (15)在 How many files would you like on your recent file list 中 使用 缺省值 4

表示在 File 菜单中将显示 4 个最后打开过的文件

(17)单击 Next 按钮 进入第五步 www.BOOKOO.com.cn

401


Visual C++6.0 编程实例与技巧

Visual C++显示一个 MFC AppWizard Step 5 of 6 的窗口 如图 9.15 所示 该窗口 是生成工程文件的第五步 (18) 在 What style of project would you like ?中 选择 MFC Standard (19)在 Would you like to generate source file comments 在生成源代 码文件中加注释 应选择 Yes

中 询问是否

因为在 AppW1zard 生成的

应用程序源代码文件(.cpp 文件)中 加上代码的注释 有助于阅读和理解 源代码的含义 (20)在 How would you like to use the MFC library 问题中 选择使用 MFC 的动态链接 库(DLL)

而不是静态链接库

图 9.14 Books 应用程序 AppWizard 的第四步

www.BOOKOO.com.cn

402


Visual C++6.0 编程实例与技巧

图 9.15 Books 应用程序 AppWizard 的第五步 (21)单击 Next 按钮 进入最后一步 Visual C++显示出生成工程文件的最后一步 如图 9.16 所示

出现

一个 MFC AppWi zard Step 6 of 6 的窗口 可以看到 AppWizard 给出的 所创建的类名和文件名 在这一步中 我们将修改应用程序视类的基类

www.BOOKOO.com.cn

403


Visual C++6.0 编程实例与技巧

图 9.16 Books 应用程序 AppWizard 的最后一步 (22)单击列表框中的 CBooksView 项 在下面的列表框显示这个派生类的名称 基类 源代码文件名称等 (23)单击 Base class 组合框的箭头按钮

在弹出的列表框中选择

CFormView 项 因为只有从 CFormView 基类中派生出来的视类对象中 才能放置控件 (24)单击 Finish 按钮 显示一个 New Project Information 的窗口 该 窗口显示了前面几 步所选择的全部设置 的类以及应用程序的全部特征 径之中 正确

通过这些

新创建

还显示了 该应用程序将生成在哪个路

你可以最后检查一下

如果发现不对的地方

包括应用程序类型

应用程序的设置是否 完全

可以按 Cancel 键取消前面的操作

www.BOOKOO.com.cn

404


Visual C++6.0 编程实例与技巧

AppWizard 重新制作

检查后若完全正确 则执行后面的步骤

(25)单击 OK 按钮 AppWizard 完成应用程序的自动生成工作 在指 定的目录下生成应用程 序框架所必须的全部文件 9.4 界面的可视化编程 9.4.1 应用程序窗口的可视化实现 AppWizard 已经为 Books 应用程序生成了工程文件和工程工作区文 件 在 Books 的工作区中选 择 Resource View 制表页 如果 Books 的工 作区还没有打开 在 File 主菜单中选择 Open Work space 项 打开 Books 工作区文件 因为 Books 应用程序的视的基类为 CFormView 所以 AppWizard 生 成一个类似对话框的窗口连 接到应用程序的视类

并且给这个对话框

取名为 IDDBOOKSFORM 这样我 们就可以在视中设 计一些控件 如 按钮

编辑框等 就像在基于对话框的应用程序窗口中设计界面一样

如果 视类的基类为 CView 则不能在视上放置控件 我们现在要做的 工作就是在这个类似对话框 的视中进行界面设计 (1) 在 Resource View 制 表 页 中 打 开 Dialog 资 源 组

然后双击

IDDBOOKS FORM Visual C++ 6.0 在右边的编辑窗口中显示可以进行可视化编辑的 IDDBOO KSFORM 对话框 并打开一个控件工具窗口 (2)删除 AppWizard 在对话框窗口中生成的控件 即一个文本框 只 有删除这个资源后 我 们才能在对话框中制作 Books 应用程序的窗口 界面

www.BOOKOO.com.cn

405


Visual C++6.0 编程实例与技巧

(3)用鼠标拖动编辑的对话框的边框

将其扩大到适当的大小

(4)在控件工具窗口中选择编辑框控件 将它们放在对话框中的适当 的位置

并通过鼠标 拖动控件边框的小黑方块

调整控件的大小

(5)在编辑状态的对话框中 将鼠标移到编辑框 Book 上 单击鼠标 右键 在弹出的列表框 中单击 Property 项 显示一个属性窗口 修改 其属性

如图 9.17 所列

图 9.17 编辑框的属性窗口 用同样的方法设置其他对象的属性

各个对象的属性设置如表 9.1

所示 表 9.1 对话框中各个对象的属性 对象

ID

Caption

编辑框

IDCBOOKEDIT

编辑框

IDCWRITEREDIT

编辑框

IDCCONTENTEDIT

静态文本框

IDCSTATIC

Book:

静态文本框

IDCSTATIC

Writer:

静态文本框

IDCSTATIC

Content:

注意 对于 Content 编辑框 我们还要在属性窗口的 Styles 制表页中

www.BOOKOO.com.cn

406


Visual C++6.0 编程实例与技巧

加上垂直滚动条和 水平滚动条

应如图 9.18 所示

按表 9.1 中所列的内容 在对话框中制作控件 完成后的对话框应 如图 9.19 所示 现在我们已经完成了对话框窗口的设计

但是现在的对话框不能执

行任何操作 因为还没有给它添加代码

图 9.18 Content 编辑框的 Styles 制表页

www.BOOKOO.com.cn

407


Visual C++6.0 编程实例与技巧

图 9.19 设计完后的 Books 应用程序对话框 9.4.2 菜单的可视化实现 Books 应用程序的框架窗口中有两个菜单

一个菜单是在应用程序

主窗口中至少有一个子窗口时显示 另一个菜单是在应用程序主窗口中 没有子窗口时显示 如何才能在 Books.rc 中看到这两个菜单呢 必须打开工作区窗口 选择 View 菜单中的 Worksp ace 项 显示出 Books 应用程序的工作区窗 口 (1)选择工作区窗口中的 Resource View 制表页 (2)单击工作区窗口中 Menu 项左边的“十”号

www.BOOKOO.com.cn

在下面显示出该资源

408


Visual C++6.0 编程实例与技巧

的 ID 号 这时就可以看到菜单资源了 如图 9.20 所示

图 9.20 Books.rc 中的菜单资源 在菜单资源中有两个菜单 IDRBOOKSTYPE 和 IDRMAINFRAME IDR MAINFRAME 是应用程序主窗口中没有子窗口时的菜单 IDRBOOKSTYPE 是应用程序主窗口中至少有一个子窗口时的菜 单 (3)双击 IDR BOOKSTYPE 则在右边显示一个可编辑的菜单条 这个菜单条中有四项 File Edit

Window 和 Help 其中 Edit 项在

Books 应用程序的菜单条中是没有的 其他几项与 Books 应用程序中是

www.BOOKOO.com.cn

409


Visual C++6.0 编程实例与技巧

完全相同的

所以对菜单的设计仅仅是删除掉 E dit 这项菜单

(4)单击一下 Edit 菜单 (5)按键盘上的 Del 键

删除掉 Edit 菜单

(6)选择 File 主菜单中的 Save 选项 保存所有的工作 对于 IDR MAINFRAME 菜单条 我们不需要做任何修改 它和 Books 应用程序中完全一样 到现在我们已经完成了 Books 应用程序的菜单的可视化制作工作 下面我们检测一下可视化设计的结果: (1)在 Build 主菜单中选择 Build Books.exe Visual C++编译并连接 Books 应用程序 (2)选择 Build 主菜单中的 Execute Books.exe 项 Visual C++执行 Books.exe 应用程序 可以看到和我们前面所设想的 界面一样

如 图 9.1 所示

现在我们看看各个菜单项已经具有了一些什么功能 注意 不要选择 File 菜单上的 Save 和 Save as 选项 虽然还没有编 写代码

这些选项已经具 有了一些功能

一不小心可能会覆盖一些重

要的文件 (3)选中 File 菜单中的 New 选项 应用程序在子窗口添加一个新的窗口 (4)重复上面的操作

增加更多的子窗口

(5)选择 Window 菜单中的 Tile 选项 应用程序将平铺子窗口 (6)选择 Window 菜单中的 Cascade 选项 应用程序按级联的格式排列子窗口 www.BOOKOO.com.cn

410


Visual C++6.0 编程实例与技巧

(7)最小化各个子窗口后

将子窗口拖到主窗口的不同位置

(8)在 Window 菜单中选择 Arrange Icons 选项 应用程序将最小化的子窗口排列到主窗口的底部 还没有编写一句代码

这时我们发现

就已经具有了一定的功能

AppWizard 生成的应用程序

下面我们要编写代码来完成其他一些功能

(9)单击窗口右上角的关闭按钮

终止 Books 应用程序

9.5 添加代码 9.5.1 声明视类的数据成员 首 先 用 ClassWizard 给 三 个 编 辑 框 控 件 连 接 变 量

注意要使

ClassWizard 中的类为 CBooksView 类 因为这三个变量是属于应用程序 视类中的数据成员 按照表 9.2 中所列的属性来声明变量 表 9.2 IDDBOOKSFORM 中的变量表 变量名

ID

类别

变量类型 IDCBOOKEDIT

mBook

Value

mWriter

Value

mContent

Value

CString IDCWRITEREDIT CString IDCCONTENTEDIT CString 按照以下步骤声明这几个变量 (1)在 ClassWizard 窗口中选择 Member Variables 制表页

www.BOOKOO.com.cn

411


Visual C++6.0 编程实例与技巧

(2)在 Classname 中选择 CBooksView (3)单击右边的 Add Variable 按钮 (4)按照表 9.2 中的内容输入变量的名称和类型 (5)单击 OK 键确认 这样在 Books 应用程序的视类中增加了几个数据成员 它们分别与 视中的编辑框相连接 9.5.2 定义文档类中的数据成员 前面在视类中定义了几个数据成员 个数据成员

下面我们要在文档类中定义几

分别与视类中的这几个数据成员相对应

虽然没有必要使文档类的数据成员与视类的数据成员有相同的名 字 但是 这些变量使用与视类中的数据成员相同的名称 便于阅读源 代码程序 数据成员的声明应该在类的头文件中 在 Books 应用程序中文档类 数据成员的声明放在头文件 BooksDoc.h 中 打开头文件 BooksDoc.h

在文件中输入以下代码

BooksDoc.h : interface of the CBooksDoc class

#if

!defined(AFXBOOKSDOCH4330680D90

8D11D2B2A0D17E73E86D71INCLUDED) #define

www.BOOKOO.com.cn

412


Visual C++6.0 编程实例与技巧

AFXBOOKSDOCH4330680D908D11D2B2A0D17E73E86D71INCLUDE D #if MSCVER > 1000 #pragma once #endif

MSCVER > 1000

class CBooksDoc : public CDocument { protected:

create from serialization only

CBooksDoc(); DECLAREDYNCREATE(CBooksDoc) Attributes public:

编写代码处 CString mBook; CString mContent; CString mWriter;

Operations public: Overrides ClassWizard generated virtual function overrides {{AFXVIRTUAL(CBooksDoc) public: virtual BOOL OnNewDocument();

www.BOOKOO.com.cn

413


Visual C++6.0 编程实例与技巧

virtual void Serialize(CArchive& ar); }}AFXVIRTUAL Implementation public: virtual ~CBooksDoc(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: Generated message map functions protected: {{AFXMSG(CBooksDoc) NOTE - the ClassWizard will add and remove member functions here. DO NOT EDIT what you see in these blocks of generated code ! }}AFXMSG DECLAREMESSAGEMAP() };

{{AFXINSERTLOCATION}} Microsoft Visual C++ will insert additional declarations immediate ly before the previous line.

www.BOOKOO.com.cn

414


Visual C++6.0 编程实例与技巧

#endif !defined(AFXBOOKSDOCH4330680D908D11D2B2A0D17E73E86D7 1INCLUDE D) 注意 数据成员的声明一般放在类声明的 public 中 将文档类数据 变量的声明设为 CString 类型 与视类中的数据变量的类型相同 下面我们就要初始化文档类的数据成员 当用户启动应用程序或从 Rle 菜单中选择 New 选项时 文档类的成 员都会被初始化 初始化的数据成员必须编写在成员函数 OnNewDocument()中 因为 无论什么时候创建一个新文档

Visual C++6.0 都将使它自动执行该函

数 打开源代码文件 BooksDoc.cpp 编辑函数 OnNewDocument() 输入 代码如下 BOOL CBooksDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; TODO: add reinitialization code here (SDI documents will reuse this document)

编写代码处 mBook=

;

mContent= mWriter=

; ;

www.BOOKOO.com.cn

415


Visual C++6.0 编程实例与技巧

return TRUE; } 这里把文档的数据变量初始化为 NULL 当然也可以把它的初始化 值设置为其他的值 9.5.3 初始化视类的数据成员 初始化视类的数据成员的代码要编写在视类的成员函数 OnInitiaiUpdate()中 因为该函 数在下列情况之一时被执行 用户启动应用程序时 选择 File 菜单中的 New 选项时 选择 File 菜单中的 Open 选项时 在函数 OnInitialUpdate()中 初始化视类的数据成员 是用文档类相 对应的数据成员的 值来更新视类的数据成员 (1)在菜单 View 中选择 C1assW1zard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 Object IDs 中选择 CBooksView 项 (3)在 Messages 中选择 OnInitialUpdate 项 (4)单击右边的 Add Function 按钮 这样 在 ClassWizard 窗口的 Member functions 中增加了一个成员函 数 OnInitialUpdate() (5)单击 ClassWizard 窗口中的 Edit Code 按钮 VisualC++ 显 示 源 代 码 文 件 BooksView.cpp OnInitialUpdate() 处

并将光标停在函数

等待你定义函数的内容

www.BOOKOO.com.cn

416


Visual C++6.0 编程实例与技巧

(6)在函数中输入以下代码 void CBooksView::OnInitialUpdate() { CFormView::OnInitialUpdate(); ResizeParentToFit();

编写代码处 CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); mBook=pDoc->mBook; mContent=pDoc->mContent; mWriter=pDoc->mWriter; UpdateData(FALSE);

} 下面分析一下编写的代码: 第一条语句 CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); 用来获得指向文档的指针 pDoc 然后用文档中的当前值更新视类的数据成员 mBook=pDoc->mBook; mContent=pDoc->mContent; mWriter=pDoc->mWriter; 最后把视类数据成员的值显示在屏幕上: UpdateData(FALSE)

www.BOOKOO.com.cn

417


Visual C++6.0 编程实例与技巧

9.5.4 向文件中写数据和读数据 在 Books 应用程序中 当我们从 File 菜单中选择 Save 或 Save as 时 要把文档类数据成员的内容写到文件中

而当我们从 File 菜单中选择

Open 时 要把文件中的数据读出来 并把这些数 据赋给文档类的数据 成员 在 Visual C++6.0 中 有一个专门的函数来完成对这些事件的响应 这就是文档类 的成员 函数 Serialize()函数 当用户选择 File 菜单中的 Save Save as 或 Open 选项时 自动调用该函数 我们要做的工作就是在这个函数中添加一些代码

来实现对文件数

据的读和写: void CBooksDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { TODO: add storing code here

编写代码处 ar<<mBook<<mContent<<mWriter;

} else { TODO: add loading code here

www.BOOKOO.com.cn

418


Visual C++6.0 编程实例与技巧

编写代码处: ar>>mBook>>mContent>>mWriter;

} } Serialize()函数的参数 ar 表示程序将要读和写的文件 if 语句的判断条件 (ar.IsStoring()) 当往文件中写数据时为真 也就是说 当从 File 菜单中选择 Save 或 Save as 时 满足条件 而选择 File 菜单中 Open 时 则执行 else 语句后面的代码 9.5.5 实现一个文档的多个视 此时 如果我们编译执行 Books 应用程序

会发现当我们打开一个

文档的多个视(子窗口) 时 更新其中一个视的话 其他的视并不会随之 而更新 这是因为被更新的视的内容没有传到文档中 到其他的视

文档也没有把数据传

所以要实现一个文档的多个视 必须分两步

(1)把视的内容传到文档中 (2)把文档的数据传到其他的视 那么

怎么知道视中的内容被更新了呢

事件 ENCHANGE

在视类中的编辑框有一个

发送对象被改变的消息 我们必须在这个事件的消

息处理函数中编写代码 在文档类中有一个很重要的成员函数 UpdateAllViews() 它用于把用

www.BOOKOO.com.cn

419


Visual C++6.0 编程实例与技巧

户修改的文档相对应的视进行更新 实际上 它是调用视类的成员函数 Update()

在函数 Update()中编写更 改视类的程序代码

注意 函数 UpdateAllViews()是文档类的成员函数 而函数 Update() 是视类的成员函数 它们之间的关系是 在执行函数 UpdateAllViews() 时调用函数 Update() 通过这两个 函数就可以实现把文档的数据传到其 他的视中 下面我们在 Books 应用程序中来一步步地实现一个文档的多个视 1. 生成消息处理函数 (1)在菜单 View 中选择 ClassWizard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 Class name 中选择 CBooksView 项 (3)在 Object IDs 中选择 IDCBOOKEDIT 项 (4)在 Messages 中选择 ENCHANGE 项 (5)按右边的 Add Function 按钮 这样 在 ClassWizard 窗口的 Member Functions 中增加了一个成员函 数 OnChangeBookEdit( ) (6)在 Object IDs 中选择 IDCWRITEREDIT 项 (7)在 Messages 中选择 ENCHANGE 项 按右边的 AddFunction 按钮 (8) 这样 在 ClassWizard 窗口的 Member Functions 中增加了一个成 员函数 OnChangeWrterEd it() (9) 在 Object IDs 中选择 IDCCONTENTEDIT (10)在 Messages 中选择 ENCHANGE 项 (11)按右边的 AddFunction 按钮 www.BOOKOO.com.cn

420


Visual C++6.0 编程实例与技巧

这样 在 ClassWizard 窗口的 Member functions 中增加了一个成员函 数 OnChangeContentEdit () 现在

我们在视类中生成了三个成员函数

OnChangeBookEdit()

OnChangeWriterEdit( )和 OnChangeContentEdit()

当视类中的三个编辑

框的内容改变时 分别执行这三个成员函数 2.编写消息处理函数 刚才我们生成了三个消息处理函数

现在要在这三个函数中编写代

码 打开文件 BooksView.cpp

增加以下一些代码

void CBooksView::OnChangeBookForm() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask() with the ENMCHANGE flag ORed into the mask. TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); pDoc->mBook=mBook; pDoc->SetModifiedFlag(); pDoc->UpdateAllViews(this);

www.BOOKOO.com.cn

421


Visual C++6.0 编程实例与技巧

} void CBooksView::OnChangeContentForm() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask() with the ENMCHANGE flag ORed into the mask. TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); pDoc->mContent=mContent; pDoc->SetModifiedFlag(); pDoc->UpdateAllViews(this);

} void CBooksView::OnChangeWriterForm() { TODO: If this is a RICHEDIT control, the control will not send

this

notification

unless

you

override

the

CFormView::OnInitDialog() function and call CRichEditCtrl().SetEventMask()

www.BOOKOO.com.cn

422


Visual C++6.0 编程实例与技巧

with the ENMCHANGE flag ORed into the mask. TODO: Add your control notification handler code here

编写代码处 UpdateData(TRUE); CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); pDoc->mWriter=mWriter; pDoc->SetModifiedFlag(); pDoc->UpdateAllViews(this);

} 这三个函数的代码基本一样

只是指针的对象不同而已

第一条语句: UpdateData(TRUE); 用控件的当前值更新控件的变量 下一条语句: CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); 获得指向文档的指针 pDoc 接着是用视类数据成员的内容更新文档类数据成员: pDoc->mBook=mBook; 函数 SetModifiedFlag()是把 Modified 标志设为 TRUE 表明文档的 内容已经修改 当用户在未存盘的情况下试图退出文档时 显示一个对 话框 询问是否存盘 最后一句语句: pDoc->UpdateAllViews(this); www.BOOKOO.com.cn

423


Visual C++6.0 编程实例与技巧

调用文档类的成员函数 OnUpdate() 参数 this 表示指向当前视图 3. 编写视类的 OnUpdate()函数 通过前面的代码编写

我们知道当用户更改编辑框中的内容时

调用 UpdateAllViews() 函数 而该函数又会调用视类的 OnUpdate()成员 函数

更新同一文档的其他视图的内容

现在开始编写 OnUpdate()函数: (1)在菜单 View 中选择 ClassWizard 选项 Visual C++显示一个 ClassWizard 的对话框窗口 (2)在 Class name 中选择 CBooksView 项 (3)在 Object IDs 中选择 CBooksView 项 (4)在 Messages 中选择 OnUpdate 项 (5)单击右边的 Add Function 按钮 这样 在 ClassWizard 窗口的 Member Functions 中增加了一个成员函 数 OnUpdate() (6)单击 ClassWizard 的 Edit Code 按钮 Visual C++显示源代码文件 BooksView.cpp

并将光标停在函数

OnUpdate()处 等待你定义函数的内容 (7)在函数中输入以下代码 void CBooksView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { TODO: Add your specialized code here and or call the base class

www.BOOKOO.com.cn

424


Visual C++6.0 编程实例与技巧

编写代码处 CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); mBook=pDoc->mBook; mContent=pDoc->mContent; mWriter=pDoc->mWriter; UpdateData(FALSE);

} 第一条语句获得指向文档类的指针 CBooksDoc* pDoc=(CBooksDoc*)GetDocument(); 然后用文档类的当前值更新视类的数据成员 mBook=pDoc->mBook; mContent=pDoc->mContent; mWriter=pDoc->mWriter; 最后

把更新后的视类数据成员的值传送到屏幕上

UpdateData(FALSE) 这样我们就完成了一个文档多个视的编写代码工作 使得当一个视 中的内容改变时

其他视中的内容也随之改变

下面我们检验一下这些代码是否有效: (1)从 Build 主菜单中选择 Build Books.exe 项 Visual C++编译和连接 Books 应用程序 (2)从 Build 主菜单中选择 Execute Books.exe 项 Visua1 C++执行 Books 应用程序 (3)在 Books 应用程序中 选择 File 菜单的 Open 选项 打开 Books1.bks 文件 www.BOOKOO.com.cn

425


Visual C++6.0 编程实例与技巧

(4)在 Window 菜单中选择 New Window 选项 打开该文件的另一个视(子窗口) (5)在其中一个子窗口中输入数据 发现另一个子窗口中内容也自动更新 说明代码的编写是正确的 9.6 增强 Books 应用程序 9.6.1 增加分割条 在本章开始时

我介绍了该应用程序中每个子窗口的水平和垂直滚

动条中有一个分割 条

图 9.21 加入分割器类 要实现这个功能 必须给应用程序增加一个分割器类

www.BOOKOO.com.cn

426


Visual C++6.0 编程实例与技巧

(1)打开 ClassWizard 窗口 (2)单击 Add C1ass 按钮

选择 New 选项

出现一个 New C1ass 窗口 (3)在该窗口中的 Name 编辑框中输入新增派生类的名字 CSplitter (4)在 Base Class 组合框的下拉列表框中选择 splitter 项 如图 9.21 所 示 (5)单击 OK 键 新增一个名为 CSplitter 的派生类 基类是分割器类 splitter 注意 新增类的头文件为 Splitter.h 用于声明该类 而定义给类的 文件为 Splitter.cpp 现在要做的工作是修改主应用文件 Books.cpp 使它用到新增的分割 器类 (6)打开 Books.cpp 文件 找到函数 InitInstance() 在函数 InitInstance()中有一条语句 pDocTemplate = new CMultiDocTemplate( IDRBOOKSTYPE, RUNTIMECLASS(CBooksDoc), RUNTIMECLASS(CChildFrame),

custom MDI child frame

RUNTIMECLASS(CBooksView)); AddDocTemplate(pDocTemplate); 将其中的参数 CChildFrame 改为 CSplitter

pDocTemplate = new CMultiDocTemplate( IDRBOOKSTYPE, RUNTIMECLASS(CBooksDoc),

www.BOOKOO.com.cn

427


Visual C++6.0 编程实例与技巧

RUNTIMECLASS(CSplitter),

custom MDI child frame

RUNTIMECLASS(CBooksView)); AddDocTemplate(pDocTemplate); 然后在主应用文件 Books.cpp 中加上 CSplitter 类的头文件 splitter.h 如果不加的话

Visu al C++6.0 将会报错“未定义类 CSplitter”

Books.cpp : Defines the class behaviors for the application.

#include

stdafx.h

#include

Books.h

#include

MainFrm.h

#include

ChildFrm.h

#include

BooksDoc.h

#include

BooksView.h

增加代码处 #include

现在

Splitter.h

Books 应用程序就可以支持分割窗口的特征了

9.6.2 在菜单中加入 split 选项 在 Window 菜单中加入 split 选项不需要编写代码 Visual C++6.0 已 经使这项功能在 内部实现了

只要把它加到菜单中就行了

(1)进入菜单 IDRBOOKSTYPE 的编辑状态 (2)在 Window 菜单中新增一项

www.BOOKOO.com.cn

428


Visual C++6.0 编程实例与技巧

新增一项的属性中 Caption 设为&Split ID 设为 IDWINDOWSPLIT 如图 9.22 所示

图 9.22 Split 菜单项的属性窗口 (3)选择 File 菜单中 Save 项 保存所做的工作 现在 在 Window 菜单中就增加了新的一项 Split

可以分割子窗口

了 9.7 执行 Books 应用程序 (1)从 Build 主菜单中选择 Build Books.exe 项 Visual C++编译和连接 Books 应用程序 (2)从 Build 主菜单中选择 Execute Books.exe 项 Visual C++执行 Books 应用程序 (3)按照本章开头的步骤测试该应用程序的功能 如果你是一步一步按前面的步骤来做的话 会与预期的结果一样 (4)选择 File 菜单的 Exit 项 退出 Books 应用程序

www.BOOKOO.com.cn

429


Visual C++6.0 编程实例与技巧

第十章 Visual C++6.0 多媒体程序设计 Windows 9X WindowsNT 下传统的多媒体制作工具是 MCI(Media Control Interface,媒体控 制接口) 但随着对多媒体性能要求的提高 MCI 采用 GDI(Graphic Divice Interface,图形 设备接口)封装计算机媒体硬件 的做法给其自身带来了不好的影响

虽然为普通用户带来了 编程上的

方便 但却严重影响计算机的速度 特别是在游戏中 这种影响更为突 出 DirectX OpenGL ActiveMovie 等是继 MCI 之后的新一代多媒体程 序设计及游戏制作 API DirectX

随着基于 Windows 的游戏的日益盛行

OpenGL 编程进行多媒体制作与游戏开发已成 为编程人员的

必备技能 但无论是 MCI 还是 DirectX OpenGL,都是非常庞大的多媒 体 API 要 想在一章之内详尽介绍是不可能的 本章只就 MCI DirectX OpenGL 视频图形方面分别做一 引导性的介绍 帮助读者入门 详细内 容请参阅有关专门书籍 10.1 用 MCIWnd 开发视频图像 还有什么方式比以视频探索的方式来学习我们的 VC++多媒体开发 更合适的呢 读过本章内容后 读者便会明白 MCI 命令接口是如何用于播放视频 文件的 我们所需要发送的只是一个简单的命令字符串 然后 MCl 命令 接口打开所需求的文件 文件(.AVI)来说

识别它的类型

并恰 当地播放

这一过程打开一个小窗口

对一个视频

从头至尾播放音频

这很

容易 但它看起来很了不起——我们还想要得到更多的东西

www.BOOKOO.com.cn

430


Visual C++6.0 编程实例与技巧

足够了 口

也许用户会暂停播放以也许他们喜欢大一些或小一些的窗

快一些或慢一些播放或改变音量会怎么样呢

不出来”

用户可能会想“不值得费力”

讶的感觉吗

“太难了

程序设计

那 么读者曾经有过又高兴又惊

把这些功能增加到用户的 VisuaI C++程 序的视频播放中

只需要几行代码 10.1.1 Video for Windows 和 MCIWnd 很久以前 Microsoft 公司的人们就认识到视频功能将是未来计算机 的一部分 老的 Windows 3.x 版本内部没有视频支持 所以又开发了 一个叫作 Video for Windows(VfW)的附加产品

当它与 Windows 安装

在一起时 VfW 提供想要显示视频剪辑的 Windows 应用程序所需的基 本 视频音频服务

开始时视频播放非常落后

它总是成为很多玩笑的

笑柄 而 VfW(1.1)的最 新版本以每秒 15 帧的速度提供 240 320 像素 视频播放 虽然这个是你观看 TerminatorII 终 结者 (或者 也许你喜 欢

Howard’sEnd )的第一选择 但对很多即将成为 PC 播放的目标视

频播放来说也是非常满意的 在新的 Windows9X 下 Video for Windows 将不再是一个独立的产品 而是 Windows 必不可少的组成部分了 Visual C++中包括了在用户的应 用程序中为提供 VfW 支持而必需的文件 VfW 功能提供了而且也支持 对我们来说最重要的 MCIWnd MCIwnd 是什么: 如果读者稍微思考一下这个问题 就会发现首字母缩略词 MCIWnd 代表 Media Control Interf ace Window 这正是它的确切含义——作为一 个 Windows 类来完成的预包装的 MCI 播放器 它 是一个复合元素 是

www.BOOKOO.com.cn

431


Visual C++6.0 编程实例与技巧

当用户想要提供多媒体播放时加在你的 VC++程序上的东西

它与 用

户能够放入你的老的 16 位的 VC++(版本 1.5)程序中的定制件(VBXs) 不同

与 VC ++2.0 支持 的 OLE Control 或 OCX 也不同 它是任何程

序都可以使用的 Windows 必不可少的组成部分 我们说 MCIWnd 是一个类

但要知道我们所指的是一个 Windows

类 而不是一个 VC++类 MCIWnd 不是 Microsoft 基础类的一部分 用 户不能从中派生出其他的类 正如我们将要看到的那样

用户所能做的是将它 用在你的程序中

这是很容易的

MCIWnd 能做什么呢 简单他说 它可以播放任何 MCI 文件——声 音 视频等等 更重要的是 它为用户提供播放控制 这些控制功能依 赖于被播放的文件的类型

当然对于视频文件来 说

包括我们在本章

的开头所说的所有功能——暂停 速度控制 大小控制等等 这一切都 已经准备好了 等着我们来使用呢 让我们开始工作吧 10.1.2 使用 MCIWnd MCIWnd 对象提供了很大的灵活性 这取决于它能够以两种方式进 行控制的事实——直接由用户通过 MCIWnd 的推拉按钮和菜单控制或 由程序命令间接控制 图 10.1 显示了

www.BOOKOO.com.cn

432


Visual C++6.0 编程实例与技巧

图 10.1 一个播放视频的 MCIWnd 窗口 一个播放视频 剪辑的 MCIWnd (play bar)

它包括一个 play

用户可以看到窗口底部的播放条

pause)按钮

(menu)按钮和一个用于报告的滑尺

一个显示莱单选项的菜单

它可用于设置文件中的 位置

意到在播放窗口的任何地方右键单击都会显示一个弹出菜单 它与通过 在播放条上 单击菜单按钮所得到的菜单是相同的

在本章中

我们将

看一下直接控制和程序控制这两种 方式 为了在用户的 VC++程序中使用 MCIWnd

用户必须包括文件

VFW.H 然后在 Project Setting 对话框的 Link 部分指出 Object

Library

Modules 对话框中的 VFW32. LIB 然后 用户执行下 一行代码建立一 个 MCIWnd hWnd=MCIWndCreate(hwndParent, hInstance,dwStyle, szFile); 参数 hwndParant 是父窗口(MCIWnd 的所有者)的句柄 hInstance 是 当前应用程序(通常由调用 AfxGetInstanceHandle()得到) dwStyle 指定

www.BOOKOO.com.cn

433


Visual C++6.0 编程实例与技巧

MCIWnd 的类型 我们将在后面解释这一 问题 szFile 是打开的 MCI 文件的名称 如果用户将一个 null 字符串传给 szFile 则建立的 窗口没 有打开的文件 变量 hWnd 是一个类型 HWND,它接受 MCIwnd 对象的 句柄 用户将在大多 数用于控制 MCIWnd 的其他函数中使用这一句柄 1. 控制 MCIWnd 类型 调用

建立一个 MCIwnd 与建立任何一个窗口一样都是很基本的

MCIWndCreate(),最终以调用 W indows API CreateWindow()函数结束 认识到以上这些问题是很重要的 因此 在 dwStyle 参数中 用户可以 包括为 CreateWindow()函数服务的任何 WS...类型常量以及下表列出的 MCI WNDF...常量 户大概知道

在这里我们不准备详细讨论 CreateWindow()

你可以控制 窗口类型的许多方面

标题(如一个标题条)

如是否可见

但用

是否有

是否是子窗口等等

MCIWndCreate() 使 用 一 定 的 缺 省 WS... 类 型

在用户调用

MCIWndCreate()时 如果 你在 dwSt yle 参数中没有指明任何 WS...类型 则这个调用自动使用 WSCHILD

WS BORDER 以及 WSVI SIBLE 如

果 你 指 出 一 个 非 NULL 父 窗 口 WSOVERLAPPEDWINDOW

则这个调用自动使用

如果你指出一个 NULL 父窗口 则该调

用自动使用 WSVBIBLE 除了 WM...类型选项以外 dwStyle 参数设置

MCIWnd 还有几个类型选项

也是由

给一个缺省类型传输一个零值 它包括所有控件

用户可以传输定义在 VFW.H 中的各种常量 来禁止 窗口的各个方面和 控制程序显示在窗口标题中的信息 例如 MCIWnd NOPLAYBAR 指 示 MC IWnd 不带控制条 意思是说窗口必须由程序控制而不是由用户 直接控制

类型常量的完整型 列表已给出

如果需要的话

www.BOOKOO.com.cn

用“|”连接 434


Visual C++6.0 编程实例与技巧

两个或更多的类型 MCIWNDF NOAUTOSIZEWINDOW

当图像大小

0x0001

改变时 MCIWNDF NOPLAYBAR0x0002

没有工具条

MCIWNDF NOAUTOSIZEMOVIE 0x0004 MCIWNDF NOMENU0x0008

当窗口大小改变时

没有来自 RBUTTONDOWN 的弹

出菜单 MCIWNDFSHOWNAME0x0010

在标题中显示名称

MCIWNDF SHOWPOS0x0020

显示标题中的位置

MCIWNDF SHOWMODE0x0040

显示标题模式

MCIWNDFSHOWALL0x0070 在本章后面的演示程序中

显示一切

将对 dwStyle 参数使用下面的值

WSCHILD|WSCAPTION|WSVISIBLE|MCIWNDFSHOWPOS|M CIWNDFSHOWNAME 用户看到 不管基本 CreateWindow()类型到哪儿 我们都指出一个 带有标题的可见的子窗口 而对于 MCIWNDF...类型 和显示在标题中的当前位置 MAK 时

我们指出文件名

当用户运行本章的 演示程序 VIDEO.

将会看到它的一切

注意到有一些其他的 MCIWND“类型”常量可用于指出当一个条件 发生时 如当媒体或媒体位置改变时 MCIWnd 用来通知父窗口 我们 不再讨论这些高一级的问题 2. 直接控制一个 MCIWnd 如果建立了一个带有缺省类型的 MCIWnd

用户对于它的操作就有

了很多控制 单击 play pa u se 按钮来播放或暂停当前文件 当文件播

www.BOOKOO.com.cn

435


Visual C++6.0 编程实例与技巧

放时滑尺就会移动

指示相对于文件大小的在文件 中的当前位置

播放或暂停时拉滑尺来改变当前位置 菜单可以通过在播放条上单击菜单按钮来显示 的任何位置右键单击来显示

也可以通过在窗口

如图 10.2 所示 可得到的菜单命令依赖

于载入的文件的类型 下面是有关这些命令的简要描述 View 选择整个

一半或双倍大小的显示窗口

Volume

控制音量

Speed

控制播放速度

Open

打开一个 MCI 文件

图 10.2 可通过单击菜单按钮或右单击图形窗口来显示的 MCIWnd 菜单 Close

关闭当前文件

Copy 将当前图形 Configure

帧拷贝到裁剪板上

设置视频显示参数

www.BOOKOO.com.cn

436


Visual C++6.0 编程实例与技巧

Command

给当前设备发送 MCI 字符串命令

特别有趣的是最后一个菜单命令 它让用户发送任意的 MCI 字符串 给当前设备 对该命令的响应也被显示 到此为止 用户已经基本完成 了对用于当前 MIC 文件的 MCI 设备的控制 当然 如此的威力也是很 危险的 不精通 MCI 命令字符串语法的用户可能会引起各种各样的麻 烦

这就是为什么大多数程序显示 MCIWnd 时 不带有它的播放条或

弹出菜单 而是由程序来控 制

在这种方式下用户可以保证 MCIWnd

永远也不会收到不合适的命令 下面我们将看一下这 是如何完成的 3. 从代码来控制 MCIWndWindow MCIWnd API 包含有几打命令来控制窗口和得到有关当前 MCI 设备 的信息 所有这些命令都把 窗 口的 HWND 作为参数 一些命令也需 要附加参数 这些函数(精确地说 它们是宏 但用户使 用时与函数差 不多)中的许多都很少用 而且只有在最复杂的和急需的 MCI 应用程序 中才需 要它们

在到达本章的演示项目之前

我们简短地看一下最经

常需要的那些命令 (1)控制 MCIWnd 操作的宏 用户最经常使用的用于控制 MCIWnd 功能——开始播放 暂停等等 的宏 在表 10.1 中列出 当 然 用户肯 定已经用本章前面所描写的宏 MCIWndCreate()建立了 MCIWnd

下面

所有宏的参数 hwnd 是窗口建立时由 MCIWndCreate()返回的值 成功 时

所有这些宏都返回一个带有 0 值的 类型 Long 不成功时则返回一

个 MCI 错误代码 表 10.1 通常用于控制 MCIWnd 的宏 宏

功能 开始寻找

MCIWndHome (hwnd) www.BOOKOO.com.cn

437


Visual C++6.0 编程实例与技巧

MCIWndPause(hwnd)

暂停播放

MCIWndPlay(hwnd)

开始播

MCIWndPlavFrom(hwnd,lpos)

从 lPos 位置开始播

MCIWndPlayTo(hwd,lpos)

从当前位置播放到

lPos MCIWndPlayFromTo(hWnd,lstart,lEnd)

从 lStrart 播放到 lEnd

MCIWndResume(hwnd)暂停后重新播放 MCIWndSeek(hwnd,lPos)

寻找 lPos 位置

MCIWndSetVolume(hwnd,iVol)

把音量设置为 iVol

MCIWndStop(hwnd)

停止播放

用户可以看到这些宏如何用于完成用户自己的 MCIWnd“控制面 通过将这些宏与命令按钮或菜单项联系起来

板”

当的命令传给窗口时

当保证不会有不恰

用户就可以为 MCI Wnd 的操作提供全面控制

(2)获得有关 MCIWnd 的信息 其他的宏允许程序获得有关 MCIWnd 的功能与状态的信息 这些宏的大多数与给定时打开 的 MCI 设备和文 件有关 如果用户的程序正在使用一个 MCIWnd 来播放不同类型的媒 那么你可以用表 10.2 中列出的宏来决定每种类型的媒体采用哪些

体 命令

表 10.2 用于获得信息的宏 宏

返回信

息 LRESULTMCIWndCanPlay(hwnd)

如果设置可播

放数据则为 TRUE www.BOOKOO.com.cn

438


Visual C++6.0 编程实例与技巧

如果设备支持

LRESULTMCIWndCanEject(hwnd) eject 则为 TRUE

结束的位置

LONGMCIWndGetEnd(hwnd) (对影像来说即最后一帧的序号)

长度(对影像来

LONGMCIWndGetLength(hwnd) 讲即为帧数)

当前位置(对影

LONGMCIWndGetPosition(hwnd) 像来讲即帧的序号)

当前音量设置

LONGMCIwndGetVolume(hwnd) 用户可以想象这些收集信息宏的很多用途

改变音量就是一个好例

子 由于没有 IncreaseVo lumeByTenPercent()函数 如果用户想要有精确 的音量控制 那么在用 MCIWndSet Volume()设置一个新的音量级之前 必须用 MCIWndGetVloume()来获得当前音量设置 (3)多功能宏 几个多功能的 MCIWnd 宏经常需要 它们在表 10.3 中给出 表 10.3 多功能操作的宏 宏

功能

目的

离开打开的 MCIwnd 时关闭

MCIwndClose(hwnd) 当前 MCI 设备 文件 MCIWndDestroy(hwnd)

破坏 MCIWnd

MCIWndSendString(hwnd,sz)

给设备发送命令字符串 sz

正如我们前面提到的 还有无数其他的 MCIWnd 宏 但是大多数情 况下用户很少用它们

如果用户还需要其他信息请参阅有关 Video for

Windows 的书籍 www.BOOKOO.com.cn

439


Visual C++6.0 编程实例与技巧

10.1.3 MCIWnd 的演示 现在本书的第一个多媒体项目——用 MCIWnd 类为用户的 VC++程 序提供视频和其他多媒体文件的高级播放进行简单的演示 项目 用 MCIWnd 播放视频文件 在用户程序中使用 MCIWnd 的功能只需要很少的代码 下面是建立 该演示程序的步骤 (1)用 AppWizard 建立一个 SDI 项目 (2)在 Link Settings(连接设置)对话框中包含 VFW.H 文件并指出 VFW32. LIB (3)按程序清单 10.1 和程序清单 10.2 所示增加代码 1. 运行该 VIDEO 项目 当运行 VIDEO 项目时 用户面前就出现了一个空的窗口 在这个窗 口的任何地方左单击 建立并显示 MCIWnd 窗口 用户将看到视频显示 的第一帧 如图 10.3 所示 再次在窗口(父窗口

不是 MCIWnd 本身)

的任何地方左单击 开始播放 再次左单击以暂停或重新开始该视频播 放

任一时刻

不管该视频文件在播放还是暂停

用户都可以指向

MCIWnd 的标题条 并将它 拉到一个新的位置 在父窗口的任何地方 右单击就破坏了这个视频窗口

www.BOOKOO.com.cn

440


Visual C++6.0 编程实例与技巧

图 10.3 VIDEO 项目 程序清单 10.1 OnLButtonDown()函数 void CVIEDOView::OnLButtonDown(UINT nFlags, CPoint point) { TODO: Add your message handler code here and CString filename( SAMPLES

VID98

d: content

MSDN98 mmedia

98VS fox.avi

or call default 1033 );

If the MCIWnd doen not exist,create it. if(mvideoWnd==NULL) mvideoWnd=MCIWndCreate(this->GetSafeHwnd(), AfxGetInstanceHandle(), WSCHILD|WSCAPTION|WSVISIBLE|MCIWNDFSHOWPOS|M CIWNDFSHOWNAME, www.BOOKOO.com.cn

441


Visual C++6.0 编程实例与技巧

filename); Otherwise,pause or resume play as needed. else { if(mpaused) { MCIWndPlay(mvideoWnd); mpaused=FALSE; } else { MCIWndPause(mvideoWnd); mpaused=TRUE; } } } 2. 项目开始 为了建立这个项目 启动 AppWizard 并建立一个单个文档接口项目 并取具有增强想象力的名称 VIDEO

关闭状态条

工具条和打印选择

项 否则用户就可以接受所有的 AppWizard 缺省 设置 由于我们将使 用 Video for Windows 用户必须在 VideoView.CPP 的开始附近增加这一 行 include 另外

vfw.h

显示 Project Settings(项目设置)对话框 单击 Link(链接)表

并将 VFW32.LIB 键入 Object

Library Modules 对话框

www.BOOKOO.com.cn

442


Visual C++6.0 编程实例与技巧

用户可能想知道我们为什么在这个演示项目中使用一个 SDI 应用程 序 我们根本没有利用文档类 显示类也只是作为显示 MCIWnd 窗口来 这一点可以肯定

使用的

我们应当用一个基于 对话的项目

但是

用户将在其中使用 MCIWnd 的大多数多媒体程序是 SDI 或者甚至可能 是 M DI 应用程序 因此用这种方式设计这个演示项目好像是最好的 3. 增加代码 在这个项目中并没有多少代码要增加

我们需要两个成员变量

个用于容纳 MCIWnd 窗口的 hwnd 另一个作为指示播放是否暂停的标 志来使用

将它键入文件 VideoView.H 中

private HWND mviedoWnd; BOOL mpaused; 剩余代码放入事件处理函数中 MCIWnd

设计本项目用鼠标单击来控制

当用户在程序的用户区的任一地方左单击时 程序就开始检

查 看 MCIWnd 是否已经存在 如果不存在 就建立它 如果它确实已 存在

播放的暂停或开始取决于当前状态

这些功能发生在函数

OnLButtonD own()中 用户必须用 ClassWizard 增加这个函效 然后按 程序清单 10.1 所示增加代码 函数中的代码由判定 MCIWnd 是否已经存在开始,由 mvideoWnd 成 员变量的值指示

如果不存在 就调用 WCIWndCreate()函数 使用在

本章的前面讨论过的风格标志的结合来生成窗口

注意视频文件的名

称已经硬代码化于程序之中 用户可以很容易地结合 Open 对话框来支 持用 户选择所需的视频文件 或者使用前面讨论过的 MCIWnd 内部已 有的文件选择功能

www.BOOKOO.com.cn

443


Visual C++6.0 编程实例与技巧

如果窗口已经存在 则程序用 MCIEndPlav()宏或 MCIwndPause()宏 来开始或继续播放

这取决于 mpaused 标志的状态

还要增加一个响应用户区在任一地方右单击破坏 MCIWnd 的功能 用 ClassWizard 为 0nWM RBUTTONDOWN 消息增加一个函数 然后按 程序清单 10.2 所示编辑它 程序清单 10.2 OnRButtonDown()中的代码破坏 MCIwnd 对象 void CVIEDOView::OnRButtonDown(UINT nFlags, CPoint point) { TODO: Add your message handler code here and

or call default

If the MCIWnd exists,destroy it. if(mvideoWnd!=NULL) { MCIWndDestroy(mvideoWnd); mvideoWnd=NULL; } } 最后在显示类的构造函数中

增加单独一行代码

mvideoWnd=NULL; 这就是这个项目的全部了——本项目已经准备建立和运行了 试一 试

运用程序控制 (也就是用鼠标单击)和 MCIWnd 内部已有的功能来

控制视频播放

这是一个非常高 级的视频播放工具

而且认识到将它

包括在你的 VC++项目中是多么容易 用户将 会发现它的很多用途 现在结束用 MCI 的 Visua1 C++多媒体程序设计 现在读者已能将用 VC++ 建立多媒体图像所需的基本 MCI 工具置于自己的统治之下 有 了 VC++的无限威力 那么读者的程 序的灵活 性与创建性只会被您的 www.BOOKOO.com.cn

444


Visual C++6.0 编程实例与技巧

想象力所限制 而不会被预包装的多媒体制作系统的不足所限制 下 一 节进入 OpenGL 3D 图形 API 部分 3D 图形设计 API

10.2 OpenGL

10.2.1 OpenGL 概述 OpenGL 是个硬件和图形的软件接口 模型库

实际上就是一个三维图形和

由于它在三维真实 感图形制作中具有优秀的性能

如 Microsoft SGI

所以

IBM DEC SUN HP 等在计算 机市场中占主导

地位的大公司 都将它做为自己的图形标准 从而使之成为新一代的三 维图 形工业标准 OpenGL

不 仅 仅 是 一 个 图 形 库

API(ApplicationProgrammingInterface)

更 是 一 个

它 本身是一个与硬件无关的编

程接口 可以在不同的硬件平台上得到实现 也正是因为如此

OpenGL

中没有包含处理窗口和用户输入的命令 在 OpenGL 中不提供三维造型 的高级命令 虽 然 OpenGL 也是通过基本的几何图元——点 线 多 边形来建立物体模型的

但更确切地说它 应该被称为新一代的三维图

形开发标准 现在有很多优秀的三维图形软件 如 3Dmax 等可 以方便 地建立模型 但难以对其进行控制 把这些模型转化为用 C 语言编写的 OpenGL 程序 维动画

就可以随心所欲地控制这些模型 制作 CAD 制作三

实现虚拟仿真

制作商业广告

们制作出的三维真实感图形更方便

进行影视采辑

这些都使我

更真实

OpenGL 可以制作各种各样的三维图形

方便地实现三维图形的交

互操作 但这些图形都是由 一些基本操作实现的 OpenGL 提供的操作

www.BOOKOO.com.cn

445


Visual C++6.0 编程实例与技巧

包括 1. 绘制物体(Drawing Object): 任何三维图形 形组成的

三维场景都是由一些基本的图元——点

实际上

一个 图形系统

线

多边

其性能是由它对这些基本图元的

绘制操作决定的 OpenGL 提供了丰富的基本图元 绘制命令 在第二章 中将详细地介绍 OpenGL 的基本图元绘制函数 2. 变换(Transformer) 任何复杂的图形都是物体经过一系列变换来实现的 了一系列基本的变换

如投 影变换定义了一个视景体

使物体在三维场景中平移

旋转和放缩

OpenGL 提供 几何变换可以

视点变换 可以从不同的角度

去观察物体 如果用特殊的显示硬件还可实现虚拟现实显示 裁剪变换 可 以定义除了裁剪体以外的裁剪平面

视口变换决定怎样把制作的图

形映射屏幕上 另外 Op enGL 提供了一系列矩阵操作函数 利用这些 函数

用户可以根据具体的需要

定义具体应用 中的变换

这样有利

于具体问题具体分析 消除了系统对应用的局限性 读者可以参考有关 的专门书籍详细地了解这些变换的应用以及矩阵操作函数 3. 着色(Rendering) OpenGL 提供了两种颜色模式 RGBA 模式和颜色索引表模式和两种 具体的物体着色方式 种颜色

如果显 示硬件允许的话

基本上是自然界所有的颜色

OpenGL 提供 16(位)

读者可以参考有 关的专门书籍

详细地了解 OpenGL 提供的颜色模式和具体的着色模式 4. 光照(lighting) 光是真实感图形的必要组成部分 成的

这四部分是环境光

漫反射光

OpenGL 中认为光是由四部分组 辐射光和镜面光

www.BOOKOO.com.cn

在定义光源 446


Visual C++6.0 编程实例与技巧

的时候要分别定义这四部分

用户可以在应用中定 义光源的属性

变光源的位置 在 OpenGL 中 光源相当于一个几何体 用户可以像控 控制光照物体表面的属性以及可以

制几 何体一样地控制光源的位置

定义聚光 有关书籍将具体 地介绍光照 5. 反走样(Antialiasing) 走样是计算机绘制图形过程中常见的问题之一 在 OpenGL 中 提 供了点 线和多边形的反走 样技术

有关书籍将会做具体地介绍

6. 混合(Blending) 在一些逼真的高质量三维图形制作过程中

经常会遇到透明物体和

半透明物体的处理 在 Op enGL 中 这种处理是通过混合技术来实现的 缺省状态下

所有的物体都是不透明的

物体 的不透明度是由表示物

体颜色的第四个分量 Alpha 来决定 OpenGL 提供了控制比例的混合的 函数 用户可以根据自己的需要 选择在实际应用中最合适的混合函数 这部分内容有关书籍将会做具体地介绍 7. 雾(Fog) 在制作真实感图形的过程中

经常要有一些烟雾等特殊效果的制作

要求 在 OpenGL 中用雾 来实现这种自然现象 可以使所制作的三维 图形更真实 这部分内容有关书籍将会做具体地 介绍 8. 位图和图像(Bitmap and Image) OpenGL 提供了两种特殊的数据类型——位图和图像 OpenGL 提供 了一系列的函数来实现位图 操作 实现英文字符的制作和显示

例如可以和显示列表结合

方便地

图像是图形制作中的 一个重要方面

OpenGL 提供了一系列的函数来实现图像操作 如图像绘制 拷贝 放 缩以及 图像数据的转换

映射和存储

这部分内容有关书籍也将会做

www.BOOKOO.com.cn

447


Visual C++6.0 编程实例与技巧

具体地介绍 9. 纹理映射(TextureMap) 纹理映射是考察三维图形系统最重要的一个方面 型制作过程中

在具体的三维模

在模型表 面加上现实世界中物体的纹理

可使三维图

形更生动 更自然 OpenGL 提供了纹理堆栈 可 以使纹理粘贴在物体 以前 对纹理进行变换 包括平移 旋转和缩放 有关书籍详细讲述了 OpenGL 提供的纹理映射 10. 交互操作和动画(Interactive and Animation) 交互操作是考察一个三维图形系统的另一个重要方面 有提供直接的交互操作函数

OpenGL 没

OpenGL 辅助库为使用标准 C 编写

OpenGL 程序提供了简单的消息响应函数 Windows 下的 Open GL 程序 一般都用辅助库编写 交互思想会一直贯穿在程序中 Visual C++ 6.0 MF C 提供了丰 富的窗口操作函数和消息响应处理函数

本小节将要详细

介绍使用 MFC 编写 OpenGL 程序 动 画是考察一个三维图形系统性能 的重要方面 OpenGL 中使用双缓存区技术实现动画绘制 可 以实时地 根据用户需求 按具体的交互效果 绘制出结果 本书不打算介绍这部 分内容 有 兴趣的读者可以参考有关 Windows 系统下 OpenGL 实现动 画的书籍 10.2.2 Windows 系统下的 OpenGL 函数 在 Windows9 X 以及 Windows NT 3.51 以上的操作系统中提供了 OpenGL 的动态库 在 VC++2.0 以上的版本中提供了 OpenGL 的静态库 所以

使用 OpenGL 编程 在微机上使用时 最好是在 上述软件环境

中编写 OpenGL 程序

www.BOOKOO.com.cn

448


Visual C++6.0 编程实例与技巧

在微机版本中

OpenGL 提供了三个函数库

它们是基本库

实用

库和辅助库 OpenGL 的基本库是 OpenGL 的核心函数库 在这个函数库中 提 供了一百多个函数 这些函数都是以“gl”为前缀 所有 OpenGL 提供的 操作都可以使用这些函数来实现 而且 对于不同 的软件和硬件平台 这些函数的使用是完全相同的 这注定了 OpenGL 程序完美的可移 植 特性 OpenGL 的实用库是 OpenGL 基本库的一套子程序 它提供了四十 多个函数 这些函数都是以“glu”为前缀 基本的 OpenGL 不支持传统上 同图形标准相关的一些几何对象 为了减少一些 编程负担 OpenGL 提 供了实用库 实用库中的所有函数全部是由 OpenGL 基本库函数来编写 的

所以 在使用上和 OpenGL 基本库的使用是完全相同的 而且

用户也可以使用基本函数库 来实现实用库的函数功能

有关书籍将详

细地介绍 OpenGL 实用库 OpenGL 的辅助库是为了方便用户用标准 C 编写 OpenGL 程序而编 写的 OpenGL 是一个图形标准 所以 在 OpenGL 中没有提供窗口管 理和消息事件响应的函数 这样使用标准 C 编写 OpenGL 程序是很不 方便的 所以提供了辅助库 OpenGL 辅助库提供了一些基本的窗口管 理函数

事 件处理函数和一些简单模型的制作函数

的大小

处理键盘时间

鼠标击键 事件

例如

定义窗口

绘制多面体等等

有关书籍

将详细地介绍 OpenGL 实用库 另外

对于编写 Windows 程序 OpenGL 也提供了一些相关的函数

库 但在这里不做详细的说 明

www.BOOKOO.com.cn

449


Visual C++6.0 编程实例与技巧

10.2.3 用 Visual C++ 6.0 MFC 编写 OpenGL 程序 这一节主要是通过用 MFC 编写一个 OpenGL 程序

来说明在

Windows 和 Visual C++环 境下编写 OpenGL 多媒体应用程序是多么简单快捷 程序运行结果 是在一个 SGI 应用程序的客户区窗口显示一些辐射线条 当然

使用 MFC 编写程序要启动 Visual C++ 6.0

(1) 使用 Appwizard 建立一个叫 myopengl 的应用程序: 选择 New

1)打开菜单<File

见图 10.4

2)在出现的 NEW 对话框中选择 Projects 3)在出现的

选项卡 见图 10.5

选项卡中<Project Name>下面的编辑框中填

Projects

写 myopengl 在左边的 项目类型列表中选择 MFC AppWizard ,然后单击 OK

按钮

见图 10.6

4)选择 Single Document>

单击 Next

5)使用缺省选项

单击 Next

6)选择<None

单击

7)选择

9)单击

OK

见图 10.7

Next 单击

3D Controls

8)使用缺省选项

exe

Next

单击 Finish 见图 10.8

(2) 打开主菜单

View

CklassWizard 对话框 选 择

选择

C1assWizard

Message Maps

出现 MFC

选项卡 在 Class Name

编辑框和 Object IDs 编辑框中选择 CMyopengIView

类 然后 在

Messages 编辑框中选择 WMCREATE 按下 Add Function 按钮

加消息 处理函数 OnCreate() 见图 10.9 同样的方法选择 WMDESTROY 和 WMSIZE 消息 增加消息处理函数 OnDestroy()和 OnSize() www.BOOKOO.com.cn

最后 450


Visual C++6.0 编程实例与技巧

选择

OK 按钮

图 10.4 VC 的 File 菜单

www.BOOKOO.com.cn

451


Visual C++6.0 编程实例与技巧

图 10.5 New 对话框

www.BOOKOO.com.cn

452


Visual C++6.0 编程实例与技巧

图 10.6 New Project 选项卡

图 10.7 MFC Appwizard Step 1 对话框

www.BOOKOO.com.cn

453


Visual C++6.0 编程实例与技巧

图 10.8 New Project Information 对话框

www.BOOKOO.com.cn

454


Visual C++6.0 编程实例与技巧

图 10.9 MFC ClassWizard 对话框 (3) 打开文件 myopenglView.cpp 在函数 OnCreate() OnDestroy() 和 OnSize()中填写代码 在 OnCreate()的

TODO Add your specialized creation code here

后加入下面代码 初始化像素格式 PIXELFORMATDESCRIPTOR pfd={ sizeof(PIXELFORMATDESCRIPTOR), 1,

对象 pfd 大小

版本号

PFDDRAWTOWINDOW|

支持视窗

PFDSUPPORTOPENGL|

支持 OpenGL

PFDDOUBLEBUFFER,

双缓冲区

PFDTYPERGBA,

RGBA 模式 www.BOOKOO.com.cn

455


Visual C++6.0 编程实例与技巧

24-bit 色

24,

忽略颜色位

0,0,0,0,0,0, 0,

无 alpha 缓存

0,

忽略位轮换

0,

无累加缓存 忽略累加位

0,0,0,0,

32-bit 深度缓存

32, 0,

无 stencil 缓存

0,

无辅助缓存 主表面

PFDMAINPLANE, 0,

保留

0,0,0

忽略层掩码

}; CClientDC clientdc(this);

获得窗口客户区设备场景

int pf=ChoosePixelFormat(clientdc.mhDC,&pfd);

返回匹配的像素

格式 bool rt=SetPixelFormat(clientdc.mhDC,pf,&pfd); hglrc=wglCreateContext(clientdc.mhDC); 在 OnDestroy()的

设置像素格式

创建着色场景

TODO Add your specialized creation code here

后加入下面代码 wgIDeleteContext(hglrc)

删除着色场景

(4) 填加函数 drawwithopengl() 首先 在 myopengIView.h 中加入 函数的说明 见下面代码 public www.BOOKOO.com.cn

456


Visual C++6.0 编程实例与技巧

voiddrawwithopengl(void) 在文件 myopengIView.cpp 中加入函数 drawwithopengl()的定义

面代码是对 drawwithop engl()的定义: void CMyOpenglView::drawwithopengl() { float z,angle,x,y; glClear(GLCOLORBUFFERBIT);

用当前的清除色清除窗口

保存矩阵状态并进行旋转变换

glPushMatrix();

对所有保留点只调用一次

glColor3f(1.0,0.0,0.0); glBegin(GLLINES); z=0.0f;

for(angle=0.0f;angle<=3.14159260f;angle+=0.1f) { 上半圆

x=50.0f*sin(angle); y=50.0f*cos(angle); glVertex3f(x,y,z);

x=50.0f*sin(angle+3.1415f);

下半圆

y=50.0f*cos(angle+3.1415f); glVertex3f(x,y,z); } glEnd();

处理图形点

glPopMatrix(); glFlush();

恢复变换

清除绘图命令

} (5) 在函数 OnDraw()中加入调用 drawwithOpengl()的语句 同时

www.BOOKOO.com.cn

457


Visual C++6.0 编程实例与技巧

在使用 OpenGL 函数时注意对翻译描述表的使用 TODO add draw code fo native data here 后

在 OnDraw()函数的 面加入以下代码

wglMakeCurrent(pDC->mhDC,hglrc); drawwithopengl(); wglMakeCurrent(pDC->mhDC,NULL); SwapBuffers(pDC->mhDC); (6) 在文件 myopengView.h 中加入以下说明 #include <gl

gl.h>

#include <gl

glu.h>

#include <gl

glaux.h>

#include <math.h> 在 CmyopenglView 类定义中添加以下说明(可以利用右击 Class View 来完成): public HGLRC hglrc (7) 在函数 OnSize()中

TODO Add your message handier code

here 后加入以下代码 int w=cx; int h=cy; GLfloat nRange=100.0f; if(h==0)

防止被零除

h=1; glViewport(0,0,w,h);

设置视口,预置坐标系统

glMatrixMode(GLPROJECTION); www.BOOKOO.com.cn

458


Visual C++6.0 编程实例与技巧

glLoadIdentity(); if(w<=h)

创建裁剪空间 w,nRange*h

glOrtho(-nRange,nRange,-nRange*h w,-nRange,nRange); else

h,nRange*w

glOrtho(-nRange*w h,-nRange,nRange,-nRange,nRange); glMatrixMode(GLMODELVIEW); glLoadIdentity();

不难发现 这些代码与普通 VC++编程代码基本类同,只要掌握了基 本的多媒体常识知识,即便是改用 OpenGL 编程也并不需要特别地学习 (8) 在 project 中加入静态库 opengl32.lib

glu32.lib 和 glaux.lib

打开<project>菜单击中 settings 出现项目设置对话框 选择 Link 选 项卡,在 Object Libr ary Modules 编辑框中输入 opengl32.lib glu32.lib 和 glaux.lib

单击<OK>,见图 10.10

www.BOOKOO.com.cn

459


Visual C++6.0 编程实例与技巧

图 10.10 项目设置对话框 (9) 运行这个程序 见图 10.11

www.BOOKOO.com.cn

460


Visual C++6.0 编程实例与技巧

图 10.11 myopeng1.exe 的执行结果 以上九步就完成了用 Visual C++ 6.0 MFC 开发 OpenGL 视频图形多 媒体程序工作 在上述例程中只是使用了 MFC 的窗口 菜单 工具条 和状态条的缺省设置 程序清单 10.3 给出这个 VC6.0 MFC 程序的所有代码 程序清单 10.3 OpenGL 应用程序

myopenglView.h

MyOpenglView.h : interface of the CMyOpenglView class

www.BOOKOO.com.cn

461


Visual C++6.0 编程实例与技巧

#if !defined(AFXMYOPENGLVIEWHC461966D8D2211D2B2A0EE05 DF303FE6INCLUDED ) #define

AFXMYOPENGLVIEWHC461966D8D2

211D2B2A0EE05DF303FE6INCLUDED #if MSCVER > 1000 #pragma once #endif

MSCVER > 1000

#include <gl

gl.h>

#include <gl

glu.h>

#include <gl

glaux.h>

#include <math.h> class CMyOpenglView : public CView { protected:

create from serialization only

CMyOpenglView(); DECLAREDYNCREATE(CMyOpenglView) Attributes public: CMyOpenglDoc* GetDocument(); Operations public:

www.BOOKOO.com.cn

462


Visual C++6.0 编程实例与技巧

Overrides ClassWizard generated virtual function overrides {{AFXVIRTUAL(CMyOpenglView) public: virtual void OnDraw(CDC* pDC);

overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); }}AFXVIRTUAL Implementation public: HGLRC hglrc; void drawwithopengl(void); virtual ~CMyOpenglView(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: Generated message map functions protected: {{AFXMSG(CMyOpenglView) afxmsg int OnCreate(LPCREATESTRUCT lpCreateStruct); afxmsg void OnDestroy(); www.BOOKOO.com.cn

463


Visual C++6.0 编程实例与技巧

afxmsg void OnSize(UINT nType, int cx, int cy); }}AFXMSG DECLAREMESSAGEMAP() }; #ifndef DEBUG

debug version in MyOpenglView.cpp

inline CMyOpenglDoc* CMyOpenglView::GetDocument() { return (CMyOpenglDoc*)mpDocument; } #endif

{{AFXINSERTLOCATION}} Microsoft Visual C++ will insert additional declarations immediate ly before the previous line. !defined(AFXMYOPENGLVIEWHC461966

#endif

D

8D2211D2B2A0EE05DF303FE6IN CLUDED)

myopenglView.cpp

MyOpenglView.cpp : implementation of the CMyOpenglView class

#include

stdafx.h

#include

MyOpengl.h

#include

MyOpenglDoc.h www.BOOKOO.com.cn

464


Visual C++6.0 编程实例与技巧

#include

MyOpenglView.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

CMyOpenglView IMPLEMENTDYNCREATE(CMyOpenglView, CView) BEGINMESSAGEMAP(CMyOpenglView, CView) {{AFXMSGMAP(CMyOpenglView) ONWMCREATE() ONWMDESTROY() ONWMSIZE() }}AFXMSGMAP Standard printing commands ONCOMMAND(IDFILEPRINT, CView::OnFilePrint) ONCOMMAND(IDFILEPRINTDIRECT, CView::OnFilePr int) ONCOMMAND(IDFILEPRINTPREVIEW,

CView::OnFileP

rintPreview) ENDMESSAGEMAP()

www.BOOKOO.com.cn

465


Visual C++6.0 编程实例与技巧

CMyOpenglView construction

destruction

CMyOpenglView::CMyOpenglView() { TODO: add construction code here } CMyOpenglView::~CMyOpenglView() { } BOOL CMyOpenglView::PreCreateWindow(CREATESTRUCT& cs) { TODO: Modify the Window class or styles here by modifying the CREATESTRUCT cs return CView::PreCreateWindow(cs); }

CMyOpenglView drawing void CMyOpenglView::OnDraw(CDC* pDC) { CMyOpenglDoc* pDoc = GetDocument(); ASSERTVALID(pDoc); TODO: add draw code for native data here wglMakeCurrent(pDC->mhDC,hglrc);

www.BOOKOO.com.cn

466


Visual C++6.0 编程实例与技巧

drawwithopengl(); wglMakeCurrent(pDC->mhDC,NULL); SwapBuffers(pDC->mhDC); }

CMyOpenglView printing BOOL CMyOpenglView::OnPreparePrinting(CPrintInfo* pInfo) { default preparation return DoPreparePrinting(pInfo); } void

CMyOpenglView::OnBeginPrinting(CDC*

CPrintInfo*

*pInfo*

*pDC*

,

*pDC*

,

)

{ TODO: add extra initialization before printing } void

CMyOpenglView::OnEndPrinting(CDC*

CPrintInfo*

*pInfo*

)

{ TODO: add cleanup after printing }

www.BOOKOO.com.cn

467


Visual C++6.0 编程实例与技巧

CMyOpenglView diagnostics #ifdef DEBUG void CMyOpenglView::AssertValid() const { CView::AssertValid(); } void CMyOpenglView::Dump(CDumpContext& dc) const { CView::Dump(dc); } CMyOpenglDoc* CMyOpenglView::GetDocument()

non-debug

version is inline { ASSERT(mpDocument->IsKindOf(RUNTIMECLASS(CMyOpenglDoc ))); return (CMyOpenglDoc*)mpDocument; } #endif

DEBUG

CMyOpenglView message handlers int CMyOpenglView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) www.BOOKOO.com.cn

468


Visual C++6.0 编程实例与技巧

return -1; TODO: Add your specialized creation code here PIXELFORMATDESCRIPTOR pfd={ sizeof(PIXELFORMATDESCRIPTOR), 1, PFDDRAWTOWINDOW| PFDSUPPORTOPENGL| PFDDOUBLEBUFFER, PFDTYPERGBA, 24, 0,0,0,0,0,0, 0, 0, 0, 0,0,0,0, 32, 0, 0, PFDMAINPLANE, 0, 0,0,0 }; CClientDC clientdc(this); int pf=ChoosePixelFormat(clientdc.mhDC,&pfd); bool rt=SetPixelFormat(clientdc.mhDC,pf,&pfd); hglrc=wglCreateContext(clientdc.mhDC); return 0; www.BOOKOO.com.cn

469


Visual C++6.0 编程实例与技巧

} void CMyOpenglView::OnDestroy() { CView::OnDestroy(); TODO: Add your message handler code here wglDeleteContext(hglrc); } void CMyOpenglView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); TODO: Add your message handler code here int w=cx; int h=cy; GLfloat nRange=100.0f; if(h==0) h=1; glViewport(0,0,w,h); glMatrixMode(GLPROJECTION); glLoadIdentity(); if(w<=h) glOrtho(-nRange,nRange,-nRange*h

w,nRange*h

w,-nRange,nRange); else glOrtho(-nRange*w

h,nRange*w

h,-nRange,nRange,-nRange,nRange); glMatrixMode(GLMODELVIEW);

www.BOOKOO.com.cn

470


Visual C++6.0 编程实例与技巧

glLoadIdentity(); } void CMyOpenglView::drawwithopengl() { float z,angle,x,y; glClear(GLCOLORBUFFERBIT); glPushMatrix(); glColor3f(1.0,0.0,0.0); glBegin(GLLINES); z=0.0f; for(angle=0.0f;angle<=3.14159260f;angle+=0.1f) { x=50.0f*sin(angle); y=50.0f*cos(angle); glVertex3f(x,y,z); x=50.0f*sin(angle+3.1415f); y=50.0f*cos(angle+3.1415f); glVertex3f(x,y,z); } glEnd(); glPopMatrix(); glFlush(); }

MainFrm.h : interface of the CMainFrame class

www.BOOKOO.com.cn

471


Visual C++6.0 编程实例与技巧

#if

!defined(AFXMAINFRMHC46196698D2

211D2B2A0EE05DF303FE6INCLUDED) #define AFXMAINFRMHC46196698D2211D2B2A0EE05DF303FE6INCLUDED #if MSCVER > 1000 #pragma once #endif

MSCVER > 1000

class CMainFrame : public CFrameWnd { protected:

create from serialization only

CMainFrame(); DECLAREDYNCREATE(CMainFrame) Attributes public: Operations public: Overrides ClassWizard generated virtual function overrides {{AFXVIRTUAL(CMainFrame) virtual BOOL PreCreateWindow(CREATESTRUCT& cs); }}AFXVIRTUAL

www.BOOKOO.com.cn

472


Visual C++6.0 编程实例与技巧

Implementation public: virtual ~CMainFrame(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected:

control bar embedded members

CStatusBarmwndStatusBar; CToolBarmwndToolBar; Generated message map functions protected: {{AFXMSG(CMainFrame) afxmsg int OnCreate(LPCREATESTRUCT lpCreateStruct); NOTE - the ClassWizard will add and remove member functions here. DO NOT EDIT what you see in these blocks of generated code! }}AFXMSG DECLAREMESSAGEMAP() };

{{AFXINSERTLOCATION}} Microsoft Visual C++ will insert additional declarations

www.BOOKOO.com.cn

473


Visual C++6.0 编程实例与技巧

immediate ly before the previous line. #endif !defined(AFXMAINFRMHC46196698D2211D2B2A0EE05DF303FE6I NCLUDED )

MainFrm.cpp

MainFrm.cpp : implementation of the CMainFrame class

#include

stdafx.h

#include

MyOpengl.h

#include

MainFrm.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

CMainFrame IMPLEMENTDYNCREATE(CMainFrame, CFrameWnd)

www.BOOKOO.com.cn

474


Visual C++6.0 编程实例与技巧

BEGINMESSAGEMAP(CMainFrame, CFrameWnd) {{AFXMSGMAP(CMainFrame) NOTE - the ClassWizard will add and remove mapping macros here. DO NOT EDIT what you see in these blocks of generated code ! ONWMCREATE() }}AFXMSGMAP ENDMESSAGEMAP() static UINT indicators

=

{ IDSEPARATOR,

status line indicator

IDINDICATORCAPS, IDINDICATORNUM, IDINDICATORSCRL, };

CMainFrame construction

destruction

CMainFrame::CMainFrame() { TODO: add member initialization code here } CMainFrame::~CMainFrame() {

www.BOOKOO.com.cn

475


Visual C++6.0 编程实例与技巧

} int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!mwndToolBar.CreateEx(this, TBSTYLEFLAT, WSCHILD

|

WSVISIBLE | CBRSTOP |

CBRSGRIPPER

|

CBRSTOOLTIPS

|

CBRSFLYBY

|

CBRSSIZEDYNAMIC) || !mwndToolBar.LoadToolBar(IDRMAINFRAME)) { TRACE0( return -1;

Failed to create toolbar

n

);

fail to create

} if (!mwndStatusBar.Create(this) || !mwndStatusBar.SetIndicators(indicators, sizeof(indicators) sizeof(UINT))) { TRACE0( return -1;

Failed to create status bar

n

);

fail to create

} TODO: Delete these three lines if you don

t want the toolbar to

be dockable mwndToolBar.EnableDocking(CBRSALIGNANY); EnableDocking(CBRSALIGNANY); DockControlBar(&mwndToolBar); www.BOOKOO.com.cn

476


Visual C++6.0 编程实例与技巧

return 0; } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; TODO: Modify the Window class or styles here by modifying the CREATESTRUCT cs return TRUE; }

CMainFrame diagnostics #ifdef DEBUG void CMainFrame::AssertValid() const { CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CFrameWnd::Dump(dc); } #endif

DEBUG

www.BOOKOO.com.cn

477


Visual C++6.0 编程实例与技巧

CMainFrame message handlers

myopengl.h

MyOpengl.h : main header file for the MYOPENGL application

#if

!defined(AFXMYOPENGLHC46196658D

2211D2B2A0EE05DF303FE6INCLUDED) #define AFXMYOPENGLHC46196658D2211D2B2A0EE05DF303FE6INCLUDE D #if MSCVER > 1000 #pragma once #endif

MSCVER > 1000

#ifndef AFXWINH #error include

stdafx.h

before including this file for PCH

#endif #include

resource.h

main symbols

CMyOpenglApp: www.BOOKOO.com.cn

478


Visual C++6.0 编程实例与技巧

See MyOpengl.cpp for the implementation of this class

class CMyOpenglApp : public CWinApp { public: CMyOpenglApp(); Overrides ClassWizard generated virtual function overrides {{AFXVIRTUAL(CMyOpenglApp) public: virtual BOOL InitInstance(); }}AFXVIRTUAL Implementation {{AFXMSG(CMyOpenglApp) afxmsg void OnAppAbout(); NOTE - the ClassWizard will add and remove member functions here. DO NOT EDIT what you see in these blocks of generated code ! }}AFXMSG DECLAREMESSAGEMAP() };

{{AFXINSERTLOCATION}} www.BOOKOO.com.cn

479


Visual C++6.0 编程实例与技巧

Microsoft Visual C++ will insert additional declarations immediate ly before the previous line. #endif !defined(AFXMYOPENGLHC46196658D2211D2B2A0EE05DF303FE 6INCLUDE D)

myopengl.cpp

MyOpengl.cpp : Defines the class behaviors for the application.

#include

stdafx.h

#include

MyOpengl.h

#include

MainFrm.h

#include

MyOpenglDoc.h

#include

MyOpenglView.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

www.BOOKOO.com.cn

480


Visual C++6.0 编程实例与技巧

CMyOpenglApp BEGINMESSAGEMAP(CMyOpenglApp, CWinApp) {{AFXMSGMAP(CMyOpenglApp) ONCOMMAND(IDAPPABOUT, OnAppAbout) NOTE - the ClassWizard will add and remove mapping macros here. DO NOT EDIT what you see in these blocks of generated code! }}AFXMSGMAP Standard file based document commands ONCOMMAND(IDFILENEW, CWinApp::OnFileNew) ONCOMMAND(IDFILEOPEN, CWinApp::OnFileOpen) Standard print setup command ONCOMMAND(IDFILEPRINTSETUP, CWinApp::OnFileP rintSetup) ENDMESSAGEMAP()

CMyOpenglApp construction CMyOpenglApp::CMyOpenglApp() { TODO: add construction code here, Place all significant initialization in InitInstance }

www.BOOKOO.com.cn

481


Visual C++6.0 编程实例与技巧

The one and only CMyOpenglApp object CMyOpenglApp theApp;

CMyOpenglApp initialization BOOL CMyOpenglApp::InitInstance() { AfxEnableControlContainer(); Standard initialization If you are not using these features and wish to reduce the size of your final executable, you should remove from the following the specific initialization routines you do not need. #ifdef AFXDLL Call this when using MFC in a shared DL L

Enable3dControls(); #else Enable3dControlsStatic();

Call this when linking to MFC

statically #endif Change the registry key under which our settings are stored. TODO: You should modify this string to be something appropriate such as the name of your company or organization.

www.BOOKOO.com.cn

482


Visual C++6.0 编程实例与技巧

SetRegistryKey(T(

Local AppWizard-Generated Applications

LoadStdProfileSettings();

));

Load standard INI file options

(including MR U) Register the application

s document templates.Document

templates serve as the connection between documents, frame windows and views. CSingleDocTemplate* pDocTemplate; pDocTemplate = new CSingleDocTemplate( IDRMAINFRAME, RUNTIMECLASS(CMyOpenglDoc), RUNTIMECLASS(CMainFrame),

main SDI frame window

RUNTIMECLASS(CMyOpenglView)); AddDocTemplate(pDocTemplate); Parse command line for standard shell commands, DDE, file open CCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); Dispatch commands specified on the command line if (!ProcessShellCommand(cmdInfo)) return FALSE; The one and only window has been initialized, so show and update it. mpMainWnd->ShowWindow(SWSHOW); mpMainWnd->UpdateWindow(); return TRUE; } www.BOOKOO.com.cn

483


Visual C++6.0 编程实例与技巧

CAboutDlg dialog used for App About class CAboutDlg : public CDialog { public: CAboutDlg(); Dialog Data {{AFXDATA(CAboutDlg) enum { IDD = IDDABOUTBOX }; }}AFXDATA ClassWizard generated virtual function overrides {{AFXVIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX);

DDX

DDV support }}AFXVIRTUAL Implementation protected: {{AFXMSG(CAboutDlg) No message handlers }}AFXMSG DECLAREMESSAGEMAP() }; www.BOOKOO.com.cn

484


Visual C++6.0 编程实例与技巧

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { {{AFXDATAINIT(CAboutDlg) }}AFXDATAINIT } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); {{AFXDATAMAP(CAboutDlg) }}AFXDATAMAP } BEGINMESSAGEMAP(CAboutDlg, CDialog) {{AFXMSGMAP(CAboutDlg) No message handlers }}AFXMSGMAP ENDMESSAGEMAP() App command to run the dialog void CMyOpenglApp::OnAppAbout() { CAboutDlg aboutDlg; aboutDlg.DoModal(); }

www.BOOKOO.com.cn

485


Visual C++6.0 编程实例与技巧

CMyOpenglApp message handlers

myopenglDoc.h

MyOpenglDoc.h : interface of the CMyOpenglDoc class

#if !defined(AFXMYOPENGLDOCHC461966B8D2211D2B2A0EE05 DF303FE6INCLUDED ) #define

AFXMYOPENGLDOCHC461966B8D22

11D2B2A0EE05DF303FE6INCLUDED #if MSCVER > 1000 #pragma once #endif

MSCVER > 1000

class CMyOpenglDoc : public CDocument { protected:

create from serialization only

CMyOpenglDoc(); DECLAREDYNCREATE(CMyOpenglDoc) Attributes public: www.BOOKOO.com.cn

486


Visual C++6.0 编程实例与技巧

Operations public: Overrides ClassWizard generated virtual function overrides {{AFXVIRTUAL(CMyOpenglDoc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); }}AFXVIRTUAL Implementation public: virtual ~CMyOpenglDoc(); #ifdef DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif protected: Generated message map functions protected: {{AFXMSG(CMyOpenglDoc) NOTE - the ClassWizard will add and remove member functions here. DO NOT EDIT what you see in these blocks of generated code ! }}AFXMSG DECLAREMESSAGEMAP()

www.BOOKOO.com.cn

487


Visual C++6.0 编程实例与技巧

};

{{AFXINSERTLOCATION}} Microsoft Visual C++ will insert additional declarations immediate ly before the previous line. !defined(AFXMYOPENGLDOCHC461966B

#endif

8D2211D2B2A0EE05DF303FE6INCL UDED)

myopenglDoc.cpp

MyOpenglDoc.cpp : implementation of the CMyOpenglDoc class

#include

stdafx.h

#include

MyOpengl.h

#include

MyOpenglDoc.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

www.BOOKOO.com.cn

488


Visual C++6.0 编程实例与技巧

CMyOpenglDoc IMPLEMENTDYNCREATE(CMyOpenglDoc, CDocument) BEGINMESSAGEMAP(CMyOpenglDoc, CDocument) {{AFXMSGMAP(CMyOpenglDoc) NOTE - the ClassWizard will add and remove mapping macros here. DO NOT EDIT what you see in these blocks of generated code! }}AFXMSGMAP ENDMESSAGEMAP()

CMyOpenglDoc construction

destruction

CMyOpenglDoc::CMyOpenglDoc() { TODO: add one-time construction code here } CMyOpenglDoc::~CMyOpenglDoc() { } BOOL CMyOpenglDoc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE;

www.BOOKOO.com.cn

489


Visual C++6.0 编程实例与技巧

TODO: add reinitialization code here (SDI documents will reuse this document) return TRUE; }

CMyOpenglDoc serialization void CMyOpenglDoc::Serialize(CArchive& ar) { if (ar.IsStoring()) { TODO: add storing code here } else { TODO: add loading code here } }

CMyOpenglDoc diagnostics #ifdef DEBUG void CMyOpenglDoc::AssertValid() const

www.BOOKOO.com.cn

490


Visual C++6.0 编程实例与技巧

{ CDocument::AssertValid(); } void CMyOpenglDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif

DEBUG

CMyOpenglDoc commands 10.3 DirectX

新一代媒体大师

Microsoft DirectX 是微软公司提供的一个格调良好的应用程序开发 接口(API),能提供设计 高速实时应用程序所需要的资源 帮助您创建新 一代的电脑游戏和多媒体应用程序,目前已达到 DirectX 6.0 版本 包括 五个组成部分: DirectDraw: 这是 Game SDK 的图像部分,它是目前为止最大及最显 著的部分 提供了对硬件加速的透明通道,以及屏幕外视频内存里位图的 直接通道,并支持硬件的页面转换特性 DirectSound: 这提供了播音及实时混合能力,如果有合适的驱动器, 可往声卡上实现,否则在软件上实现 DirectPlay: 这提供了一套建立多人游戏的功能,可以用 Modem 来操 作,或可以在网络上来操作

www.BOOKOO.com.cn

491


Visual C++6.0 编程实例与技巧

DirectInput: 这提供了一套游戏杆输入功能 DirectSetup:这是一个在用户系统上安装其他 DirectX 组件的简单的 API 函数,使编写安装程序更简单 在以上个部分中,DirectDraw 是最革新 最精彩的部分,这里从激发读 者用 DirectX 进行多媒体与游戏开发的兴趣出发,只简要介绍 DirectX 的图形系统——DirectDraw 其他请参考有关专门读物 DirectDiraw 是既能使用系统 RAM 又能使用视频 RAM,可以提供软 件仿真测试的独立于硬件设备的 bltting 发动机 主要用途是尽可能快, 尽可能可靠并且尽可能连续地将图形拷贝到视频设备上 10.3.1 编写 DirectDraw 应用程序 我们已经介绍过 DirectDraw API 下面来谈一谈如何利用它来编写 完整的应用程序 本节中 我们大致介绍一下 DirectDraw 应用程序的典 型结构 10.3.1.1 窗口应用程序 DirectDraw 应用程序主要有两种型式

窗口的和全屏的

窗口

DirectDraw 应用程序看起来就像一个常规的 Windows 程序 我们很快将 讨论到全屏应用程序 窗口应用程序包括窗口边界,标题框以及菜单

这些都是传统的

Windows 应用程序中常见的部分 由于窗口应用程序同其他窗口一起出 现在桌面上 因此它们被迫使用 Windows 当前所使 用的分辨率和比特 深度 窗口程序有一个主表面 但只在进行真实页面翻转时才显现 而且

www.BOOKOO.com.cn

492


Visual C++6.0 编程实例与技巧

主表面并不代表窗口的客户区域(该区域在窗口边界内) 整个桌面

这就是说

主表面还代表

你的程序必须追踪 窗口的位置和大小

窗口内正确显示可见的输出

换言之

以便在

利用窗口化的应用程序 可以在

整个桌面上进行绘图 如果不允许页面翻转 那么图像就必须从离屏缓冲区中 blt 到主表面 上

这就增加了图像撕裂的可能性 因为 blt 比页面翻转速度慢 为了

避免图像撕裂

blt 操作可以与监视器的刷新 率保持同步

如果与窗口客户区域同样大小的离屏缓冲区被创建到显示 RAM 中 窗口应用程序就可以很好地运行 这样 窗口的内容可利用离屏表面合 成

然后离屏表面可以通过硬件加速很快地到 主表面上 由于显示存储器的缺乏而不得不将离屏缓冲区创建到系统 RAM 中

时 会严重影响性能 不幸的是 这种情况常常发生 尤其是在只有 2MB 显示卡的时候 这是因为人们总希望为自己 Win dows 的桌面设置高分 辨率的显示模式 例如

采用 1024x768xl6 显示模式的主表面自己就要

占用 2MB 的 RAM 在一个 2MB 显示卡上 几乎没有显示 RAM 留给 离屏表面 窗口应用程序的另一个问题是剪裁

一个性能良好的应用程序必须

有一个连接到主表面的剪裁器对象 这是有损性能的 原因在于为了检 查剪裁器的剪裁清单内容 blt 操作只能在一 个小的矩形部分内进行 而且

不能使用优化的 BltFast()函数 Bltting 必须用较慢的( 而且更笨

重的)Blt()函数 最后要讲的是 窗口应用程序不允许全调色板控制 由于 Windows 保留了 20 个调色板项 所以在 256 种颜色中只有 236 种颜色可被设定 被 Windows 保留的颜色只用系统调色板的前 10 个 项和后 10 个项 因 www.BOOKOO.com.cn

493


Visual C++6.0 编程实例与技巧

此在给图像上色时

只能使用中间的 236 个调色板项

10.3.1.2 全屏应用程序 包含对显示设备进行专有存取的 DirectDraw 应用程序就是全屏应用 程序 这种应用程序可以任意选取显示卡所支持的显示模式 并享有全 调色板控制

另外

全屏应用程序还可进行页 面翻转

因此同窗口应

用程序相比 全屏应用程序速度更快 灵活性更好 典型的全屏应用程序首先检查所支持的显示模式 并激活其中一个 然后创建具有一个或更多后备缓冲区的可翻转主表面

剩下的显示

RAM 用于创建离屏表面 当显示 RAM 耗尽时 就启 用系统 RAM 屏 幕被在后备缓冲区中合成的第一个场景更新 然后进行页面翻转 即使 主表 面占用了所有的可用的显示 RAM 全屏应用程序还可输出执行其 窗口化的副本

这是因为全 屏应用程序可进行真实页面翻转

由于全屏应用程序不一定非得使用 Windows 的当前显示模式 所以 显示 RAM 的可用与否不存在多少问题

如果只检测到 2MB 的显示

RAM 就可使用低分辨率显示模式来保留内存 如果检测到 4MB 的显 示 RAM 应用程序就可使用要求的显示模式并仍能保持良好性能 全调色板控制也是个有利因素 这样可以使用所有 256 个调色板项 而无需根据 Windows 保留的 20 种颜色重新分配位图 10.3.1.3 混合应用程序 混合应用程序既可以在全屏方式下运行也可在窗口方式下运行

合应用程序内部非常复杂 但却可以提供良好的性能 用户可在窗口方 式下运行 如果太慢

则可切换到全屏方式下运行

www.BOOKOO.com.cn

494


Visual C++6.0 编程实例与技巧

编写混合应用程序最好的方法是编写一个定制库

该库包括那些与

应用程序使用全屏方式还是窗口方式无关的函数 本小节的目的是教授 DirectDraw 而不是教授任何使用库 所以以后不再讨论混合应用程序 10.3.2 准备好工具 既然已经有了坚实的概念基础 际使用情况

我们就可以集中精力来研究一下实

在开始编写 DirectDraw 应用程序前 必须配置好工具

以下 4 个软件组件是必需的 (1)whdowsNT 或 wmdows9X; (2)DirectX 运行期文件; (3)DirectXSDK6.0

5.0;

(4)Visual C++ 6.0 你的计算机上可能已经安装了 Windows NT 或 Windows9X 如果使 用 Windows NT 其版本至少应在 4.0 以上(最好有 Service Pack3) 如果 使用 Windows9X 任何版本都能用 接下来我们详细讨论其他 3 项内容 10.3.2.1 DirectX 运行期文件 DirectX 包括两部分 运行期部分和 SDK 在 DirectX 开发时 这两 个部分都要用到

但在运行 DirectX 程序时只用运行期部分

Windows NT4.0 及 4.0 以 上 版 本 含 有 DirectX 运 行 期 部 分 Windows95 没有, Windows98 有 但 Windows 95 可以很容易地获得或 安装 DirectX 运行期部分 而 Windows NT4.0 以前的版本不能 运行 DirectX 程序 许多基于 DirectX 的应用程序和游戏都包含 DirectX 运行期部分 这

www.BOOKOO.com.cn

495


Visual C++6.0 编程实例与技巧

些 DirectX 运行期部分是 专为 Windows9X 设计的 不能在 Windows NT 下安装 确定 Windows 95 是否安装了运行期部分有很多方法 一种那就方法 是试运行本节或 DirectX SDK 提供的演示 如果能运行 说明运行期部 分己被安装 另一种方法是打开控制面板 选择 Ad d Remove 程序 如果存在“DirectX Drivers”项 Windows 95

尽管 Windows NT 含有 DirectX 但“DirectXDrivers”项在

WindowsNT A dd 提示

说明运行期部分己被安装(这仅适用 于

Remove 程序对话框中并不出现)

DirectX6 增强功能:

启动 DirectX6 一个 DirectX 图标就会出现在控制面板中 DirectX65 中的调色板较以前版本能提供更高级的检查和修改 DirectX 设置的方 法 下面讨论运行期部分的版本 总共有 5 个版本 1 2 3 5 和 6(没 有版本 4) 每一个版 本都有不同的运行期部分 由于 DirectX 以 COM 为基础 而 COM 具有强大的向上支持的功能 因而新 版本的运行期部 分应与旧版本的 DirectX 应用程序配合使用 打开 Add Remove 程序对 话框检 查计算机中所安装的运行期部分的版本 选择“DirectX Drivers” 项并按动 Add Remove 按 钮 成份是 DirectX l 的成份

就会出现一份成份清单 V4.02 版本的

V4.03 版本表示 DirectX 2;V4.04 版本表示

DirectX 3; V4.05 版本表示 DirectX 5

V4.06 版本表示 DirectX 6

10.3.2.2 DirectX SDK DirectX SDK 包括开发基于 DirectX 应用程序所需要的全部文件 SDK 包括示例和帮助文件 但这些都是可选资源 必需的文件是头文件

www.BOOKOO.com.cn

496


Visual C++6.0 编程实例与技巧

(.h 文件)和库文件(.1ib 文件) 获得 DirectX SDK 要比获得运行期部分困难一些 Windows NT 和 Windows 95 都不带 DirectX S DK 要获得 SDK 可以通过以下 3 种方法 (1)购买 Visual C++ 6.0(包括 DireciX 3 SDK); (2)访问 MicrosoftWeb 站点的 DirectX 下载页; (3)成为 MSDN(Microsoft 开发网络)用户 有了 Visual C++ 6.0 就有了 SDK 尽管 3.0 不是最新版本 但足以支 持本节提供的资料 SDK 也可从 MicrosoftWeb 站点上获取 载量很大 尤其是在拨号连 接时

有可能要用一整夜的时间

成为 MSDN 用户是获得 SDK 的好方法 除非你反对通过向 Microsoft 付费的方式获得升级其操作 系统的程序开发特权 SDK 由 MSDK level 2 及以上提供 10.3.2.3 Visual C++ 一旦安装了 SDK 就得马上通知 Visual C++ SDK 的位置: 默认状态 下 SDK 安装在 m ssdk 目录下 头文件放在 mssdk include 目录下 库文件放在 mssdk

lib 目录下

可利用下述两种方法之一通知 Visual C++ SDK 的位置 一种方法是 在使用文件时 给出完整的文件路径 Visual 的 搜 索 路 径 中

另一种方法是将这些目录加到

第二种方法更好一些

可以

通过

Too1s|Options|Directories 对话框实现 图 10.12 显示了增加头文件路径 设置后出现的 对话框

www.BOOKOO.com.cn

497


Visual C++6.0 编程实例与技巧

图 10.12 Visual C++目录对话框 增加 mssdk lib 目录的方法大体上同增加 mssdk include 目录的方 法相同 图中 DirectX 目录位于常规的 Visual 目录上面 如果你获得了一个 含有 Visual C++ 的 Dire ctX SDK 的较新版本 这一点就非常重要 否 则就得使用旧版本(Visual C++从上 至下查找目录) 根据我们已经讨论过的内容 你应该能够编辑 DirectX 程序了 然而 还有最后一个潜在的障 碍 除非 INITGUID 符号已经被定义 否则在 DirectX GUIDs 下调用 QueryInterface()函数的程序同 Direc X2 SDK 的链 接会失败

INITGUID 符号只能由一个源文件定义

#include 语句之前

并且必须出现 在

如下列代码所示

define INITGUID include<ddraw.h>

www.BOOKOO.com.cn

498


Visual C++6.0 编程实例与技巧

...other includes... 对于 DirectX3 及以上版本 这种解决方法都是有效的 但还有一个 更好的方法

即 将 dxguid .1ib 文 件 链 接 到 你 的 工 程 上 ( 用

Build|Settings|Link 对话框)

以替代 INITGUID 符号的定义

10.3.2.4Windows NT 和 Windows9X 的比较 Windows NT 环境下和 Windows 9X 环境下的 DirectDraw 的开发,在 涉及到 DirectDraw 时 这两个操作系统有些差别和不兼容性 另外还有很多其他的不同点

尤其是在显示模式方面

首先

Windows NT 不支持 ModeX 显示模式 EnumDisplayModes()函数检索不 到 Mode X 显示模式 而 SetDisplayMode()函数也无法激活 ModeX 显示 模式 第二个不同点是 Windows 9X 允许指定显视器配置 而 Windows NT 不支持 在 Windows 9X 下 卡和显视器的限制 支持

DirectDraw 检查到的显示模式反映出显示

如果显示模式由显示设备支持

而不是由显视器

那么 DirectDraw 不会枚举该显示模式 但是 Windows NT 不提

供 显视器设置

因此

不管所安装的显视器是否支持

DirectDraw 将

枚举所有由显示卡支持的 显示模式 这说明 显示模式切换 DirectDraw 应用程序应该仔细设计 在商业 应用程序中必须建立一个安全机构 以使用户在自己使用之前先测试一 下显示模式 在 Windows 显示模式改变时 Win dows NT 和 Windows 9X 都使用这样一个机构 当你指令 Windows 激活一个新的显示模式时 需 要 15 秒钟 然后保存先前的显示模式 随后出现一个对话框 询问新 设置是否正常工作

www.BOOKOO.com.cn

499


Visual C++6.0 编程实例与技巧

商业化的 Windows 9X 也采用了这种型式的安全机构 毕竟无法保 证显视器是被正确设置过的 如果将错误的显视器型号提供给 Windows 9X 那么检测到的显示模式很有可能不被支持 开发例程 10.3.3 视频回放——DirectDraw 视频回放 虽然视频回放在几年前己用于游戏

但它仅用于简单的显示以及各

层之间的过渡序列 然而 也有些游戏内容采用了视频 例如在 Sierra OnLine sPhantasmagoria 中 就使用了把 带有真实演员的图片加到计 算机产生的背景环境中的视频技术

结果是产生了一个令人惊叹 又令

人信服的组合 从这种意义上讲 视频回放将使得某种程度的真实成为 可能 这种真实 由真实的演员是不可能办到的 在本节 我们将学习如何在 DirectDraw 表面上读和写 AVI 文件 本 章中的材料虽然仅是针对于简单的视频回放

但它提供了一个用

DirectDraw 视频回放的良好开端 另外 本节的内容 以 AviPlay 示例 一个基于 DirectDraw 的 AVI 播放器的形式提供 10.3.3.1 开始启动 在开始讨论细节之前

先让我们复习几个重要的与视频回放有关的

术语和概念 如果你曾经与视频回放打过交道 那么这一部分也可作为 回顾之用 一个视频文件是一组位图序列

其中的每一幅在一个给定时间内描

绘一幅场景 所有的位图将具有统一的尺寸 并以一定的速率播放 通 常这些位图将伴随着声音数据

如叙述 对话 或音乐

一个视频序列的图形和可选声音部分通常称为流(Stream) 这个术语

www.BOOKOO.com.cn

500


Visual C++6.0 编程实例与技巧

暗示着组成视频序列 (位图 也即视频数据)的各个部分是相关联的 并 以一定的顺序排列

我们将在下面的讨 论中经常用到“流”这个术语

10.3.3.2 AVI 文件 Microsoft 公司为了满足视频存储和回放的需要 开发了 AVI 文件格 式 一个 AVI 文件 除了存储一序列的位图外 还包含一个或多个与视 频序列在一起的声音轨迹 我们将在本节中广 泛涉及到 AVI 文件 视频为何能如此特殊

以至于必须有它独特的格式

系列的位图和一个声音文 件来代表视频 中最重要的一点是压缩

为什么不用一

这要牵涉到许多问题

但其

即便对于一个普通的很 短的视频图像

用非

压缩的文件格式(例如 BMP 文件格式)来存储一个视频序列中的某一帧 也需要大量的存储空间 例如 一个 1 分钟的视频图像 它的分辨率是 320 240 以每秒 3 0 帧的速率回放 就需要多于 100MB 的存储空间 很显然

视频图像用于远程播放时

题就是使用哪种压缩方法 点 也各有缺点

进行压缩是必要的

现在有许多各种各样的压缩算法

剩下的问 各有优

单个算法是不可能满足每个人的需要的

因此 AVI 文件提供了广泛的压缩支持 你也可以想像 这将使得 AVI 文件必须在一个开发得很成功的 API 帮助下才可读 幸运的是 Windows 提供了这样的 API: Windows API 视频 我们很快就会看到它的内容 10.3.3.3 视频数据 视频文件格式 例如 AVI 文件 实际上使用了两种压缩形式 除了 对视频序列中的每一帧进 行压缩的各种算法外 传送

一些图像不再被整幅

而只传送变化的部分

www.BOOKOO.com.cn

501


Visual C++6.0 编程实例与技巧

这可能是由于一个典型的视频序列是由相似的图像组成

例如

果一个视频显示了一个人走过一间屋子 那么 场景中代表人走动的那 一部分将逐帧变化

但其他的部分很可能不改 变

因此这个视频将由

第 1 帧的全部场景和每帧的变化组成 在视频序列中表示全部场景的帧叫关键帧

一个视频序列中可以有

任意多个关键帧 主要看场景变化的频度和幅度 场景的过渡 熔化效 果 照相机镜头等尤其需要调用关键帧 关键帧对于一个视频序列是很重要的

没有这些帧

中间帧是没有

意义的 你也可以显示一个非关键帧 但是整个图像不可能被表示出来 因为它表示的仅是上一帧的变化而非整个图像 一个开放式的视频文件主要指流 在后面用到 Windows API 视频时 将和这个概念成为一体 10.3.3.4 VideoForWindows Windows 给 开 发 者 们 提 供 了 一 个 创 建 和 读 取 AVI 文 件 的 手 段 ——Video For Windows(VFW) API API 提供了一种寻找和提取一个视 频序列中各帧的简单方便的手段

使得视频回放的任务大大简化

VFW 允许压缩程序和解压程序的安装和拆卸 它没有为每一种压缩 方法提供支持 而仅仅提供对一种或多种压缩技术的支持 这样 VFW 就可以根据开发者的需要

提供了一种选择合 适的压缩程序或解压程

序的手段 除此以外 VFW API 还提供了查询帧 提取帧以及其他与 流 相关的操作 10.3.3.5 VFW API VFW API 提供的函数中大部分以“AVI”开头 另一些与压缩有关的 www.BOOKOO.com.cn

502


Visual C++6.0 编程实例与技巧

函数

以“IC”开头 也有例外的 但绝大多数的 VFW 函数均以两种中

的一种作为开头 表以下是按字母顺序排列的 V FW 函数: AVIBuildFilter() AVIClearClipboard() AVIFileAddRef() AVIFileCreateStream() AVIFileEndRecord() AVIFileExit() AVIFileGetStream() AVIFileInfo() AVIFileInit() AVIFileOpen() AVIFileReadData() AVIFileRelease() AVIFileWriteData() AVIGetFromClipboard() AVIMakeCompressedStream() AVIMakeFileFromStreams() AVIMakeStreamFromclipboard() AVIPutFileOnCilpboard() AVISave() AVISaveOptions( AVISaveOptionsFree() AVISaveV() AVIStreamAddRef() AVIStreamBeginStreaming()

www.BOOKOO.com.cn

503


Visual C++6.0 编程实例与技巧

AVIStreamCreate() AVIStreamEndStreaming() AVIStreamFindSample() AVIStreamGetFrame() AVIStreamGetFrameClose() AVIStreamGetFrameOpen() AVIStreamInfo() AVIStreamLength() AVIStreamOpenFromFile() AVIStreamRead() AVIStreamReadData() AVIStreamReadFormat() AVIStreamRelease() AVIStreamSampleToTime() AVIStreamSetFormat() AVIStreamStart() AVIStreamTimeToSample() AVIStreamWrite() AVIStreamWriteData() CreateEditableStream() EditStreamClone() EditStreamCopy() EditStreamCut() EditStreamPaste() EditStreamSetInfo() EditStreamsetName( ICClose() www.BOOKOO.com.cn

504


Visual C++6.0 编程实例与技巧

ICCompress() ICCompressorChoose() ICCompressorFree() ICDecompress( ICDecompressEx() ICDecompressExBegin() ICDecompressExQuery() ICDraw() ICDrawBegin() ICDrawSuggestFormat() ICGetInfo() ICGetDisplayFormat() ICIImageCompress() ICImageDecompress() ICInfo() ICInstall() ICLocate() ICOpen() ICOpenFunction() ICRemove() ICSendMessage() ICSeqCompressFrame() ICSeqCompressFrameEnd() ICSeqCompressFrameStart() ICSetStatusProc() MyStatusProc()

www.BOOKOO.com.cn

505


Visual C++6.0 编程实例与技巧

当然 视频回放并不需要所有的函数 但很可能要用到其中的一些 因此你必须学会修改或扩展本节函数的功能 下面让我们看一些特殊的 函数 提示 在项目中加入 VFW 在使用 VFW 函数前 你必须在项目中包含 vfw.h 的头文件

并把 vfw32.1i b 文件加入连接文件表中

为了使以下所列的各个函数能正确运行 VFW 必须调用 AVIFileInit() 函数进行初始化

AVIF ileInit()函数没有参数 也没有返回值 因此很

容易使用 一旦 VFW 被初始化 就由 AVIStreamOpenFromFile()函数创建一个 流 该函数以 AVI 文件为参数 并为流生成一个句柄 该句柄又可作为 大部分 VFW 函数的参数 以指定哪一个流将被使用 在另一些场合中 我们将应用由 AVIStreamOpenFromFile()函数提供 的流句柄来获取流中视频序列的信息

AVIStreamReadFormat()函数以

BITMAPINFOHEADER 结构为例给出了序列中 的帧数以及各帧的宽 高和像素深度等信息(如果你觉得 BITMAPINFOHEADER 看起来很熟 悉

那 是 因 为 在 描 述 BMP 图 像 中 也 用 到 了 同 样 的 结 构 )

Windowswingdi.h 文件中定义的这种 结构如下 typedefstructtagBITMAPINFOHEADER{ DWORD biSize; LONG Width; LONG biHeight; WORD biPlanes; WORD biBitCount;

www.BOOKOO.com.cn

506


Visual C++6.0 编程实例与技巧

DWORD biCompression; DWORDbiSizeImag; LONGbiXPelsPerMeter; LONG biYPelsPerMeter;r DWORDbiClrUsed; DWORDbiClrImportant; }BIMAPINFOHEADER

FAR*

LPBITMAPINFOHEADER,*PBITMAPINFOHEADER; 结 构 中 的 几 个 字 段如 b iXPelsPerMeter 和 biYPelsPerMeter 没有应用于 AVI 文件 其他的 都应用于 AVI 文件 当成功地调用 AViStreamReadFormat()函数后 可 以从 BITMAPNFOHEADER 结构中提取出视频流的尺 寸 位深度 压 缩方法及色彩数目 关 于 流 的 更 多 信 息 可 以 从 AVIStreamInfo() 函 数 中 获 得 AVIStreamReadFrom()函数相类 似

AVIStreamInfo()函数以流句柄作为

它的参数 并含有一个包含流信息的结构 AVISt reamInfo()函数以一个 AVISTREAMINFO 结构返回流的信息 AVISTREAMINFO 结构在 vfw.h 头 文件中定义

如下所示

typedef struct{ DWORDfccType; DWORDfccHandler; DWORDdwFlag; DWORDdwCaps; WORD wPriority WORD wLanguage; DWORDdwScale;

www.BOOKOO.com.cn

507


Visual C++6.0 编程实例与技巧

DOWRD dwRate; DWORDdwStart; DWORDdwLengh; DWORDdwInitialFrames; DWORDdwSuggestedBufferSize; DWORDdwQuality; DWORDdwSampleSize; RECT rcFrame; DWORD dwEditCount; DWORD dwFormatChangeCount; charszName

64

;

AVISTREAMINFO; 让我们看看几个关键字段 第 1 个字段是 fccType 类型

AVI 文件支持 4 种流数据

这表示了流的

视频 音频 MIDI(音乐)和文本 本

书中仅涉及视频流 AVISTREAMINFO 结构中第 2 个字段是 fccHandle 表示视频流所用 的压缩方法

在节后面你将看到

VFW 允许使用这个字段创建一个解

压程序 从流中将各帧解压 dwStart 表示流中第 1 帧的索引(这很重要 因为视频中的第 1 帧只被 注释为 0 或 1),该值可由 AVIStreamStart()函数直接检索 视 频 序 列 中 的 总 帧 数 存 储 在 dwLength 字 段 中

该值可由

AVISTREAMINFO 结构或 AVIStreamLeng th()函数检索 现在我们已经知道了如何打开一个视频流并从中检索信息 我们看看如何提取并显示视频帧

下面让

帧由 AVIStreamRead()函数提取

函数应用我们所要检索的指定的帧的流句柄和 索引 www.BOOKOO.com.cn

并将原始帧数据

508


Visual C++6.0 编程实例与技巧

存入给定的缓冲区 该原始帧数据是经过压缩的 在播放之前 首先 要 进行解压缩 使用 ICDecompress()函数进行解压缩 2 个缓冲区传给该函数 一个 用于存放压缩数据 另一个用于存放解压后的数据 该函数还需要一个 解压程序句柄

以便处理帧数据时用

解压程序句柄由 ICDecompressOpen()函数检索 当给出视频数据的 描述后 ICDecompress Open()函数就查找一个所需要的解压算法的解压 程序 一旦使用完一个流 AVIStreamRelease()函数将被调用关闭该流 当 该函数被调用之后 后

代表流的句柄将无效(除非这个流被重新打开)

当你的程序终止时 AVIFileExi t()函数将被调用释放 VFW 模块所

使用的所有资源 10.3.3.6 AviPlay——DirectDraw 视频播放编程示例 AviPlay 示例是在一个 DirectDraw 表面上使用 VFW 打开并播放 AVI 文件 该示例允许进行 AVI 文件选择 并为用于播放的示例指定显示模 式

该程序运行时显示 AVI 文件选择对话框如图 10. 13 所示 在 AVI 文件选择对话框中选择正在使用的分辨率工作模式(亦可调整

以适应之) 然后按<Ch oose>按钮 弹出文件选择对话框 选择一个(.avi) 文件 按<OK>,返回 AVI 文件选择对话 框 按<Play>钮 则程序接管 整个屏幕的控制权 在全屏幕方式下播放所选定的 AVI 文件

图 10.14

为正在播放关于地球的(.avi)文件

www.BOOKOO.com.cn

509


Visual C++6.0 编程实例与技巧

图 10.13 AviPlay 示例 AVI 选择对话框

图 10.14 AviPlay 全屏幕播放 AVI 文件 如果你还未曾使用过 AviPlay 示例 也未曾察觉 我们必须指出 该 示例是有一定限制的 一方面 它不能用以播放可能也在 AVI 文件表中 列出的音频流——它只提取并播放文件中的图形部分 另一方面

它也不能对所播放的视频实行速率控制

它只能对各帧

尽可能快地提取并播放 如果该 AVI 文件播放器是高性能的 那么它可 对 AVI 文件中的时间信息进行利用 并使用这些数据 AviPlay 示例可支持 16 位和 24 位显示模式 这是一种很有用的特性 不考虑所播放的视频的位深度

该示例可支持 8 位显示模式

www.BOOKOO.com.cn

510


Visual C++6.0 编程实例与技巧

现在我们开始动手编程 (1)启动 Visual C++ 6.0,选择<File>菜单<New>命令

在弹出的 New

对 话 框 中 输 入 项 目 名 AviPlay, 在 项 目 类 型 列 表 中 选 择 DirectDraw AppWizard,如图 10.15 所示

按 OK

图 10.15 创建 AviPlay 项目 (2)在 DirectDraw AppWizard 对话框中是一些介绍信息 如图 10.16, 按<Next>钮

www.BOOKOO.com.cn

511


Visual C++6.0 编程实例与技巧

图 10.16 DirectDraw AppWizard 向导框 (3)在 Application Type 框中选择应用程序类型 全屏幕型与视窗型 选<Full Screen>,如图 10.17,按<Next>钮

www.BOOKOO.com.cn

512


Visual C++6.0 编程实例与技巧

图 10.17 选择应用程序类型 (4)在 Initial Setting 框中选择适合的工作模式

如图 10.18

<Next>钮

www.BOOKOO.com.cn

513


Visual C++6.0 编程实例与技巧

图 10.18 初始化设置 (5)在 Application Content 框中选<None>,因为我们并不是来显示位 图或制作动画的 如图 10.19, 按<Next>钮

www.BOOKOO.com.cn

514


Visual C++6.0 编程实例与技巧

图 10.19 应用程序内容框 (6)在 Class Name 框中列出了 DirectDraw AppWizard 自动为你创 建的应用程序类名 可以在编辑框中进行修改 缺省时为*App 和*Win 两个类 其中*代表项目名 如图 10.20, 按<Fin ish>钮

www.BOOKOO.com.cn

515


Visual C++6.0 编程实例与技巧

图 10.20 应用程序类名列表 (7)最后为新建项目的总结信息 此时是返回修改设置的最后一次机 会 如果对前述设置不满意 可以按<Cancel>钮返回重新设置 如果是 无要修改的地方 按<OK>,如图 10.21

www.BOOKOO.com.cn

516


Visual C++6.0 编程实例与技巧

图 10.21 项目总结信息框 项目建立起来后

即可编译运行

但由于尚未添加任何代码

故此

时的项目是一个空项目 运行结果为全黑的屏幕(这也正是我们想要的效 果

我们有了对全屏幕的控制权

)此时

若展开项目工作区窗口

(Workspace Window)中的资源视图(Resource View) 则会发现 A ppwizard 已自动加入了一个 Dialog 资源 如图 10.22 删除 Cancel 钮 保留其他 控件 按表 10.4 添加其他控件并同时设置各控件及对话框的属性和 ID 值 结果应如图 10.23 所示

www.BOOKOO.com.cn

517


Visual C++6.0 编程实例与技巧

图 10.22 缺省对话框资源

图 10.23 修改后的对话框资源 利用前面章节的 VC++ Classwizard 知识 按照后面的源程序清单添 加各种消息处理函数和成员变量 或修改已有函数的代码,由于这不是新 的内容 故不再详述具体过程 但要注意的是 别忘了按照前边介绍的 方法把 vfw32.lib 与应用程序连接 并把 vfw.h 及其他几个头文件(.h)嵌 入项目文件

对于其中几个重要函数的解释请参阅后面的程序说明部

分 表 10.4 各控件的对话框的属性和 ID 值 类别

ID 值

性 对话框

IDDAVIDIALOG

标题

DirectDraw AVI Player www.BOOKOO.com.cn

518


Visual C++6.0 编程实例与技巧

风格

Title

System menu 按钮

IDCOPENAVI

标题 Choose... 风

Visible ,Tab stop 按钮

IDCOK

标题 Play 风

Visible ,Tab stop 列表框

IDCMODELIST

Visible ,Tab stop

风格

Vertic al scrollNotify ,No integral

height,

Selection: Single 成组框(两个)

IDCSTATIC

标题

IDCSTATIC

标题

AVI ,Display Modes 静态文本框(四个)

Name,Locator,Dimensio ns,Frames 附 源程序清单

AviPlayWin.h

#ifndef AVIPLAYWINH #define AVIPLAYWINH #include

DirectDrawWin.h

www.BOOKOO.com.cn

519


Visual C++6.0 编程实例与技巧

#include

AviDialog.h

class AviPlayWin : public DirectDrawWin { public: AviPlayWin(); protected: {{AFXMSG(AviPlayWin) afxmsg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afxmsg void OnRButtonDown(UINT nFlags, CPoint point); afxmsg int OnCreate(LPCREATESTRUCT lpCreateStruct); afxmsg void OnDestroy(); }}AFXMSG DECLAREMESSAGEMAP() private: int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces() { return TRUE; } void DrawScene(); void RestoreSurfaces(); void GetSystemPalette(); void ShowDialog(); BOOL LoadAvi(); BOOL CreateAviSurface(); BOOL UpdateAviSurface(); BOOL InstallPalette(); private: AviDialog* avidialog;

www.BOOKOO.com.cn

520


Visual C++6.0 编程实例与技巧

CString fullfilename; CString filename; CString pathname; CRect displayrect; LPDIRECTDRAWSURFACE avisurf; CRect avirect; int x,y; DisplayModeArray displaymode; LPDIRECTDRAWPALETTE syspal; LPDIRECTDRAWPALETTE avipal; PAVISTREAM avistream; AVISTREAMINFO streaminfo; HIC decomp; long fmtlen, buflen; long startframe, endframe; long curframe; LPBITMAPINFOHEADER srcfmt; LPBITMAPINFOHEADER dstfmt; BYTE* rawdata; BYTE* finaldata; }; #endif

AviPlayWin.cpp

www.BOOKOO.com.cn

521


Visual C++6.0 编程实例与技巧

#include

Headers.h

#include

resource.h

#include

AviDialog.h

#include

AviPlayWin.h

#pragma comment (lib, ddraw.lib

)

#pragma comment (lib, dxguid.lib

)

#pragma comment (lib, vfw32.lib

)

BEGINMESSAGEMAP(AviPlayWin, DirectDrawWin) {{AFXMSGMAP(AviPlayWin) ONWMKEYDOWN() ONWMRBUTTONDOWN() ONWMDESTROY() ONWMCREATE() }}AFXMSGMAP ENDMESSAGEMAP() AviPlayWin::AviPlayWin() { syspal=0; avipal=0; decomp=0; avisurf=0; avidialog=0; x=0; y=0;

www.BOOKOO.com.cn

522


Visual C++6.0 编程实例与技巧

avistream=0; srcfmt=0; dstfmt=0; rawdata=0; finaldata=0; startframe=0; endframe=0; curframe=0; } void AviPlayWin::DrawScene() { long r; r=AVIStreamRead( avistream, curframe, 1, rawdata, buflen, 0 *&bytes*

, 0 );

if (r) { TRACE(

AVIStreamRead failed:

);

switch (r) { case AVIERRBUFFERTOOSMALL: TRACE(

BUFFERTOOSMALL n

);

break; case AVIERRMEMORY: TRACE(

MEMORY n

);

break; case AVIERRFILEREAD:

www.BOOKOO.com.cn

523


Visual C++6.0 编程实例与技巧

TRACE(

FILEREAD n

);

break; } } r=ICDecompress( decomp, 0, srcfmt, rawdata, dstfmt, finaldata); if (r != ICERROK) TRACE(

ICDecompress: failed (error code %d)

n

, r);

curframe=(curframe<endframe) ? curframe+1 : startframe; UpdateAviSurface(); backsurf->BltFast( x, y, avisurf, 0, DDBLTFASTWAIT ); primsurf->Flip( 0, DDFLIPWAIT ); } BOOL AviPlayWin::CreateAviSurface() { if (avisurf) avisurf->Release(), avisurf=0; avisurf=CreateSurface( srcfmt->biWidth, srcfmt->biHeight ); CRect displayrect=GetDisplayRect(); x=(displayrect.Width()-srcfmt->biWidth)

2;

y=0; return TRUE; } BOOL AviPlayWin::UpdateAviSurface() { HRESULT r; if (finaldata==0)

www.BOOKOO.com.cn

524


Visual C++6.0 编程实例与技巧

return FALSE; DWORD dwWidth = (srcfmt->biWidth+3) & ~3; DWORD dwHeight = srcfmt->biHeight; DDSURFACEDESC desc; ZeroMemory( &desc, sizeof(desc) ); desc.dwSize = sizeof(desc); r = avisurf->Lock( 0, &desc, DDLOCKWAIT, 0 ); if (r==DDOK) { BYTE* src = finaldata + dwWidth * (dwHeight-l); BYTE* dst = (BYTE *)desc.lpSurface; for (DWORD y=0; y<dwHeight; y++) { memcpy( dst, src, dwWidth ); dst += desc.lPitch; src -= dwWidth; } avisurf->Unlock( 0 ); } return TRUE; } void AviPlayWin::RestoreSurfaces() { avisurf->Restore(); } int AviPlayWin::SelectInitialDisplayMode() { www.BOOKOO.com.cn

525


Visual C++6.0 编程实例与技巧

GetSystemPalette(); int i, nummodes=GetNumDisplayModes(); DWORD w,h,d; for (i=0;i<nummodes;i++) { DisplayModeDescription desc; GetDisplayModeDimensions( i, w, h, d ); if (d==8) { desc.w=w; desc.h=h; desc.d=d; desc.desc.Format(

%dx%d

, w, h );

displaymode.Add( desc ); } } int curdepth=GetDisplayDepth(); if (curdepth!=8) ddraw2->SetDisplayMode( 640, 480, curdepth, 0, 0 ); for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480 && d==8) return i; } return 1; } www.BOOKOO.com.cn

526


Visual C++6.0 编程实例与技巧

void AviPlayWin::OnKeyDown(UINT key, UINT nRepCnt, UINT nFlags) { switch (key) { case VKESCAPE: case VKSPACE: case VKRETURN: ShowDialog(); break; } DirectDrawWin::OnKeyDown(key, nRepCnt, nFlags); } void AviPlayWin::ShowDialog() { const CRect& displayrect=GetDisplayRect(); if (displayrect.Width()<640 || displayrect.Height()<480) ddraw2->SetDisplayMode( 640, 480, 8, 0, 0 ); ClearSurface( backsurf, 0 ); ClearSurface( primsurf, 0 ); primsurf->SetPalette( syspal ); ddraw2->FlipToGDISurface(); ShowCursor( TRUE ); if (avidialog==0) { avidialog=new AviDialog(); avidialog->SetArray( &displaymode ); www.BOOKOO.com.cn

527


Visual C++6.0 编程实例与技巧

} if (avistream) AVIStreamRelease( avistream ), avistream=0; if (avidialog->DoModal()==IDCANCEL) { PostMessage( WMCLOSE ); return; } ShowCursor( FALSE ); fullfilename=avidialog->fullfilename; filename=avidialog->filename; pathname=avidialog->pathname; int index=avidialog->GetIndex(); DWORD w,h,d; w=displaymode index

.w;

h=displaymode index

.h;

d=displaymode index

.d;

ActivateDisplayMode( GetDisplayModeIndex( w, h, d ) ); LoadAvi(); CreateAviSurface(); InstallPalette(); curframe=startframe; } BOOL AviPlayWin::LoadAvi() { long r;

www.BOOKOO.com.cn

528


Visual C++6.0 编程实例与技巧

CWaitCursor cur; if (avistream) AVIStreamRelease( avistream ), avistream=0; r=AVIStreamOpenFromFile( &avistream, filename, streamtypeVIDEO, 0, OFREAD | OFSHAREEXCLUSIVE, 0 ); TRACE( failed

AVIStreamOpenFromFile: %s n

, r==0 ?

OK

:

OK

:

);

r=AVIStreamFormatSize( avistream, 0, &fmtlen ); TRACE( failed

AVIStreamFormatSize: %s

n

, r==0 ?

);

int formatsize=fmtlen+sizeof(RGBQUAD)*256; if (srcfmt) delete

srcfmt;

srcfmt = (LPBITMAPINFOHEADER)new BYTE formatsize

;

ZeroMemory( srcfmt, formatsize ); if (dstfmt) delete

dstfmt;

dstfmt = (LPBITMAPINFOHEADER)new BYTE formatsize

;

ZeroMemory( dstfmt, formatsize ); r=AVIStreamReadFormat( avistream, 0, srcfmt, &fmtlen ); TRACE( failed

AVIStreamReadFormat: %s

n

, r==0 ?

OK

:

);

TRACE(

--- %s ---

TRACE(

biSize: %d

TRACE(

biWidth x biHeight: %dx%d

n

, filename); n

, srcfmt->biSize );

www.BOOKOO.com.cn

n

, srcfmt->biWidth,

529


Visual C++6.0 编程实例与技巧

srcfmt->biHeight ); if (srcfmt->biPlanes != 1) TRACE(

- biPlanes: %d

n

, srcfmt->biPlanes );

TRACE(

biBitCount: %d

n

, srcfmt->biBitCount );

CString comp; switch (srcfmt->biCompression) { case BIRGB: comp=

BIRGB ;

break; case BIRLE8: comp=

BIRLE8

;

break; case BIRLE4: comp= BIRLE4

;

break; case BIBITFIELDS: comp= BIBITFIELDS ; break; } TRACE(

biCompression: %s

TRACE(

biSizeImage: %d

TRACE(

------------------

n n

n

, comp ); , srcfmt->biSizeImage );

);

memcpy( dstfmt, srcfmt, fmtlen ); dstfmt->biBitCount = 8; dstfmt->biCompression = BIRGB; www.BOOKOO.com.cn

530


Visual C++6.0 编程实例与技巧

dstfmt->biSizeImage = dstfmt->biWidth * dstfmt->biHeight; startframe = AVIStreamStart( avistream ); TRACE(

stream start: %d

n

, startframe);

endframe = AVIStreamEnd( avistream ); TRACE(

stream end: %d

n

, endframe );

r=AVIStreamInfo( avistream, &streaminfo, sizeof(streaminfo) ); TRACE(

AVIStreamInfo: %s

n

, r==0 ?

OK

:

failed

);

buflen = dstfmt->biSizeImage; int finalbuflen=((dstfmt->biWidth+3) & ~3) * dstfmt->biHeight; if (streaminfo.dwSuggestedBufferSize) if ((LONG)streaminfo.dwSuggestedBufferSize < buflen) { TRACE(

adjusting buflen to suggested size

n

);

buflen = (LONG)streaminfo.dwSuggestedBufferSize; } if (decomp) ICClose( decomp ); decomp = ICDecompressOpen( ICTYPEVIDEO, streaminfo.fccHandler, srcfmt, dstfmt ); TRACE( failed

ICDecompressOpen: %s

n

, decomp ?

OK

:

);

if (rawdata) { TRACE( delete

delete

rawdata...

n

);

rawdata;

} www.BOOKOO.com.cn

531


Visual C++6.0 编程实例与技巧

rawdata = new BYTE

buflen

;

if (finaldata) { TRACE(

delete

finaldata...

n

finalbuflen

;

);

finaldata;

delete }

finaldata = new BYTE return TRUE; }

BOOL AviPlayWin::InstallPalette() { ICDecompressGetPalette( decomp, srcfmt, dstfmt ); PALETTEENTRY pe

256

;

LPBITMAPINFO info=(LPBITMAPINFO)dstfmt; for (int i=0; i<256; i++) { pe

i

.peRed = info->bmiColors

pe

i

.peGreen = info->bmiColors

pe

i

.peBlue= info->bmiColors

pe

i

.peFlags = 0;

i

.rgbRed; i

i

.rgbGreen; .rgbBlue;

} if (avipal) avipal->Release(); ddraw2->CreatePalette( DDPCAPS8BIT, pe, &avipal, 0 ); primsurf->SetPalette( avipal ); return TRUE; www.BOOKOO.com.cn

532


Visual C++6.0 编程实例与技巧

} void AviPlayWin::OnRButtonDown(UINT nFlags, CPoint point) { ShowDialog(); DirectDrawWin::OnRButtonDown(nFlags, point); } void AviPlayWin::OnDestroy() { DirectDrawWin::OnDestroy(); if (avistream) AVIStreamRelease( avistream ), avistream=0; if (decomp) ICClose( decomp ), decomp=0; if (srcfmt) delete

srcfmt, srcfmt=0;

if (dstfmt) dstfmt, dstfmt=0;

delete if (rawdata) { TRACE(

delete

delete

rawdata...

n

);

finaldata.. n

);

rawdata, rawdata=0;

} if (finaldata) { TRACE( delete

delete

finaldata, finaldata=0;;

www.BOOKOO.com.cn

533


Visual C++6.0 编程实例与技巧

} if (avidialog) delete avidialog, avidialog=0; AVIFileExit(); } void AviPlayWin::GetSystemPalette() { PALETTEENTRY pe

256

;

HDC dc = ::GetDC( 0 ); if (GetDeviceCaps(dc, RASTERCAPS) & RCPALETTE) { GetSystemPaletteEntries( dc, 0, 256, pe ); ddraw2->CreatePalette( DDPCAPS8BIT, pe, &syspal, 0 ); } ::ReleaseDC( 0, dc ); } int AviPlayWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1; AVIFileInit(); ShowDialog(); return 0; }

AviPlayApp.h

www.BOOKOO.com.cn

534


Visual C++6.0 编程实例与技巧

#ifndef AVIPLAYAPPH #define AVIPLAYAPPH #include

DirectDrawApp.h

class AviPlayApp : public DirectDrawApp { public: BOOLInitInstance(); protected: {{AFXMSG(AviPlayApp) }}AFXMSG DECLAREMESSAGEMAP() }; #endif

AviPlayApp.cpp

#include

Headers.h

#include

resource.h

#include

AviPlayApp.h

#include

AviPlayWin.h

BEGINMESSAGEMAP(AviPlayApp, DirectDrawApp) {{AFXMSGMAP(AviPlayApp) }}AFXMSGMAP ENDMESSAGEMAP()

www.BOOKOO.com.cn

535


Visual C++6.0 编程实例与技巧

AviPlayApp theapp; BOOL AviPlayApp::InitInstance() { #ifdef DEBUG afxTraceEnabled=FALSE; #endif AviPlayWin* win=new AviPlayWin; if (!win->Create(

High Performance AviPlay Demo

, IDIICON ))

return FALSE; mpMainWnd=win; return DirectDrawApp::InitInstance(); }

AviPlayDialog.h

#ifndef AVIDIALOGH #define AVIDIALOGH struct DisplayModeDescription { int w, h, d; CString desc; }; typedef CArray<DisplayModeDescription, DisplayModeDescription> DisplayModeArray;

www.BOOKOO.com.cn

536


Visual C++6.0 编程实例与技巧

AviDialog dialog class AviDialog : public CDialog { Construction public: AviDialog(CWnd* pParent = NULL);

standard constructor

BOOL InitControls(); int GetIndex() { return index; } void SetArray( DisplayModeArray* ); BOOL FilePalettized(){ return filepalettized; } BOOL GetAviSpecs(LPCTSTR filename, int* w, int* h=0, int* d=0, int* f=0); Dialog Data {{AFXDATA(AviDialog) enum { IDD = IDDAVIDIALOG }; CListBoxmodelist; CString filename; CString pathname; CString avidims; CString aviframes; }}AFXDATA CString fullfilename; Overrides ClassWizard generated virtual function overrides

www.BOOKOO.com.cn

537


Visual C++6.0 编程实例与技巧

{{AFXVIRTUAL(AviDialog) protected: virtual void DoDataExchange(CDataExchange* pDX);

DDX

DDV support }}AFXVIRTUAL Implementation protected: Generated message map functions {{AFXMSG(AviDialog) afxmsg void OnOpenAvi(); afxmsg void OnSelchangeModelist(); virtual void OnOK(); virtual BOOL OnInitDialog(); }}AFXMSG DECLAREMESSAGEMAP() private: DisplayModeArray* displaymodelist; int filepalettized; int index; }; #endif

AviPlayDialog.cpp

AviDialog.cpp : implementation file

www.BOOKOO.com.cn

538


Visual C++6.0 编程实例与技巧

#include

headers.h

#include

resource.h

#include

AviDialog.h

#ifdef DEBUG #define new DEBUGNEW #undef THISFILE static char THISFILE

= FILE;

#endif

AviDialog dialog AviDialog::AviDialog(CWnd* pParent

*=NULL*

)

: CDialog(AviDialog::IDD, pParent) { {{AFXDATAINIT(AviDialog) filename = T(

);

pathname = T(

);

avidims = T( aviframes = T(

); );

}}AFXDATAINIT index=-1; } void AviDialog::DoDataExchange(CDataExchange* pDX) www.BOOKOO.com.cn

539


Visual C++6.0 编程实例与技巧

{ CDialog::DoDataExchange(pDX); {{AFXDATAMAP(AviDialog) DDXControl(pDX, IDCMODELIST, modelist); DDXText(pDX, IDCFILENAME, filename); DDXText(pDX, IDCPATH, pathname); DDXText(pDX, IDCAVISDIMS, avidims); DDXText(pDX, IDCAVIFRAMES, aviframes); }}AFXDATAMAP } BEGINMESSAGEMAP(AviDialog, CDialog) {{AFXMSGMAP(AviDialog) ONBNCLICKED(IDCOPENAVI, OnOpenAvi) ONLBNSELCHANGE(IDCMODELIST, OnSelchangeModelist) }}AFXMSGMAP ENDMESSAGEMAP() void AviDialog::SetArray( DisplayModeArray* d ) { displaymodelist=d; }

AviDialog message handlers void AviDialog::OnOpenAvi() { www.BOOKOO.com.cn

540


Visual C++6.0 编程实例与技巧

SetCurrentDirectory(

c:

avi

static char BASEDCODE filter

); =

AVI Files (*.avi)|*.avi|| ;

CFileDialog opendialog( TRUE, 0, 0, OFNFILEMUSTEXIST, filter, this ); if ( opendialog.DoModal() == IDOK ) { filename = opendialog.GetFileName(); fullfilename=opendialog.GetPathName(); int pathlen=fullfilename.ReverseFind(

);

pathname=fullfilename.Left( pathlen ); InitControls(); UpdateData(FALSE); } } void AviDialog::OnSelchangeModelist() { int cursel=modelist.GetCurSel(); ASSERT(cursel!=LBERR); index=cursel; if (fullfilename!=

)

GetDlgItem(IDOK)->EnableWindow(); } void AviDialog::OnOK() { index=modelist.GetCurSel(); if (index==LBERR)

www.BOOKOO.com.cn

541


Visual C++6.0 编程实例与技巧

index=-1; CDialog::OnOK(); } BOOL AviDialog::GetAviSpecs( LPCTSTR filename, int* w, int* h, int* d, int* f ) { long r; PAVISTREAM avistream; r=AVIStreamOpenFromFile( &avistream, filename, streamtypeVIDEO, 0, OFREAD | OFSHAREEXCLUSIVE, 0 ); TRACE( failed

AVIStreamOpenFromFile: %s n

, r==0 ?

OK

:

OK

:

);

long fmtlen; r=AVIStreamFormatSize( avistream, 0, &fmtlen ); TRACE( failed

AVIStreamFormatSize: %s

n

, r==0 ?

);

LPBITMAPINFOHEADER format; format = (LPBITMAPINFOHEADER)new BYTE

fmtlen

;

ZeroMemory( format, fmtlen ); r=AVIStreamReadFormat( avistream, 0, format, &fmtlen ); TRACE( failed

AVIStreamReadFormat: %s

n

, r==0 ?

n

, *w, *h, *d);

OK

:

);

*w=format->biWidth; *h=format->biHeight; *d=format->biBitCount; TRACE(

Dialog: w=%d h=%d d=%d

www.BOOKOO.com.cn

542


Visual C++6.0 编程实例与技巧

*f=AVIStreamLength( avistream ); AVIStreamRelease( avistream ); delete

format;

return TRUE; } BOOL AviDialog::OnInitDialog() { TRACE(

OnInitDialog()... n

);

CDialog::OnInitDialog(); modelist.ResetContent(); int size=displaymodelist->GetSize(); for (int i=0;i<size;i++) modelist.InsertString( i, displaymodelist->GetAt(i).desc ); GetDlgItem(IDOK)->EnableWindow( FALSE ); if (fullfilename!=

)

InitControls(); return TRUE;

return TRUE unless you set the focus to a control

EXCEPTION: OCX Property Pages should return FALSE } BOOL AviDialog::InitControls() { GetDlgItem(IDOK)->EnableWindow( FALSE ); int w, h, depth, frames; if (GetAviSpecs( fullfilename, &w, &h, &depth, &frames )==FALSE) return TRUE; avidims.Format(

%dx%d

, w, h );

www.BOOKOO.com.cn

543


Visual C++6.0 编程实例与技巧

aviframes.Format(

%d

, frames );

if (index!=-1 && modelist.GetCount()>0) { modelist.SetCurSel( index ); GetDlgItem(IDOK)->EnableWindow( TRUE ); } return TRUE; }

Headers.h

#define VCEXTRALEAN #include <AfxWin.h> #include <AfxExt.h> #include <AfxTempl.h> #include <fstream.h> #include <vfw.h> #include <math.h> #include <ddraw.h>

Headers.cpp

#include

Headers.h

10.3.3.7 AviPlay 应用程序说明 1. AviPlayWin 类 www.BOOKOO.com.cn

544


Visual C++6.0 编程实例与技巧

AviPlay 示例的大部分功能是由 AviPlayWin 类提供的 该类继承了 DirectDrawWin 类 与本书中的其他示例不同的是 AviPlayWin 类使用 了文件选择对话框 该类不是在每次启动时创建一个显示表面 而是要 等用户选择了文件后才创建一个显示表面

然后根据所选的文件的特

性建立一个表面并做好准备工作 在程序 10.4 中给出了 AviPlayWin 类 的定义 程序 10.4 AviPlayWin 类 class AviPlayWin : public DirectDrawWin { public: AviPlayWin(); protected: {{AFXMSG(AviPlayWin) afxmsg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afxmsg void OnRButtonDown(UINT nFlags, CPoint point); afxmsg int OnCreate(LPCREATESTRUCT lpCreateStruct); afxmsg void OnDestroy(); }}AFXMSG DECLAREMESSAGEMAP() private: int SelectInitialDisplayMode(); BOOL CreateCustomSurfaces() { return TRUE; } void DrawScene(); void RestoreSurfaces(); void GetSystemPalette(); void ShowDialog(); www.BOOKOO.com.cn

545


Visual C++6.0 编程实例与技巧

BOOL LoadAvi(); BOOL CreateAviSurface(); BOOL UpdateAviSurface(); BOOL InstallPalette(); private: AviDialog* avidialog; CString fullfilename; CString filename; CString pathname; CRect displayrect; LPDIRECTDRAWSURFACE avisurf; CRect avirect; int x,y; DisplayModeArray displaymode; LPDIRECTDRAWPALETTE syspal; LPDIRECTDRAWPALETTE avipal; PAVISTREAM avistream; AVISTREAMINFO streaminfo; HIC decomp; long fmtlen, buflen; long startframe, endframe; long curframe; LPBITMAPINFOHEADER srcfmt; LPBITMAPINFOHEADER dstfmt; BYTE* rawdata; BYTE* finaldata; }; www.BOOKOO.com.cn

546


Visual C++6.0 编程实例与技巧

AviPlayWin 类中首先定义了构造函数 其目的是初始化类中的数据 成员

该类中定义了 4 个消息处理函数

OnKeyDown()

OnRButtonDown() OnCreate()和 OnDestroy() onK eyDown()函数用于 在视频播放时对空格键和 Esc 键做出反应 中断正在播放的视频并显示 A VI 选择对话框(我们本可使用 DirectInput 帆 OnRButtonDown()

但这并不值得)

函数也是在播放过程中对外做出反应的

过按鼠标右 键弹出文件选择对话框的

但它是通

OnCreate()函数和 OnDestroy()

函数分别用于初始化和终止 任务的 该类中还声明了 10 个专用函数 第一个是 SelectInitialDisplayMode() 函数 可完成 3 个 任务 选择一个初始显示模式 在 AVI 选择对话框 中构成一个 8 位模式的列表;捕获系统调色板

这些我们都将很快看到

GetsystemPalette()函数是被 SelectInitialDisplayMo de()调用的 如何调用 可看 SelectInitialDisplayMode()函数 下一个是 CreateCustomSurfaces()函数 该函数仅返回一个真值 以 表示成功

因为在启动时并没有创建别的表面

接下来是 ShowDialog()函数 用于显示 AVI 选择对话框 当一个有 效的 AVI 被选中时 将在 LoadAvi()函数帮助下装入该 AVI 文件 由于 该示例的功能主要由这两个函数提供

因此我 们将重点详细地分析这

两个函数 在 LoadAvi()函数之后是 DrawScene()函数 它将用于显示视频帧 除了提取和解压视频流中的每一个帧 DrawScene()还执行要求显示帧的 blt 和页面翻转操作 RestoreSurface()函数负责恢复显示 RAM 中的表面分配 该示例中这 个函数是普通的 可以在以后看到 www.BOOKOO.com.cn

547


Visual C++6.0 编程实例与技巧

CreateAviSurface()函数和 UpdateAviSurface()函数负责 AVI 表面的创 建和保持 AVI 表面尺寸依赖于用户所选的 AVI 文件中视频的尺寸 因 此当一个新的 AVI 文件被打开时 一个新 的 AV 表面也将同时创建 UpdateAviSurface()函数通过拷贝表面内存中 ICDecompress( )函数的输 出来更新 AVI 表面的内容 最后声明的一个成员函数是 InstallPalette()函数 它是在视频回放开 始前装入 AVI 调色板 但在安装 AVI 显示所需的调色板之前 必须从 AVI 流中提取调色板数据 该类的其他部分声明了一些数据成员 我们将在用到时介绍 2. OnCreate()函数 按运行的顺序粗略看看这些函数 我们将从 OnCreate()函数开始 int AviPlayWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (DirectDrawWin::OnCreate(lpCreateStruct) == -1) return -1; AVIFileInit(); ShowDialog(); return 0; } 首先 OnCreate()作为基类版本被调用(用于初始化 DirectDraw) 果调用失败

将返回 -1

然后 调用 AVIFileInit()函数 对程序中的 VFW 各函数进行初始化 我们可根据自己的意愿自由使用 VFW 函数 最后

调用 ShowDialog()函数 给用户提供一个 AVI 选择对话框

www.BOOKOO.com.cn

548


Visual C++6.0 编程实例与技巧

并等待用户输入

然而

在看 ShowDialog()函数之前

必须先看看

SelectiInitiaIDisplayMode()函数 该函 数是在调用 DirectDrawWin 版本 的 OnCreate()时由 DirectDrawWin 类调用的 3. SelectInitialDisplayMode()函数 如上所述 SelectInitialDisplayMode()函数要完成 3 个任务 该函数 如下所列举 int AviPlayWin::SelectInitialDisplayMode() { GetSystemPalette(); int i, nummodes=GetNumDisplayModes(); DWORD w,h,d; for (i=0;i<nummodes;i++) { DisplayModeDescription desc; GetDisplayModeDimensions( i, w, h, d ); if (d==8) { desc.w=w; desc.h=h; desc.d=d; desc.desc.Format(

%dx%d

, w, h );

displaymode.Add( desc ); } } int curdepth=GetDisplayDepth(); if (curdepth!=8) www.BOOKOO.com.cn

549


Visual C++6.0 编程实例与技巧

ddraw2->SetDisplayMode( 640, 480, curdepth, 0, 0 ); for (i=0;i<nummodes;i++) { GetDisplayModeDimensions( i, w, h, d ); if (w==640 && h==480 && d==8) return i; } return 1; } 在执行常规任务之前(为初次使用选择一个显示模式) 用 GetSystemPalette()函数

该函数会调

GetSystemPalette()函数创建一个 DirectDraw

调色板 该调色板是基于 Windows 当 前调色板的 这个新建的调色板 用以确保 AVI 选择对话框后正确弹出 而不管何种调色板己 被 安装用 于显示视频 要注意的是 Windows 并不知道 DirectDraw 的任何信息 它将用系统的 调色板来显示对话框 即使 DirectDraw 已经放弃了该系 统调色板 接下来

SelectInitialDisplayMode()函数通过可见到的显示模式重复

并用一个数组 di splaymode 来存放 8 位模式的描述 最后该数组将被传 递给 AVI 选择对话框 这样就可以显示该模式了 最后

该函数搜寻一个 8 位显示深度的 640*480 的显示模式 该模

式使用方便 因为它被大多数显示卡支持 用户可在 AVI 选择对话框出 现时选中 8 位模式 4. ShowDialog()函数 现在让我们看一看显示 AVI 选择对话框的函数 ShowDialog()函数 如程序 10.5 之所示 www.BOOKOO.com.cn

550


Visual C++6.0 编程实例与技巧

程序 10.5 ShowDialog()函数 void AviPlayWin::ShowDialog() { const CRect& displayrect=GetDisplayRect(); if (displayrect.Width()<640 || displayrect.Height()<480) ddraw2->SetDisplayMode( 640, 480, 8, 0, 0 ); ClearSurface( backsurf, 0 ); ClearSurface( primsurf, 0 ); primsurf->SetPalette( syspal ); ddraw2->FlipToGDISurface(); ShowCursor( TRUE ); if (avidialog==0) { avidialog=new AviDialog(); avidialog->SetArray( &displaymode ); } if (avistream) AVIStreamRelease( avistream ), avistream=0; if (avidialog->DoModal()==IDCANCEL) { PostMessage( WMCLOSE ); return; } ShowCursor( FALSE ); fullfilename=avidialog->fullfilename; filename=avidialog->filename; pathname=avidialog->pathname; www.BOOKOO.com.cn

551


Visual C++6.0 编程实例与技巧

int index=avidialog->GetIndex(); DWORD w,h,d; w=displaymode index

.w;

h=displaymode index

.h;

d=displaymode index

.d;

ActivateDisplayMode( GetDisplayModeIndex( w, h, d ) ); LoadAvi(); CreateAviSurface(); InstallPalette(); curframe=startframe; } ShowDialog()函数首先要检查当前显示分辨率 如果当前显示模式 的尺寸小于 640*480 那么模式被改变 这样可以避免用 Mode X 模式 来显示对话框 由于 Windows 并不支持 Mode X 并且由于 Mode X 显 示模式的非线性像素布局 试图用 Mode X 模式显示是不可能成功的 如果想知道为什么 Mode X 显示模式在第一处有效 就要知道在视 频回放中当用户按下 ESC 键

空格键或鼠标右键时 该函数就会被调

用 由于完全有可能先前的视频是用 Mode X 显示模式播放的 所以在 显示对话框之前必须先调用 ShowDialog()函数进行检查 接下来主表面和后备缓冲区各被清空

系统调色板被装入

并不严

格要求清屏 但由于我们恢复了系统的调色板 所以最好清屏 否则屏 幕上的图画看起来将很怪异 当系统调色板安装后 DirectDraw 的 FlipToGDISurface()函数将被调 用 这确保将被显示的 Windows 对话框会弹出在可见的表面 而不是在

www.BOOKOO.com.cn

552


Visual C++6.0 编程实例与技巧

后备缓冲区中 当然 鼠标也是可见的( 否则用户将无法按下对话框按 钮进行选择) 如果先前调用 ShowDialog()函数没有生成 AviDialog 类 那么将生成 一 个 AviDialog 类

注意

当对话框被建立时

先前由

SelectInitialDisplayMode()函数生成的 8 位显示模式数 组将被传递给新 的对话框 然后存在的 AVI 流将被关闭 这样做是因为 AviDialog 类保留有它 自身的 AVI 文件支持 用以显示所选 AVI 文件的尺寸和帧数 如果不关 闭己被打开的文件 当同一文件又被打开时 对话框将无法提取这个消 息 接下来用于对话的 DoModa1()函数被调用 将显示对话框并允许用 户进行选择 当用户按 Cancel 按钮后 WMCLOSE 消息将被传递 否 则 将提取所选文件名(以 3 种不同 形式) 同时提取的还有显示模式的 索引(显示模示必须在开始播放前选择) 然后是从 displaymod e 数组取 得所选显示模式的尺寸 并做为参数传给 SetDisplayMode()函数 LoadAvi 函数被调用 我们将看到 LoadAvi()函数并没有装入全部的 视频 它打开文件并取得一些视频信息 例如帧的尺寸和数量 接下来 调用的是 CreateAviSurface()函数 按视频的尺寸创建一个存储视频流的 每一帧的表面 InstallPalette()函数用于从 AVI 文件中提取调色板数据 并建立最适 合于 AVI 文件的 DirectDraw 调色板 最后用于索引视频帧的 curframe 数据成员被 startframe 成员初始化 5. LoadAvi()函数 下面让我们看看实际用于打开 AVI 文件的函数 LoadAvi()函数如程 www.BOOKOO.com.cn

553


Visual C++6.0 编程实例与技巧

序 10.6 所示 程序 10.6 LoadAvi()函数 BOOL AviPlayWin::LoadAvi() { long r; CWaitCursor cur; if (avistream) AVIStreamRelease( avistream ), avistream=0; r=AVIStreamOpenFromFile( &avistream, filename, streamtypeVIDEO, 0, OFREAD | OFSHAREEXCLUSIVE, 0 ); TRACE( failed

AVIStreamOpenFromFile: %s n

, r==0 ?

OK

:

OK

:

);

r=AVIStreamFormatSize( avistream, 0, &fmtlen ); TRACE( failed

AVIStreamFormatSize: %s

n

, r==0 ?

);

int formatsize=fmtlen+sizeof(RGBQUAD)*256; if (srcfmt) delete

srcfmt;

srcfmt = (LPBITMAPINFOHEADER)new BYTE formatsize

;

ZeroMemory( srcfmt, formatsize ); if (dstfmt) delete

dstfmt;

dstfmt = (LPBITMAPINFOHEADER)new BYTE formatsize

;

ZeroMemory( dstfmt, formatsize ); r=AVIStreamReadFormat( avistream, 0, srcfmt, &fmtlen );

www.BOOKOO.com.cn

554


Visual C++6.0 编程实例与技巧

TRACE( failed

AVIStreamReadFormat: %s

n

, r==0 ?

OK

:

);

TRACE(

--- %s ---

n

, filename);

TRACE(

biSize: %d

TRACE(

biWidth x biHeight: %dx%d

n

, srcfmt->biSize ); n

, srcfmt->biWidth,

srcfmt->biHeight ); if (srcfmt->biPlanes != 1) TRACE(

- biPlanes: %d

n

, srcfmt->biPlanes );

TRACE(

biBitCount: %d

n

, srcfmt->biBitCount );

CString comp; switch (srcfmt->biCompression) { case BIRGB: comp=

BIRGB ;

break; case BIRLE8: comp=

BIRLE8

;

break; case BIRLE4: comp= BIRLE4

;

break; case BIBITFIELDS: comp= BIBITFIELDS ; break; }

www.BOOKOO.com.cn

555


Visual C++6.0 编程实例与技巧

TRACE(

biCompression: %s

TRACE(

biSizeImage: %d

TRACE(

------------------

n n

, comp ); , srcfmt->biSizeImage );

n

);

memcpy( dstfmt, srcfmt, fmtlen ); dstfmt->biBitCount = 8; dstfmt->biCompression = BIRGB; dstfmt->biSizeImage = dstfmt->biWidth * dstfmt->biHeight; startframe = AVIStreamStart( avistream ); TRACE(

stream start: %d

n

, startframe);

endframe = AVIStreamEnd( avistream ); TRACE(

stream end: %d

n

, endframe );

r=AVIStreamInfo( avistream, &streaminfo, sizeof(streaminfo) ); TRACE(

AVIStreamInfo: %s

n

, r==0 ?

OK

:

failed

);

buflen = dstfmt->biSizeImage; int finalbuflen=((dstfmt->biWidth+3) & ~3) * dstfmt->biHeight; if (streaminfo.dwSuggestedBufferSize) if ((LONG)streaminfo.dwSuggestedBufferSize < buflen) { TRACE(

adjusting buflen to suggested size

n

);

buflen = (LONG)streaminfo.dwSuggestedBufferSize; } if (decomp) ICClose( decomp ); decomp = ICDecompressOpen( ICTYPEVIDEO, streaminfo.fccHandler, srcfmt, dstfmt ); TRACE(

ICDecompressOpen: %s

n

, decomp ?

www.BOOKOO.com.cn

OK

: 556


Visual C++6.0 编程实例与技巧

failed

);

if (rawdata) { TRACE(

delete

rawdata...

n

);

finaldata...

n

);

finalbuflen

;

rawdata;

delete }

rawdata = new BYTE

buflen

;

if (finaldata) { TRACE( delete

delete finaldata;

} finaldata = new BYTE return TRUE; } LoadAvi()函数使用 AVIStreamRelease()函数以关闭先前打开的 AVI 文件流 接着 AVIStrea mOpenFromFile()函数打开新的 AVI 文件 注意 参数

AVIStreamOpenFromFile()函数接受一个标志作为它的第 3 个

该标志说明可以打开的文件流的类型

在我们看来

streamtypeVIDEO 用以表示视频流,但 AVIStreamOpenFromF ile()函数通 过 保 留 的 3 个 标 志 (streamtypeAUDIO

streamtypeMIDI 和

streamtypeTEXT )可用于打开非视频流 接下来

流 格 式 信 息 由 AVIStreamReadFormat() 检 索 ( 在

AVIStreamFormatSize()函数的 协助下) 这里已经将 TRACE( )宏留在了

www.BOOKOO.com.cn

557


Visual C++6.0 编程实例与技巧

描述有关 AVI 文件的信息的代码中 此时还将初始化一些重要的数据成员 例如 startframe 和 endframe 成员被初始化 以便帧提取代码知道什么范围内的帧序号是有效的 然后 将检索到一个解压部件 在给出描述 AVI 和输出格式的结构 后 ICDecompressOpen ()函数将返回一个解压模块的句柄 该解压模块 稍后将被用以解压帧数据

最后

分配了 两个缓冲区

一个用于存储

从 AVI 流中提取的原始(压缩)数据 另一个用于存储结果(解压后)帧数 据 6. CreateAviSurface()函数 现在应用程序己打开一个 AVI 流 并且己提取足够的视频信息准备 提取帧 然而 当帧己被提取并解压后又该如何 这就需要一个用于存 储数据的表面 显示视频将是简单的事情 将 AVI 表面 blt 到应用程序 的后备缓仲区 并执行一个页面翻转 CreateAviSurface()函数就 是完成 这项任务的

CreateAviSurface()函数如下所示

BOOL AviPlayWin::CreateAviSurface() { if (avisurf) avisurf->Release(), avisurf=0; avisurf=CreateSurface( srcfmt->biWidth, srcfmt->biHeight ); CRect displayrect=GetDisplayRect(); x=(displayrect.Width()-srcfmt->biWidth)

2;

y=0; return TRUE; } 当释放先前创建的表面后

CreateAviSurface() 函 数 将 利 用

www.BOOKOO.com.cn

558


Visual C++6.0 编程实例与技巧

DirectDraw Createsurface( ) 函 数 创 建 一 个 同 样 尺 寸 的 表 面

同时

CreateAviSurface()函数还将初始化 x 和 y 这两个数 据成员

y 将决

x

定后备缓冲区中 AVI 表面的位置 一般来说 视频画面将被置于屏幕上 方

因此要利用 DirectDrawWin

GetDisplayRect()函数检索屏幕尺寸

以便计算时使用 7. InstallPalette()函数 AVI 文件格式和 VFW API 提供了一个检索更适合于视频图像的调色 板数据的手段 InstallPal ette()函数的任务就是要检索信息 并用它创建 一 个

DirectDraw

该 函 数 如 下 所 示

调 色 板

BOOL

AviPlayWin::InstallPalette() { ICDecompressGetPalette( decomp, srcfmt, dstfmt ); PALETTEENTRY pe

256

;

LPBITMAPINFO info=(LPBITMAPINFO)dstfmt; for (int i=0; i<256; i++) { pe

i

.peRed = info->bmiColors

pe

i

.peGreen = info->bmiColors

pe

i

.peBlue= info->bmiColors

pe

i

.peFlags = 0;

i

.rgbRed; i

i

.rgbGreen; .rgbBlue;

} if (avipal) avipal->Release(); ddraw2->CreatePalette( DDPCAPS8BIT, pe, &avipal, 0 ); primsurf->SetPalette( avipal ); www.BOOKOO.com.cn

559


Visual C++6.0 编程实例与技巧

return TRUE; ICDecompressGetPalette()函数应用于检索调色板数据 接下来的一 个循环是把调色板转换成我们可以使用的格式 DirectDraw CreatePalette()函数的参数

结果数组作为

最后一 步是把新的调色板连接

到主表面上 8. DrawScene()函数 最后 己做好了显示视频序列帧的准备 每显示一帧 DirectDrawWin 类就要调用一次 DrawScene()函数 DrawScene()函数如下所示 void AviPlayWin::DrawScene() { long r; r=AVIStreamRead( avistream, curframe, 1, rawdata, buflen, 0 *&bytes*

, 0 );

if (r) { TRACE(

AVIStreamRead failed:

);

switch (r) { case AVIERRBUFFERTOOSMALL: TRACE(

BUFFERTOOSMALL n

);

break; case AVIERRMEMORY: TRACE(

MEMORY n

);

break; case AVIERRFILEREAD:

www.BOOKOO.com.cn

560


Visual C++6.0 编程实例与技巧

TRACE(

FILEREAD n

);

break; } } r=ICDecompress( decomp, 0, srcfmt, rawdata, dstfmt, finaldata); if (r != ICERROK) TRACE(

ICDecompress: failed (error code %d)

n

, r);

curframe=(curframe<endframe) ? curframe+1 : startframe; UpdateAviSurface(); backsurf->BltFast( x, y, avisurf, 0, DDBLTFASTWAIT ); primsurf->Flip( 0, DDFLIPWAIT ); } DrawScene()函数利用 AVIStreamRead()函数从 AVI 流中提取一帧 并把结果帧存放在 raw data 缓冲区中 在该函数中滞留了一些 TRACE() 宏

以便调试时使用

但希望用不上

接着 通过调用 ICDecompress()函数从 LoadAvi()函数中得到解压模 块的句柄 ICDecom press()函数用两个缓冲区作参数 一个用于原始压 缩数据 然后 AVI 表面

另一个用于存放解压后的图 像 UpdateAviSurface()函数将被调用

以把解压的帧数据拷贝到

一旦 AVI 表面准备就绪 就由 DirectDrawSurfaceBltFast()函

数将其 blt 到后备缓冲区

之后 cur frame 数据成员被增值或复位 这

取决于它的值以及视频序列的帧数 最后 调用 DirectDra wSurface Flip() 函数 把最新获得的视频帧显示到屏幕上 9. UpdateAviSurface()函数 在看此函数之前 必须提醒大家注意 这个函数有点像装入 BMP 文 www.BOOKOO.com.cn

561


Visual C++6.0 编程实例与技巧

件的 DirectDrawWin 代码 和 DirectDrawWin 的 BMP 装入函数一样 UpdateAviSurface()函数锁定表面

然后把数据拷贝到表面内存中

UpdateAviSurface()函数如下所示 BOOL AviPlayWin::UpdateAviSurface() { HRESULT r; if (finaldata==0) return FALSE; DWORD dwWidth = (srcfmt->biWidth+3) & ~3; DWORD dwHeight = srcfmt->biHeight; DDSURFACEDESC desc; ZeroMemory( &desc, sizeof(desc) ); desc.dwSize = sizeof(desc); r = avisurf->Lock( 0, &desc, DDLOCKWAIT, 0 ); if (r==DDOK) { BYTE* src = finaldata + dwWidth * (dwHeight-1); BYTE* dst = (BYTE *)desc.lpSurface; for (DWORD y=0; y<dwHeight; y++) { memcpy( dst, src, dwWidth ); dst += desc.lPitch; src -= dwWidth; } avisurf->Unlock( 0 ); }

www.BOOKOO.com.cn

562


Visual C++6.0 编程实例与技巧

return TRUE; } 一旦表面被锁定 该函数就通过一个循环把 AVI 数据的每一行像素 拷贝到表面内存中 和 BMP 数据格式一样 AVI 数据格式也是倒置存 放的

所以应从 AVI 数据缓仲区的底部开始向顶部读数据

10. RestoreSurfaces()函数 我们已经完成了最困难的工作 RestoreSurfaces()函数是非常易于实现的

剩余部分将很容易了 RestoreSurfaces()函数如下所

示 void AviPlayWin::RestoreSurfaces() { avisurf->Restore(); } 记住 RestoreSurfaces()函数只在表面丢失时被调用 DirectDrawWin 类将自动恢复主表面和后备缓冲区 对于 AviPlay 示例 由于仅剩下 AVI 表面要恢复 故可以仅调用一个 Direct DrawSurface Restore()函数恢复 AVI 表面 在有些示例中 曾经使用了 RestoreSurfaces()函数来恢复表面内容和 内存 这里 由于 AVI 画面将被下一帧更新 所以有足够的内存来恢复 画面 它并没有对调用 Restore()函数恢 复未丢失的表面而产生破坏(和 用系统 RAM 创建表面情形类似) 11. 处理用户输入 在 AviPlay 示例中 用户输入只占很小的一部分 该示例只对 3 个键 有响应 并且响应是一样的 键盘输入是由 OnKeyDown()函数处理的

www.BOOKOO.com.cn

563


Visual C++6.0 编程实例与技巧

OnKeyDown()函数如下所示 void AviPlayWin::OnKeyDown(UINT key, UINT nRepCnt, UINT nFlags) { switch (key) { case VKESCAPE: case VKSPACE: case VKRETURN: ShowDialog(); break; } DirectDrawWin::OnKeyDown(key, nRepCnt, nFlags); } 3 个键的输入都将导致 ShowDialog()函数的调用 鼠标输入也差不 多 只是由 OnRButtonDown ()函数处理 void AviPlayWin::OnRButtonDown(UINT nFlags, CPoint point) { ShowDialog(); DirectDrawWin::OnRButtonDown(nFlags, point); } 以上我们己看到了用户输入是如何被处理的

当用户在取消 AVI

选择对话框时 ShowDialog ()函数将输出消息 WMCLOSE 标志应用程 序终止 12. OnDestroy()函数 剩下该做的只是终止应用程序了

OnDestroy()函数将关闭打开的

www.BOOKOO.com.cn

564


Visual C++6.0 编程实例与技巧

AVI 流 释放解压部分 释放 AVI 数据缓冲区 OnDestroy()函数如下所 示 void AviPlayWin::OnDestroy() { DirectDrawWin::OnDestroy(); if (avistream) AVIStreamRelease( avistream ), avistream=0; if (decomp) ICClose( decomp ), decomp=0; if (srcfmt) delete

srcfmt, srcfmt=0;

if (dstfmt) dstfmt, dstfmt=0;

delete if (rawdata) { TRACE(

delete

rawdata...

n

);

finaldata.. n

);

rawdata, rawdata=0;

delete } if (finaldata) { TRACE(

delete

delete

finaldata, finaldata=0;;

} if (avidialog) delete avidialog, avidialog=0; AVIFileExit(); www.BOOKOO.com.cn

565


Visual C++6.0 编程实例与技巧

} 注意

OnDestroy() 函 数 最 后 调 用 的 是 AVIFileExit() 函 数

AVIFileExit()将关闭 VFW 并释放它所分配的资源 10.3.3.8 小结 以上所述为视频回放 要想从 AviPlay 示例中做一个真正的 AVI 播 放器

工作量是很大的

除了声音和时间支持外

VFW 是一个要求有

许多实验的相当强大的 AP1 最后一点 由于未知的原因 VFW 不能用于处理用 IR32 和 IR42 压 缩的 AVI 文件(也许的压缩格式也不适宜) 但另一方面 使用 MS CRAM 和 Cinepak 的 Avi 文件却工作得很好

www.BOOKOO.com.cn

566


Visual C++6.0 编程实例与技巧

第十一章 Visual C++ 6.0 数据库应用程序 如果你已经顺利地阅读到本章时

我认为你完全有资格获得 Visual

C++ 6.0 的“行车执照”了 喜悦之情一定是难以言表 最初对 Visual C++ 6.0 的那种新奇和望而生畏的感觉也一定烟消云散了 它那神奇般风采和强劲的功能时

当你充分领略到

相见恨晚和如获至宝之情便 会油然

而生 在你欣喜之余

需要提请你注意的是

这仅仅是你在学习和使用

Visual C++ 6.0 中迈出的第一步 是一个良好的开端 常言道 学无止境 这句话用于 Visua1 C++ 6.0 再合适不过了 因 为它毕竟是一个十分高级的开发工具 要真正将它应用到得心应手的程 度 除了会熟练使用工具集成开发平台外 还要能灵活掌握其丰富而不 断更新扩充的 MFC(基类库)的运用 这是一个需要通过大量的实践 来 不断深入学习和结累经验的过程 可以说 Visaual C++ 6.0 的精华 一个是集中在它的集成开发平台上 另一个是集 中在它的 MFC 中 前者大家已经比较熟知了 后者尚是有 所了解 MFC 是 Visual C++ 6.0 为 帮助用户 开发各种应用程序所提供 的基类库

每一个类库包含有一组 C++类

其中封装着为 Micros

oftWindows 操作系统所编写的应用程序的各种功能 用好它们能为用户 编程带来极大的方便 目前 它已能提供包含 Intenet 类在内的大量的基 类 为帮助大家对 Visual C++有进一步的了解和提高使用水平 我们以 用 MFC 的 ODBC 类和 DAO 类为例 来开发一个现实生活中应用特别 广泛的数据库应用程序

www.BOOKOO.com.cn

567


Visual C++6.0 编程实例与技巧

记住 MFC 是个万花筒 它可以帮你编织出多彩的世界 使你具有 非凡的能力 11.1 登录 ODBC 数据源 当今社会正处于信息爆炸的时代 业

数据库技术的应用己遍及各行各

这给数据库技术的研究和数据库应用程序的开发提供了良好的环

境 Visual C++为顺应这一发展需求 开发了 ODBC 类 以方便用户对 多种流行数据库开发应用程序 ODBC 是指开放数据库互联 是一套开 放的数据库标准接口

使用它可以构 造与数据库无关的客户服务器应

用程序 数据库应用程序当然是需要一个数据库作为后台支持

所以在建立

它之前 首先要使 用 ODBC 登录一个确定的数据库文件 假定我们将 要选择一个预先建好的用于管理学生 记录的 MicrosoftAccess 数据库文 件 STDREG32 (1) 在 Windows9X 的桌面上 始”按钮

从弹出的菜单中

用鼠标双击屏幕左下端任务栏的“开

通过“设置”命令

找到并打开“控制面板”

对话框 用鼠标双击 ODBC(32bit)图标 就 可打开 ODBC 管理对话框 如图 11.1 所示

www.BOOKOO.com.cn

568


Visual C++6.0 编程实例与技巧

图 11.1 双击控制面板中的 ODBC(32bit)图标 (2) 在打开 ODBC 的 Data Source Administrator 对话框中 UserData Sources 列表是空的 表明当前无用户数据源被登录 如图 11.2 所示 选择 Add 按钮 以确定要登录的数据库文件的 ODBC 驱动器

www.BOOKOO.com.cn

569


Visual C++6.0 编程实例与技巧

图 11.2 ODBC Data Source Adminstrator 对话框 (3) Create New Data Source 对话框打开 在该对话框的列表中 给出 了各种己有的数据源驱动器 用户可以从中选择一种所需的驱动器 用鼠标选 Microsoft Access Driver 再击“完成”按钮 如图 11.3 所示

图 11.3 Create New Data Source 对话框

www.BOOKOO.com.cn

570


Visual C++6.0 编程实例与技巧

(4) 与 Microsoft Access 驱动器对应的 ODBC Setup 对话框打开 在 DataSourceName 框内键入 Student Registration 在 Description 框内为数 据库输入说明描述 在 Database 组框中

选击 Select 按钮

在 Select Database 对话框中(如图 11.4 所示)

选择预先准备好的

MicrosoftAccess 数据库文件 单击 OK 按钮 确认并返回前一对话框

图 11.4 选择 Access 数据库文件 (5) STDREG32.MDB 数据文件是一个有关学生记录的样本文件 单 击 OK 按钮确定选择并返回前一对话框 在 ODBC Setup 对话框内的 Database 组框中 将给出完整的数据库文件路径 如图 11.5 所示 用 户认可后 击 OK 按钮

退出 ODBC 至此 通过 ODBC 完成了数据

库的登录

www.BOOKOO.com.cn

571


Visual C++6.0 编程实例与技巧

图 11.5 安装 Access 数据库文件 11.2 ODBC 数据库应用程序的生成 基 于 ODBC 类 建 立 数 据 库 应 用 程 序 的 基 本 用 法 是

首先使用

AppWizard 生成带有数据库支持的应用程序框架并为数据源中的表嵌入 一个记录集合对象 其次通过集成开发平台的编程工具来设计表格并实 现用户界面上的控件与数据库数据的相互连接 再使用记录集合和记录 视 11.2.1 利用向导生成应用程序框架 使用 AppWizard 可以很方便地生成与相应数据库对应的应用程序的 框架 (1) 选择平台 File 的 New 命令 并选择打开 New 对话框的 Project 窗区 在 Name 框中可键人 Enr oll 在 Location 框中指定该项目的目录 在 Type 列表框中要确保 MFC AppWizard(exe)是 选中的 在 Platforms 框中

确保只有 Win32

如图 11.6 所示

www.BOOKOO.com.cn

572


Visual C++6.0 编程实例与技巧

选择 OK 按钮

(2) 在 AppWizard Step 1 对话框中 选择 Single

Document 单选按钮 如图 11.7 所示 当用户生成不带文件支持的数据库应用程序时

AppWizard 总是作

为一个单文档应用程序来 生成 单击 Next 按钮来选择下一个对话框(Step2) (3) 在 AppWizard 对话框中 主要提供一些与数据库应用有关的选 项 如图 11.8 所示 选 Database view without file supoort 选项 可使 Data Source 按钮有 效 单击该按钮打开 DatabaseOption 对话框 选择 ODBC 以及数据库文 件 Student Registration

单击 OK 按钮

图 11.6 新建项目

www.BOOKOO.com.cn

573


Visual C++6.0 编程实例与技巧

图 11.7 创建单文档应用程序

www.BOOKOO.com.cn

574


Visual C++6.0 编程实例与技巧

图 11.8 应用程序向导第二步 (4) 从 打 开 的 Select Database Tables 对 话 框 中 选 择 数 据 库 的 SECTION 表名如图 11.9 所示 并击 OK 按钮

返回 Step2 对话框 并

从 Step2 对话框直至 Step5 对话框中 单击 Next 钮来接受缺省选项 在打开的 Step6 对话框中 可修改缺省生成的类和文件名 以便和 表名 Section 对应

www.BOOKOO.com.cn

575


Visual C++6.0 编程实例与技巧

图 11.9 选择数据库表 (5) 选择类 CEnrollSet 将其更名为 CSectionSet 为 Section.h(.cpp)

选择类 CEnrollView 将其更名为 CSectionForm 并

将对应文件更名为 SectionForm.h(.cpp) 按 Finish 钮

并将对应文件更名

如图 11.10 所示

并在被打开的 New Project Information 对话框中单击

OK 按钮 这样就可以使 A ppWizard 生成此项目

www.BOOKOO.com.cn

576


Visual C++6.0 编程实例与技巧

图 11.10 应用程序向导最后一步 11.2.2 查看记录集合 数据应用程序与文档应用程序是有很大区别的

在文档应用程序中

用户主要与文档类和视窗类打交道 而在数据应用程序中 用户主要与 记录集合和记录视类打交道 记录集合(CRecordset)对象是从数据源中选择出来的一组记录 它可 以是从一个或多个数据库表的行中所选择的一个或多个确定的列

www.BOOKOO.com.cn

577


Visual C++6.0 编程实例与技巧

图 11.11 Enroll 项目的类集合 记录视(CRecordView)是一种特定的视类 它使用对话模板资源中的 控件来查看和编辑类似对话表格中记录集合的字段 在 AppWizard 过程中 由用户指定 ODBC 数据源以及数据的表 而 AppWizard 则生成一对类 记录集合类和记录视类 (1) 当 Enroll 项目生成后 就可通过 ClassView 来查看 AppWizard 所 生成的类以及各类所包含的缺省成员函数和成员变量 在 ClassView 窗口中 扩展 Enroll 项从中可以看到己生成的类集合 很丰富 如图 11.11 所示

(2) 为查看 CSectionSet 这个新的记录集合类

可继续展开这个类的图标

如图 11.12 所示

从中可以显示出己生成好的所有成员变量

它们己包括了数据表格

Section 中每列对应的变量

www.BOOKOO.com.cn

578


Visual C++6.0 编程实例与技巧

图 11.12 展开 CSectionSet 项 (3) 用户可以通过 ClassWizard 来查看 AppWizard 是如何将数据表 Section 的列与这些成员变量相连 接的

使用平台 上 View 菜单的

ClassWizard 命令来打开 ClassWizard 对话框 如图 11.1 3 所示 ClassWizard 对话框中

选择标签 Member Variable

(4) 在

从类名框中选择

CSectionSet 并 从相应的列表框中 已可看到 Section 表的所有列与该类 的成员变量相连接了

如图 11.14 所 示

这些成员变量叫做“字段数据变量” 它是由 AppWizard 基于数据源 的列名自动命名的

www.BOOKOO.com.cn

579


Visual C++6.0 编程实例与技巧

图 11.13 ClassWizard 对话框

www.BOOKOO.com.cn

580


Visual C++6.0 编程实例与技巧

图 11.14 Section 表的所有列与成员变量被连接 (5) 如果用户不需要将表格的所有列与用户记录集合相连接 则可以 从 ClassWizard 选择不想要的列所对应的记录集合字段数据变量并单击 Delete Variable 按钮 就可删除它 但不 要删除作为表的主关键字的任 何字段 11.2.3 查看其他类和资源 AppWizard 除生成记录集合类 CSectionSet 外 CSectionForm 文档类 CEnr ollDoc

还生成有记录视类

菜单栏资源和工具栏资源

记录视 CSectionForm 是一种特定的视类 它使用对话模板资源中的 控件来查看和编辑类似对话表格中记录集合的字段 记录视对象与一个 记录集合对象以及对话框模板资源相关联 文档类通常是用于在文档内存储数据并串行化它磁盘文件中

但在

数据库应用程序中 数据是存在数据库中的 用户通常是以记录形式查 看数据而不以文件形式 用于串行化支持

这样

文档在数据库 应用程序中通常就不是

它有着特殊的作用

(1) 为了查看记录视类 CSectionForm 的源代码 可以使用 ClassView 跳到其成员函数 OnIniti alUpdate 定义处

如图 11.15 所示

www.BOOKOO.com.cn

581


Visual C++6.0 编程实例与技巧

图 11.15 查看 OnInitalUpdate 函数定义 如图所示 基类框架函数 CrecordView::OnInitialUpdate 在数据库未 打开时打开它和记录集合 并通过调用 CformView::OnInitialUpdate 来初 始化表格 (2) 为了查看文档类 CEnrollDoc 的头文件 可以在 Class View 中双 击 CEnrollDoc 的图标以打开 EnrollDoc.h 文件 从中可以看出 Enroll 应 用程序中文档类的作用是去拥有记录集合 嵌在文档对象中

记录集合对象 msectionSet

它随文档对象构造而自动构造

随文 档对象删除而

自动删除 如图 11.16 所示 (3) 为查看 AppWizard 为 Enroll 所生成的资源 可通过 Resource View 窗口扩展 Enrol1.rc 资源文件

www.BOOKOO.com.cn

582


Visual C++6.0 编程实例与技巧

图 11.16 查看 CEnroll Doc 文件 程序如图 11.17 所示 它们都是与项目相关的资源

图 11.17 Enroll 项目的资源文件 (4) 为 查 看 Enroll 的 菜 单 栏 资 源

可 以 扩 充 Menu 并 双 击

IDRMANFRAME 如图 11.18 所示 菜单编辑器会打开并显示 AppWizard 为 Enroll 应用程序所生成的缺 省菜单

www.BOOKOO.com.cn

583


Visual C++6.0 编程实例与技巧

(5) 为 查 看 Enroll 的 工 具 栏 资 源

可 以 扩 充 ToolBar 并 双 击

IDRMAINFRAME 如图 11.19 所示 工具栏编辑器会打开并显示 AppWizard 为 Ellroll 应用程序所生成的 缺省工具栏及按钮

图 11.18 菜单栏资源

www.BOOKOO.com.cn

584


Visual C++6.0 编程实例与技巧

图 11.19 工具栏资源 11.2.4 为表格定制对话框界面 AppWizard 还为 Enroll 应用程序生成了一个名为 IDRENROLLFORM 对话框资源 记录视类 CRecordView 的派生类 CSectionForm 用它来显 示其表格控件 由于类 CRecordView 是从类 CFormView 中派生出来的 所以记录视 的客户区是由对话框模板资源布置 该表格的布局设置是由开发者来设 计的 AppWizard 会在对话框模板资源上放置一个静态文 其控件标签 为“TODO Place form control on this dialog”

如图 11.20 所示

www.BOOKOO.com.cn

585


Visual C++6.0 编程实例与技巧

图 11.20 在此处设置表格布局 (1) 从 Resource View 窗 口 中 扩 展 Dialog 双 击 其 内 的 IDDENROLLFORM 对话框模板资源 该对话框编辑器会打开 可删除标签为“TODO Place form contro1 on this dialog”的静态文本控件 (2) 使用静态文本控件在对话框界面上布置表格的列名 先从 Edit 菜单中选择 Properties 命令来打开特性窗口并把它钉住 对 于每取一个静态控件 可在 Caption 编辑框中为它确定一个与表列名一 一对应的标签

如图 11.21 所示

www.BOOKOO.com.cn

586


Visual C++6.0 编程实例与技巧

图 11.21 使用静态文本控件布置格表的列名 (3) 当所有静态控件布置完成后 就可以为它们增加对应的编辑控件 (也可以成对地增加静 态控件和编辑控件) 对每一个编辑控件 使用特 性窗口中的 ID 框来确定基于表中列名称的 ID(如 IDCCOURSE) 这是 一种常规做法

如图 11.22 所示

www.BOOKOO.com.cn

587


Visual C++6.0 编程实例与技巧

图 11.22 增加相应的编辑控件 (4) 对于使用者不能更新的关键字列

可以通过选择性性窗口的

Styles 区并设置“ReadOn1y ”复选框使其编辑控件为只读 在这里可用此方法分别将 Course 和 Section 编辑控件设为只读型 (5) 当完成各种控件设置后(如图 11.23 所示) 可以通过 Layout 菜单 的 TabOrder 命令来

www.BOOKOO.com.cn

588


Visual C++6.0 编程实例与技巧

图 11.23 完成表格布局设计 查看各控件当前的 Tab 顺 序 通过单击在此次序中的每个控件来确 定开发者所希望的 Tab 顺序 也可以通过 Layout 菜 单的其他命令来进 行控件布局调整

如图 11.24 所示

www.BOOKOO.com.cn

589


Visual C++6.0 编程实例与技巧

图 11.24 表格中各控件的 Tab 顺序 最后保存编辑好的资源文件 11.2.5 控件与记录集合字段相连接 当表格界面设计好后

需要指明各编辑控件映射到表中的相应列

更准确地说就是各编辑控件映射到相应记录集合字段数据成员 要完成 这项工作 通常

开发者要使用 ClassWizard 的“外 部对象”机制 开发者使用 ClassWizard 来将对话框或表格中的控件与用户

CDialog 或 CFormView 派生类的成员变量相连接 但在 CRecordView 这 种情况下

开发者不是将表格的控件与记录视类 的数据成员连接

是与记录视相关的记录集合类的数据成员相连接

www.BOOKOO.com.cn

590


Visual C++6.0 编程实例与技巧

(1) 在 CRecordView 这种情况下 其派生类 CSectionForm 有一个名 为 mpSet 的数据成员 可以通过扩展 ClassView 中的 CSectionForm 类来查看 mpSet 11.25 所示

如图

它是 一个指向 Enroll 记录集合类 CSectionSet 的指针

图 11.25 查看 m 数据成员 (2) 控件通过 mpSet 与相应的 CSectionSet 字段数据成员相连接 为 实现表格控件 与记录集合数据成员相连接

首先要将布局己设计好的

对话框资源 IDDENROLLFORM 在对话框编辑器中打开 如图 11.26 所 示

www.BOOKOO.com.cn

591


Visual C++6.0 编程实例与技巧

图 11.26 设计好的对话框资源 (3) 为将 Course 编辑控件实现这样的连接 mpSet

CourseID

在对话框编辑器窗口中 按下 Ctrl 并双击 Course 编辑控件可打开 Add Member Variable 对话框 确保 Member Variable Name 框内显示为 mpSet

mCourseID 如图 11.27 所示

www.BOOKOO.com.cn

592


Visual C++6.0 编程实例与技巧

图 11.27 添加成员变量 单击 OK 来接受该名字 (4) 与 Course 编辑控件的处理过程相同 分别使 Section Instructor Room Schedule 和 C apacity 编辑控件与记录集合对应的数据成员相连 接(不必为静态控件生成上述的映射关系) (5) 当实现了所有表格控件与记录集合数据成员相连接后 开发者可 以通过 ClassWizard 中类 CSectionForm 相应的 Member Variables 区内查 看完整的映射

例如

在 Control IDs 列 中出现 IDCCOURSE

Member 列中可看到“ mCourseID”

则在

如图 11.2 8 所示

www.BOOKOO.com.cn

593


Visual C++6.0 编程实例与技巧

图 11.28 所有表格控件与记录数据成员相连接 11.2.6 运行 ODBC 数据库应用程序 到目前为止虽然我们并没有编写一条程序 MFC 的 ODBC 类和资源编辑器

但借助于 AppWizard

通过生成起始数据库应用程序 定制

数据库表格和将表格控件与记录集合字段相连接等几 个简单步骤

基本建立起了一个可运行的数据库应用程序 该应用程序将具有使记录 集中 的记录滚动和更新某些字段等基本功能 尽管其功能还不够强大 但它为开发者的进一步开 发提供了良好的基础 下面

我们通过编译并运行该数据库应用程序

来测试一下该应用

程序的功能 (1) 为检查 Enroll 数据库应用程序的编程结果 可以通过 Build 菜单 中的 Build 命令来生成可 执行文件 如果 Build 过程正常通过 则就可以运行通过 Build 菜单的 Execute 命令来运行该数据库应用 程序了

如图 11.29 所示

www.BOOKOO.com.cn

594


Visual C++6.0 编程实例与技巧

图 11.29 运行 ODBC 数据库应用程序 (2) Enroll 应用程序的数据库界面显示了用户设计的控件 如图 11.30 所示 当 CSectionS et 记录集合打开时 它从数据库的表中选择记录并 使第一条记录集合成为“当前记录”

使 它呈现在界面的编辑控件中

www.BOOKOO.com.cn

595


Visual C++6.0 编程实例与技巧

图 11.30 Enroll 应用程序的数据库界面 使用者可以使用数据库的向左向右按钮去查询数据库记录 (3) AppWizard 为应用程序缺省提供了 Record 菜单 Record Privious Record

它拥有 First

NextRecod 和 Last Record 命令并在工具栏上

有相对应的按钮 如图 11.31 所示 通过这些命令或相应的按钮可实现记录集合内记录的滚动控制

www.BOOKOO.com.cn

596


Visual C++6.0 编程实例与技巧

图 11.31 Record 菜单选项 (4) 对于新出现的工具按钮 可通过鼠标在其上的滑动来获取简要提 示说明 如图 11.32 所示 被设置成只读型的编辑控件(如 Course 和 Section) 用灰色呈现 以 加以区别

www.BOOKOO.com.cn

597


Visual C++6.0 编程实例与技巧

图 11.32 工具按钮的提示说明 (5) 由于在应用程序时选择了“Database view without file support”选 项 所以在 File 菜单中没有了 New Open Save 和 Save As 命令

图 11.33 所示 如果用户选择了“Dat abase view with file support”选项 则 AppWizard 将提供那些没有的 File 命令

www.BOOKOO.com.cn

598


Visual C++6.0 编程实例与技巧

图 11.33 File 菜单选项 11.3 DAO 数据库应用程序的生成 DAO(Data Access Object)是 MFC 为数据库应用程序编程所提供的新 数据库类 它是基于 OL E 的应用程序编程界面 通常来说 DAO(数据 访问对象)数据库类提供了比 ODBC 数据库类更 完整的数据库功能 DAO 可以直接读取 Microsoft Access 的.MDB 文件以及可安装的 ISAM 数据库(如 dBase FoxPro 等) 也可以通过 ODBC 访问 Oracle 和 SQLServer 等类型的数据库 11.3.1 利用向导生成应用程序框架 使用 AppWizard 建立 DAO 数据库应用程序与建立 ODBC 数据库应 用程序的过程一样便捷 具有类似的步骤 (1) 选择平台 File 的 New 命令 New Project Workspace 对话框打开 如图 11.34 所示 在 Name 框中可键入 DaoEnro1l 在 Location 框中指定该项目的目录 在 Type 列表框中要确保 M FC AppWizard(exe)是选中的 在 Platforms 框中

确保只有 win32

选择 OK 按钮 (2) 在 AppWizard Step1 对话框中 选择 Single Document 单选按钮 Step2 对话框(数据库 选项)中选 Database view without file support 选项 可使 Data Source 按钮有效 如图 1 1.35 所示 单击该按钮打开 Database Option 对话框 并选择 Dao (3) 单击浏览按钮( )

以打开“打开”对话框

www.BOOKOO.com.cn

通过该对话框查找到

599


Visual C++6.0 编程实例与技巧

Microsoft Acce ss 数据库文件 STDREG32.MDB

如图 11.36 所示

图 11.34 新建 MFC 项目

图 11.35 设置数据库选项

www.BOOKOO.com.cn

600


Visual C++6.0 编程实例与技巧

图 11.36 选择 Access 数据库文件 然后单击“打开”按钮 (4) 这时 Select Database Tables 对话框将显示出来 在数据库表名列 表框中选取表 Secti on 并击 OK 钮 返回 Step2 对话框 如图 11.37 所示 从 Step2 对话框直至 Step5 对话框中 单击 Next 钮来接受缺省选项 在 打开的 Step6 对话框中 可修改缺省生成的类和文件名 以便和 表名 Section 对应

www.BOOKOO.com.cn

601


Visual C++6.0 编程实例与技巧

图 11.37 选择数据库表 (5) 选 择 类 CDaoEnrollDoc 将 其 头 ( 实 现 ) 文 件 更 名 为 DENRLDoc.H(.CPP) 选择类 CDaoEnro llView 将其更名为 CSectionFom 并将对应文件更名为 SECTFORM.H(.CPP) 选择类 CDaOEnr o1lSet 将 其 更 名 为 CSectionSet

并 将 对 应 文 件 更 名 为 SECTSET.H(.CPP), 使

AppWizard 生 成此项目

如图 11.38 所示

www.BOOKOO.com.cn

602


Visual C++6.0 编程实例与技巧

图 11.38 修改类和文件名 11.3.2 查看 DAO 类及资源 DAO 记录集合 数据库以及记录视类等的实现代码与 ODBC 数据库 类的十分相似 这表明作为使用 ODBC 数据库类的结果 开发者可以将 从中己获取的技能和知识应用于 DAO 数据库类 当使用 AppWizard 生成了 DAO 项目后 开发者就可以使用 ClassView 来查看它所包含的类 成员函数以及成员变量 也可以通过 ClassWizard 来查看 AppWizard 为开发者确定的成员变量连接 可将查看结果与 ODBC 项目作一比较 (1) 当 DaoRnroll 项目生成后 就可通过 ClassView 来查看 AppWizard 所生成的类以及各类所包含的缺省成员函数和成员变量 在 ClassView 窗口中 扩展 DaoEnroll 项目 从中可以看到己生成的 类及字段数据成员 如图 11.39 所示

www.BOOKOO.com.cn

603


Visual C++6.0 编程实例与技巧

图 11.39 Section 表的所有列与类的成员变量相连接 (2) 在 Class Wizard 对话框中 选择标签 Member Variable 如图 11.40 所示

从类名框中选择 CSectionSet 并 从相应的列表框中

已可看到

Section 表的所有列与该类的成员变量相连接了 这些成员变量叫做“字 段数据变量”

它是由 AppWizard 基于数据源的列名自动命名的

(3) 为了查看记录视类 CSectionForm 的源代码 可以使用 ClassView 跳到其成员函数 OnInita lUpdate 定义处 如图 11.41 所示

www.BOOKOO.com.cn

604


Visual C++6.0 编程实例与技巧

图 11.40 DaoEnroll 项目的集合

www.BOOKOO.com.cn

605


Visual C++6.0 编程实例与技巧

图 11.41 CSeeTionForm 类的 OnInitialUpdate 函数定义 如图所示 基类框架函数 CdaoRecordView::OnInitialUpdate 在数据库 未 打 开 时 就 已 打 开 它 和 记 录 集 合

并 通 过 调 用

CformView::OnInitialUpdate 来初始化表格 (4) 为了查看文档类 CDaoEnrollDoc 的头文件 可以在 Class View 中 鼠标双击类 CDaoEnrollD oc 的图标以打开 CdaoEnrollDoc.h 文件 从图 11.42 中可以看出 DaoEnroll 应用程序中文档类的作用是去拥有记录集 合 记录集合对象 msectionSet 嵌在文档对象中 它随文档 对象构造而 自动构造

随文档对象删除而自动删除

www.BOOKOO.com.cn

606


Visual C++6.0 编程实例与技巧

图 11.42 查看文档类 CDao Enroll Doc 的头文件 (5) 为 查 看 DaoEnroll 的 菜 单 资 源

可 以 扩 展 Menu 并 双 击

IDRMAINFRAME 菜单编辑器会打开并显示 AppWizard 该应用程序所 生成的菜单 如图 11.43 所示 在 Record 菜单 它

www.BOOKOO.com.cn

607


Visual C++6.0 编程实例与技巧

图 11.43 Dao Enroll 的菜单资源 拥有 First Record Previou s Record Next Record 和 Last Record 命 令并在工具栏上有相对应的按钮

它们可实现记录 集合内记录的滚动

控制 11.3.3 完成 DAO 数据库应用程序 在生成一个 DA0 数据库应用程序之后 要建立该应用程序还需要以 下三个基本操作 (1) 为数据库应用程序的表格定制对话框界面 (2)将对话框表的编辑控件与记录集合字段相连接 (3)建立并运行该数据库应用程序

www.BOOKOO.com.cn

608


Visual C++6.0 编程实例与技巧

这些操作过程基本与前述的 ODBC 数据库应用程序的实现过程相 同 而且通过这两种方法所建立的数据库应用程序具有相同的功能 (1) 从 Resource View 窗 口 中 扩 展 Dialog 并 双 击 其 内 的 IDDDAOENROLFORM 对话框模板资源 该对话框编辑器会打开(如图 11.44 所示)

可删除标签为“TODo

Place form control on t his dialog”的静态文本控件

图 11.44 Dao Enroll 的对话框模板资源 (2) 同前一样 在对话框模板上设置好静态控件 编辑控件并对只读 型编辑控件设置 RedOn1y 复选框

如图 11.45 所示

www.BOOKOO.com.cn

609


Visual C++6.0 编程实例与技巧

图 11.45 在对话框模板上设置表格布局 通过 Layout 菜单命令为控件确定好 Tab 顺序并布局调整 测试并保 存最后的工作 (3) 控件通过 mpSet 与相应的 CSectionSet 字段数据成员相连接 为 将 Course 编辑 控件实现这样的连接 mpSet

CourseID

在对话框编辑器窗口中 按下 Ctl 并双击 Course 编辑控件可打开 Add Member Variable 对话框 确保 Member Variable Name 框内显示为 mpSet mCourselID 如图 11. 46 所示

www.BOOKOO.com.cn

610


Visual C++6.0 编程实例与技巧

图 11.46 添加成员变量 单击 OK 来接受该名字 (4) 与 Course 编辑控件相同 分别使 Section

Instructor Room

Schedule 和 Capacity 编辑控件与记录集合对应的数据成员相连接 当实现了所有表格控件与记录集合数据成员相连接后

可以通过

ClassWizard 中类 CSe ctionForm 相应的 Member Variables 区内查看完整 的映射 如图 11.47 所示 (5) 为检查 DaoEnroll 数据库应用程序的编程结果 可以通过 Build 菜单中的 Build 命令来生成可执行文件并通过 Execute 命令来运行该数 据库应用程序

www.BOOKOO.com.cn

611


Visual C++6.0 编程实例与技巧

图 11.47 所有表格控件与记录集合数据成员的完整映射 可通过该应用程序对话框上与 Record 有关的命令或按钮来检查该应 用程序的运行情况 如图 11.48 所示

www.BOOKOO.com.cn

612


Visual C++6.0 编程实例与技巧

图 11.48 运行 Dao Enroll 数据库应用程序

www.BOOKOO.com.cn

613


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.