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


计 算 机 类 丛 书 www.BOOKOO.com.cn VVVVisual C++6.0isual C++6.0isual C++6.0isual C++6.0 编程实例与技巧编程实例与技巧编程实例与技巧编程实例与技巧 博 库 中国 美国 台湾 版权所有 翻印必究   ⱘଚᷛ %22.22  मᑧ ঞⳌ݇೒ᔶㄝЎ %22.22,QF  ᡸ ӏԩҎϡᕫ։ᆇ ⸈ണ मᑧ㔥㞾㸠ᓔথ៪䞛⫼ⱘᡔᴃ᥾ᮑ ᡔᴃ᠟↉ফ⊩ᕟֱ  ߽ໄᯢ੠ᴗ߽ㅵ⧚ֵᙃ ᳾㒣मᑧ㔥ⱘ䆌ৃ ӏԩҎϡᕫׂᬍ ߴ䰸मᑧ㔥ⱘᴗ  ⱘ䖬ᰃ䴲ଚϮⳂⱘ ,QF 䆌ৃ ӏԩҎϡᕫ᪙㞾Փ⫼԰ક ᮴䆎ᰃߎѢଚϮⳂ %22.22 䆒ゟ㔥キ Ϟ䕑 ϟ䕑 ᳾㒣मᑧ݀ৌ ڣԧ ॄࠊ 䬰 ㄝ⦄೼Ꮖ᳝ⱘঞᇚᴹᡔᴃথሩ᠔ѻ⫳ⱘ⬉ᄤ੠៪᭄ᄫ䕑 ⲬܝӴ䕧 থ㸠 ߎ⾳ ᪁ᬒ Ӵ᪁ ሩ⼎ ࠊ԰Ў⺕Ⲭ៪ ,QF ֱ⬭ϔߛⱘ⠜ᴗᴗ߽ ࣙᣀԚϡ䰤Ѣ ߎ⠜ ໡ࠊ %22.22 ϟ䕑ⱘ԰ક ҙ䰤Ѣᆊᒁݙ㞾Ꮕ⾕Ҏ䯙䇏 मᑧ݀ৌ ZZZ%22.22FRPFQ ੠៪ ZZZ%22.22FRP ᇍҢमᑧ㔥  ᴗᴗᴗᴗ ߽߽߽߽ ໄໄໄໄ ᯢᯢᯢᯢ Visual C++6.0 编程实例与技巧 1 www.BOOKOO.com.cn 内内内内 容容容容 提提提提 要要要要 本书主要介绍 Visual C++6.0 编程技术 内容涉及相当广泛 既包括 Visual C++常规编程技术和应用程序基础的介绍 又有图形用户界面 ActiveX 控件 多媒体 数据库等高级话题的介绍 通过这些内容的学 习 会使用户充分领略到 Visual C++事件驱动可视编程技术的威力所在 书中内容实例丰富 讲解清晰 力避代码复杂冗长 简短的实例特 别有助于初学者仿效理解 把握问题的精髓;能够帮助读者快速建立对应 用程序框架的整体认识 本书是学习 Visual C++编程人员不可多得的参 考书 前前前前 言言言言 过去 Windows 编程是项非常复杂而且难以驾驭的任务 如今 这 已成为历史 由于 Visual C++强大开发工具的出现 编程技术的更新 使得编写类似于 Windows 98 这样的图形用户界面应用程序不再是不可 能的事情 用户可以非常容易地创建出像菜单栏 工具栏 按钮 对话 框 窗口等高级而又通用的图形元素 可以充分体验编程的乐趣 将自 己的研究成果以专业的水准提供给别人 本书主要针对 Windows 9X/NT 系统 介绍了应用程序的 Visual C++编程 本书的侧重点是理论与实践 相结合 遵循循序渐进 由浅入深的认知特点来安排各个章节的内容顺 序 从而使读者达到学以致用的目的 通过本书 读者将不仅学会如何 创建基本的 Windows 程序 也要学到如何在程序中添加一些必要的东西 以达到特定的目的 同时 还要学到如何设计事件驱动程序来响应 Visual C++6.0 编程实例与技巧 2 www.BOOKOO.com.cn 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 概述 Visual C++6.0 编程实例与技巧 3 www.BOOKOO.com.cn 概略介绍 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++没有留给读者太多值得思考的东西 只能死记硬背 Visual C++6.0 编程实例与技巧 4 www.BOOKOO.com.cn 一些函数的功能而不知所以然 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 操作系统下为实现资源和功能共享所依赖的关键 Visual C++6.0 编程实例与技巧 5 www.BOOKOO.com.cn 技术 进行多语言混合编程如 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 两种数据库驱动接口来操纵和访问数据的 应用程序 Visual C++6.0 编程实例与技巧 6 www.BOOKOO.com.cn Visual C 6.0 编程实例与技巧编程实例与技巧编程实例与技巧编程实例与技巧 第一章第一章第一章第一章 Visual C 6.0 概述概述概述概述 进入 90 年代中期以来 随着 Windows 操作系统在全球的盛行 GUI(图形用户界面)应用程序设计也在全球领域内风靡起来 现在很难 看到有哪个程序员在开发面向字符(DOS)的应用 程序 了 DOS 已失去 了市场 随之即来的是 功能丰富灵活 操作简单易学的可视化应用程 序设 计时代的到来 这是一个划时代的变革 结合面向对象程序设计 (OOP)方法 这必将有力推 动新一轮信息技术革命的到来 随着计算机多媒体技术 图形图像技术 计算机通信与网络技术的 发展 应用程序设计也需要有强大的可视化设计工具来支持 Visual C++ 就是 Microsoft 公司推出的支持可 视化编程 的集成环境 一般来说 可视化技术包含两方面的含义:一是软件开发阶段的可视化 即可 视化 编程 可视化编程使编程工作成为一件轻松愉快 饶有趣味的事情 二 是利用计算机图 形技术和方法 对大量的数据进行处理 并用图形图 像的方式形象而具体地加以显示 本章首先就 Visual C++ 6.0 的特点 软硬件配置及安装作一简要讨 论 1.1 Visual C++ 6.01.1 Visual C++ 6.01.1 Visual C++ 6.01.1 Visual C++ 6.0 的特点的特点的特点的特点 Visual C++ 6.0 是 Microsoft 公司推出的 VC 最新版本 它是在早期版 本的基础上不断改变 完善发展而来 用于支持Win32平台(Windows 95 Visual C++6.0 编程实例与技巧 7 www.BOOKOO.com.cn 98 NT4.0 5.0)应用程序(applicatio n) 服务(service)和控件(control)的 开发 1. Visual C++ 6.0 集成开发环境(IDE) Visual C++ 6.0 开发环境 Developer Studio 是由 Win32 环境下运行的 一套集成开发 工具所组成 包括文本编辑器(text editor) 资源编辑器 (resource editor) 项目建立工具(proje ct build facilities) 优化编译器 (optimizing compiler) 增量连接器(incremental li nker) 源代码浏览器 (source code browser) 集成调试器(integrated debugger)等 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 Wizard 来创建新类 (class) 定义消息处理函数(message handler) 覆盖虚拟函数(virtual function) 从对话框(dialog box) 表单 视图(form view)或者记录视图 Visual C++6.0 编程实例与技巧 8 www.BOOKOO.com.cn (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 页中 Visual C++6.0 编程实例与技巧 9 www.BOOKOO.com.cn 1.2 Visual C++ 6.01.2 Visual C++ 6.01.2 Visual C++ 6.01.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.01.3 Visual C++ 6.01.3 Visual C++ 6.01.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 智能提示功能智能提示功能智能提示功能智能提示功能 这个功能使编辑源代码速度更快 错误更少 当写一个函数时 在 未写全函数名前 系统会根据当前输入自动弹出一条信息 提示编程人 员该函数拥有的参数类型及个数 可以按回 车键让系统自动补足尚未 写完的函数名 这对于那些名字较长较难记忆的函数特别有用 既 提 Visual C++6.0 编程实例与技巧 10 www.BOOKOO.com.cn 高了输入速度 又防止写错函数名或者漏掉函数参数 当书写一个类对 象时 在键入“. ”或在对象指针后键入“ ”后 系统会自动弹出一个列表 框 提示编程人员该类对象所拥 有的全部成员变量和成员函数 可以 用垂直滚动条上下滚动 找到所要的变量或函数后双击 它或按回车键 则该变量或函数名会自动输入到“.”到“ ”后面 方便了输入 而更经常 的使用方法是 在“.”或“ ”后输入两三个函数名的字母 系统会据此将 高亮光跳到 你想要的变量或函数名上 直接敲回车键即可补全整个变 量或函数名 如图 1.1 图 1.1 智能提示示例 1.3.2 新的联机帮助新的联机帮助新的联机帮助新的联机帮助 Visual C++ 6.0 的联机帮助已做成一个独立于 Visual Studio 的 HTML 文本 它利用 Internet Explorer 浏览器来完成浏览 检索功能 这样做 的好处很明显 编写程序与检索帮助可同时 进行 不会因为为了查询 帮助文件而中断源程序的编辑工作 这在于 MSDN 脱离 Visual Studi o IDE(集成开发环境)而运行于 IE 不会占用 IDE 的控制权 可以把 MSDN Visual C++6.0 编程实例与技巧 11 www.BOOKOO.com.cn 最小化到任务栏而在 IDE 源代码窗口进行编程工作 在需要时随时激 活 MSDN 帮助窗口 如图 1.2 图 1.2 新的 MSDN 联机帮助利用 Internet Explorer 1.3.3 新的项目风格新的项目风格新的项目风格新的项目风格 可以创建非 MFC 标准的类似 Windows 资源管理器的文档窗口 拥 有左右两个视窗 这对于编写某些实用工具类应用程序提供了极大的方 便 1.3.4 中文语言支持中文语言支持中文语言支持中文语言支持 在 Visual C++ 5.0 版中 在项目配置第一步对话框中不支持中文 资 Visual C++6.0 编程实例与技巧 12 www.BOOKOO.com.cn 源编辑时需从资 图 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)帮助库后 进行联机检索 Visual C++6.0 编程实例与技巧 13 www.BOOKOO.com.cn 第二章第二章第二章第二章 Visual C++ 6.0 开发环境开发环境开发环境开发环境 Visual C++ 6.0 开发环境由一套综合的开发工具所组成 提供了良好 的可视化编程环境 可以对 C 和 C++程序进行各种操作 包括建立 打 开 浏览 编辑 保存 编译 链 接和调试等 这些操作都可借助鼠 标单击工具按钮来完成 方便快捷 本章详细介绍 Visual C++ 6.0 开发环境的各种功能 并介绍相对以前 版本的新增特点 2.1 Visual C++ 6.02.1 Visual C++ 6.02.1 Visual C++ 6.02.1 Visual C++ 6.0 主窗口界面主窗口界面主窗口界面主窗口界面 在按第一章所介绍的步骤将Visual C++ 6.0 安装到Windows 98中后 单击“开始 ”按钮 从开始菜单中单击“程序”菜单项 可以看到 Visual C++ 6.0 菜单命令已加入到开 始菜单的子菜单中 单击 Visual C++ 6.0 进入开发环境 IDE(图 2.1) Visual C++6.0 编程实例与技巧 14 www.BOOKOO.com.cn 图 2.1 Visual C++ 6.0 集成开发环境 IDE Developer Studio 由标题栏 菜单栏 工具栏组成 屏幕最上端是标题栏 标题栏用于显示应用程序名和打开的文件名 标题栏的颜色用于指明对应窗口是否为激活的 标题栏左端为控制菜单 框 是用于打开窗口控制菜单的图标 用鼠标单击该图标或按 Alt+空格 键 将弹出窗口控制菜单 窗口控制菜单用于控制窗口的大小和 位置 如还原 关闭 最大化和最小化等 标题栏的右边有三个控制按钮 从 左到右分别为 最小化按钮 还原按钮和关闭按钮 这些按钮用于快速 设置窗口大小 例如 使窗口填充整个屏幕 将窗口最小化为图标或关 闭窗口 标题栏的下面是菜单栏和工具栏 工具栏的下面是两个窗口 一是 Visual C++6.0 编程实例与技巧 15 www.BOOKOO.com.cn 工作区窗口 二是源代码编辑窗口 这两窗口的下面是输出窗口 用于 显示项目建立过程中所产生的错误信息 屏幕 最底端是状态栏 给出 当前操作或所选命令的提示信息 2.2 Visual C++ 6.02.2 Visual C++ 6.02.2 Visual C++ 6.02.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 在当前插入点处插入到剪贴板中 Visual C++6.0 编程实例与技巧 16 www.BOOKOO.com.cn 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 插入或删除断点 Visual C++6.0 编程实例与技巧 17 www.BOOKOO.com.cn 图 2.3 Build 工具栏 如果要在屏幕上 显示或者隐藏某个工具栏 请在屏幕工具栏区域单 击鼠标右按钮 从工具栏快捷菜单选择或者清除相应的工具栏 用户可 以用这种方法来突出自己的习惯使用的界面 特征 比如 去掉 Build 工 具栏 代之 WizardBar 工具栏与 Build miniBar 工具栏或许会更好 用些 2.3 Visual C++ 6.02.3 Visual C++ 6.02.3 Visual C++ 6.02.3 Visual C++ 6.0 菜单栏菜单栏菜单栏菜单栏 菜单栏由多个菜单项组成 与 Windows 98 95 操作一致 选择菜单 有两种方法 一种是用鼠标左按钮单击所选的菜单 另一种是键盘操作 即同时按下 Alt 键和所选菜单的热键字母(带下划线的字母 如 File 中的 F) 选中某个菜单后 就会出现相应的下拉式子菜单 在下拉式 子菜 单中 有些菜单项的右边对应着相应的快捷键(如 Save 对应 Ctrl+S) 表 示按快捷键将直 接执行菜单命令 这样可以减少进入多层菜单的麻烦 有些菜单选项后面带有三个圆点符( 如(New ) 表示选择该项后将自 动弹出一个对话框 有些菜单选项后面带有黑三角箭头( )表示选择 该项后将自动弹出级联菜单 若下拉式子菜单中的某些菜单选项显示为 灰色 则表示这些选项在当前条件下不能选择 此外 在窗口的不同位置单击鼠标右按钮将弹出不同快捷菜单 从 中可以选择与当前位置数极为相关的要频繁执行的命令 Visual C++6.0 编程实例与技巧 18 www.BOOKOO.com.cn 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:创建二进制文件 Visual C++6.0 编程实例与技巧 19 www.BOOKOO.com.cn 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)单击 Visual C++6.0 编程实例与技巧 20 www.BOOKOO.com.cn 要创建的项目类型 然后在“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 创建 SQL Server 应用程序 ISAPI Extensiopn Wizard 创建 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 静态库 Visual C++6.0 编程实例与技巧 21 www.BOOKOO.com.cn 图 2.6 “New”对话框的“Project”选项卡 请注意 以上配置选项随安装类型(典型安装 完全安装 自定义安 装等)或版本类型(学习 版 专业版 企业版)的不同而有所不同 并且 对于从事某一专业的工程人员来说并非所有 选项都能用得上 (3) 创建新的工作区 如果要创建新的工作区 请从“New”对话框的“Workspace”选项卡选 择一种工作区类型 然后在“Workspace name”文本框中键入工作区的名 字 缺省下只有“Blank Workspace” 选项 用于创建一空白工作区 (4) 创建新的文档 如果要创建新的文档 请从“New”对话框的“Other Documents”选项 卡单击要创建的文档 类型 然后在“File”文本框中键入文档的名字 如 果要添加新的文档到已有的项目中 请选中“Add to project”复选框 然 后选择项目名 Visual C++6.0 编程实例与技巧 22 www.BOOKOO.com.cn Visual C++ 6.0 可以创建诸如 Excel 工作表 Excel 图表 PowerPoint 演示文稿和 W ord 文档等文档类型 2. “Open”选项 该选项用于打开已有的文件 如 C++文件 Web 文件 宏文件 资 源文件 定义文 件 工作区文件 项目文件等 与“Open”选项对应的 按钮是 Btandard 工具栏左边数第二个 其上需有 打开的文件夹图案 选择“Open”选项将弹出“Open”对话框 与 Windows 操作方法相同 可以从中选择要打开 文件所在驱动器 路径以及文件名 3. “Close”选项 该选项用于关闭已打开的文件 如果系统中包含有多个已打开的文 件 那么使用该选项就会将当前活动窗口或选定的窗口中的文件关闭 激活或选择某一窗口可以通过鼠标单击该窗口来完成 如果某个文件改 动后未被保存就被关闭 则系统会提示用户是否保存该文件 4. “Open Workspace”项 与“Open”选项类似 该选项也是用于打开已有的文件 但主要用于 打开工作区文件 选择该项将弹出“Open Workspace”对话框 5. “Save Workspace”项 该选项保存打开的工作区项目 6. “Close Workspace”项 该选项用于关闭打开的工作区 7. “Save”项 该选项用于保存活动窗口或者当前选定窗口中的文件内容 与该选 项对应的操作按钮是 Stam dard 工具栏左边第三个按钮 其上会有一个 软盘图案 Visual C++6.0 编程实例与技巧 23 www.BOOKOO.com.cn 如果所保存的文件是新文件 则系统会弹出“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) Visual C++6.0 编程实例与技巧 24 www.BOOKOO.com.cn 图 2.7 “Page Setup”对话框 可以建立每个打印页的标题和脚注 并设置上 下 左 右边距 表 2.1 列出了每个格式码对应的标题和脚注的类型 表 2.1 标题和脚注的格式码 格式码 使用结果 &C 正文居中 &P 加入页号 &D 加入系统日期 &R 右对齐正文 &F 使用文件的名字 &T 加入系统时间 &L 左对齐正文 11. “Print”项 该项用于打印当前活动窗口中的内容 选择该项将弹出“Print”对话 框 可以对打印范围和打印机类型进行设置 12. “Recent Files”项 选择该项将打开级联菜单 其中列出了最近打开过的文件名 用鼠 标单击某个名字即可打开相应文件 Visual C++6.0 编程实例与技巧 25 www.BOOKOO.com.cn 13. “Recent Workspaces”项 选择该选项将打开级联菜单 其中包含有最近打开的工作区 用鼠 标单击某个名字即可打开相应的工作区 以上两选项为打开操作提供了一种快捷方法 14. “Exit”项 选择该选项将退出 Visual C++ 6.0 开发环境 在退出前 系统会自动 提示用户保存名窗口的内容 2.3.2 “Edit”菜单菜单菜单菜单 在“Edit”菜单中包含用于编辑或者搜索的命令选项(图 2.8) 图 2.8 “Edit”菜单 Visual C++6.0 编程实例与技巧 26 www.BOOKOO.com.cn 1. “Undo”选项 该选项用于取消最近一次的编辑修改操作 2. “Redo”选项 该选项用于最近一次的“Undo”操作 可以恢复被“Undo”命令取消的 修改操作 3. “Cut”选项 该选项将当前活动窗口中帧选定的内容拷贝到剪贴板中 然后再将 其从当前活动窗口中删除 可以用鼠标选定所要剪切的内容 方法为将 光标移到要剪切内容的开始并按住鼠标左按钮 然后拖动鼠标移到要剪 切的内容的末尾 这时 整个被选内容就会反白显示 “Cut”选 项通常 与“Paste”选项联合使用来移动选定的内容 4. “Copy”选项 该选项将当前活动窗口中被选定的内容拷贝到剪贴板中 但并不将 其从当前活动窗口中删除 “Copy”选项通常与“Paste”选项联合使用来复 制选定的内容 5. “Paste”选项 该选项用于将剪贴板中的内容插入到当前光标所在的位置中 必须 先剪切或者复制选定到剪 贴板后 才能进行粘贴 6. “Delete”选项 该选项用于删除被选定的内容 删除以后 还可以使用“Undo”命令 来恢复删除操作 7. “Select All” 该选项用于选择当前活动窗口中的所有内容 8. “Find”选项 Visual C++6.0 编程实例与技巧 27 www.BOOKOO.com.cn 该选项用于查找指定的字符串 选择“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 Visual C++6.0 编程实例与技巧 28 www.BOOKOO.com.cn $ 匹配以指定字符串结果的每一行 Exam$匹配以 Exam 结果的 各行 匹配指定字符集中的字符 Exam 1 9 匹配 Exam4 但不 匹配 ExamS 匹配与指定字符一致的字符 Exam A Zn 0 9 匹配 ExamSn5 但不匹配 ExamAm9 { } 匹配花括号晕指定的字符串的任意序列 Exm {# }* 可匹配 Exam Exam # 和 Exzm# # 9. “Find in Files”选项 该选项用于在多个文件间搜索文本 而且搜索的对象可以是文本字 符串 也可以是正则表达式 10. “Replace”选项 该选项用于替换指定的字符串 选择“Replace”选项将弹出“Replace” 对话框 可以在“ Find What ”文本框中输入替换的文本串 再在“Replace With ”文本框中输入被替换 的文本串 与“Find”选项一样 也可以选 择匹配方式(如剪字匹配 大小写区分或不区分 正则表达式等) 11. “Go To ”选项 选择该项将弹出“Go To”对话框 可以指定如何将光标移到当前活动 窗口的指定位置(如指定的行号 地址 书签 对象的定义位置 对象的 引用位置等) 菜单项标题后的“ ”说明单击该项后要弹出对话框以提供进一步的 信息 12. “Bookmarks”选项 选择该选项将弹出“Bookmarks”对话框 可以设置或取消书签 书签 Visual C++6.0 编程实例与技巧 29 www.BOOKOO.com.cn 用于在源文件中做标记 13. “Advanced”选项 选择了该项将弹出级联菜单 其中包含有用于编辑或者修改的高级 命令 例如 增量式搜索 将选定内容全部转换为大写或者小写 显示 或者隐藏制表符等 14. “Breskpoints”选项 选择该项将弹出“Breskpoints”对话框(图 2.10) 可以设置 删除和查 看断点 图 2.10 “Breskpoints”对话框 断点实际上是告诉调试器应该在何时何地中断程序的执行过程 以 便检查程序代码 度量和寄存器值 必要的话可以修改 继续执行或中 断执行 在 Visual C++ 6.0 中 断 点有位置断点 数据断点 消息断点 和条件断点等类型 所有已设置的断点都出现在“Breskpoints” 对话框底 Visual C++6.0 编程实例与技巧 30 www.BOOKOO.com.cn 部的“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函数名自动补足 Visual C++6.0 编程实例与技巧 31 www.BOOKOO.com.cn 上述最后四个选项也可通过在源代码窗口右击鼠标得到 在弹出式 菜单上列出了与之相同的四个选择项 2.3.3 “View”菜单菜单菜单菜单 在“View”菜单中包含用于检查源代码和调试信息的命令选项(图 2.11) 图 2.11 “View”菜单 1. “ClassWinzard”选项 选择该项将启动 Class Winzard Class Winzard 是一个运用于 MFC 应用程序的专用工具 使用它可以 创建新类 新类是从处理 Windows 消息和记录集(Recordset)的主 框架基类派生的 映射消息给与窗口 对话框 控件 菜单选项和加速键有关的处 理函数 创建新的消息处理函数 删除消息处理函数 Visual C++6.0 编程实例与技巧 32 www.BOOKOO.com.cn 查看已经拥有处理函数的消息并跳转到相应的处理代码中去 定义成员变量用于自动初始化 收集并验证输入到对话框或表单 视图(Form View)中的数 据 创建新类时 添加自动化方法和属性 2. “Resource Symbols”选项 选择该项将打开资源符号浏览器(图 2.12) 从中可以浏览和编辑资源 符号 资源符号是映射到整数值上的一串字符 可以在源代码或资源编 辑器中通过资源符号引用资源 图 2.12 资源符号浏览器 3. “Resource Includos”选项 选择该项将自动弹出“Resource Includos”对话框(图 2.13) 从中可以 修改资源符号文件名和预处理器指令 缺省时 系统自动将资源符号保存在文件 resource.h 中 如果同一目 Visual C++6.0 编程实例与技巧 33 www.BOOKOO.com.cn 录中有多个资源文件 就必须改变系统缺省的资源符号文件名 为此 可以在“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”文本 框用于列出以下资源文件 这些文件在编译时将成为可 执行文件的一 部分 Visual C++6.0 编程实例与技巧 34 www.BOOKOO.com.cn 独立于主资源文件中的资源而创建和编辑的文件 含有编译指示的文件 含有用户定制资源的文件 此外 “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”选项卡来 设置 Visual C++6.0 编程实例与技巧 35 www.BOOKOO.com.cn “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”菜单 Visual C++6.0 编程实例与技巧 36 www.BOOKOO.com.cn 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)用于管理项目和工作区 Visual C++6.0 编程实例与技巧 37 www.BOOKOO.com.cn 图 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 文件) 并更新工作区窗口中的信息 Visual C++6.0 编程实例与技巧 38 www.BOOKOO.com.cn 3. “Dependencies”选项 选择该项将弹出“Project Dependecies”对话框 可以编辑项目的依赖 关系 4. “Scttings”选项 选择该项将弹出“Project Settings”对话框(图 2.17) 可以为项目配置 指定不同的设置说明 图 2.16 “Component and Controls Gallery”对话框 Visual C++6.0 编程实例与技巧 39 www.BOOKOO.com.cn 图 2.17 “Project Settings”对话框 5. “Exprot Makefile”选项 选择该项将按外部 Make 文件格式导出可建立的项目 6. “Insert Project into Workspace”选项 选择该项将弹出“Insert Project into Workspace”对话框 可以插入已有 的项目到工作区中 2.3.6 “Build”菜单菜单菜单菜单 “Build”菜单中的命令选项(图 2.18)用于编译 建立和执行应用程序 Visual C++6.0 编程实例与技巧 40 www.BOOKOO.com.cn 图 2.18 “Build”菜单 1. “Compile”选项 该选项用于编译显示在源代码编辑窗口中的源文件 用于检查源文 件中是否有语法错误 如果在编译过程中检查出语法错误(警告或错误) 那么将在输出窗口中显示错误信息 可以向前或向后浏览输出窗口中的 错误信息 然后按 F4 键在源代码窗口显示相应的代码行 2. “Build”选项 通常 Windows 应用程序都是由多个文件组成的 而这些文件可能分 别来自编译器 程序员 操作系统甚至第三方厂商 这就使整个应用程 序变得相当复杂 如果应用程序又是由多个开 发组分别开发的 那么 其结构组成就更为复杂 因此 在编译 链接整个应用程序时 就要 花 费很多的精力和时间 这时 就可以使用 Build 这个有力的工具 Build 查看项目中的所有文件 并对最近修改过的文件(其标志日期比可执行文 件日期要新)进行编译和链接 如果建立过程中检测出某些语法错误 就将它们显示在输出窗口中 Visual C++6.0 编程实例与技巧 41 www.BOOKOO.com.cn 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”菜单中与调试有关的选 项将可以使用 “View”菜单包含用于显 Visual C++6.0 编程实例与技巧 42 www.BOOKOO.com.cn 示调试信息的命令 从“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) 函数计时记录每个函数被调用了多少 Visual C++6.0 编程实例与技巧 43 www.BOOKOO.com.cn 次(命中次数)以及每个函数和被调用函数中所花的时间 函数计数(Function count) 函数计数记录每个函数被调用的次数 (命中次数) 要启动函数计数功能 必须启动定制批处理文件来指定要 剖视的源模块和行 方法为先选择“Cust om” 然后在“Custom Settings” 文本框中选择要访问的批处理文件 图 2.19 “Profile”对话框 函数覆盖(Funcion Couerage) 函数覆盖记录函数是否被调用过 通过函数覆盖 可以知道代码的哪一部分未被执行 剖视器列出所有被 剖视的函数 在被执行的函数前用星号标记 行计数(Line count) 行记数记录每行被执行了多少次(命中次数) 要使用行计数功能 必须使用其批处理文件指定要剖视的源模块和行 方法为先选择“Custom” 然后在“ Castom Settings”文本框中选择要访问 的批处理文件 行覆盖(Line coverage) 行覆盖用于确定代码的哪一部分未被执 行 剖视器列出所有被剖视执行 被执行的行前用星号标记 通常对整个程序进行剖视是没有意义的 剖视器使用 PROFILERINI Visual C++6.0 编程实例与技巧 44 www.BOOKOO.com.cn 文件来指定在系统安装目录下的 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”按钮 ) 执行时 程序会一直正常运行 直到到达 断点处停止 这样 在调试过程中就可以越过 某些已知正确或不感兴 趣的程序段 从而提高调试速度 Visual C++6.0 编程实例与技巧 45 www.BOOKOO.com.cn 图 2.20 “Debug”菜单 2. “Restort”选项 在调试过程中 我们经常会进行一种循环操作 首先找到一条“错误” 语句 接着对其做些 修改 然后再从头开始对程序进行调试执行 从 而确定刚刚修改的语句是否按期望的结果执行 这时就可以使用 “Restort”选项 选择该选项 系统重新装载程序到内存并放弃所有变量 的当前值(断点表达式和观察点表达式仍可用) 3. “Sap Delbrggizg”选项 该选项用于中断当前的调试过程并返回正常的编辑状态 4. “Bresks”选项 在当前位置暂停程序运行 5. “Apply Code Change”选项 该选项用于使修改部分代码生效 以便随时修改调试中发现问题的 程序代码 6. “Step Into”选项 Visual C++6.0 编程实例与技巧 46 www.BOOKOO.com.cn 在调试过程中单步执行程序 而且当程序执行到某一函数调用语句 时 进入该函数内部从头单步执行 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) 显示与当前程序有关 的所有异常 可以控制调试器如何处理系统异常和用户自定义异常 Visual C++6.0 编程实例与技巧 47 www.BOOKOO.com.cn 图 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++等)或者更改选项设置等 Visual C++6.0 编程实例与技巧 48 www.BOOKOO.com.cn 图 2.22 “Tools”菜单 1. “Source Browse”选项 缺省情况下 在建立项目时 编译器会创建与项目中每一程序文件 信息有关的.SBR 文件 实用程序 BSCMAKE 将汇编这些.SBR 文件为单 个浏览信息数据库 浏览信息数据库的名字为项目 基类名加上扩展 名.BSC 组成 选择“Source Browse”选项将弹出浏览窗口(图 2.23) 从中显示与程序 中所有符号(类 函数 数据 宏 类型)有关的信息 对于不同类型的 对象或上下文 浏览窗口的外观和控制 是不同的 Visual C++6.0 编程实例与技巧 49 www.BOOKOO.com.cn 图 2.23 浏览窗口 通常 使用浏览窗口主要可以查看以下信息 源文件中所有符号的信息 包含某个符号定义的源代码行 利用某个符号的所有源代码行 基类和派生类之间的关系图 调用函数和被调用函数之间的关系图 缺省时 系统在建立项目中自动创建浏览信息库 当然 为了加速 项目的建立过程 可以关闭缺省设置 需要时再打开 可以使用“Project Setting”对话框的“Browse Info”选项 卡来设置是否在建立项目过程中创 建浏览信息文件 打开工作区时 Visual C++ 6.0 就自动打开相应的浏览信息数据库 如果要查看其他项目的浏览信息 可以使用“File”菜单的“Open”命令打 开相应的浏览信息数据库 2. “Close Source Browser File”选项 选择该项用于关闭打开的浏览信息数据库 Visual C++6.0 编程实例与技巧 50 www.BOOKOO.com.cn 3. “Spy++”选项 Spy++是 Win32 实用程序 用于给出系统的进程 线程 窗口和窗 口消息的图形表示 使用 Spy++ 可以做以下工作 显示系统对象(包括进程 线程和窗口)间的图形关系 搜索指定的窗口 线程 进程或消息 查看选定对象的属性 例如 对于窗口 可能查看窗口标题 窗 口句柄 窗口函数 边界矩形 窗口客户区 实例句柄等 对于进程 可以查看模块名 线程号 CPU 时间等 对于线程 可以查看模块名 线程号 线程状态等 对于消息 可以查看窗口句柄 嵌套深度 参 数值等 从视图中直接选择一个窗口 线程 进程或消息 使用查找工具选择鼠标指定的窗口 选择“Spy++”选项将启动 Spy++ Spy++启动后 打开标题为 “Windows 1”的窗口(图 2.24) 该窗口为 Spy++的“Windows”视图 用于 显示所有窗口的树形关系并控制系统 中的活动对象 Spy++还包含以下 三个视图 “Process”视图 Windows 98 或 Windows NT 支持多进程 每个进 程有自己的一个或多个线程 每个线程有与自己相关的一个多顶层窗 口 每个顶层窗口又可以拥有一系列子窗口 使用“Process”视图可以 查看特定的系统进程(通常对应一个正执行的程序) “Threads”视图 “Threads”视图是所有线程及其相关窗口的一个平 面列表 “Threa ds”视图中不包括进程 但可以从“Threads”视图快速找 到拥有选定线程的进程 “Message”视图 每个窗口都有相应的消息流 可以使用 Visual C++6.0 编程实例与技巧 51 www.BOOKOO.com.cn “Message”视图查看消息流 也可以为线程或进程创建“Message”视图 从而可以查看传递给由指定线程或进程所拥 有的所有窗口的消息 图 2.24 Spy++窗口 4. “Cuotomize”选项 选择该项将弹出“Cuotomize”对话框(图 2.25) 可以对命令 工具栏 工具菜单和键盘加速键进行定制 例如 添 加命令到“Tools”菜单 中 删除“Tools”菜单中的命令 更改命令加速键 修改工具栏等等 Visual C++6.0 编程实例与技巧 52 www.BOOKOO.com.cn 图 2.25 “Cuotomize”对话框 5. “Options”选项 选择该项将弹出“Options”对话框(图 2.26) 可以对 Visual C++ 6.0 的 环境设置进行更改(如调试器设置 窗口设置 目录设置 工作区设置等) Visual C++6.0 编程实例与技巧 53 www.BOOKOO.com.cn 图 2.26 “Options”对话框 6. “Macro”选项 创建和编辑宏文件 7. 其他选项 “Tools”菜单中的其他命令选项将启动相应用户自定义工具(如“MFC Tracer” “Regist or Control”等) 2.3.9 “Window”菜单菜单菜单菜单 在“Window”菜单中包含用于控制窗口属性的命令选项(图 2.27) Visual C++6.0 编程实例与技巧 54 www.BOOKOO.com.cn 图 2.27 “Window”菜单 1. “New Window”选项 选择该选项将打开新的窗口 从中显示当前文档信息 2. “Split”选项 将窗口拆分为多个面板 为同时查看同一文档的不同内容提供了方 便 3. “Docking View”选项 打开或者关闭窗口的船坞化(docking)特征 船坞窗口总是附属于应 用程序窗口的边界 或者浮动于屏幕上的任意位置 4. “Close”选项 关闭选定的活动窗口 5. “Close All”选项 Visual C++6.0 编程实例与技巧 55 www.BOOKOO.com.cn 关闭所有打开的窗口 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 Visual C++6.0 编程实例与技巧 56 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 57 www.BOOKOO.com.cn 图 2.28 “MSDN”帮助窗口 Visual C++6.0 编程实例与技巧 58 www.BOOKOO.com.cn 图 2.29 扩展帮助窗口 2.4 2.4 2.4 2.4 项目及项目工作区项目及项目工作区项目及项目工作区项目及项目工作区 Developer Studio 以项目工作区(project workspace)的方式来组织文 项 项目和项目配置 通过项目工作区窗口可以查看和访问项目中所有 元素 每个项目工作区由工作区目条中的项目工作区文件所组成 项目工 作区文件用于描述工作区及其内容 扩展名.dsw 工作区目条是项目工 作区的根目录 添加到项目工作区中的项目可以 位于其他路径甚至不 同的驱动器中 首次创建项目工作区时 将创建一个项目工作条目 一个项目工作 区文件以及相关的文件(包括一个项目文件和一个工作区选项文件) 工 作区选项文件用于存储项目工作区设置 扩 展名为.oopt 每个项目由一组项目配置和一组源文件组成 创建项目时 系统缺 省自动为每个平台创建两种项目配置 即 Win32 Debug Win32 Release Win32 Release 配置不包含调试信息并可以选 择优化配置 通常情况下 系统缺省创建的项目配置所指定的设置说明对于项目设置说明进 行更 改 可以使用“Project Settings”对话框 建立项目后 可以添加任何其他目条的文件到项目中 添加文件到 项目中并不改变文件的物理位置 项目仅仅是记录文件的名字和位置 并在工作区窗口显示图标以便指明在项目中该 文件与其他文件的关 系 在项目工作区中 项目之间可以有以下关系 顶层项目(top-level project) 顶层项目与任何其他项目不具有依 Visual C++6.0 编程实例与技巧 59 www.BOOKOO.com.cn 赖关系 不包含任何其他项目的子项目 每一个项目工作区至少有一个 顶层项目 子项目(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 视图 可以定义新类 直接跳转到代码(如类定义 函数或者方法定义等) 创建函数或方法声明等 Visual C++6.0 编程实例与技巧 60 www.BOOKOO.com.cn 图 2.30 项目工作区窗口 图 2.31 Class View 视图 2. Resource View 面板 Visual C++6.0 编程实例与技巧 61 www.BOOKOO.com.cn Resource View 面板用于显示项目中包含的资源文件 扩展顶层文件 夹可以显示资源类型(图 2.32) 扩展资源类型可以显示其下的资源 3. File View 面板 File View 面板显示不项目之间的关系以及包含在项目工作区中的文 件 扩展顶层文件夹可以显示包含在项目中的文件(图 2.33) 图 2.32 Resource View 面板 Visual C++6.0 编程实例与技巧 62 www.BOOKOO.com.cn 图 2.33 File View 面板 2.5 2.5 2.5 2.5 资源与资源编辑器资源与资源编辑器资源与资源编辑器资源与资源编辑器 资源作为一种界面成份 可以从中获取信息并在其中执行某种动作 Visual C++ 6.0 可以处 理的资源有加速键(Accelerator) 位图(Bitmap) 光标(Cursor) 对话框(Dialog Box) 图标(Icon) 菜单(Menu) 串表(String Table) 工具栏(Toolbar)和版本信息(Version In formation)等 2.5.1 资源编辑器资源编辑器资源编辑器资源编辑器 Developer Studio 提供有功能强大且易于使用的资源编辑器 用于创 建和修改应用程序的资源 使用资源编辑器 可以创建新的资源 修改 已有的资源 拷贝已有的资源以及删除不再 要的资源 例如 用加速 键编辑器处理加速键表 用图形编辑器处理图形资源(工具栏 位 图 光标和图标等) 用对话编辑器处理对话框 用菜单编辑器处理菜单 等 等 创建或者打开资源时 系统将自动地打开相应的编辑器 编辑器打 开后 单击鼠标右按钮将弹出快捷菜单 其中列有与当前资源有关的命 令 例如 对于对话编辑器 其快捷菜单含有 “Cut” “Copy” “Paste” “Insert ActiveX Control” “Size to Content” “Align Left Edges” “Align Top Edges” “CH Check Mnemonics” “Class Wizar d” “Events”和 “Properties”等命令 其中 “Insert ActiveX Control”命令用 于插入新的 ActiveX 控件到对话框中;“Class Wizard” 命令用于启动 Class Wizard;“Prope rties”命令用于启动属性对话框 1. 创建新的资源 Visual C++6.0 编程实例与技巧 63 www.BOOKOO.com.cn 从“Insert”菜单选择“Resource”命令 弹出“Insert Resource”对话框(如 图 2.34 所示 ) 如果要创建新的资源 从“Resource Type”列表选择资源 类型 然后单击“New”按钮 图 2.34 “Insert Resource”对话框 新创建的资源将加入到当前资源文件中 此外 可以单击 Resource 工具栏中的相应按钮(图 2.35)来创建新的 资源 Visual C++6.0 编程实例与技巧 64 www.BOOKOO.com.cn 图 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(光标)文 Visual C++6.0 编程实例与技巧 65 www.BOOKOO.com.cn 件 (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 文件来改变与多个资源 或对象有关的符号 Visual C++6.0 编程实例与技巧 66 www.BOOKOO.com.cn 符号名通常带有描述性的前缀来表示所代表的资源或对象类型 例 如 加速键或工具栏前缀 为“IDR” 对话框前缀为“IDD” 光标前缀为 “IDC” 图 标前缀为“IDI” 位图前 缀为“IDB” 菜单项的前缀为“IDM” 命令前缀为“ID” 控件前缀为“IDC” 串表中的串前缀为“IDS” 消息 框中的串前缀 为“IDP” 符号值通常有一定的限制 例如 资源(加速键 位图 光标 对话 框 图标 菜单 串表 及版本信息)的符号值范围为十进制的 0 32767 而资源构件(如对话框控件或串表中的串) 的符号值范围为 0 65534 或 -32767 32767 2.5.3 资源符号浏览器资源符号浏览器资源符号浏览器资源符号浏览器 随着应用程序的大小和复杂程度的增加 与其相关的资源符号也会 不断增多 要手工跟踪分散在不同文件中的大量资源符号是相当困难 的 资源符号浏览器简化了符号的管理 使用资源符号在浏览器可以: 快速浏览已有资源符号的定义以便了解每个资源的符号值 已使 用的资源符号列表及与每个符号相关的资源 创建新的资源符号 更改资源的符号名和符号值 删除不再使用的资源符号 快速切换到某个资源所对应的编辑器中 2.5.4 对话编辑器对话编辑器对话编辑器对话编辑器 对话框作为一种 Windows 资源 用于显示并从用户处获取信息 对 话编辑器用于创建或者编辑对话框资源或对话框模板 使用对话编辑 Visual C++6.0 编程实例与技巧 67 www.BOOKOO.com.cn 器 可以作以下工作: 添加 排列或编辑控件 改变控件的制表顺序(Tab Order)或助忆键(Mnemonic Key) 调整对话布局 添加或编辑 ActiveX 控件 创建用户自定义控件 导入 Visual Basic 表单到对话资源中 测试对话框 图 2.37 是打开某一对话框资源后的对话编辑器 对话编辑器打开时 将显示对话框工具栏和控件工具栏 图 2.37 对话编辑器 1. 添加并编辑控件 创建对话框的第一步就是添加控件到对话框中 可以添加到对话框 中的控件主要有图片(Pic ture) 静态文本(Static Text) 编辑框(Edit Box) Visual C++6.0 编程实例与技巧 68 www.BOOKOO.com.cn 或组框(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 键) 然后 移光标到要添加控件的位置 单击并拖动鼠标 当控件大小满足 要求 时释放鼠标 Visual C++6.0 编程实例与技巧 69 www.BOOKOO.com.cn 图 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:使选定的控件两两垂直间隔相同 Visual C++6.0 编程实例与技巧 70 www.BOOKOO.com.cn 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 简称 Visual C++6.0 编程实例与技巧 71 www.BOOKOO.com.cn 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 类库中的 Visual C++6.0 编程实例与技巧 72 www.BOOKOO.com.cn CDialog Bar 类来嵌入对话栏到 程序中 7. 测试运行对话框 Visual C++ 6.0 可以在对话框编辑器中测试运行对话框 从而可以及 时了解对话框的布局和 行为 选择“Layout”菜单的“Test”命令或单击对 话框工具栏的“Test”按钮 即可进入 测试运行状态 在测试运行状态 可以输入文本 以组合框或列表框进行选择 测试对话框 的热键是否 有效 等等 2.5.5 菜单编辑器菜单编辑器菜单编辑器菜单编辑器 菜单编辑器用于创建并编辑菜单资源 使用菜单编辑器 可以创建 标准菜单和菜单选项 为菜单或菜单选项定义热键 加速键和状态栏提 示 也可以创建快捷菜单 以便用鼠标右控钮 来执行要频繁使用的命 令 建立菜单或菜单选项后 可以用 Class Wizard 为菜单选项编写要 执 行的代码 图 2.40 为打开某一资源后的菜单编辑器 图 2.40 菜单编辑器 1. 创建菜单和菜单选项 Visual C++6.0 编程实例与技巧 73 www.BOOKOO.com.cn 进入菜单编辑器后 就可以在菜单栏中创建菜单和菜单选项 要创建菜单栏中的菜单 方法为: (1) 在菜单栏中选择新项方框 或按 Tab 键(向右移) Shift+Tab 键(向 左移)或左右箭头键移到新项方框 如果要在某一菜单前插入新的菜单 则移到该菜单处按 Ins 键 (2) 输入菜单名 开始输入名字时 系统弹出“Menu Item Properties” 对话框(图 2.41) 在对话框的“Caption”文本框输入菜单名 图 2.41 “Menu Item Properties”对话框 如果要为菜单定义助忆符 则在相应的字母前加 注意 要确保 同一菜单栏上的助忆符不 相冲突 如果要建立单项菜单而不带菜单选 项 则应清除“Prop-up”复选框 创建菜单后就可以为其添加菜单选项 方法为: Visual C++6.0 编程实例与技巧 74 www.BOOKOO.com.cn (1) 选择菜单的新项方框 或者选中某个已有菜单选项再按 Ins 键 新项自动插在该项之前 (2) 输入菜单选项的名字 开始输入名字时 系统弹出“MenuItem Properties”对话框 在对话框的“Caption”文本框输入菜单选项名 如果 要为菜单定义助忆符 则在相应的字 母前加 (3) 在“ID”文本框输入菜单选项的 ID 号或选取已有的 ID 号 如果不 输入 ID 值 则系统根 据选项名称自动生成一个 ID 值 (4) 在菜单选项的属性对话框可为菜单选项指定风格 有些菜单选项可能还含有下级子菜单(即级联菜单) 要创建级联菜 单 方法为: (1) 在菜单中欲显示级联菜单的位置按 Ins 键 输入菜单选项名称 开始输入时 系统弹出 “Menu Item Properties”对话框 或者选取已有的 菜单选项 然后按 Alt+Enter 键 (2) 在菜单选项的属性对话框中选中“Pop-up”复选项 于是该项便被 标以级联菜单符号( ) 且在该项的右侧出现新项方框 (3)为级联菜单添加子菜单项(图 2.42) Visual C++6.0 编程实例与技巧 75 www.BOOKOO.com.cn 图 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 键 系统弹出 Visual C++6.0 编程实例与技巧 76 www.BOOKOO.com.cn “Menu Item Propertie s”对话框 (2) 在“Prompt”对话框输入描述性文字 图 2.43 是为菜单选项“Go”定义加速键和状态栏提示后的“Menu Item Properties”对话框 图 2.43 为菜单选项定义加速键和状态栏提示 4. 创建快捷菜单 我们知道 单击鼠标右按钮将弹出相应的快捷菜单 快捷菜单包含 有与当前光标所指位置最为相关的命令 在应用程序中要使用快捷菜 单 首先要创建菜单本身 然后将其与应用程序代码链接 (1) 创建带空标题的菜单栏 (2) 输入临时字符为菜单标题 以便在菜单栏创建菜单 (3) 在菜单下创建快捷菜单的菜单选项 (4) 再次使菜单栏为空 以便使快捷菜单显示在空的菜单栏下 (5) 保存菜单资源 (6) 添加以下代码到源文件中: Visual C++6.0 编程实例与技巧 77 www.BOOKOO.com.cn 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 是打开某一加速键表资源后的加速键 编辑 器 Visual C++6.0 编程实例与技巧 78 www.BOOKOO.com.cn 图 2.44 加速键编辑器 如果要在加速键表中添加新的加速键 按 Ins 键或选中表尾的新项方 框输入加速键名 弹出 “Accel Properties”对话框(图 2.45) 在“Key”文本 框输入键名 在 ID 文本框输入加速 键标识符 图 2.45 “Accel Properties”对话框 “Key”文本框中的合法输入为: 0 255 间的某个整数 可以写成十进制 十六进制式八进制的 Visual C++6.0 编程实例与技巧 79 www.BOOKOO.com.cn 格式 “Type”框的设置用于确定该数是作为 ASCII 码或是虚拟键码值 通常 一位数(0 9)被直接解释成相应的键 而不是 ASCII 码值 如果 要输入 0 9 间的 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 的串放在第二段 等等 要将串从某一段移到另一段 将串从 某一资源文件移到另一 资源文件 修改串及其标识符 等等 Visual C++6.0 编程实例与技巧 80 www.BOOKOO.com.cn 图 2.46 是为打开某一应用程序的串表资源后的串编辑器 图 2.46 串编辑器 在串编辑器中 串表中的每个段用水平线分开 如果要在串表中添 加新的串 在要添加串的串段中 选择新项方框输入串标识符或选择某 个串再按 Ins 键 弹出“String Properties” 对话框(图 2.47) 在“ID”文本 框输入串标识符和值 在“Caption”文本框输入串标题 图 2.47 “String Properties”对话框 如果要从串表删除某个串 可以先选择该串再按 Del 键 如果要修 改串及其标识符 则先指 定欲修改的串 然后按 Alt+Enter 键 弹出 Visual C++6.0 编程实例与技巧 81 www.BOOKOO.com.cn “String Properties”对话框 从中修改串 2.5.8 版本信息编辑器版本信息编辑器版本信息编辑器版本信息编辑器 版本信息主要由公司名称 产品标识 产品版本号 版权和商标注 册等信息组成 版本信息 编辑器是用于编辑和维护版本信息的工具 尽管版本信息不是应用程序所必须的 但它是标 识应用程序的有效手 段 每个应用程序只能有一个版本信息资源 其名称为 VSVER SIONINFO 如果要在应用程序中访问版本信息 必须在应用程序中调用函数 GetFile VersionInfo 和 Ver Query Value 图 2.48 是打开某一应用程序的版本信息资源后的版本信息编辑器 图 2.48 版本信息编辑器 每个版本信息资源由多个串块组成;每个串块分别表示不同的语言 或字符集 用户所要做的 就是在版本信息编辑器中定义与产品有关的 字符集和语言 在版本信息资源的顶部有一个固定信息块 固定信息由可编辑的数 Visual C++6.0 编程实例与技巧 82 www.BOOKOO.com.cn 字框和可选择的下拉列表 组成 版本信息资源的底部含有一个或多个 可编辑的文本框 可以使用“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 是正在编 辑某一位图的图形编辑器窗口 Visual C++6.0 编程实例与技巧 83 www.BOOKOO.com.cn 图 2.50 图形编辑器 图形编辑器窗口用两个视图来显示图形 视图间用分割线隔开 左 边的视图以实际尺寸显示 图形 右边的视图则是放大后的图形(缺省放 大 6 倍) 在其中某一视图所做的改动会立即反 映到另一视图中 这是 由系统自动完成的 可以用分割条来调节两个视图的相对尺寸 如果 要 激活某一视图 请按 Tab 键或 F6 键 或者直接在其中单击任一处 视 图激活后 其四周会 出现选择边界 表明该视图是激活的 图形工具栏由两部分组成 选项选择器用于设置绘图选项(如画刷宽 度);工具栏由选择(Sele ct) 自由选择(Select Region) 颜色拾取(Select Color) 橡皮擦(Erase) 填充(Fill) 放大器(Magnify) 铅笔(Peneil) 画 刷(Brush) 喷枪(Airbrush) 画线(Line) 曲线(C urve) 文字(Text) 空 心矩形(Rectangle) 边界矩形(Outlined Reetangle) 实心矩形(F illed Visual C++6.0 编程实例与技巧 84 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 85 www.BOOKOO.com.cn 图 2.51 “Bitmap Properties”对话框 对话框含两个选项卡 即“General”和“Palette”选项卡 其中 “General” 选项卡用 于设置位图资源的标识符 大小和颜色数等;“Palette”选项卡 用于改变位图中的所有颜色 属性 2. 创建图标和光标 图标和光标的编辑操作和位图基本上相同 但图标和光标具有与位 图不同的属性 例如 对 于不同的显示设备 每个图标或光标可以包 含不同的图像 此外 光标具有热点(hotstop) ——Windows 使用热点来 跟踪光标位置 在创建新的图标或光标时 图形编辑器首先创建 VGA 图像 图像开 始以屏幕色来填充(图 2.52 ) 对于光标而言 热点被初始化为图像的左 上角 坐标为(0,0) 在创建新的图标或光标时 必须指定好目标显示设备 当打开图标 或光标资源时 与当前显 示设备最为匹配的图形被自动打开 缺省时 图形编辑器支持如表 2.3 所列的显示设备 Visual C++6.0 编程实例与技巧 86 www.BOOKOO.com.cn 图 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”列表框选取所要的 显示设备 Visual C++6.0 编程实例与技巧 87 www.BOOKOO.com.cn 图 2.53 “New Icon Image”对话框 如果要创建除标准设备外的其他定制显示设备 在“New Icon Image” 对话框单击“Custom ”按钮 从弹出的“Custom Image”对话框(图 2.54)输 入宽度 高度和颜色数 图 2.54 “Custom Image”对话框 赋给图标或光标屏幕色或反转色主要有两个用处 一是勾画出派生 图像的轮廓并对其着色 另一是指出着反转色的区域 用户可以根据 需要改变代表屏幕色和反转色属性的颜色 但这 种改变不会影响图标 或光标在应用程序中的外观 要在图标或光标中建立透明或反转的区域 方法为:在颜色调色板中 单击屏幕色或反转色选 择器 应用屏幕色或反转色到图像中 Visual C++6.0 编程实例与技巧 88 www.BOOKOO.com.cn 要改变代表屏幕色或反转色属性的颜色 方法为:在颜色调色板中单 击屏幕色或反转色选择 器 从颜色调色板中选取一种颜色为屏幕色或 反转色 系统自动将所选颜色的补色赋给另一 未选中的选择器 如果双击屏幕色或反转色指示器 系统将弹出“Custom Color Selector”对话框 从中可 以对屏幕色或反转色进行定制 在图形编辑器中创建光标时 控制栏将显示光标热点坐标和“Set Hotspot”按钮 缺省时 热点光标为(0,0) 如果要改变热点坐标 方法为: 单击控件栏中的“Set Hotspot”按钮 然后单击要设为当前热点的位置即 可 2.5.10 工具栏编辑器工具栏编辑器工具栏编辑器工具栏编辑器 工具栏通常由多个工具按钮组成 通过工具按钮可以快速执行使用 最频繁的命令 系统将每 个工具栏保存为相应的位图 其中包括工具 栏上每个工具按钮的图像 工具按钮的图像具有 相同的尺寸 缺省时 是 16 15(以像素为单位) 工具按钮的图像在位图中依次排列 这种排 列 次序表明了屏幕上显示时工具按钮在工具栏上的排列次序 每个工 具按钮都有相应的状 态和风格(被按下的 面上的 面下的 无效的 向下无效的或不确定的) 工具栏编辑器用于创建工具栏资源并可以将已有位图转换为工具栏 资源 工具栏编辑器以图 形方式显示要处理的工具栏及正被选择的工 具栏按钮图像 图 2.55 是打开某一工具栏后的工 具栏编辑器 与图形编辑器类似 工具栏编辑器用两个视图显示被编辑的按钮图 像 视图间用分割条分 隔 被编辑按钮图像的上面是要处理的工具栏 工具栏中由模糊边界包围的是正被选择的按钮 Visual C++6.0 编程实例与技巧 89 www.BOOKOO.com.cn 创建新的工具栏有两种方法:一是直接创建 另一是将已有的位图转 换为工具栏 如果要直接创建 请从“Insert”菜单选择“Resource”命令 在弹出的 “Insert Resourc e”对话框中选择“Toolbar” 然后单击“New”按钮 进入工 具栏编辑器后直接编辑 图 2.55 工具栏编辑器 如果要将已有的位图转换为工具栏 方法为: (1) 在图形编辑器中打开已有的位图资源 (2) “Image” 菜单选择“Toolbar Editor” 命令 弹出“New Toolbar Resource”对话框 在对话框中设置与位图匹配的图标图像的高度和宽 度 然后 单击“OK”按钮进入工具栏 编辑器 (3) 完成转换后 从“Edit”菜单选择“Properties”命令 从弹出的属性 对话框设置工具 栏按钮的命令 ID Visual C++6.0 编程实例与技巧 90 www.BOOKOO.com.cn 创建工具栏资源后 可以使用 Class Wizard 将工具栏按钮与源代码 连接 2.6 2.6 2.6 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 Visual C++6.0 编程实例与技巧 91 www.BOOKOO.com.cn 图 2.56 New 对话框 Projects 选项菜单 Visual C++6.0 编程实例与技巧 92 www.BOOKOO.com.cn 图 2.57 AppWizard 开发过程的 Step1 对话框 Visual C++6.0 编程实例与技巧 93 www.BOOKOO.com.cn 图 2.58 确认项目配置的 New Project Information 对话框 在经过上述操作之后 AppWizard 自动为用户构造应用程序 你可“坐 享其成”啦!经过 创建 后 项目建立起来了 此时项目工作区窗口显示 出 AppWizard 为你创建的项目内容 选择“C lass View” 如图 2.59 扩 展顶层文件夹后 显示该项目拥有三个类:CAboutApp CDddApp CDddDlg(AppWizard 自动根据项目名来构造类名) 扩展 Globals 文件夹 显示项目拥有一 个全局变量 theApp Visual C++6.0 编程实例与技巧 94 www.BOOKOO.com.cn 图 2.59 “ddd.dseu”项目的 Class View 视图 好了 可以单击 Build 菜单下的“Build ddd.exe”选项来编译该项目了 之后选择“Ex cute ddd.exe”运行它(当然也可从 Build 工具条上相应按 钮来完成) 运行结果如图 2.60 显示一 图 2.60 ddd.exe 运行结果为显示一对话框 个对话框 上面含有两个按钮及一个静态文本 单击标题栏上的控 Visual C++6.0 编程实例与技巧 95 www.BOOKOO.com.cn 制菜单 弹出下拉菜单 有“移动” “关闭” “关于 ”三个常见选项如 图 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 Visual C++6.0 编程实例与技巧 96 www.BOOKOO.com.cn 图 2.63 单击控制菜单“About...”选项显示信息对话框 需要说明的是 上述代码看不懂不要紧 重要的是让你感受一下 Visual C++ 6.0 为你提供的 工具是如何功能强大 方便易用 本书后面 部分为给出许多编程实例 到那时 你会自然明 白上述代码的含义 对于已熟练了 C++语言编程的读者 可从此处直接阅读第四章:传统 Windows API 编 程” 第三章“C++基础”是为那些尚不十分了解 C++语 言的读者编写的 Visual C++6.0 编程实例与技巧 97 www.BOOKOO.com.cn 第三章第三章第三章第三章 C++语言基础语言基础语言基础语言基础 在 C 语言基础上发展起来的 C++语言是一种面向对象的程序设计语 言 由于 C++提出了把数据 和在数据之上的操作封装在一起的类 对 象和方法的机制 并通过派生 继承 重载和多态 性等特征 实现了 人们期待已久的软件重用和程序自动生成 使得软件 特别是大型复杂 软 件的构造和维护变的更加有序和容易 并使软件开发能更自然地反 映事物的本质 从而大 大提高了软件的开发效率和质量 本章对 C++语言的基本结构做了简要介绍 为了使读者在短时间内 掌握 C++语言的精华 我们 略去了一些深层次的细节描述 但这绝不 影响 C++语言的基本功能 如果已经熟练 掌握了 C++语言的基本结构 那么可以越过本章 直接开始下一章的学习 3.1 3.1 3.1 3.1 简单的简单的简单的简单的 C++C++C++C++程序程序程序程序 首先看下面这个简单的 C++程序(为了方便起见 程序的每一行都加 上了行号) 1. 这是一个简单的 C++程序 2. # include 3. void main (void) 4. { 5. int i; 6. cout<< 欢迎使用 Visual C++ 5.0 使用与开发 ! n ; 7. cout n 8. cin >> i; 9. } Visual C++6.0 编程实例与技巧 98 www.BOOKOO.com.cn 上面这个程序是一个典型的 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 3.2 3.2 3.2 标识符标识符标识符标识符 标识符(identifier)在程序中可以用作变量(variable) 对象(object) 类 (class) 结构( structure) 联合(union) 枚举类型(enumerated type) 类型 (type) 函数(function)和 标号(abel)等的名字 标识符必须是以大写字母 Visual C++6.0 编程实例与技巧 99 www.BOOKOO.com.cn 小写字母和直划线()开始 标 识符可以 由以下字符组成 大写字母 小写字母 下划线()和数字 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 3.3 3.3 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 类型用于描述的值的空集 主要用于声明不返回值的函数 或指向任一 类型的指针 Visual C++6.0 编程实例与技巧 100 www.BOOKOO.com.cn 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 127 unsigned char 1 0 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 65535 Visual C++6.0 编程实例与技巧 101 www.BOOKOO.com.cn 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 2147483648 unsigned int 4 0 4294967295 3.3.4 类型类型类型类型 long 类型 long(或 long int)是一种整数类型 其长度大于或等于类型 int 类型为 long 的对象可 以声明为 signed long 和 unsigned long signedlong 即为 long 类型 long 的长度及存储范围如表 3.4 所列 表 3.4 类型 long 的长度及存储范围 类型 名称长度(字节) 取值范围 long(signed long) 4 -2147483648 2147483648 unsigned ling 4 0 4294967295 3.3.5 类型类型类型类型 intn 类型 intn 用于表示整数变量的大小 其中 n 表示位数 可以是 8 16 32 或 64 使 用类型 intn 可以声明 8 位 16 位 32 位或 64 位的整 数变量 例如 Visual C++6.0 编程实例与技巧 102 www.BOOKOO.com.cn int8 nSmall; 声明变量 nSmall 为 8 位整数 int16 nMedium; 声明变量 nMedium 为 16 位整数 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 1.7976931348623158e+308(正数) long double 8 2.2250738585072014e-308 1.7976931348623158e+308(正数) 3.3.7 常量常量常量常量 C++的常量主要包括整型变量 浮点变量 字符变量及字符串常量 C++ 语言增加了操作符 Const 用于声明标识符 标识符值在程序运行 期间是不可更改的 const 的使用方式为 Visual C++6.0 编程实例与技巧 103 www.BOOKOO.com.cn const 数据类型 标识符二值 例如 const int i = 10; 声明整型变量 i 等于 10 3.3.3.3.4 4 4 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++3.5 C++3.5 C++3.5 C++存储类存储类存储类存储类 存储类控制对象或变量的存在时间(生存期) Visual C++支持四种存 Visual C++6.0 编程实例与技巧 104 www.BOOKOO.com.cn 储类 auto register extern 和 static 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 声明的变量为外部变量 一般是指定义在程序外部的变 量 当变量被定义为外部 变量时 所有其他函数或程序段都可引用这 个变量 这种变量的作用域是所有的函数或程序 段 一般用于在函数 之间传递数据 例如 Visual C++6.0 编程实例与技巧 105 www.BOOKOO.com.cn 文件 1 Extern 1.CPP # mclude extern int evalue; 声明外部变量 evalue 注意这里只是使该变量对 main()可见 并不是实际的声明部分 vois main() { evalue++; 使用外部变量 evalue cout<<“外部变量”< extern int evalue; 引用外部变量 evalue(在文 件 1 中声明) Visual C++6.0 编程实例与技巧 106 www.BOOKOO.com.cn void fun2() 函数声明 fun2() { evalue++; 使用外部变量 cout<<“外部变量”< void fun() 函数声明 { int 1 = 0; 声明 auto 型变量 i cout<< i = < 大于 != 不等于 < 小于 >= 大于等于 <= 小于等于 3.6.3 逻辑运算逻辑运算逻辑运算逻辑运算 C++语言中的逻辑运算操作符如表 3.8 所示 表 3.8 逻辑操作符 操作符 含义 Visual C++6.0 编程实例与技巧 109 www.BOOKOO.com.cn ! 非(NOT) && 与(AND) 或(OR) 3.7 3.7 3.7 3.7 自定义数据类型自定义数据类型自定义数据类型自定义数据类型 除了提供基本数据类型外 C++语言还允许用户建立自定义的数据 类型 3.7.1 typedef typedef 用于某个标识符定义成数据类型 然后将这个标识符当作数 据类型使用 例如 typedef int natural; 将 notural 定义为整型 natural i1,i2; 将 i1,i2 声明为 natural 类型 也即声明为整型 用 typedef 来重新定义数据类型可以提高程序的可靠性和可移植性 3.7.2 结构结构结构结构 C++语言中提供的结构数据类型是一种复合数据类型 用于将某些 相关的具有不同 类型的 数据组织到一个新的数据类型中 结构数据类 型的声明以关键字 struct 开始 语法形式为 struat structname{ mtype1 m1; mtypeN mN; }; Visual C++6.0 编程实例与技巧 110 www.BOOKOO.com.cn 其中 mtype1 mtypeN 可以是不同的数据类型 结构声明后 就可 以使 用 例如 struct square { int length; int width; Char name; }; 声明结构 square struare square S1; 声明 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 { Visual C++6.0 编程实例与技巧 111 www.BOOKOO.com.cn }; S1 var1; Struct S2; }; 同样 在声明嵌套结构的变量时 也可以直接设置其初值 3.7.3 联合联合联合联合 C++语言中的联合(union)可使不同的数据类型共享同样的内存位 置 其定义方式 与结 构极为相似 但实质都不同 联合每次只能包含 一种数据类型的信息 联合的语法形式为 union unionname { type1 field1; type2 field2; typeN fieldN; }; 其中 type1,type2, typeN 可以是不同的数据类型 在使用关键字 union 直接声明变量时 其声明方式与结构类似 例如 union variant { int i; Char ch; } U1; 声明 union 变量 U1 union variant U2 在 C++语言中 可以省略 union 在使用 union 变量时要记住 union 中的各种数据类型共享同一内存 位置 因此 union 的各 分量实际所用的是同一个值 Visual C++6.0 编程实例与技巧 112 www.BOOKOO.com.cn 3.7.4 枚举枚举枚举枚举 枚举类型是用户自己定义的数据类型 采用这种数据类型的目的是 为了提高程序的可读性 枚举类型的语法形式为 enum enumname {member1,member2 memberN} 其中 member1,member2 memberN 分别表示要枚举的数据 在声明枚举类型的变量时 也有几种形式 例如 enum color { red,green,blue } mycolor; 直接声明枚举变量 mycolor emumplor yourcolor 声明变量 yourcolor color hiscolor 声明变量 hiscolor 可以 省去 enum 使用枚举类型变量时 既可以直接使用 也可以用整数直接引用 enum 数据类型的值 例如 int i; mycolor = red; 变量 mycolor 的值为 red i = blue; 相当于给 i 赋值 2 3.8 3.8 3.8 3.8 控制结构控制结构控制结构控制结构 编写程序时最重要的就是要了解应用程序的控制结构 控制结构是 通过控制语句来实现的 3.8.1 条件语句条件语句条件语句条件语句 C++语言支持四种基本的条件语句 if 语句 if else 语句 ?条件和 swith 语句 条件语句用来判断程序的执行方式 下面分别介绍 Visual C++6.0 编程实例与技巧 113 www.BOOKOO.com.cn 1. if 语句 在程序中 可使用 if 语句来有条件地执行某一语句序列 语法形式 为 if(expression) { statment 1; statementN } 其中 表达式 expression 必须用圆括号()括起来 其值为 TRUE 或 FALSE 当值为 TRUE 时 就 执行 statment 1, statementsN 当值为 FALSE 时 就执行 if 语句后面的语句 注意 若 if 语句中只有一条可 执行语句 可以省略花括号{} 例如 if (i>=0) i = 0; 2. if else 语句 if else 语句表示根据不同的条件分别执行不同的语句序列 语法形 式为 if (experssion) { statement 1; } else { statement 2; } Visual C++6.0 编程实例与技巧 114 www.BOOKOO.com.cn 当表达式 expresion 的值为 TRUE 时 执行 statement 1 语句序列 当 值为 FALSE 时 执行 state ment 2 语句序列 当 if else 语句中的语句 序列只包含一条语句时 可以省略花括号{} 3. if else 语句 if 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 语 Visual C++6.0 编程实例与技巧 115 www.BOOKOO.com.cn 句序列 否则(所有条件都不成立) 就直接执行 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; Visual C++6.0 编程实例与技巧 116 www.BOOKOO.com.cn } 在执行 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; Visual C++6.0 编程实例与技巧 117 www.BOOKOO.com.cn } while 循环的功能与 for 循环完全一样 都是进行循环控制 当 expression 值为 TRUE 时 就继 续执行循环体内的语句序列 否则 就 退出 while 循环 while 循环也可以嵌套 3. do while 循环 do while 循环的语法形式为 do { statement; } while(expression); do while 循环与 for while 循环的不同之处在于 for 和 while 循环 都是将测试循环的语句 放在循环的起始位置 do while 循环则是在执 行完循环体后才测试循环是否结束 do 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 语句 Visual C++6.0 编程实例与技巧 118 www.BOOKOO.com.cn 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 3.9 3.9 3.9 数组数组数组数组 字符串和指针字符串和指针字符串和指针字符串和指针 在 C++语言中 数组 字符串和指针是相互关联的 Visual C++6.0 编程实例与技巧 119 www.BOOKOO.com.cn 3.9.1 数组数组数组数组 数组是具有相同数据类型的一组数据 用一个名称来表示 要访问 数组内的数据时 可使用 数组的下标值来说明 在 C++语言中 数组 是一个复合数值数据类型 其元素可以 是任何 数据类型 整型 实型 字符型 数组类型 指针类型 结构等等 数组的各个元素在内存 中 是连续存放的 第一个元素的下标值为 0 数组的声明格式为 数据类型 数组名称 长度 例如 int arrayint 100 ; 声明一个整型数组 包含 100 个元素 Char arrayChar 50 ; 声明一个字符数组 包含 50 个元素 同其他数据类型一样 数组在声明的同时也可以赋初值 例如 int arrayint 5 = {2,4,6,8,10}; int arrayint = {1,3,5}; 从上例可以看出 在设置数组初值时可以不指定数组长度 系统会 自动计算数组的长度 从 而为其分配相应的存储空间 除了一维数组 在 C++语言中还可以声明多维数组 声明数组时 若包含一个方括 号 就表示一维数组 若包含两个方括号 则表示二维数组 等等 例如 int arrayint 2 2 = { {1,1},{0,0}. }; 声明一个二维数组并赋值 引用数组元素时 通过下标值来表明 例如 int arrayint 10 ; Visual C++6.0 编程实例与技巧 120 www.BOOKOO.com.cn arrayint 0 = 0; 给各数组元素赋值 arrayint 1 = 10; 3.9.2 字符串字符串字符串字符串 字符串和字符数组非常相似 但它们之间仍有区别 字符串是指由 一串字符组成的数组 但 字符串的末尾字符一定是 0 而字符数 组则不一定 例如可以声明以下字符串 Char string ={ w , h , y , 0 }; Char string Why! ; 在 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 函数用于字符串串接 语法形式为 Visual C++6.0 编程实例与技巧 121 www.BOOKOO.com.cn 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 即为数组的起始地址 其元素的地址分别为 Visual C++6.0 编程实例与技巧 122 www.BOOKOO.com.cn arr 1 =arr 地址+(1*4) 整数的长度为 4 和 arr 1 =arr 地址+(2*4) 这样 在使用数组 arr 时 *(arr+1)和 arr 是完全一致的 C++语言提供有 new 操作符 可以很方便地为指针分配内存空间 语法形式为 pointervar = new datatype; 其中 pointervar 是指针变量名 datatype 是数据类型名 C++语言还提供另外一种操作符 delete 可以很方便地释放指针所占 有的内存空间 语法形式为 delete pointervar; 这样 就可以将指针变量是 pointervar 所占用的内存空间释放给系 统 3.10 3.10 3.10 3.10 函数函数函数函数 函数是由功能相关的语句序列所组成的独立模块 是结构化程序的 基本结构 函数的定义为 函数类型 函数名(数据类型参数 1 数据类型 参数 2) { 函数体(语句序列) } 其中 函数类型是指函数的返回值类型 可以是 C++语言中任何一 种数据类型 若 未设置函 数类型 系统会自动将其假设为整数类型 如果在设计函数时不希望返回任何值 那么就将 函数类型设置成 void Visual C++6.0 编程实例与技巧 123 www.BOOKOO.com.cn 型 参数 1 参数 2 是函数的形式参数 在调用函数时 必须用相应 的实参与其匹配 若希望 在 调用函数时更改传入参数的值 可以采用 传送地址的方法 也就是说 通过传送参数地址 函数可以向调用函 数返回任意多个值 在 C++语言中 函数声明有一个新的值 即允许程序内含有多名称 相同的函数 这 就是函数重载 例如 可以构造以下三个名称相同但 功能不同的函数 put (int i); 输出一个整数 put(float f); 输出一个浮点数 put(Char Ch); 输出一个字符 在调用 put 函数时 只需直接使用 不必理会其参数是什么 函数可以调用自身 这叫做递归调用 例如 int fun(int i) { int x; fun(x); } 在设计递归函数时 一定要注意函数的出口设置 否则极易产生无 限递归循环 3.11 3.11 3.11 3.11 类与对象类与对象类与对象类与对象 我们知道 传统的结构化语言(如 C Pascal PL 1 等)都是采用面 向过程的方法来解决问题 结构化程序通常包含一个过程和若干个过 程 由其中每个子过程来处理某个小问题 再由 主过程自顶向下调用 Visual C++6.0 编程实例与技巧 124 www.BOOKOO.com.cn 各子过程逐步解决整个问题 在结构化程序设计方法中 代码和数据是 分离的 由此带来了很多缺陷 其中最主要的就是程序的可维护性差 当对某段程序进行修 改或删除时 整个过程中所有与之相关的部分都 要进行相应的修改 这样 在开发和调试时 就会花费大量的时间 因 此我们就需要一种更好的方法来解决这类问题 面向对象程序设计方法为我们提出了一个全新的概念 它的主要思 想是将数据(数据成员)及 处理这些数据的相应函数(成员函数)封装到 Class (C++的一种新的数据类型) 使用类的变量则称为对象(Object) 如图 3.1 所示 在对象内 只有属于该对象的成员函数 才可以存取该对 象的数据成员 这样 其他函数就不会无意中破坏其内容 从而达到保 护和 隐藏数据的 效果 图 3.1 类与对象关系示意图 与传统的面向过程的程序设计方法相比 面向对象程序设计方法有 三个优点 第一 程序的 可维护性好 面向对象程序易于阅读和理解 程序员只需了解必要的细节 因此 降低了的 复杂性 第二 程序的 易修改性好 即程序员可以很容易地修改 添加或删除程序的属性 这 是通过增加删除对象(Object)来完成的 第三 对象(Object)可以使用多 Visual C++6.0 编程实例与技巧 125 www.BOOKOO.com.cn 次 即可重用性 好 程序员可以根据需要将类和对象保存起来 随时 插入到应用程序中 无需做什么修改 面向对象程序设计方法提出了一些新的概念 如类(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 后面声明 它们是 类与外部的接口 任何 函数都可以访问公有类型数据和成员函数 公 Visual C++6.0 编程实例与技巧 126 www.BOOKOO.com.cn 有类型与私有类型的关系 如图 3.2 所示 图 3.2 公有类型与私有类型关系示意图 如果将类看成是一座冰山 那么公有类型数据就是冰山浮出水面的 部分 对外部可见 而私 有类型数据则是冰山隐藏在水下的部分 对 外部不可见 (3) 保护类型(protected):保护类型用于类的继承 后面将详细介绍 类是面向对象程序最基本的单元 在设计面向对象程序时 首先要 以类的方式设计实际待解 决的问题 也就是将问题所要处理的数据定 义成类的私有类型数据或公有类型数据 同时将 处理问题的方法定义 成类的公有或私有成员函数 以下是类的简单例子: 简单的类例子) # include # include class counter { double datavalue; 声明一个私有类型数据 public: void setvalue(double); 声明一个公有类型的成员函数 double getvalue(void); 声明一个公有类型的成员函数 Visual C++6.0 编程实例与技巧 127 www.BOOKOO.com.cn 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:“< const int QUARTER = 25; const int DIME = 10; Class coins 声明类 coins { int number; public coins(){cout<<“类的初始化 n”;} 声明构造 函数 coins coins(){cout<<“类的终止 n”;} 声明析构函数 coins void getcents(int); 声明三个公有类型的成员函数 int convertquarter(void); int convertdirne(int); }; void coins::getcents(int cents) 成员函数 getcents { number = cents; cout<>cc; 输入 cents 数 coins coins1; 声明 coins 类的对象 coins1 coins1.getcents(cc); qq = coins1.convertquarter(); dd = coins1.convertdime (qq); cout< Class distance 声明类 distance { int mile,yard; public: distance(); 声明构造函数 distance(int,int); 声明重载的构造函数 void getvalue(); 声明成员函数 distance addvalue(distance); 声明成员函数 addvalue 参数为 distance 类的对象 返回值也是 distance 类的对象 void display(); 声明成员函数 }; distance::distance() 定义构造函数 { mile = 0; yard = 0; } distnace::distance(int v1,int v2) 定义重载的构造函数 { mile = v1; yard = v2; } void distance::getvalue() 定义成员函数体 Visual C++6.0 编程实例与技巧 136 www.BOOKOO.com.cn { 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”< Class iscore; 预声明类 iscore Class uscore; 声明类 uscore Visual C++6.0 编程实例与技巧 140 www.BOOKOO.com.cn { int scorevalue; public void getscore(); 声明成员函数 friend void total(iscoreX,uscore Y); 声明友元函数 total }; void uscore:: getscore() 声明类 uscore 的 成 员函数体 { cout<<“Enter score in University:”; Cin>>scorevalue; } class iscore 正式声明类 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) Visual C++6.0 编程实例与技巧 141 www.BOOKOO.com.cn 声明友元函数 total 的函数体 在类的外部声明 该友元函数使用了类 iscore 和 uscore 中的私有数据 { cout<<“The total score is:“< const int LEN = 80; class scoreclass 定义基类 scoreclass { Visual C++6.0 编程实例与技巧 147 www.BOOKOO.com.cn 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:“<>cscore; } void display() 声明该派生类的成员函数 { scoreclass::display(); 调用基类的成员函数 cout<<“Computer score:“<>Pscore; } void display() 声明该派生类的成员函数 { scoreclass::display(); 调用基类的成员函数 cout<<“Physics score:”<>chscore; } void display() 声明该派生类的成员函数 { scoreclass::display(); 调用基类的成员函数 cout<<“Chemistry score:“< # include const int LEN = 50; Visual C++6.0 编程实例与技巧 151 www.BOOKOO.com.cn class Station 声明基类 Station { protected: Char fromstation LEN ; Char tostation LEN ; public: Station (char fs ,charts ) 基类 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 Visual C++6.0 编程实例与技巧 152 www.BOOKOO.com.cn { protected: int mile; public: Mile(int m) 声明基类 Mile 的构造函数 { mile = m; } void inputmile() 声明基类 Mile 的成员函数 { cout<<“Enter mile:”; cin >> mile; } void display() 声明基类 mile 的成员函数 { cout<<“is”<>price; } void display() 声明派生类 Price 的成员函数 { Station::display(); Mile::display(); cout<< , The price is < #include const int LEN = 50; class Stack 声明基类 Stack { protected: int head; int stack LEN ; public: Stack(){head=0;}; 声明基类的构造函数 void push (int val) 声明基类的成员函数 { head++; stack head =val; Visual C++6.0 编程实例与技巧 155 www.BOOKOO.com.cn } void POP () 声明基类的成员函数 { int temp; temp = stack head ; head--; return temp; } }; class op-stack:public Stack 声明第一层派生类 op stack { public: void push(int val) 声明第一层派生类的成员函数 { if (head>LEN) { cout<< Stack underflow! <>val; while((val<0) (val>100)) { cout<< Input Error! <>val; } opstack::push(val); } }; void main() { usestack A; 声明第二次派生类的对象 A A.ProdouceStack(); A.ProdouceStack(); cout<< < 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: < class OperClass 声明一个类 { int x; public; OperClass(); 构造函数 OperClass Operator ++ (); 声明重载的操作符++ 返回值类型为 OperClass 类 这里的++为前置运算 void display(); 成员函数 }; OperClass::OperClass() 声明构造函数 { Visual C++6.0 编程实例与技巧 161 www.BOOKOO.com.cn x=0; } void OperClass::display() 声明成员函数 { cout<< X= < class Price 声明类 Price { int sum; public: Price(); 构造函数 Price(int); 构造函数 带有一个参数 Operator flat(); 声明重载函数 float 转换成浮点数 Operator int(); 声明重载函数 int 转换成整数 }; Price::Price() 声明构造函数的体 { sum = 1; } Price::Price (int S) 声明构造函数的体 { sum = S; Visual C++6.0 编程实例与技巧 164 www.BOOKOO.com.cn } Price::Operator float() 声明重载函数 float 的体 { float pp; pp = sum * 1.5; return pp; } Price::Operator int() 声明重载函数 int 的体 { int pp; pp = sum * 1.5; return pp; } void main() 主程序开始 { Price A(10); 声明对象 A Price B; 声明对象 B float fprice; int iprice; fprice = float(A); 将对象 A 强制转换成浮点数 iprice = int(B); 将对象 B 强制转换成整数 fprice = B; 未显式调用转换函数 但根据等号左边的变量 类型(浮点数)自动确定所要调用的转换函数 float iprice = A; 与上个语句类似 系统自动调用转换函数 int } Visual C++6.0 编程实例与技巧 165 www.BOOKOO.com.cn 3.14 3.14 3.14 3.14 多态性多态性多态性多态性 多态性(playmorphism)指的是一个接口名称具有多种功能 是面向对 象程序设计的重要特性 前面介绍的函数重载和操作符重载都是用来表 示名称相同而功能不同的 因此属于程序编 译时的多态性 在 C++语 言中 还有一种运行时的多态性 又称为虚拟函数(virtu al function) 以 下介绍的就是这种多态性 3.14.1 虚似函数虚似函数虚似函数虚似函数 虚拟函数指的是某个函数在某类中被声明成 virtual 而在派生类中 又 重新定义了此函数 声明虚拟函数的语法形式为 virtual 函数类型 函数名称() { } 例如 以下是一个声明虚拟函数的简单例子 Class BaseC 定义基类 BaseC { p8blic: virtual void display() 声明虚拟函数 在基类中必须带有关键字 virtual { cout<< Virtual Function .< Class BaseC BaseC { protected: int x; public: virtual void initvalue(int i) Visual C++6.0 编程实例与技巧 171 www.BOOKOO.com.cn { x = i; } virtual void display() = 0; 该函数被声明成纯虚拟函数 当各派生类中均未定义 display()虚拟函数时 则执行此虚拟函数 }; class SquareC:public BaseC 声明派生类 SquareC { public: void display() 派生类中的虚拟函数 display { cout<< X= <>等;ostream 类主要用于 处理输出功能 包含成员函数 put write 和<<等 第二层派生类包含 istrstream 类:istreamwithassign 类 if stream 类 iostream 类 ofst ream 类 ostreamwithassign 类和 ostrstream 类 这些类 是由 istream 类和 ostream 类派生的 因此继承了相应的输入或输出功 能 第三层派生类包含 fstream 类 strstream 类和 stdiostream 类 fstream 支持碰盘文件的输入 与输出 strstream 类支持内存中字符串的输入与 输出 stdiostream 类是标准 I O 文件的输入 输出类 在前面的例子中 已多次用到 C++语言的输入和输出操作符:cout 和 cin 事实上 cout 是派生类 ostreamwithassign 类的一个对象 必须结合 操作符<<使用 <<操作符是从 o stream 类继 承下来的 称为插入操作 符 引导待输出的数据输出到屏幕上 cout 最大的特点是输出的数 据 可以是各种类型数据 包括整型 实型 字符型等 使用该语句时根本 不用考虑待输出数 据的类型 也不必有相应的格式控制符(如 printf 函 Visual C++6.0 编程实例与技巧 174 www.BOOKOO.com.cn 数中的 d 和 c 等) 这样 无论输出变 量的类型是否做了修改 cout 语句都完全一致 这在设计大型程序时是十分有用的 Cin 是派生类 istreamwithassign 类的一个对象 必须结合操作符>> 使用 >>操作 符是从 is tream 类继承下来的 称为提取操作符 表示从 标准输入设备上读取数据 Cin 与 cout 一样 在输入数据时 论哪种 数据类型 使用的格式都相同 因此在设计 C++程序时 大多数使用 这种方式进行输入 图 3.7 I O 流的类结构示意图 3.15.2 其他输入其他输入其他输入其他输入 输出函数输出函数输出函数输出函数 在设计 C++程序时 除了 cout 和 cin 函数 还可以使用其他的输入 输出函数 在 派生类 ostr eam 类中 包含许多用于处理输出功能的 成员函数 由于 cout 继承了 ostream 类的特性 因此 可以使用这些成 员函数 例如: # include # include Visual C++6.0 编程实例与技巧 175 www.BOOKOO.com.cn void main() 主程序开始 { char str =“输出”; int len = strlen (str); cout.write(str,lent1); 调用成员函数 write 将 len+1 个字符输出 其中包括字符 串的结果字符 cout< cout.put (str i ); 调用成员函数 put 输出单个字符 } 在派生类 istream 类中 包含许多用于处理输出功能的成员函数 由 于 cin 继承了 istream 类 的特性 因此可以使用这些成员函数 例如: # include 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, $ ); Visual C++6.0 编程实例与技巧 176 www.BOOKOO.com.cn 调用成员函数 get 读取 LEN 长度的字符串或读到 $ 为 止 并放到 str1 中 cin.getline(str2,LEN); 调用成员函数 getline 读取 LEN 长度的字符串或读到换行 符为止 该 函数将换行符也读到字符串内 并放到 str2 中 cin.getline(str2,LEN, $ ); 调用成员函数 getline 读取 LEN 长度的字符串或读到 $ 为止 并放到 s tr2 中 cin.read(st2,10); 项用成员函数 read 读取 10 个字符 若字符串长未到 10 该函数会一直等待 继续输入 } Visual C++6.0 编程实例与技巧 177 www.BOOKOO.com.cn 第四章第四章第四章第四章 Windows API 程序的组织结构程序的组织结构程序的组织结构程序的组织结构 本章介绍 Windows 程序的动态组织结构 包含:非优先权式多任务 消息 消息队列 消息处理函数(或称窗口函数) CALL-BACK 函 数 这些都是 Windows 程序的必备知识 务必在一开始就了解清 楚 往后的路才会走得顺利 4.1 4.1 4.1 4.1 单工与多工作操作系统单工与多工作操作系统单工与多工作操作系统单工与多工作操作系统 在介绍 Windows 程序的基本构架之前 我们必需先了解单任务与多 任务的不同 4.1.1 单任务 以往的 MS-DOS 是一种单一任务类型的操作系统 也就是说 计算 机中同时只允许一件任务进行 必须等到第一个任务结束或者主动让出 使用权时 才能进行下一个任务 就好像在电话 亭打公用电话 一部 话机只供一个人使用 必须到第一个人打完电话离开后 下一个人才能 进入电话亭使用电话 这种单用户的系统 运行中的程序完全掌握了计算机的控制权 各 位可以回想一下 当您在设计程序时 所画的框图 是不是一步步都在 自己的掌控之下呢?您是否还考虑到其他程序的状态呢?当然不会 4.1.2 多任务 所谓多任务是指同一时间有好几个任务一起进行 一般中大型的计 算机操作系统 都是采用多用户多任务的方式 就好像一间办公室中各 忙各的工作 但人们相互之间并不干扰一样 Visual C++6.0 编程实例与技巧 178 www.BOOKOO.com.cn 1. 优先级式的多任务 单任务时 CPU 的时间几乎完全被应用程序所占用 操作系统只是 被动等候程序要求 提供必要的服务 如文件的存取等 多任务时 系 统就必须掌握主动 由它来分配时间与资源给每一个运行中的程序 例 如 在某一段时间中 CPU 用来运行一个字处理程序 当时间一到 系 统就必须立刻将 CPU 时间分配给另一个工程计算程序 或是交给绘图 程序 一切的资源 如内存 印表 屏幕显示 一样都必须由系统管 制 以达到数个程序同时工作 却又不致 于互相干扰的目的 这种由一个凌驾所有程序之上的系统 来操纵生杀大权的控制方式 称为“优先级”式的多任务(Preemptive Multitasking) 普遍见于各类主机 系统上 2. 非优先级式的多任务 另外还有一种多任务作业的方法 系统的权力并没有那么大 仅仅 只运行一些分配的工作 其余全靠各应用程序间相互合作 做完一件事 后立刻让出使用权给其他程序 如此达成的多 任务方式 称为“非优先 级”式的多任务(Ninpreemptive Multitasking) 意思是说 大家 要共同生 存 就必须遵守游戏规则 彼此互相礼让 Windows 的作业方式 便是采用后者“非优先权级的多任务” 其全 局工作过程是这样的:程序从系统取得控制权 然后进行程序本身的工 作 工作完毕后 再将控制权交还系统 以便 分配给下一个程序 因 此 Windows 的编程者就得随时注意适时地让出使用权 否则系统 会 因为某一程序的干扰而无法顺利的运作 如果有某个程序硬要独占 CPU 时间 占用内存资源 则系统对它也无可奈何 除了控制权之外 各种输出 输入设备 内存等资源也是依循“取得 Visual C++6.0 编程实例与技巧 179 www.BOOKOO.com.cn —使用—归还”的三部 曲节奏与韵律来进行的 系统只负责将资源分配 给各程序 当资源不再使用时 程序应该立 刻交出资源的使用权 这 样才能维持多任务作业的顺利进行 所以编 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; 所欲送达的窗口代码(handle) UINTmessage;消息 为-Unsign 整数(int) WPARAMwParam;相关参数 后文说明 Visual C++6.0 编程实例与技巧 180 www.BOOKOO.com.cn 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 有空时再来分配消息 Visual C++6.0 编程实例与技巧 181 www.BOOKOO.com.cn 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 读取消息 并 Visual C++6.0 编程实例与技巧 182 www.BOOKOO.com.cn 存入 msg 结构变量 然后再由循环内部来处理消息 GetMessage():多任务的基础 当 GetMessage()从消息队列读消息时 则表示目前没有工作给该程 序做 此时 GetMessage()会自动将程序的控制权交给 Windows 以便 Windows 将控制权转移给下一个程序 这正是 Wind ows 多任务的基础 所以 Windows 程序中一定要用 GetMessage()来取消息 多任务才能进行 4.3 4.3 4.3 4.3 窗口函数窗口函数窗口函数窗口函数::::消息所要送达的对象消息所要送达的对象消息所要送达的对象消息所要送达的对象 当我们操作窗口时(如按钮) 窗口必须对该操作有所反应 在 Windows 下 每个窗口背后都有一个窗口函数 负责窗口对操作(输入) 的反应 也就是负责窗口的行为 如图 4.4 所示 因为窗口的“输入— 反应”动作是通过消息来传送的 所以窗口函数的工作 就是按所收到 的消息种类来决 定反应的动作 因此窗口函数有时也称为“消息处理函 数” 因为窗口函数对消息反应之不 同 每个窗口才有不同的行为 Visual C++6.0 编程实例与技巧 183 www.BOOKOO.com.cn 图 4.4 每个窗口背后 都有一个窗口函数 典型的窗口函数的格式为: Switch (message){case MSGI: 依据消息的种类 (procedure for MSGI);处理 MSGI 消息的程序 做不同的行为 break; case MSG2: (procedure for MSG2); 处理 MSG2 消息的程序 break; : : default:不想处理的消息 就交给系统处理! return(DefWindowPro((hwnd message Visual C++6.0 编程实例与技巧 184 www.BOOKOO.com.cn 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 所示 Visual C++6.0 编程实例与技巧 185 www.BOOKOO.com.cn 图 4.5 同时打开多个窗口 如果 USER 将 A 窗口拖动到它处 这些活动的消息就如前述的会被 存入消息队列 然后程序用 G etMessage()读取 WinMain()读到消息后 接下去就是想办法把消息送给 A 窗口的处理程序 当 A 窗口的处理程 序由消息得知原来 USER 在拖动窗口 所以便使 A 窗口随着鼠标移动的 方向 移动 一切尽如预计的一样进行顺利 但是 A 窗口移开后 那 原本被 A 窗口遮住一半的 B 窗 口呢?B 窗口是否应该将原本遮住的那一 半显示出来呢?要如何显示呢? 首先我们会想到的是 Windows 应该会将掩盖的部分保存起来 等遮 盖物移开时再予显示 但 是这样做有一个问题 那就是占用了太多的 内存 如果窗口重叠有十层 九层 可就不得了 如果不保存被遮盖 Visual C++6.0 编程实例与技巧 186 www.BOOKOO.com.cn 的窗口 另外一个方法就是当遮盖物移去时 重新画出被遮盖的部分 提到重画 一个窗口画面是由应用程序和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 所示 Visual C++6.0 编程实例与技巧 187 www.BOOKOO.com.cn 图 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()这个函数的 Visual C++6.0 编程实例与技巧 188 www.BOOKOO.com.cn 地址登记到 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 来分配消息来分配消息来分配消息来分配消息 接着我们来看一看消息被程序读取之后的情形 当程序取得消息后 Visual C++6.0 编程实例与技巧 189 www.BOOKOO.com.cn 接着就必须将消息分配给所属的窗口函数处理 但是 这项分配工作 系统本来就在做了 怎么说呢?因为所有的 窗口函数地址都已登记到 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 Windows4.4 Windows4.4 Windows4.4 Windows 程序的流程程序的流程程序的流程程序的流程 现在全部细节已经说明完毕 我们将 Windows 程序的两大过 程:WinMain()和 WndProc()整理说明 Visual C++6.0 编程实例与技巧 190 www.BOOKOO.com.cn 4.4.1 WinMain() WinMain()是 Windows 程序的起始函数 通常它会做窗口注册 创 建窗口 显示窗口的动作 然后进入消息循环 此循环会不断地往消息 队列中读取消息 一旦取得消息立刻用 Dispatch Massage()通过系统来调 用消息处理函数 如此一直循环 直到 GetMassage()找不到消息 让 出控制权 才暂告休息 4.4.2 WndProc() WndProc()是处理消息的函数或称窗口函数 WndProc()统一由 Windows 来调用 为一回调型的函数 WndProc()按消息来做出响应 是控制窗口行为的过程 整体的消息通信流程如图 4.8 所示 图 4.8 消息通信流程 4.4.3 CHECK POINT 至于 Windows 的程序流程则整理如下: 外部输入动作发生 被转换成消息 Visual C++6.0 编程实例与技巧 191 www.BOOKOO.com.cn 放入消息 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 long FAR PASCAL WndProc(HWND WORK WORK LONG); int PASCAL WinMain(HANDLE hInstance HANDLE hPre-vInstance LPSTR lps2CmdParam intnComShow) { HWND hwnd; MSG msg; Visual C++6.0 编程实例与技巧 192 www.BOOKOO.com.cn 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 = ClassW1 ; 指定类别名称 RegisterClass( wc); 注册窗口类别 } hwnd = Creat Window( 建立窗口 “ClassW1” 窗口类型名 “Hello World-Windows style” 窗口标题字符串 WSOVERLAPPEDWINDOW, 可重叠窗口 CWUSEDEFAULT, x:缺省左上角的水平位置 CWUSEDEFAULT, y:缺省左上角的垂直位置 CWUSEDEFAULT, w:缺省高度 Visual C++6.0 编程实例与技巧 193 www.BOOKOO.com.cn CWUSEDEFAULT, n:缺省高度 NULL, 无父窗口 NULL, 无菜单 hInstance() 程序 Instance 模块 NULL); 额外参数 Show Window(hwnd,nComdShow); 用 nCmdShow 的方式显示窗口 Update Window(hwnd); 更新窗口工作区 While(GetMessage( msg,NULL,NULL,NULL)) Dispatch Message( 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 的第一程序 有五十多种 Visual C++6.0 编程实例与技巧 194 www.BOOKOO.com.cn 呢 许多程序都是以这么一个程序做为 Windows 初学者的示范程序 如 果我们也是一开始就给您这个程序 感想如 何呢?但通过学习 此程序 就不是那么难以理解 深奥莫测了 就让我们一起来分析一下吧 4.5 4.5 4.5 4.5 程序分析程序分析程序分析程序分析((((一一一一):WinMain()):WinMain()):WinMain()):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 才需要登记窗口类 其次便是创建 显示窗 口等必要的步骤 在 ShowWindow()之后 程序中多了一个 Update Window()的动作 这是为了保证窗口的内容能更新成最新状态 本 书 后文会再给予说明 Visual C++6.0 编程实例与技巧 195 www.BOOKOO.com.cn 4.5.2 WMQUIT 消息消息消息消息 做完显示 更新窗口的工作 程序便进入消息循环 这样程序才能 和外界通信 才不会成为断线的风筝 由消息队列取得的若为 WMQUIT 消息 则 GetMessage()会返回零 使 循环停止 结束程序 当消息不为 WMQUIT 时 GetMessage 会返回一个非零值 使循环 得以继续 并且由 D ispatchMessage ( msg)将消息交由系统来分派给所 属的窗口函数去处理 当 GetMessage()在消息队列中取不到消息时 则表示目前没有工作 给程序做 此时 GetMes sage()会将程序的控制权交给 Windows 就是这 样分工合作 完成了整个 Windows 的多任务操 作 4.6 4.6 4.6 4.6 程序分析程序分析程序分析程序分析((((二二二二):):):):窗口函数窗口函数窗口函数窗口函数 WndProc()WndProc()WndProc()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, Visual C++6.0 编程实例与技巧 196 www.BOOKOO.com.cn 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 个参数输入的) 在这里 只处理了 WMDESTROY 和 default 两种状况 WMDESTROY 这是窗口程序重要的消息之一 它 关系着窗口的结束过程 Visual C++6.0 编程实例与技巧 197 www.BOOKOO.com.cn 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 4.7 4.7 4.7 模块定义文件模块定义文件模块定义文件模块定义文件 现在 我们是不是马上可以开始编译 连接 并运行这个程序呢? Visual C++6.0 编程实例与技巧 198 www.BOOKOO.com.cn 还不行 一般来说 发展一个 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 文件中 但不会被运行 这 Visual C++6.0 编程实例与技巧 199 www.BOOKOO.com.cn 个字段的目的是让编程者可以放一些让自己或别人看的消息于程序中 例如版权或版本说明等 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 都要有一个自己的数据段 正如前面说过的 Visual C++6.0 编程实例与技巧 200 www.BOOKOO.com.cn 同一个程序有好几个同时运行的 instance 则 DATA 声明成 MULTIPLE 的作用就是在 使数据段不要相混 7. HEAPSIZE 调整内存空间字段 当程序中要求动态分配内存时 系统就会将 heap 中的内存分配给该 程序 因此 这个字段决定要保留多少内存给动态分配使用 尽管我们 在程序中并没有任何分配内存的要求 我们还要必须保留 1K 的空间给 heap 8. STACKSIZE 堆栈区大小字段 堆栈是 C 程序中很重要的区域 所有调用函数 传递参数的动作 都必须要有足够的堆栈区 我们的程序中并没有作很多层的调用 也没 有太多的消息 所以要求的堆栈区并不大 如 果您的程序中 有用到 递归调用 或是使用相当多的 local 变量 就必须声明较大的堆栈区 9. EXPORTS 外加嵌入字段 前面提到过 消息处理函数 WndProc 必须被嵌入到系统中 才能由 系统来调用 所有在程序中必须被嵌入而由系统来调用的函数 都必须 在此字段中声明 此处只有一个 WndProc 将 来我们会看到许多:定时 处理 对话框处理 4.8 4.8 4.8 4.8 编译运行编译运行编译运行编译运行 现在 我们终于可以开始来编译并运行我们的第一个 Windows 程序 了 我们将以 Borland 或 TURBOC++3.1 版来编译这个程序 如果您是 MSC 的用户 请先设置好必要的环境变量(可以使用 BIN 目录下的 setvars.bat 来设置) 再创建一个工程文件如下: Visual C++6.0 编程实例与技巧 201 www.BOOKOO.com.cn 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 中写出一个类似的程序 恐怕要数百行呢 但无论如何 如果每次写一个程序 都要键入那么多内容 恐怕您 Visual C++6.0 编程实例与技巧 202 www.BOOKOO.com.cn 早就大喊吃不消了 其实 每一个 Windows 的内容 都有许多大同小异 的地方 尤其是 WinMain()主程序 几乎只要更改 几个字符串或数字就 行了 因此我们就将 winhello.C 保留成一个样板程序 每次写新程序时 就将样板拷贝一份 更改必要内容 如 Creat Window 的标题字符串 WndProc 处理各类消 息的语句 模块定义文件中 NAME 和 DESCRIPTION 的字段以及其他需要更改的设置 便成为另 一份完整的 Windows 程序了 4.9 Hunfarian4.9 Hunfarian4.9 Hunfarian4.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 DTVCENTER IDC cursor id IDCARROW Visual C++6.0 编程实例与技巧 203 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 204 www.BOOKOO.com.cn 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 世界吧 Visual C++6.0 编程实例与技巧 205 www.BOOKOO.com.cn 第五第五第五第五章章章章 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 5.1 5.1 5.1 创建创建创建创建 MDIMDIMDIMDI 示例应用程序示例应用程序示例应用程序示例应用程序 为方便 Windows 应用程序执行流程的讲解 这里创建一个基于 MFC Visual C++6.0 编程实例与技巧 206 www.BOOKOO.com.cn 的 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(); Visual C++6.0 编程实例与技巧 207 www.BOOKOO.com.cn // 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. // Visual C++6.0 编程实例与技巧 208 www.BOOKOO.com.cn #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, CWinApp::O Visual C++6.0 编程实例与技巧 209 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 210 www.BOOKOO.com.cn #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); Visual C++6.0 编程实例与技巧 211 www.BOOKOO.com.cn // 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) Visual C++6.0 编程实例与技巧 212 www.BOOKOO.com.cn // 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(); } ///////////////////////////////////////////////////////////////////////////// Visual C++6.0 编程实例与技巧 213 www.BOOKOO.com.cn // 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) Visual C++6.0 编程实例与技巧 214 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 215 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 216 www.BOOKOO.com.cn 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); Visual C++6.0 编程实例与技巧 217 www.BOOKOO.com.cn } ///////////////////////////////////////////////////////////////////////////// // 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 } ///////////////////////////////////////////////////////////////////////////// Visual C++6.0 编程实例与技巧 218 www.BOOKOO.com.cn // 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) Visual C++6.0 编程实例与技巧 219 www.BOOKOO.com.cn #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; Visual C++6.0 编程实例与技巧 220 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 221 www.BOOKOO.com.cn #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() { } Visual C++6.0 编程实例与技巧 222 www.BOOKOO.com.cn 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(); Visual C++6.0 编程实例与技巧 223 www.BOOKOO.com.cn } 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 Visual C++6.0 编程实例与技巧 224 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 225 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 226 www.BOOKOO.com.cn // 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; Visual C++6.0 编程实例与技巧 227 www.BOOKOO.com.cn if (!mwndToolBar.CreateEx(this, TBSTYLEFLAT, WS CHILD | WSVISIBLE | CBRSTOP | CBRSGRIPPER | CBRSTOOLTIPS | CBRSFLYBY | CBRSSIZEDYNAMIC) || !mwndToolBar.LoadToolBar(IDRMAINFRAME)) { TRACE0( Failed to create toolbar @n ); return -1; // fail to create } if (!mwndStatusBar.Create(this) || !mwndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0( Failed to create status bar n ); return -1; // 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); return 0; } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CMDIFrameWnd::PreCreateWindow(cs) ) Visual C++6.0 编程实例与技巧 228 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 229 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 230 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 231 www.BOOKOO.com.cn 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) ) Visual C++6.0 编程实例与技巧 232 www.BOOKOO.com.cn 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 5.2 5.2 5.2 应用程序类和源文件应用程序类和源文件应用程序类和源文件应用程序类和源文件 使用 MFC AppWizard 生成 MDI 应用程序的基本框架时 将派生五 个主要的类 文档类 视图类 主边框窗口类 子边框窗口类和应用 程序类 主要的程序任务均分布在这些类中实 现 此外 MFC AppWizard 为每个类生成各自的源文件 并从程序名中获取类名和类源 文件 名作为缺省名 类之间通过调用公有成员函数并传递消息来进行 相互间的通信和交换数据 假设创建了名为 MyApp 的 MDI 应用程序的基本框架 则 MyApp Visual C++6.0 编程实例与技巧 233 www.BOOKOO.com.cn 程序的文档类称为 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 类派生的对象 应用程序的一个且是 Visual C++6.0 编程实例与技巧 234 www.BOOKOO.com.cn 唯一的一个对象将创建并管理应用 程序所支持的文档模板 文档模板 使得所创建的文档 视图和边框窗口有机地结合起来 除了生成主要类的源文件外 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 文件 用于描述为应用程 序生成的所有源文件 Visual C++6.0 编程实例与技巧 235 www.BOOKOO.com.cn 5.3 5.3 5.3 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 成员函数 在运行应用程序的消息循环时 WinMain 函数将调用 Run 成员函数 在程序结束时 WinMain 函数将调用应用程序对象的 Exi tInstance 成员函数 图 5.1 说明了应用程序的执行顺序 Visual C++6.0 编程实例与技巧 236 www.BOOKOO.com.cn 图 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( Visual C++6.0 编程实例与技巧 237 www.BOOKOO.com.cn 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; Visual C++6.0 编程实例与技巧 238 www.BOOKOO.com.cn // 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 来 Visual C++6.0 编程实例与技巧 239 www.BOOKOO.com.cn 处理消息循环 成员函数 Run 不断执行消息循环 检查消息队列中有没 有消息 如果有消息 将消息发送下去 以便采取动 作 如果没有消 息 则调用消息 CWinApp 的成员函数 OnIdle 来做空闲时的处理工作 如果既 没有消息要处理 也没有空闲时的处理工作要做 则应用程序 将一直等待 直到消息出现 当应用程序结束时 成员函数 Run 将调 用 ExitInstance 来做最后的清理工作 图 5.2 说明了消 息循环的执行顺 序 图 5.2 消息循环的执行顺序 5.3.3 成员函数成员函数成员函数成员函数 ExitInstance 每当要终止应用程序的某一副本时 都要调用 CwinApp 的成员函数 ExitInstance 如果要做专门的清理工作 如释放 GDI 资源或释放执行期 间所占用的内存 则可以覆盖成员函数 Exi tInstance 5.3.4 成员函数成员函数成员函数成员函数 OnIdle 如果没有消息要处理 则应用程序将调用 CwinApp 的成员函数 Visual C++6.0 编程实例与技巧 240 www.BOOKOO.com.cn OnIdle 来做空闲处理 缺省时 该成员函数将更新用户界面对象 如工 具栏按钮 的状态 并完成在运行过程中所创 建的临时对象的清理工 作 5.3.5 CWinApp 的服务功能的服务功能的服务功能的服务功能 除了运行消息循环 初始化应用程序并在程序结束后做清理工作外 CWinApp 还提供有以下两种功能服务 1. Shell 注册 缺省时 可以在文件管理器 或资源管理器 中通过双击来打开应 用程序创建的数据文件 对于 MDI 应用程序 如果在创建文件时指定 了文件扩展名 MFC AppWizard 将在 InitInstance 中增加以下代码 EnableShellOpen RegisterShellFileTypes 其中 成员函数 RegisterShellFileTypes 将向文件管理器 或资源管 理器 注册应用程序的文档类型 该函数往 Windows 注册数据库中增加 一些条目 用于将扩展名与文档类型关联起 来 指定打开应用程序所 用的命令并指定打开某类文档所用的动态数据交换 DDE 命令 成 员函数 EnableShellOpen 允许应用程序接收来自文件管理器 或资源管 理器 的 DDE 命令 以便打开用户选中的文件 2. 拖放功能 Windows 允许用户从文件管理器 或资源管理器 窗口中拖放文件 到应用程序窗口中 例如 把多个文件名拖到 MDI 主边框窗口中 应 用程序搜索文件名并在 MDI 子窗口打开这些文件 要在应用程序中实 现文件拖放功能 可以在成员函数 InitInstance 中增加以下代码 Visual C++6.0 编程实例与技巧 241 www.BOOKOO.com.cn mpMainWnd->DragAcceptFiles 如果不想实现拖放功能 可以去掉这个函数调用 注意 还可以用 OLE 对象链接与嵌入 实现通用的拖放功能 即在文档内部或文档之 间拖放数据 5.4 5.4 5.4 5.4 文档模板文档模板文档模板文档模板 文档模板用于存放与应用程序文档 视图和边框窗口有关的信息 MFC 类库提供有两种文档模板类 即用于 SDI 应用程序的 CSingleDocTemplate 和用于 MDI 应用程序的 CMultiDocTemplat e 对于 每种文档类型 CSingleDocTemplate 每次只能创建并管理一个文档 而 CMultiDocTe mplate 可以创建并管理多个文档 有些应用程序还可以同时支持多种文档类型 例如 文本文档和图 形文档 在这种情况下 每个应用程序对象管理多个文档模板 每个文 档模板创建并管理一个 对于 SDI 应用程序 或多个文档 对于 MDI 应用程序 文档模板由应用程序对象创建和维护 在 WinMain 函数调用成员函 数 InitInstance 进行初始 化时 必须创建一个或多个适当类型的文档模 板 应用程序对象保存指向模板列表中每个文 档模板的指针 并提供 添加文档模板的接口 如果应用程序支持多种文档类型 则对每种文 档 类型都要调用一次 CwinApp 的成员函数 AddDocTemplate. 此外 在响应“File”菜单中的“New”或“Open”命令时 文档模板将创 建新的边框窗口 以便查看文档 新的文档及其相关视图和边框窗口的 创建是应用程序对象 文档模板 新 创建的文档和新创建的边框窗口 共同合作而产生的 参阅图 5.3 所示的创建关系 Visual C++6.0 编程实例与技巧 242 www.BOOKOO.com.cn 图 5.3 对象间的创建关系 5.5 5.5 5.5 5.5 窗口类与窗口对象窗口类与窗口对象窗口类与窗口对象窗口类与窗口对象 在 MFC 类库中 所有窗口最终都是从 CWnd 类派生的 5.5.1 窗口对象窗口对象窗口对象窗口对象 窗口对象 不管是边框窗口还是其他类型的窗口 与 Windows 窗口 HWND 是有区别的 窗口对象是可以由应用程序直接创建的 CWnd 类 或派生类 的对象 它随着对应用程序的构造 函数和析构函数的 调用而出现和消失 而 Windows 窗口是 Windows 内部数据结构的句柄 在显 示时要消耗系统资源 Windows 窗口由窗口句柄 HWND 标识 并由 CWnd 类的成员函数 Creat e 在创建 CWnd 对象之后创建 窗口可 以被应用程序调用销毁 也可以被用户动作销毁 窗口 句柄保存在窗 口对象的 mhWnd 成员变量中 Visual C++6.0 编程实例与技巧 243 www.BOOKOO.com.cn 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 子窗口的客户区中 它是文档与用 户间沟通的桥梁 Visual C++6.0 编程实例与技巧 244 www.BOOKOO.com.cn 对话框 对话框是用从 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 Visual C++6.0 编程实例与技巧 245 www.BOOKOO.com.cn 成员函 数前 可能要调用 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 5.6 5.6 5.6 设备文本与图形对象设备文本与图形对象设备文本与图形对象设备文本与图形对象 窗口的使用主要有两种 一是处理 Windows 消息 另一是在窗口中 进行绘制 对于 Windows 消息的处理 可以使用 ClassWizard 把这些消 息映射到窗口类中 然后在该类中编写消息处理 函数 在框架应用程序中 主要的绘制工作都出现在视图中 每当窗口内 容必须绘制时 都要调用 视图的 OnDraw 成员函数 如果窗口是视图 Visual C++6.0 编程实例与技巧 246 www.BOOKOO.com.cn 的一个子窗口 则可以把视图的某些绘制工作交给 子窗口处理 而让 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 对象 在框架应用 程序中 这个过程大部分是自动完成的 Visual C++6.0 编程实例与技巧 247 www.BOOKOO.com.cn 当从 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 的字体对象 Windows 句柄为 Visual C++6.0 编程实例与技巧 248 www.BOOKOO.com.cn HFONT Cbitmap 封装了 GDI 的位图对象 Windows 句柄为 HBITMAP Cpalette 封装了 Windows 调色板 Windows 句柄为 HPALETTE CRgn 封装了 GDI 的区域对象 Windows 句柄为 HRGN 要改变当前的图形对象 既可以使用库存 stock 图形对象 也可 以创建定制的图形对象 然后将其选入设备文本 5.7 5.7 5.7 5.7 消息与命令消息与命令消息与命令消息与命令 所有 Windows 应用程序都是消息驱动的 消息处理是所有 Windows 应用程序的核心部分 当用户单击鼠标或改变窗口大小时 都将给适当 的窗口发送消息 每个消息都对应于某个特定的 事件 与所有其他 Windows 应用程序一样 框架应用程序也要处理 Windows 消息 但框架 应用 程序使消息处理更容易 更易于维护并且封装得更好 5.7.1 消息与消息处理消息与消息处理消息与消息处理消息与消息处理 前面说过 应用程序初始化完成后 将调用 CwndApp 成员函数 Run 来处理消息循环 消息循环不断检索由各种事件所产生的消息 并将消 息分发给适当的窗口 窗口接受到消息后 将调用专门的处理函数来处 理各种消息 消息处理函数通常是某一类的成员函数 编写消息处 理 是编写框架应用程序的主要任务 可以使用 ClassWizard 创建消息处理函数 然后从 Class Wizard 直接 跳到源文件中消息处理函数的定义部分 从中编写消息处理函数的代 Visual C++6.0 编程实例与技巧 249 www.BOOKOO.com.cn 码 事实上 在 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 Visual C++6.0 编程实例与技巧 250 www.BOOKOO.com.cn 关键字 afxmsg 用于把处理函数与其他 CWnd 成员函数区分开来 这 些函数并不真正是虚拟的 而是通过消息映射实现的 消息映射仅仅依 赖于标准的预处理宏 而不是依赖于 C++语言的任何扩展 经过预处理 以后 关键字 afxmsg 的位置就变成空白 如果要覆盖 CWnd 类中已定 义的某一消息处理函数 只需用 ClassWizard 在派生类中用同样的原型 定义一个函数并为该 函数做一个消息映射条目即可 当 ClassWizard 为 某一给定的消息编写处理函数的轮廓时 将使用被覆盖成员函数推荐使 用的形式 例如 WMCREATE 消息的 OnCreate 处理函 数将首先调用 基类的处理函数 只有在不返回-1 的条件下才继续执行: int CMyAppView::OnCreate LPCREATESTRUCT lpCreateStruct { if Cview::OnCreate lpCreateStruct ==-1 return -1 //TODO 在此处添加专门的创建代码 return 0 } 标准 Windows 消息常见的有 WMCHAR 消息 鼠标消息 WMPAINT 消息 WMHSCROLL 消息 WMVSCROLL 消息和 WMTIMER 消息等 1. WMCHAR 消息 当用户按下键盘上的某一键时 都会产生 WMCHAR 消息 WMCHAR 消息的处理函数为 OnChar 函数原型为 afxmsg void OnChar UINTnChar,UINT nRepCni,UINT nFlags 其中 参数 nChar 是所按键的字符代码值 nRepCnt 表示重复次数 Visual C++6.0 编程实例与技巧 251 www.BOOKOO.com.cn 该值是用户按键时重复击键的次数 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 并不 记录窗口中 的具体内容 因为维护窗口内容是编程人员而不是系统的 Visual C++6.0 编程实例与技巧 252 www.BOOKOO.com.cn 责任 多数情况下 应用程序比系 统更了解窗口内容 也更易于维护 窗口内容 当然系统也能帮助维护窗口内容 其方式是向 Windows 应 用程序发送 WMPAINT 消息 应用程序检索到该消息后 就需要重新显 示 窗口内容 WMPAINT 消息的处理函数为 OnPaint 该函数无参数 4. WMHSCROLL 消息 WMHSCROLL 消息是用户单击窗口的水平滚动条时产生的消息 WMHSCROLL 消息的处理函数为 OnHScroll 函数原型为 afxmsg void OnHScroll UINT nSBCode UINT nPos CScrollBar* pScrollBar 其中 参数 nSBCode 是指示用户滚动请求的滚动条代码 其取值可 以查阅有关资料 当滚动条代码为 SBTHUMBPOSITION 和 SBTHUMBTRACK 时 nPos 用于指定滚动框的位置 否则 无意义 如果滚动消息来自滚动条 控件 则 pScrollBar 为指向控件的指 针 如果单击窗口滚动条 则该值 为 NULL 5. WMVSCROLL 消息 WMVSCROLL 消息是用户单击窗口的垂直滚动条时产生的消息 WMVSCROLL 消息的处理函数为 OnVScrol1 函数原型为 afxmsg void OnVScroll UINT nSBCode UINT nPos CScrollBar* pScrollBar 其中 参数 nSBCode 是指示用户滚动请求的滚动条代码 其取值可 以查阅有关资料 当滚动条代码为 SBTHUMBPOSITION 和 SBTHUMBTRACK 时 nPos 用于指定滚动框的位置 否则 无意义 如果滚动消息来自滚动条 Visual C++6.0 编程实例与技巧 253 www.BOOKOO.com.cn 控件 则 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 通知消息 命令消息的处理与其他消息的处理 Visual C++6.0 编程实例与技巧 254 www.BOOKOO.com.cn 不同 命令消息可以被更广泛的对象 如文档 文档 模板 应用程序 对象 窗口和视图等 处理 如果某条命令直接影响某个特定的对象 则应 该让该对象来处理这条命令 例如 当应用程序接收到“File”菜单 中的“New”命令时 将调用 CWinApp 类的成员函数 OnFileNew 打开新 的空文档 1. 用户界面对象 菜单项 工具栏按钮和加速键都是可以产生命令的用户界面对象 每个这样的对象都有一个 ID 通过给对象和命令分配同一 ID 可以把用 户界面对象与命令联系起来 命令是被当作特殊 的消息来处理的 当 用户界面对象产生一条命令后 应用程序的某个对象就将处理这条命 令 2. 命令 ID 命令完全是由命令 ID 来描述的 命令 ID 分配给产生该命令的用户 界面对象 通常 命令 ID 是以其所表示的用户界面对象的功能来命名 的 例如 “Eidt”菜单中的“Copy”命令就可以 用 IDEDITCOPY 来命名 MFC 类库预定义了某些命令 ID 而其他命令 ID 则 要编程人员自己定 义 所有预定义命令 ID 的列表 可参见 AfxRes.h 文件 3. 命令目标 当用户界面对象被单击后 将调用处理函数执行所产生的命令 Windows 把不是命令消息的消息直接发送给窗口 该窗口中用于处理这 条消息的处理函数将被调用 但是 对于命令消 息 则把命令发送给 多个候选对象 称为命令目标 其中通常总有一个要引用该命令的处 理函数 处理函数处理命令的方法与处理标准 Windows 消息的方法是一 样的 但调用机制不 一样 Visual C++6.0 编程实例与技巧 255 www.BOOKOO.com.cn 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 给其发送消息的那个窗口处理 该窗口可能是主边框窗口 Visual C++6.0 编程实例与技巧 256 www.BOOKOO.com.cn MDI 子窗口 标准控件 对话框 视图或其他类型 的子窗口 在运行 时 每个 Windows 窗口都与窗口对象 从 CWnd 直接或间接派生 联 系在一起 每个窗口对象都有自己的消息映射和处理函数 框架利用消 息映射把接收到的消息与处 理函数进行匹配 2. 命令消息的发送 处理命令消息时 编程人员除了要建立命令与处理函数间的消息映 射关系外 还必须编写大部分的命令处理函数 对于命令消息 框架把命令发送给标准命令目标对象链 其中之一 会有该命令的处理函数 每个命令目标对象都将检查自己的消息映射 看看能否处理相应的消息 不同的命令目标类检查消息映射的时机是不同的 通常 每个命令 目标类先把命令发送给某 些其他对象 给予其他对象先行处理的机会 如果这些对象都不能处理该命令 则起始类检 查自己的消息映射 如 果也不能提供处理函数 则把该命令发送给更多的命令目标 命令目标链发送命令的一般顺序为 先发送给当前活动的子命令目 标对象 然后发送给自己 最后再发送给其他命令目标 例如 在 MDI 应用程序中 选择“Edit”菜单的“Clear All”命令时 将 产生一条命令消息 假定命令处理函数是应用程序文档类的成员函数 那么该命令的发送顺序为 首先边框窗 口收到命令消息 接着 MDI 主 边框窗口给当前活动的 MDI 子窗口处理该命令的机会 MDI 子窗 口在 检查自己的消息映射前 按标准的发送顺序给其视图处理该命令的机 会 视图检查其消 息映射 如果没有找到处理函数 再把该命令发送 给与其相连的文档 文档检查其消息映射 找到该命令的处理函数并 调用 发送过程结束 Visual C++6.0 编程实例与技巧 257 www.BOOKOO.com.cn 如果文档没有处理函数 就再把该命令发送给文档模板 然后返回 到视图 再返回边框窗口 最后边框窗口检查其消息映射 如果还找不 到处理函数 则该命令将被回送到 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! Visual C++6.0 编程实例与技巧 258 www.BOOKOO.com.cn //}}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 则包含有消 息映射的内容 应注意的是消息映射宏后面不能带有分号 此外 消息映射还包含 以下形式的注释 Visual C++6.0 编程实例与技巧 259 www.BOOKOO.com.cn //{{AFXMSGMAP(CMyAppView) //}}AFXMSGMAP 这些注释与普通编程中的注释性质不同 它们用于把许多映射条目 括起来 ClassWizard 在编写映射条目时将使用这些注释,所以千万不要 删掉它们 所有 ClassWizard 映射条目都位于 注释行之间 当用 C1assWizard 创建新类时 将为该类提供一个消息映射 1. 消息映射的继承关系 在消息处理过程中 检查每个命令目标类自己的消息映射并不是消 息映射的最后一步 例如 CMyAppView 类的基类是 CView 类 该类 是从 CWnd 类派生的 这样 CMyAppView 是一个 CView ,也是一个 CWnd 从而每个 CMyAppView 对象都是将拥有所有这些类的消息映射 因此 如果消息在 CMyAppView 类的消息映射中找不到匹配 则还 要搜索该类的直接基类的消息映射 消息映射开始的 BEGINMESSAGEMAP 宏 其参数是两个类名 BEGINMESSAGEMAP(CmyAppView Cview) 第一个参数指出该消息映射所属类的名称 第二个参数指出所属类 的直接基类 因此 基类中的消息处理函数就被派生类继承了 如果在 所有基类的消息映射中都找不到处理函数 则执行缺省的消息处理函 数 如果消息是命令消息 就将其发送给下一命令目标 如果是标准 的 Windows 消息 就将其传给适当的缺省窗口函数 2.消息映射的结构 在源文件中 每个消息映射都由一组预定义的宏组成 这些在消息 映射内部的宏称为映射宏 消息映射中使用的映射宏依所处理消息的类 型而定 表 5.2 总结了各种类型的映射宏 每个映射条目都由一个映射 Visual C++6.0 编程实例与技巧 260 www.BOOKOO.com.cn 宏组成 映射宏中可以没有参数或者可以有多个参数 表 5.2 消息映射的预定义映射宏 消息类型 宏格式 参数 预定义 Windows 消息 ONWMXXXX 无参 数 命令 ON0000COMMAND 命 令 ID 处理函数名 更新命令 ONUPDATECOMMANDUI 命令 ID 处理函数名 控件通知 ONXXXX 控 件 ID 处理函数名 用户自定义消息 ONMESSAGE 自定 义消息 ID 处理函数名 已注册 Windows 消息 ONREGISTEREDMESSAGE 消息 ID 处理函数名 命令 ID 范围 ONCOMMANDRANGE 连续 范围内命令 ID 的开始和结束 将更新的命令 ID 范围 ONUPDATECOMMANDUIRAN GE 连续 范围内命令 ID 的开始和结束 控件 ID 的范围 ONCONTROLRANGE 控件通 知码和连续范围内命令 ID 的开始和结束 其中 XXXX 表示的名称是以 Windows 标准消息名称或控件通知码 为基础的 例如 ONW MPAINT ONWMLBUTTONDOWN 和 Visual C++6.0 编程实例与技巧 261 www.BOOKOO.com.cn ONENCHANGE 等 尽管 ONWMXXXX 宏没有参数 但对应的处理函 数通常是有参数的 3.消息映射的范围 可以把某一范围内的消息映射成单个的消息处理函数 例如 把某 一范围内的命令 ID 映射成一个命令更新处理函数 使得所有命令一起 有效或一起无效 在 ClassWizard 中 不支持消息映射范围 必须自己 编写这些消息映射条目 5.7.6 用户界面对象的更新用户界面对象的更新用户界面对象的更新用户界面对象的更新 通常 菜单项和工具栏按钮都有不止一种状态 例如 菜单项可以 是不可用的 显示为灰色 被选中的或未被选中的 工具栏按钮可以 是不可用的或被选中的 如果应用程序的条件发生变化 那么如何更新这些用户界面对象 呢 例如 当用户首次打开包含“ClearAll”命令的弹出式菜单 “Edit”菜 单 时 将发出一条更新命令 ONUPDATECOMMANDUI,由于该消息是 在弹出式菜单可见之前被发送的 因此要调用更新命令处理函数 OnUpdateEditClearAll 来初始化命令 以便根据应用程序的当前 状态使 命令有效或无效 Visual C++6.0 编程实例与技巧 262 www.BOOKOO.com.cn 第六章第六章第六章第六章 对话框对话框对话框对话框 控件和控件栏控件和控件栏控件和控件栏控件和控件栏 对话框用于显示和获取用户输入 对话框通过一个或多个控件 如 按钮 列表框 组合框和编辑框等 与用户进行交互 控件是一种特定 类型的输入或输入窗口 通常为其父窗口 如对话框 边框窗口 视图 窗口或控件栏等 所拥有 控件栏 如工具栏 状态栏和对话栏 是包含按钮 编辑框 复选 框或其他控件的窗口 控 件栏通常放置在边框窗口的顶部或底部 6.1 6.1 6.1 6.1 对话框对话框对话框对话框 在Windows中 对话框是应用程序与用户交互的主要途径 Developer Studio 的对话编辑器使得对话框的设计以及对话模板资源的创建更为容 易 而 C1assWizard 则简化了对话框控件 的初始化和验证以及获取用户 输入的过程 在 MFC 类库中 对话框由 CDialog 类管理 6.1.1 对话框的组成对话框的组成对话框的组成对话框的组成 对话框主要由以下两部分组成 (1) 对话模板资源 对话模板资源用于定义对话框控件及其分布 对 话资源中保存有用于创建并显示对话窗口的对话模板 对话模板定义对 话框的特性 如大小 位置和风格等 以及对话框控件的类型和位置 除了直接使用作为资源保存的对话模板外 还可以根据需要 在内存中 创建对话模板 (2) 对话类 从 CDia1og 派生的对话类提供编程接口来管理对话框 每个对话框是一个窗口 对话窗口创建后 将自动根据对话模板资 源来创建对话框控件 Visual C++6.0 编程实例与技巧 263 www.BOOKOO.com.cn 6.1.2 模态和非模态对话框模态和非模态对话框模态和非模态对话框模态和非模态对话框 由 CDialog 类管理的对话框有两种类型 即模态对话框和非模态对 话框 模态对话框是最常用的对话框 要求用户在应用程序继续执行之 前作出响应 即模态对话框不允许在未收到 响应之前就进入应用程序 的其他部分 非模态对话框允许用户为对话框提供信息 而无需删 除 该对话框就可以回到前面的任务 即非模态对话框不阻止应用程序的运 行 在输入转到应 用程序的其他部分之前 并不需要作出响应 创建 对话模板时的资源编辑和 ClassWizad 过程 对模态对话框与非模态对话 框都是一样的 创建应用程序对话框的主要过程为 1 用对话编辑器设计对话框并创建相应的对话模板资源 2 用 ClassWizard 创建对话类 3 把对话资源的控件与对话类中的消息处理函数链接起来 4 用 ClassWizard 添加与对话控件有关的成员变量并为控件指定 对话数据交换 DDX 和对话数据验证 DDV 6.1.3 创建对话资源创建对话资源创建对话资源创建对话资源 可以使用 Developer Studio 的对话编辑器来设计对话框并创建对话 资源 在对话编辑器中 可以调整对话框显示时的大小和位置 从控件 工具栏拖放各种类型的控件到对话框中 用对话工具栏调整控件的位 置 测试对话框的外观和行为 测试对话框时 可以在文 本框输入文 本 单击按钮等来操纵对话框中的控件 对话模板资源保存在应用程序的资源文件中 如果需要 可以对其 进行编辑修改 Visual C++6.0 编程实例与技巧 264 www.BOOKOO.com.cn 用同样的方法也可以创建 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. 创建模态对话框 Visual C++6.0 编程实例与技巧 265 www.BOOKOO.com.cn 创建模态对话框时 首先要调用两个 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 Visual C++6.0 编程实例与技巧 266 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 267 www.BOOKOO.com.cn 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 TRUE 如果参数 bSaveAndValidate 为 FALSE 则初始化对话框 如果为 TRUE 则获取并验证对话数 据 CDialog:: OnInitDia1og 的缺省实现是 以 FALSE 为参数调用 UpdateData 而 Cdialog::On Ok 的缺省实现是以 Visual C++6.0 编程实例与技巧 268 www.BOOKOO.com.cn 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 函数将显示消息框提示用户 并把焦点 Visual C++6.0 编程实例与技巧 269 www.BOOKOO.com.cn 放在违反规则的控件中以便用户重新输入数据 给定控件的 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 属性即 可 Visual C++6.0 编程实例与技巧 270 www.BOOKOO.com.cn 对话类的成员变量可以拥有 Control 属性 而不是 Value 属性 Value 属性指的是从控件所返回数据的类型 如 CString 或 int Control 属性 允许通过某一数据成员对控件进行直接访 问 数据成员的类型必须是 MFC 控件类 如 CButton 或 CEdit 之一 对于某一给定的控件 可以 有多个具有 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 删除对话窗口 如果对话框是独立的 则应该覆盖函数 Visual C++6.0 编程实例与技巧 271 www.BOOKOO.com.cn 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 对 象 Visual C++6.0 编程实例与技巧 272 www.BOOKOO.com.cn 5 函数 NextDlgCtrl 移输入焦点到对话框中的下一控件上 如果 输入焦点在对话框的最后一个控件上 则移到第一个控件 函数原型为 void NextDlgCtrl 6 函数 PrevDlgCtrl 移输入焦点到对话框中的上一控件上 如果 输入焦点在对话框的第一个控件上 则移到最后一个控件 函数原型为 void PrevDlgCtrl 7 函数 SetDefID 更改对话框的缺省按钮控件 函数原型为 void SetDefID UINT nID 参数 nID 指定要设置为缺省按钮的控件 6.2 6.2 6.2 6.2 通用对话类通用对话类通用对话类通用对话类 通用对话框是系统定义的对话框 可以使用通用对话框来执行各种 标准操作(如选择文件名 指定字体 选择颜色等) MFC 类库提供有 几个从 CDialog 派生的通用对话类(如表 6.1 所列) 这些类封装了 Windows 通用对话框 相应的对话模板资源和代码在通用对话框中提 供 表 6.1 通用对话类 通用对话类 说明 CcolorDialog 选择颜色 CfontDialog 指定字体 CfileDialog 选择要打开或保存的文件 CprintDialog 指定与打印有关的信息 CfindReplaceDialog 在文本文件中查找和替换 Visual C++6.0 编程实例与技巧 273 www.BOOKOO.com.cn 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 返回选择字体的磅数 Visual C++6.0 编程实例与技巧 274 www.BOOKOO.com.cn 函数 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 用于验证输入到对话框中的颜色 可以在派生的 子类中覆盖此函数 Visual C++6.0 编程实例与技巧 275 www.BOOKOO.com.cn 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 6.3 6.3 6.3 控件控件控件控件 控件是一种特定类型的输入或输入窗口 通常为其父窗口(如对话 框 边框窗口 视图窗口或控件栏等)所拥有 可以在对话框或窗口中直 Visual C++6.0 编程实例与技巧 276 www.BOOKOO.com.cn 接创建控件对象 还可以从对话模板资源 中创建对话框中的控件 通过 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. 静态文本控件 Visual C++6.0 编程实例与技巧 277 www.BOOKOO.com.cn 静态文本控件用于显示文本串 框 矩形 图标 光标 位图或元 文件 静态文本控件通常不接收输入也不提供输出 但是当以 SSNOTIFY 风格创建时将传送鼠标单击消息给父窗口 用于操作静态文本控件的 CStatic 成员函数主要有 函数 SetBitmap 指定静态文本控件中要显示的位图 函数 GetBitmap 获取由 SetBitmap 设置的位图句柄 函数 SetIcon 指定静态文本控件中要显示的图标 函数 GetIcon 获取由 SetIcon 设置的图标句柄 函数 SetCursor 指定静态文本控件中要显示的光标 函数 GetCursor 获取由 SetCursor 设置的光标句柄 函数 SetEnhMetaFile 指定静态文本控件中要显示的元文件 函数 GetEnhMetaFile 获取由 SetEnhMetaFi1e 设置的元文件句柄 2. 按钮控件 按钮控件是一种矩形子窗口 通过单击或双击可以执行某一任务 按钮可以单独出现 也可以成组出现 典型的按钮控件有复选框 单选 钮和按钮(pushbutton) 在调用成员函数 Cr eate 初始化按钮对象时 通 过指定按钮风格可以确定要创建的按钮类型 如果要处理按钮控件传送给父窗口的控件通知消息 则必须为每条 消息添加消息映射条目和消息处理函数 单击按钮时 将产生控件通知 消息 BNCLICKED 双击按钮时 将产 生控件通知消息 BNDOUBLECLICKED 用于操作按钮控件的 CButton 成员函数主要有 函数 GetState 获取单选钮或复选框的状态 函数 SetState 设置按钮控件的高亮状态 高亮状态影响按钮控件 Visual C++6.0 编程实例与技巧 278 www.BOOKOO.com.cn 的外观 不影响单选钮或复选框的选中状态 函数 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 风格时 传送此消息 Visual C++6.0 编程实例与技巧 279 www.BOOKOO.com.cn 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 复制列表项到缓冲区 Visual C++6.0 编程实例与技巧 280 www.BOOKOO.com.cn 函数 GetTextLen 返回列表项的字节数 函数 SetColumnWidth 设置多栏列表框的栏宽 函数 SetTabStops 设置列表框中制表符的位置 函数 AddString 添加字符串到列表框中 函数 DeleteString 从列表框中删除字符串 函数 InsertString 在列表框的指定位置插入字符串 函数 ResetContent 从列表框中清除所有入口 函数 Dir 从当前目录添加文件名到列表框 函数 FindString 搜索列表框中的指定字符串 函数 FindStringExact 搜索列表框中第一个与指定字符串匹配的 字符串 函数 SelectString 搜索并选择单选择列表框中的指定字符串 此外 与单选择列表框有关的函数有 GetCurSet 和 SetCurSe1 与多 选择列表框有关的函数 Se tSe1 GetCaretIndex SetCaretIndex GetSelCount GetSelItems 和 SetItemRange 等 4. 编辑控件 编辑控件是一个矩形子窗口 从中可以输入文本信息如果要处理编 辑控件传送给父窗口的控件通知消息 则必须为每条消息添加消息映射 条目和消息处理函数 与编辑控件有关的 常见消息有 ENCHANGE 改变编辑控件中的文本 与消息 ENUPDATE 不同 此消息 是在 Windows 更新显示后传送的 ENERRSPACE 编辑控件不能分配足够的内存 ENHSCROLL 单击编辑控件的水平滚动条 在屏幕更新前 父窗口被通知 Visual C++6.0 编程实例与技巧 281 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 282 www.BOOKOO.com.cn 函数 EmptyUndoBuffer 重设或清除编辑控件的 Undo 标记 函数 GetModify 判断编辑控件的内容是否已修改 函数 SetModify 设置或清除编辑控件的修改标记 函数 SetReadOnJy 设置编辑控件为只读状态 函数 GetPasswordChar 得到编辑控件中显示的口令字符(当用户 输入文本时) 函数 SetPasswordChar 设置编辑控件中显示的口令字符(当用户输 入文本时) 函数 GetFirstVisibleLine 判断编辑控件中最顶端的可见行 函数 LineLength 得到编辑控件的行长度 函数 LineScroll 滚动多行编辑控件中的文本 函数 LineFromChars 得到包含指定字符索引的行号 函数 GetRect 得到编辑控件的格式化区域 函数 LimitText 限制可以输入到编辑控件中的文本长度 函数 GetLineCount 得到多行编辑控件的行数 函数 GetLine 从多行编辑控件中得到指定的文本行 5. 组合框控件 组合框是由列表框和静态文本控件或编辑控件组成的 控件的列表 框部分可能一直显示或可能在用户选择下拉箭头时下拉显示 列表框中 当前选择项显示在静态文本控件或编 辑控件中 简单的组合框由一个 编辑控件和一直显示的列表框组成 下拉组合框由编辑控 件和列表 框组成 仅当用户选择下拉箭头时才显示列表框 下拉列表框由静态文 本控件和 列表框组成 仅当用户选择下拉箭头时才显示列表框 如果要处理组合框控件传送给父窗口的控件通知消息 则必须为每 Visual C++6.0 编程实例与技巧 283 www.BOOKOO.com.cn 条消息添加消息映射条目和消息处理函数 与组合框控件有关的常见消 息有 CBNCLOSEUP 组合框的列表框已关闭 如果组合框有 CBSSIMPLE 风格 则不传送此消息 CBNDBLCLK 双击组合框中的列表项 仅当组合框有 CBSSIMPLE 风格 时传送此消息 对于具有 CBSDROPDOWN 或 CBSDROPDOWNLIST 风格的组合框 由于单 击已将组合框隐藏 因 此不会发生双击事件 CBNDROPDOWN 组合框的列表框即将下拉 仅当组合框具有 CBSDROP DOWN 或 CBSDROPDOWLIST 风格时传送 CBNEDITCHANGE 更改组合框中编辑控件部分的文本 与 CBNEDITUPDATE 消息不同 此消息是在 Windows 更新显示后传送 如果组合框有 CBSDROPDOW NLLST 风格 则不传送此消息 CBNEDITUPDATE 编辑控件部分即将显示被更改的文本 如果 组合框有 CBSDROPDOWNLIST 风格 则不传送此消息 CBNERRSPACE 组合框不能分配足够的内存 CBNSELENDCANCEL 用户选择被取消 用户单击某一项 然 后单击另一窗口或控件隐藏组合框的列表框 CBNSELENDOK 用户选择一项并按回车键 CBNKILLFOCUS 组合框失去输入焦点 CBNSETFOCUS 组合框接收输入焦点 CBNSELCHANGE 用户单击列表框或使用箭头键导致组合框的列 表框选择即将被更改 要处理这个消息 可以通过 GetLBText 或其他类 似的成员函数获取组合框的编辑控件中 的文本 注意 不能使用函数 Visual C++6.0 编程实例与技巧 284 www.BOOKOO.com.cn GetWindowText 与组合框控件有关的 CComboBox 成员函数基本上与列表框控件 静态文本控件和编辑控件类似 详细信息请读者参见有关资料 6. 滚动条控件 滚动条在 Windows 系列软件中使用相当普遍 滚动条有两种存在方 式 一是窗口滚动条 另一是滚动条控件 与滚动条控件有关的 CScrollBar 成员函数主要有 函数 GetScrollPos 得到滚动框的当前位置 函数 SetScrollPos 设置滚动框的当前位置 函数 GetScrollRange 得到指定滚动条的滚动范围 函数 SetScrollRange 设置指定滚动条的滚动范围 函数 ShowScrollBar 显示或隐藏滚动条 函数 EnableScrollBar 使能或禁能一个或两个滚动箭头 函数 SetScrollInfo 设置滚动条有关的信息 函数 GetScrollInfo 得到滚动条有关的信息 函数 GetScrollLimit 得到滚动条的最大滚动位置 6.3.2 附加的控件类附加的控件类附加的控件类附加的控件类 除了 Windows 标准控件类外 MFC 类库还提供有其他几个控件类(表 6.3) 表 6.3 附加控件类 控件类 说明 CbitmapButton 以位图而不是文本作标签的按钮控 件 Visual C++6.0 编程实例与技巧 285 www.BOOKOO.com.cn CcheckListBox 复选列表框 列表中的每一项是一 个复选框 CdragListBox 拖放列表框 允许用户移动列表框 CtoolBar 含有其他控件的工具栏 CstatusBar 含有面板或指示符的状态栏 CdialogBar 从对话模板资源创建的对话框 这里先讨论一下位图按钮 复选列表框和拖放列表框 控件栏(如工 具栏 状态栏和对话栏 )将在后面讨论 1. 位图按钮 位图按钮是以位图而下是以文本作标签的按钮控件 每个 CBitmapButton 类对象包含有四个 位图 表示按钮的各种状态 上(正 常的) 下(被选择的) 拥有焦点和无效的 仅仅第 一个位图是必须的 其他位图为可选的 CBitmapButton 的基类为 CButton 创建位图按钮时 必须设置 BSOWNERDRAW 风格指定按钮是 Owner drawn 按钮 Wi ndows 将传送 WMMEASUREITEM 和 WMDRAWITEM 消息给按钮 框架将处理这些 消息并管理按钮的外 观 要在窗口客户区中创建位图按钮 必须遵循以下步骤 (1)为位图按钮创建 1 4 个位图图像 (2)构造 CbitmapButton 对象 (3) 用成员函数 Create 创建 Windows 按钮控件并将其与 CBitmapButton 对象链接 (4)在构造位图按钮后调用成员函数 LoadBitmaps 装载位图资源 要在对话框中包含位图按钮控件 必须遵循以下步骤 Visual C++6.0 编程实例与技巧 286 www.BOOKOO.com.cn (1)为位图按钮创建 1 4 个位图图像 (2)创建带 Ower drawn 按钮的对话模板 (3) 设置按钮标题( 如 “BMPIMAGE”) 并定义按钮符号( 如 “IDCBMPIMAGE”) (4)在应用程序资源文件中 为每个位图图像构造资源符号 如果按 钮标题为“BMPIMAGE ” 则位图图像的标题分别为“BMPIMAGEU” “BMPIMAGED” “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 使列表项有效或者无效 Visual C++6.0 编程实例与技巧 287 www.BOOKOO.com.cn 函数 IsEnabled 确定列表项是否有效 函数 OnGetCheckPosition 获取列表框中复选框的位置 函数 DrawItem 在更改列表框的可见部分时被调用(可覆盖) 函数 MeasureItem 在创建列表框时被调用(可覆盖) 3. 拖放列表框 拖放列表框允许用户移动列表项 与 CDragListBox 类有关的列表框 控件不能具有 LBSSORT 或者 LBSMULTIPLESELECT 风格 要在已有的对话框中创建拖放列表框 必须首先在对话编辑器中添 加列表框控件到对话模板资源中 然后使用 ClassWizard 赋成员变量给 对应的列表框控件 成员变量必须拥有 Control 属性 类型必须是 CDragListBox 与拖放列表框有关的 CDragListBox 成员函数有 函数 ItemFromPt 返回正被拖放列表项的坐标 函数 BeginDrag 在拖放操作开始时被调用(可覆盖) 函数 CancelDrag 在取消拖放操作时被调用(可覆盖) 函数 Dragging 在拖动列表项时被调用(可覆盖) 函数 Dropped 在放下列表项后被调用(可覆盖) 6.3.3 控件与对话框控件与对话框控件与对话框控件与对话框 对话框中的控件通常在创建对话框时从对话模板中创建 可以用 ClassWizard 来管理对话框中的控件 1. 使用对话编辑器添加控件 用对话编辑器创建对话模板资源时 可以从控制工具栏中拖动控件 再将其放到对话框中 这时 将在对话模板资源中添加相应类型的控件 Visual C++6.0 编程实例与技巧 288 www.BOOKOO.com.cn 当构造对话对象并调用成员函数 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() 调用基类的 OnInitDialog CRect rect(80 100 160 200) 建立矩形区域 调用成员函数 Create 创建编辑控件 Visual C++6.0 编程实例与技巧 289 www.BOOKOO.com.cn medit.Create(WSCHILD|WSVISIBLE|WSTABSTO P|ESAUTOHSCROLL|WSBORDER rect this IDEXTRAEDIT) medit.SetFocus() 设置输入焦点 return FALSE 说明已经设置焦点 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 消息给指定的单选钮 Visual C++6.0 编程实例与技巧 290 www.BOOKOO.com.cn (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 从组合框的列表框中获取当前选择 Visual C++6.0 编程实例与技巧 291 www.BOOKOO.com.cn 的文件或目录 函数原 型为 BOOL DlgDirSelectComboBox(LPTSTR lpString int nIDComboBox) 其中 参数 lpString 为组合框的列表框中的当前选择 (8)函数 GetDlgItem返回指向对话框中指定控件的指针 函数原型为 CWnd* GetDlgItem(int nID) 其中 参数 nID 为指定的控件 ID (9)函数 GetDlgItemInt 返回指定控件中由文本表示的整数值 函数原 型为 UINT GetDlgIemInt(int nID BOOL* lpTrans NULL BOOL bSigned 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 表示返回下一个控件的指针 否则为上一个控件的指针 Visual C++6.0 编程实例与技巧 292 www.BOOKOO.com.cn (12)函数 GetNextDlgTabItem 返回指向下一个或(上一个)具有 WSTABSTOP 风格 的控件的指针 函数原型为 Cwnd* GetNextDlgTabItem(Cwnd* pWndCtl BOOL 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 设置由对话框控件显示的文本 函数原型 Visual C++6.0 编程实例与技巧 293 www.BOOKOO.com.cn 为 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 公用控件类 公用控件类 Windows 公用控件说明 CanimateCtrl 动画控件以 Windows 标准视频/音频格式 显示 AVI 剪样 CheaderCtrl 标题控件显示列标题 ChotKeyCtrl 热键控件显示用户创建的热键 CimageList 图像列表控件管理大小相同的图标或位图 集 ClistCtrl 列表控件(或列表查看控件)管理由图标和 标签组成的列表项 并 4 种不同方式(图 标 小图标 列表和详细资料)显示列表项 内容 Visual C++6.0 编程实例与技巧 294 www.BOOKOO.com.cn CprogressCtrl 进展条控件指示某一长任务完成的进展程 度 CsliderCtrl 滑动条控件(或轨道条控件)包含滑动条和 可选 tick 标记的窗口 SpinButtonCtrl 上 下控件(或旋转控件)向上和向下的箭头 当 与某个编辑控件相连 时称为旋转控件 CstatusBarCtrl 状态栏控件显示应用程序的有关信息 CtabCtrl 制表控件一次显示多页信息或控件 CtoolBarCtrl 工具栏控件包含按钮和可选控件的窗口 CtreeCtrl 树形控件(或树形查看控件)显示项的层次 列表结构 2.公用控件的使用 公用控件可以在基于对话模板的对话框 表单视图 记录视图和任 何其他窗口中使用 还可以作为其他任何窗口的子窗口使用 公用控件 作为子窗口可以传送控件通知消息给父窗口 多数公用控件传送控件 通知消息 WMNOTIFY 该消息的缺省处理函数是 CWnd::OnNot ify 消 息映射条目为 ONNOTIFY 此外 还可以在公用控件类的派生类中处 理自己 的控件通知消息 下面以动画控件为例说明公用控件的使用方法 其他公用控件的使 用请读者参见有关资料 3.动画控件的使用 动画控件以标准 Windows 视频 音频格式(AVI)显示剪样(Clip) 类 似于电影 每个 AV I 剪样是由一系列位图帧组成的 动画控件只能播 放简单的 AVI 剪样 不支持声音 如果要支 持多媒体播放和记录功能 Visual C++6.0 编程实例与技巧 295 www.BOOKOO.com.cn 必须使用 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 指定要播放的 帧 Visual C++6.0 编程实例与技巧 296 www.BOOKOO.com.cn (5)调用成员函数 Stop 停止 AVI 剪样的播放 (6)调用成员函数 Close 关闭动画控件中打开的 AVI 剪样 (7)销毁动画控件 动画控件传送两种类型的控件通知消息 当动画控件开始播放 AVI 剪样时 传送 ACNSTART 消息 当完成或停止播放时 传送 ACNSTOP 消息 6.4 6.4 6.4 6.4 控件栏控件栏控件栏控件栏 控件栏(如工具栏 状态栏和对话栏等)可以快速单步执行命令动作 它极大地改进了程序的可用性 所有控件栏的基类都是 CControlBar 基类 CControlBar 提供了在父边框窗口中定 位控件栏的功能 控件栏是 主边框窗口的一个子窗口 控件栏对象(工具栏对象 状态栏对 象和对 话栏对象)是作为主边框窗口类的数据成员被声明的 在创建主边框窗口 时创建控件 栏对象 在销毁主边框窗口时销毁控件栏对象 6.4.1 工具栏工具栏工具栏工具栏 工具栏包含一组用于激活命令的位图按钮 工具栏通常放在父边框 窗口的顶部 此外 可以 将工具栏拖动并停泊在父边框窗口的任何其 他边上 并可以使其成为浮动的 即放在浮动的 窗口中 随着用户在 工具栏按钮上移动鼠标 工具栏还可以显示工具提示 工具提示是一个 小型弹出式窗口 显示工具栏按钮用途的简短描述 工具栏按钮与菜单选项是类似的 单击工具栏按钮相当于选择菜单 选项 将产生相应的命令 应用程序通过提供消息处理函数来处理产 生的命令 如果工具栏的某个按钮没有 COMMAND 或 Visual C++6.0 编程实例与技巧 297 www.BOOKOO.com.cn 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) 用于将按钮 分组 在确定按钮位置时 分隔符是被忽略的 工具栏按钮可以以按钮 复选框或单选钮的形式出现和起作用 创 Visual C++6.0 编程实例与技巧 298 www.BOOKOO.com.cn 建复选框按钮时 应赋风格 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” 选项 则在主边框窗口类的源文件中将创建指示符 Visual C++6.0 编程实例与技巧 299 www.BOOKOO.com.cn 数组 例如 static UINT indicators = { IDSEPARATOR 状态行指示符 IDINDICAIORCAPS LDINDICAIORNUM IDINDICATORSCRL } 指示符从左到右水平排列在状态栏中 在数组中添加更多的标识符 就可以增加更多的指示符 可以根据需要改变指示符的大小 通过增加 IDSEPARATOR 元素还可以增加分隔符 最左边的指示符(位置 0)通常 用作消息区 可以在其中显示命令提示之类的文本字符串 要创建状态栏 应遵循以下步骤 (1) 构造 CStatusBar 对象 (2) 调用成员函数 Create 创建状态栏窗口并将其与 CStatusBar 对象 链接 (3)调用成员函数 SetIndicators 设置指示符的标识符为数组中相应元 素指定的值 装载每 个标识符指定的字符串资源并将字符串设为指示 符文本 此外 还可以按以下方法更新指示符文本 调用 CWnd 成员函数 SetWindowsText 更新最左边的指示符的文 本 在状态栏的命令更新处理函数中调用 CCmdUI 的成员函数 SetText 设置支本 Visual C++6.0 编程实例与技巧 300 www.BOOKOO.com.cn 调用 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” Visual C++6.0 编程实例与技巧 301 www.BOOKOO.com.cn 在“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 6.5 6.5 6.5 编程范例编程范例编程范例编程范例 作为练习 这里介绍一个简单的程序例子 MyApp 该应用程序是 SDI 单文档类型 功能是当用户鼠标单击窗口客户区时 在单击位置处画一 个圆 圆的颜色和线型粗细可以通过菜单命令 改变 其中要用到自己 定义的两个对话框类 CcolorDlg 和 CLineTypeDlg 下面让我们开始吧 Visual C++6.0 编程实例与技巧 302 www.BOOKOO.com.cn 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 所示添加菜单项“设置” 并在其下拉 菜单中添加“颜色”和“线型 ”项 Visual C++6.0 编程实例与技巧 303 www.BOOKOO.com.cn 图 6.1 New 对话框 图 6.2 MFC 应用程序向导第一步 图 6.3 添加“设置”菜单项 (2) 用鼠标单击“颜色”菜单项 按键盘 Enter 键 弹出“Property” 属性对话框 如 图 6.4 所示 在其中设置 ID 码和标题 Caption 用同样 的方法设置“线型”项 两者的设置结果为 ID Caption “颜色”项 IDSETUPCOLOR 颜色 “线型”项 IDSETUPLINETYPE 线型 Visual C++6.0 编程实例与技巧 304 www.BOOKOO.com.cn 图 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 标题 为“设置颜色” 关 闭之 Visual C++6.0 编程实例与技巧 305 www.BOOKOO.com.cn 图 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 对话框 Visual C++6.0 编程实例与技巧 306 www.BOOKOO.com.cn 图 6.7 “Adding a class”对话框 在 Class Wizard 对话框中 选择 Message Map 页 给 IDCRED IDCGREEN IDCBLUE 分别建立消息处理函数 方法为在 Object IDs 列表框选 ID 码 在 Messag es 列表框中选 BNCLICKED 按“Add Function”钮 在弹出的“Add Member Funct ion”对话框中按“OK”钮确认 接受缺省函数名 如图 6.9 覆盖虚拟对话框成员函数 OnInitDialog() 在 Object IDs 列表框选 CColorDlg 在 Messages 列表框中选 WMINITDIALOG 按 “Add Function”钮系统即完成添加任务 Visual C++6.0 编程实例与技巧 307 www.BOOKOO.com.cn 图 6.8 “New Class”对话框 Visual C++6.0 编程实例与技巧 308 www.BOOKOO.com.cn 图 6.9 Class Wizard 对话框 从 Class View 视图中 右击 CColorDlg 项 在弹出菜单中选“Add Member Variable... ” 在弹出的对话框中输入欲添加变量的类型 int 及变 量名 mnColor 如图 6.10 图 6.10 添加成员变量 Visual C++6.0 编程实例与技巧 309 www.BOOKOO.com.cn 给上述的四个消息处理函数添加代码如下(程序中黑色部分) 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 类 仿照上述方法创建线型对 Visual C++6.0 编程实例与技巧 310 www.BOOKOO.com.cn 话框及 CLineTypeDlg 类 创建的线型对话框如图 6.11 其上编辑控件 的 ID 码为 IDCLINETYPE 对话框窗体 的 ID 码为 IDDLINETYPEDLG 标题为“设置线型” 给控件 IDCLINETYPE 增加一个连接变量 在把对 话框定义成 CLineTypeDlg 类后 Class Wizard 已自动打开 选择 Member Variables 页 在 Control IDs 列表框中选择 IDCLINETYPE,按 Add Variable 钮 在弹出的对话框中输入欲添加变量的类型 int 及变量名 mnLineType 如图 6.12 图 6.11 “设置线型”对话框 Visual C++6.0 编程实例与技巧 311 www.BOOKOO.com.cn 图 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 Visual C++6.0 编程实例与技巧 312 www.BOOKOO.com.cn 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() Visual C++6.0 编程实例与技巧 313 www.BOOKOO.com.cn { 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() { Visual C++6.0 编程实例与技巧 314 www.BOOKOO.com.cn // 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 Visual C++6.0 编程实例与技巧 315 www.BOOKOO.com.cn 图 6.13 应用程序运行界面 在窗口客户区单击 则系统自动在鼠标单击处绘制一个圆 如图 6.14 选择“设置”菜单项下的“颜色”菜单项 弹出如图 6.15 所示对话框 在其中更改颜色后 系统自动以新的颜色绘制圆 如图 6.16 选择“设置”菜单项下的“线型”菜单项 弹出如图 6.17 所示对话框 在其中更改线型后 系统自动以新的线型绘制圆 Visual C++6.0 编程实例与技巧 316 www.BOOKOO.com.cn 图 6.14 在窗口中绘制出一个圆 图 6.15 “设置颜色”对话框 Visual C++6.0 编程实例与技巧 317 www.BOOKOO.com.cn 图 6.16 用新设置的颜色所绘制 的圆 图 6.17 “设置线型”对话框 Visual C++6.0 编程实例与技巧 318 www.BOOKOO.com.cn 第七章第七章第七章第七章 如何创建一个应用程序如何创建一个应用程序如何创建一个应用程序如何创建一个应用程序 在前面一章 举了一个创建对话框应用程序的简单例子 通过这个 例子 你应该对如何用 Vi sual C++6.0 编写和设计应用程序有一个简单 的认识 知道了创建应用程序的基本 方法和步骤 在这一章中 将详 细地介绍每一步以及相关的内容 7.1 7.1 7.1 7.1 工程文件工程文件工程文件工程文件(Project)(Project)(Project)(Project) 在 Visual C++6.0 中创建一个应用程序 是通过使用 Microsoft Wizards 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++中 你的应用程序框架被当作包含所使用的所有文件的一 个工程 在工程工作区窗口中可以看到这个工程的具体内容 工程工作 区窗口中有三个制表框 Visual C++6.0 编程实例与技巧 319 www.BOOKOO.com.cn (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 Visual C++6.0 编程实例与技巧 320 www.BOOKOO.com.cn (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 新建工程文件窗口 Visual C++6.0 编程实例与技巧 321 www.BOOKOO.com.cn 如图 7.1 所示 在窗口 Project 页的右上角的 Project name 编辑框中 输入你给应用程序的工程文件取的名称 必须给工程文件取个名字 才 能继续下一步 在下面的 Location 编辑框中输入工程文件放置的目录 Visual C++ 为你选放了一个缺省的路 径 你可以更改这个路径 在这个子目录下 将存放 AppWizard 为你生成的所有文件 以及你 以后要增加的资源文 件 生成的可执行文件等 所以一般要给这个子目录预留比较大的空 间 Platform 编辑框中是该工程文件的操作系统环境 一般根据计算机 的硬件配置决定 通常选 用 Visual C++给出的缺省环境就行了 确定了工程文件的类型 名称 路径和操作系统环境后 单击 OK 按钮进入 AppWizard 产生应用程序的框架 7.2 AppWizard7.2 AppWizard7.2 AppWizard7.2 AppWizard AppWizard 能为你产生应用程序的一个基本框架 这是一个通用的 基本框架 在此基础上 可以根据应用程序的具体要求来添加一些特征 和资源 AppWizard 通过人机对话的形式 一步一步地指导你产生一个什么 样的应用程序框架 首先是 AppWizard 的第一步 如图 7.2 所示 Visual C++6.0 编程实例与技巧 322 www.BOOKOO.com.cn 图 7.2 AppWizard 的第一步 What type of application would you like to create 这是询问你要产生 一个什么类型 的应用程序 下面有三种选择 选择不同的项时 左边 会显示出不同类型的窗口模型 (1) Single document 选择这一项是允许你产生一个单文本界面(SDI) 结构的应用程序 单文本界面 就像 Windows 中的写字板 不允许用户 同时使用多个文本 只能使用一个文本 (2) Multiple documents 选择这一项是允许你产生一个多文本界面 (MDI)结构的应用程 序 多文本界面 就像 Windows 的文件管理器 允许用户在主窗口中一次打开多个文本 缺 省选项为该项(图 7.2 显示 的就是选择该项时的窗口) Visual C++6.0 编程实例与技巧 323 www.BOOKOO.com.cn (3) Dialog based 选择这一项是允许你产生一个基于对话框结构的应 用程序 基于对话框结构的应用程序 就像 AppWizard 在每一步所显示 的对话框 如图 7.3 所示 主要用于人机 对话 让用户传送一些消息给 计算机 我们这一章的各种控件的制作 都是在对话框窗口中进行的 所以 选择 Dialog based 这一项 选择了这一项后 将显示如图 7.3 所示的窗 口 图 7.3 选择 Dialog based 项后的窗口 What languange would you like your resources in?这是询问在你的资 源中选择哪种语言 按右边的箭头 弹出一个下拉列表框 列出了系统 中的各种可使用的语言 如果你想要选 择的不是英语 那么系统中必 Visual C++6.0 编程实例与技巧 324 www.BOOKOO.com.cn 须有关于这种语言的动态链接库(. DLL) 文件 文件的名称 是 APPWZxxx.DLL,其中 xxx 这三个字母表示的是该语言的简称 例如 选 择汉语的话 则.DLL 文件名为 APPWZCHS.DLL 选择英语的话 则 为 APPWZENU.DLL 确定了应用程序的类型和使用资源的语言后 单击底部的 NEXT 按 钮 进行 AppWizard 的第二 步 进入第二步后 显示一个如图 7.4 所示的窗口 图 7.4 AppWizard 生成对话框的第二步 在这一步的窗口中 询问了好几个问题 What features would you like include 这是询问你希望你的对话框窗 口拥有哪些特征 下面有供选择的三项 Visual C++6.0 编程实例与技巧 325 www.BOOKOO.com.cn (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 所示的窗口 Visual C++6.0 编程实例与技巧 326 www.BOOKOO.com.cn 图 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)连接到你的应用程序中 当运行应用程序时可以调用这个动态 链接库 如果你的程序中包含多个可执行 文件 那么动态链接库可以 Visual C++6.0 编程实例与技巧 327 www.BOOKOO.com.cn 节省很多硬盘和内存空间 缺省情况时选择这一项 (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 应用程序 则会生成 更多的派生类 在后 面我们将会讲到这两种类型的应用程序 Visual C++6.0 编程实例与技巧 328 www.BOOKOO.com.cn 图 7.6 AppWizard 生成对话框的最后一步 在 Class name 编辑框中你可以自定义派生类名 但一般使用 AppWizard 给出的缺省名 其他 几项是不可编辑的 但可以看到给出 的缺省状况 单击底部的 Finish 按钮 弹出一个 New Project Infomation 窗口 如 图 7.7 所示 显示出前 Visual C++6.0 编程实例与技巧 329 www.BOOKOO.com.cn 图 7.7 AppWizard 的信息对话框 面四步中你为新的工程文件所定义的所有特性 现在你可以最后确 认一下 你要建立的应用程序框架是否具有这些特性 单击 OK 按钮 AppWizard 就开始按照这些特征为你生成应用程 序框架 生成以后就 不能再更改了 单击 Cancel 按钮退回到前一步 并且可以依次退回到任何一步 更 改某一项特性 确认这些特性后 单击 OK 按钮就完成了 AppWizard 的框架编程工 作 接下去该是可视化编辑界面并编写代码与之相连的过程 Visual C++6.0 编程实例与技巧 330 www.BOOKOO.com.cn 7.3 7.3 7.3 7.3 单文档界面单文档界面单文档界面单文档界面(SDI)(SDI)(SDI)(SDI)编程编程编程编程 在前面的章节中 我们制作的应用程序是基于对话框窗口的应用程 序 目的是学习控件的制作和用法 在这一章中我们将要介绍如何制作 一个单文档界面(SDI)的应用程序 通 过一个简单的单文档应用程序的 例子 主要理解可视化和文档的概念以及它们之间的关系 7.3.1 Person 应用程序应用程序应用程序应用程序 在编写 Perm 应用程序之前 我们先看看该应用程序要完成一些什么 功能 运行应用程序后的主窗口如图 7.8 所示 图 7.8 Person 应用程序主窗口 在应用程序的主窗口中有四个编辑框 Name Age Sex 和 Job 我们还可以看到在窗口的左上角有两个菜单项 File 和 Help 这是 两个弹出式菜单 图 7.9 和图 7.10 显示了这两个弹出式菜单 Visual C++6.0 编程实例与技巧 331 www.BOOKOO.com.cn 图 7.9 Person 应用程序的 File 弹出式菜单 图 7.10 Person 应用程序的 He1p 弹出式菜单 (1)在这些编辑框中输入一些内容 如图 7.11 所示 图 7.11 在 Person 的主窗口中输入内容 (2)在应用程序的 File 菜单中选择 Save as 项 保存输入的内容 显示一个 Save as 对话框 如图 7.12 所示 Visual C++6.0 编程实例与技巧 332 www.BOOKOO.com.cn 图 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 应用程序的功能 下面我们开始制作该应用程序 Visual C++6.0 编程实例与技巧 333 www.BOOKOO.com.cn 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 中 选择“英语 美国 ” 项 Visual C++6.0 编程实例与技巧 334 www.BOOKOO.com.cn (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 的第一步 Visual C++6.0 编程实例与技巧 335 www.BOOKOO.com.cn 图 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) 该窗口是生 成工程文件的第四步 Visual C++6.0 编程实例与技巧 336 www.BOOKOO.com.cn 图 7.15 Person 应用程序 AppWizard 的第三步 Visual C++6.0 编程实例与技巧 337 www.BOOKOO.com.cn 图 7.16 person 应用程序 AppWizard 的第四步 (14)在 What features would you like to include 问题中 选择 3D controls(三维 图形控件)选项 (15)在 How n1any fi1es would you like on your recent filelist 中 使用缺省值 4 表示在 File 菜单中将显示 4 个最后打开过 的文件 (16)单击 Next 按钮 进入第五步 Visua1 C++显示一个 MFC AppWizard 一 Step 5 of 6 的窗口(如图 7.17) 该窗口是 生成工程文件的第五步 Visual C++6.0 编程实例与技巧 338 www.BOOKOO.com.cn 图 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 给出的所创建的 类名和文件名 在这一步中我们将修改应 用程序视类的基类 Visual C++6.0 编程实例与技巧 339 www.BOOKOO.com.cn 图 7.18 Person 应用程序 AppWizard 的最后一步 (21)单击列表框中的 CPersonView 项 在下面的列表框显示这个派生类的名称 基类 源代码文件名称等 (22)单击 base class 组合框的箭头按钮 在弹出的列表框中选择 CFormView 项 因为只有 从 CFormView 基类中派生出来的视类对象中 才能放置控件 (23)单击 Finish 按钮 显示一个 New Project Information 的窗口 该 窗口显示了前面几 步所选择的全部设置 包括应用程序类型 新创建 的类以及应用程序的全部特征 还显示了 该应用程序将生成在哪个路 径之中 通过这些 你可以最后检查一下 应用程序的设置是否 完全 正确 如果发现不对的地方 可以按 Cancel 键取消前面的操作 用 Visual C++6.0 编程实例与技巧 340 www.BOOKOO.com.cn AppWizard 重新制作 检查后若完全正确 则执行后面的步骤 (24)单击 OK 按钮 AppWizard 完成应用程序的自动生成工作 在指定的目录下生成应 用程序框架所必须的全部 文件 7.3.3 文档和视文档和视文档和视文档和视 什么是视 对于初次接触 Visual C++6.0 的读者来说 “视”是一个难以理解的概 念 因为在 汉语中这 个词是一个动词 是“看”的意思 而在这里是作 为一个名词来用 实际上 “视”是英语 中的单词“View”翻译过来的 在 MFC 类库中 有一个很重要的基类 CView 这就是通常所说 的“视 类” 也许理解“视”的英语意思“View”更加容易一些 它可以理解为“显 示” 也就是说 将 数据显示出来 可以显示在屏幕上 也可以从打印 机中输出 所以视用于连接文档和输出设备 对于用户来说 它实际上是一个普通的窗口 可以被关闭 移动和 改变它的的尺寸 就像 Wi ndows 的其他窗口一样 而对于程序员来说 它是从类库中的类 CView 派生出来的一个对象 视对象的行为由 CView 类及其派生类的成员函数和数据成员所决定 文档的概念很好理解 它就是存储数据的载体 可以在文档中对数 据进行存盘和调出数据 那么为什么要使用“文档 视”结构呢 使用这种结构的一个明显的 好处就是 一个文档的数据可以有多个视的显示 Visual C++6.0 编程实例与技巧 341 www.BOOKOO.com.cn 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 在对话框窗口中生成文本 TODO Place form contro1s on this dial og,删除这段文本后 我们才能在对话框中制作 Person 应用程序的界面 (3)在控件工具窗口中选择相应控件 将它们放在对话框中的适当的 位置 并通过鼠标拖 动控件边框的小黑方块 调整控件的大小 用前面几章中介绍的方法设置所有对象的属性 各个对象的属性设 置如表 7.1 所列 Visual C++6.0 编程实例与技巧 342 www.BOOKOO.com.cn 表 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 所示 Visual C++6.0 编程实例与技巧 343 www.BOOKOO.com.cn 图 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 应用程序中是不需要的 应删除 Visual C++6.0 编程实例与技巧 344 www.BOOKOO.com.cn (2)单击菜单条中的 Edit 项 屏幕上将显示打开的 Edit 弹出式菜单 (3)按键盘上的 Delect 键 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 按钮 Visual C++显示一个 Add Member Variable 窗口 设置成员变量名为 mName 类别为 Value 变量类型为 CString (6)按 OK 按钮 Visual C++把 mName 变量加到了 CPersonView 类中 Add M ember Variable 窗口消失 (7)按 ClassWizard 中的 OK 按钮 这样编辑框 IDNAMEEDIT 就连接 了一个 变量 mName 根据表 7.2 中的定义 用同样的方法连接变量到其他的编辑框中 表 7.2 IDDPERSONFORM 对话框的变量表 ID 变量名 类别变 Visual C++6.0 编程实例与技巧 345 www.BOOKOO.com.cn 量类型 IDCNAMEEDIT mName Value CString IDCAGEEDIT mAge Value CString IDCSEXEDIT mSex Value CString IDCJOBEDIT mJob Value 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 Visual C++6.0 编程实例与技巧 346 www.BOOKOO.com.cn 中的编 辑框连接的变量 在对话框 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 Visual C++6.0 编程实例与技巧 347 www.BOOKOO.com.cn #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); Visual C++6.0 编程实例与技巧 348 www.BOOKOO.com.cn }}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. Visual C++6.0 编程实例与技巧 349 www.BOOKOO.com.cn #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()) Visual C++6.0 编程实例与技巧 350 www.BOOKOO.com.cn return FALSE; TODO: add reinitialization code here (SDI documents will reuse this document) 编写代码处 mAge= ; mName= ; mJob= ; mSex= ; return TRUE; } 初始化文档类数据成员的代码很简单 就是给每一个编辑框变量赋 一个空值 当应用程序第一次运行时 编辑框中什么也不显示 当然 如果你希望显示一些数据的话 可以给这些数据成员赋其他的值 3. 初始化视类的数据成员 我们知道对于类中的数据成员 首先应该声明 然后才能初始化 那么为什么这里不用先声明视类的数据成员就初始化呢 其实我们在前面的可视化设计时就已经声明了视类的数据成员 在 可视化设计时 有一步是给编辑框连接变量 实际上是在视类中把这些 编辑框声明为它的数据成员 所以就不需要编 写代码来声明视类的数 据成员了 视的作用是显示文档中的数据 我们已经初始化了与视类的数据成 员相对应的文档类的数据 成员 所以初始化视类的数据成员也就是把 Visual C++6.0 编程实例与技巧 351 www.BOOKOO.com.cn 文档类的初始化值传递到视类中 显示在屏幕上 初始化视类的数据成员的代码要编写在视类的成员函数 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() { Visual C++6.0 编程实例与技巧 352 www.BOOKOO.com.cn 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; 注意 在这几条语句中 虽然等号两边的变量名是一样的 但实际 上并不是同一个变量 左边的变量表示的是视类的数据成员 而右边的 变量因为有一个指向文档的指针 所以表示的 是文档类的数据成员 最后一行语句把视类的数据成员显示在屏幕上 Visual C++6.0 编程实例与技巧 353 www.BOOKOO.com.cn 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 中增加了一个成员函 Visual C++6.0 编程实例与技巧 354 www.BOOKOO.com.cn 数 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) 用于将编辑框的当前值来代替编辑框在视类中相对应的变量 要注 意这里的变量不是文档类中相对应的变量 所以下面我们还要用这个视 Visual C++6.0 编程实例与技巧 355 www.BOOKOO.com.cn 类的变量值来修改文档类中的变量值 下面两条语句 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() Visual C++6.0 编程实例与技巧 356 www.BOOKOO.com.cn 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. Visual C++6.0 编程实例与技巧 357 www.BOOKOO.com.cn 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(); } Visual C++6.0 编程实例与技巧 358 www.BOOKOO.com.cn 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; } } Serialize()函数的参数 ar 表示程序将要读和写的文件 6. if 语句的判断条件(ar.IsStoring()) 当往文件中写数据时为 TRUE 也就是说 当从 File 菜单中选择 Save 或 Save as 时 满足条件 而选择 File 菜单中 Open 时 则执行 else 语句后面的代码 所以 if 语句后面的代码 ar<>mAge ar>>mName Visual C++6.0 编程实例与技巧 360 www.BOOKOO.com.cn ar>>mJob ar>>mSex 是把文件中存的数据调出来 流操作符“>>”表示将文件 ar 中的数 据装载到文档类的数据成员中 注意 从文件中读数据和写数据的顺序应该是一致的 即写数据的 语句 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 项 Visual C++6.0 编程实例与技巧 361 www.BOOKOO.com.cn 显示一个 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 所示了 Visual C++6.0 编程实例与技巧 362 www.BOOKOO.com.cn 图 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 应用程序 Visual C++6.0 编程实例与技巧 363 www.BOOKOO.com.cn 第八章第八章第八章第八章 动态链接库动态链接库动态链接库动态链接库(DLL) 本章将介绍什么是动态链接库 如何用 Visual C++ 6.0 创建一个动态 链接库 以 及如何使用动态链接库等 8.1 8.1 8.1 8.1 动态链接库的概念动态链接库的概念动态链接库的概念动态链接库的概念 动态链接库是一个包含函数的库文件 程序员可以很容易地分配新 的函数和资源 动态链接 库和其他的 C++库不一样 它们是在运行时 和应用程序连接 而不是在编译 链接 过程中被应用 动态链接库的英语拼写是 Dynamic Link Library 简称 DLL 与之 相对应的是静态链接库 (Static Link Library) 它们之间的区别在于 静 态链接库是在应用程序的编译过程中 与应用程序相连接的 而动态链 接库是在应用程序的执行过程中与应用程序相连接的 如果与 应用程序连接的是静态链接库 每一个应用程序在编译过程中都必须拷 贝一份库的 代码 这样造成了资源的浪费 而且使应用程序本身的代 码开销很大 而动态链接库在编译 过程中并不连接应用程序 而是当 应用程序运行时连接 可以和其他的应用程序共享库中的函数和资源 减少了因重复拷贝而造成的应用程序的冗长以及计算机资源的占用 当用户创建使用动态链接库的应用程序时 必须将动态链接库文件 和应用程序的可执行文件 一同分发 为了使应用程序正常地使用动态链接库 这些.DLL 文件必须存放在 下列任何一个子 目录 之中 (1)Windows 的 SYSTEM 子目录 (2)应用程序所在的子目录 (3)配置文件中定义的自动搜索的子目录 Visual C++6.0 编程实例与技巧 364 www.BOOKOO.com.cn Visual C++6.0 的强大功能使我们编写动态链接库的工作变得非常简 单 下面我们 编写一个简单的.DLL 文件 学习怎样制作动态链接库文 件 8.2 8.2 8.2 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 所示 Visual C++6.0 编程实例与技巧 365 www.BOOKOO.com.cn 图 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) Visual C++6.0 编程实例与技巧 366 www.BOOKOO.com.cn 第一项是建立一个静态链接库 第二项是建立一个 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 所示 Visual C++6.0 编程实例与技巧 367 www.BOOKOO.com.cn 图 8.3 OneDLL 应用程序 AppWizard 的 New Project Information 窗口 该窗口显示了在前面所选择的全部设置 包括 AppW1zard 生成的动 态链接库程序的源代码文 件名和头文件名 还显示了该应用程序将生 成在哪个路径之中 通过这些 你可以最后检查 一下 应用程序的设 置是否完全正确 如果发现不对的地方 可以按 Cancel 键取消前面的操 作 用 AppWizard 重新制作 检查后若完全正确 则执行后面的步骤 (10)单击“OK”按钮 AppWizard 完成动态链接库程序的自动生成工 作 在指定的目录下 生成了工程文件所必须的全部文件 Visual C++6.0 编程实例与技巧 368 www.BOOKOO.com.cn 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); Visual C++6.0 编程实例与技巧 369 www.BOOKOO.com.cn 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. Visual C++6.0 编程实例与技巧 370 www.BOOKOO.com.cn #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() Visual C++6.0 编程实例与技巧 371 www.BOOKOO.com.cn { 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) Visual C++6.0 编程实例与技巧 372 www.BOOKOO.com.cn 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. ,NULL, Visual C++6.0 编程实例与技巧 373 www.BOOKOO.com.cn 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 文件所有的代码 下一步 Visual C++6.0 编程实例与技巧 374 www.BOOKOO.com.cn 是创建该动态链接库文件 (2)选择 Project 菜单中的 Build OneDLL.DLL 项 Visual C++将编译并建立 OneDLL.DLL 文件 这样 我们就已经创建了一个动态链接库文件 OneDLL.DLL 8.3 8.3 8.3 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 所示 Visual C++6.0 编程实例与技巧 375 www.BOOKOO.com.cn 图 8.4 TestDLL 应用程序主窗口 图 8.5 TestDLL 应用程序的 DLL 菜单 (1)选择 Load.dll 项 载入 OneDLL 动态链接库 (2)再选择 Test.dll 项 则弹出一个消息对话框窗口 如图 8.6 所示 显示一句话 “ This is the example of testing DLL ” Visual C++6.0 编程实例与技巧 376 www.BOOKOO.com.cn 图 8.6 选择 Test.dll 后弹出的消息对话框 (3)如果你在选择 Test.dll 之前 没有先选择 Load.dll 那么将显示另 一个消息对话框 如图 8.7 所示 图 8.7 不先载入动态链接库而选择 Test.dll 项 这个消息对话框提示要先载入动态链接库 才能使用该动态链接库 Visual C++6.0 编程实例与技巧 377 www.BOOKOO.com.cn (4)选择 File 菜单的 Exit 选项 退出 TestDLL 应用程序 知道了 TestDLL 应用程序的功能 下面我们开始制作该应用程序 8.3.2 创建应用程序的工程文件创建应用程序的工程文件创建应用程序的工程文件创建应用程序的工程文件 首先是生成 TestDLL 应用程序的工程文件 按下面的步骤生成 TestDLL 应用程序的工程文 件 (1)在 Visual C++中选择 File 菜单中的 New 选项 出现一个对话框窗 口 该窗口中 有四个制表页 File Projects Workspaces 和 Other Documents 如图 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 中 选择“英语 美国 ” 项 Visual C++6.0 编程实例与技巧 378 www.BOOKOO.com.cn (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 问题中 只选择 3D controls(三维图形控件)选项 其余各项都不需要 (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 生成的应 Visual C++6.0 编程实例与技巧 379 www.BOOKOO.com.cn 用程序源代码文件(.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 创建的菜单条中的三项是 Visual C++6.0 编程实例与技巧 380 www.BOOKOO.com.cn 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; Visual C++6.0 编程实例与技巧 381 www.BOOKOO.com.cn #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 应用程序中 就应该把加载动态链接库 的 Visual C++6.0 编程实例与技巧 382 www.BOOKOO.com.cn 代码放在 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 编写代码处 Visual C++6.0 编程实例与技巧 383 www.BOOKOO.com.cn 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 ); Visual C++6.0 编程实例与技巧 384 www.BOOKOO.com.cn 该语句使用 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 项 Visual C++6.0 编程实例与技巧 385 www.BOOKOO.com.cn (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) { Visual C++6.0 编程实例与技巧 386 www.BOOKOO.com.cn 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 动态链接库 那是 因为用户没有把该动态链接 库拷贝到下面三个目录的任何一个目录 下 Visual C++6.0 编程实例与技巧 387 www.BOOKOO.com.cn Windows 的 SYSTEM 子目录 应用程序所在的子目录 配置文件中定义的自动搜索的子目录 这样就无法将动态链接库加载到应用程序中 Visual C++6.0 编程实例与技巧 388 www.BOOKOO.com.cn 第九章第九章第九章第九章 多文档界面多文档界面多文档界面多文档界面(MDI)编程编程编程编程 本章我们将介绍如何制作一个多文档界面(MDI)的应用程序 通过创 建一个简单的多 文档应用程序的例子 理解视的概念以及视和文档之 间的相互关系 9.1 9.1 9.1 9.1 什么是多文档界面什么是多文档界面什么是多文档界面什么是多文档界面(MDI)(MDI)(MDI)(MDI) 多文档界面就是指在一个主窗口中可以同时对多个文档进行浏览 编辑和维护 在 Windows 风格的应用程序中 有许多多文档界面的应 用程序 例如 Microsoft Word 就是最经典的多 文档应用程序 用过 Word 的读者都知道 在 Word 中可以同时打开多个文本文件 仅仅通过窗口的切换就能分 别对它们进行编辑等 但是在同一时刻 只能对当前窗口的那一个文本进行编辑 每一个窗 口对应一个文本 但一个文本可以在不同的窗口中打开 在前面的章节中 我们介绍了单文档界面(SDI) SDI 和 MDI 的最大 区别就是 SDI 一次只能 对一个文档进行操作 在主窗口中不能同时 打开多个子窗口 而多文档界面就可以做到这一 点 所以多文档界面 比单文档界面的效率更高 更灵活 同时也更复杂 下面我们要编写的 MDI 应用程序是支持一个文档的多个视 也就是 说 用户可以在多个窗口中打开同一个文档 9.2 Books9.2 Books9.2 Books9.2 Books 应用程序应用程序应用程序应用程序 在编写 Books 应用程序之前 我们先看看该应用程序要完成一些什 么功能 运行应用程序后的主窗口如图 9.1 所示 Visual C++6.0 编程实例与技巧 389 www.BOOKOO.com.cn 图 9.1 Books 应用程序主窗口 从图 9.1 中我们可以看到 在应用程序的主窗口中还有一个窗口 这个窗口称为子窗口 主窗口的标题栏显示为 Books Booksl 子窗口的标题为 Booksl 在主窗口的上端有一个菜单条 莱单条中有三个主菜单项 File Window 和 Help 在 Booksl 子窗口中有三个编辑框 可以在这三个编辑框中输入数据 三个编辑框分别是 Book Writer 和 Content 用于输入书名 作者和内 容 (1)在这三个编辑框中任意输入一些数据 (2)选择 File 菜单中的 Save 出现一个 Save As 对话窗口 缺省文件名的后缀为.bks 保存数据到 Visual C++6.0 编程实例与技巧 390 www.BOOKOO.com.cn 文件 Booksl.bds 中 显示的界面如图 9.2 所示 图 9.2 保存文档到 Books1.bks 后的 Books 应用程序 下面我们检测一下菜单中各项的作用 (1)选择 File 中的 New 选项 在 Books 主窗口中又打开一个命名为 Books2 窗口 而原来的子窗口 仍然还在 如图 9.3 所 示 Visual C++6.0 编程实例与技巧 391 www.BOOKOO.com.cn 图 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 所示 Visual C++6.0 编程实例与技巧 392 www.BOOKOO.com.cn 图 9.4 Save As 询问对话框 (5)单击“是”按钮 这时 Books2 文件中的内容已经被 Bookl 文件代替了 (6)选择 Window 菜单中的 New Window 选项 在主窗口中又打开一个与 Books1.bks 子窗口一样的子窗口 但两个 子窗口的标题不一样 原子窗口的标题变为 Books1.bks 1 新建的子 窗口的标题设为 Books2.bks 2 如图 9.5 所示 两个窗口中编辑框的内容也是相同的 图 9.5 打开一个与原子窗口相同的子窗口 Visual C++6.0 编程实例与技巧 393 www.BOOKOO.com.cn 在任意一个子窗口中输入或修改数据 另一个窗口中也发生相应的 改变 实际上 这就是一个文档中的多个视 这两个于窗口对应的文档是一个文档 更新其中一个视时 对应的 文档发生改变 与这个文档相关联的其他视也跟着更新 (7)选择 Window 菜单中的 Tile 选项 应用程序以平铺的格式排放 Books1 和 Books2 子窗口 如图 9.6 所 示 图 9.6 平铺格式放置子窗口 (8)选择 Window 中的 Cascade 选项 应用程序以级联的格式将两个子窗口排放在主窗口中 如图 9.7 所 示 Visual C++6.0 编程实例与技巧 394 www.BOOKOO.com.cn (9)单击两个子窗口右上端的最小化按钮 两个窗口缩小成两个图标 显示在主窗口的左下端 (10)用鼠标拖动最小化图标 放在主窗口的任意位置 如图 9.8 所示 图 9.7 级联格式放置子窗口 Visual C++6.0 编程实例与技巧 395 www.BOOKOO.com.cn 图 9.8 最小化子窗口在主窗口中的随机分布 (11)选择 Window 菜单的 Arrange Icons 选项 两个图标按顺序整齐地排列在主窗口的左下端 如图 9.9 所示 Visual C++6.0 编程实例与技巧 396 www.BOOKOO.com.cn 图 9.9 最小化子窗口的顺序排列 (12)单击 Books1 子窗口的最大化按钮 (13)选择 Window 菜单的 Split 选项 发现在子窗口中出现一根横线和一根竖线 而且此时移动鼠标 两 条线的交叉点也随着移动 (14)将鼠标移到适当的位置 单击一下鼠标左键 此时子窗口被这一根横线和一根竖线分成四个窗口 如图 9.10 所示 窗口中的内容是一样的 更新其中任何一个窗口 其他的窗口也跟着改 变 Visual C++6.0 编程实例与技巧 397 www.BOOKOO.com.cn 图 9.10 被分割后的子窗口 (15)选择 Help 菜单中 About 选项 弹出一个 About 对话框窗口 现在我们已经完成了 Books 应用程序的各个菜单项的实验 其实这 个应用程序还有一个 特性我们没有看到 还原子窗口的分割状态 使之为一个窗口 仔细观察这个子窗口 发现在水平滚动条的最左 端和垂直滚动条的最右端 各有一个分隔条 (16)用鼠标拖动着两个分隔条 能实现 Window 菜单中 Split 选项的功能 如图 9.10 所示 (17)选择 File 菜单的 Exit 选项 退出 Books 应用程序 现在 我们已经了解了 Books 应用程序的功能 下面将根据这些功 能开始我们的编程 Visual C++6.0 编程实例与技巧 398 www.BOOKOO.com.cn 9.3 9.3 9.3 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 所示 该窗口是生成工程文件的第二步 Visual C++6.0 编程实例与技巧 399 www.BOOKOO.com.cn (10)在 What database support would you like to include 中选择 None 表示不需要数据库的支持 (11)单击 Next 按钮 进入第三步 图 9.11 Books 应用程序 AppWizard 的第一步 Visual C++6.0 编程实例与技巧 400 www.BOOKOO.com.cn 图 9.12 Books 应用程序 AppWizard 的第二步 Visual C++显示一个 MFC AppWizard Step 3 of 6 的窗口 如图 9.13 所示 该窗口是生成工程文件的第三步 Visual C++6.0 编程实例与技巧 401 www.BOOKOO.com.cn 图 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 按钮 进入第五步 Visual C++6.0 编程实例与技巧 402 www.BOOKOO.com.cn 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 的第四步 Visual C++6.0 编程实例与技巧 403 www.BOOKOO.com.cn 图 9.15 Books 应用程序 AppWizard 的第五步 (21)单击 Next 按钮 进入最后一步 Visual C++显示出生成工程文件的最后一步 如图 9.16 所示 出现 一个 MFC AppWi zard Step 6 of 6 的窗口 可以看到 AppWizard 给出的 所创建的类名和文件名 在这一步中 我们将修改应用程序视类的基类 Visual C++6.0 编程实例与技巧 404 www.BOOKOO.com.cn 图 9.16 Books 应用程序 AppWizard 的最后一步 (22)单击列表框中的 CBooksView 项 在下面的列表框显示这个派生类的名称 基类 源代码文件名称等 (23)单击 Base class 组合框的箭头按钮 在弹出的列表框中选择 CFormView 项 因为只有从 CFormView 基类中派生出来的视类对象中 才能放置控件 (24)单击 Finish 按钮 显示一个 New Project Information 的窗口 该 窗口显示了前面几 步所选择的全部设置 包括应用程序类型 新创建 的类以及应用程序的全部特征 还显示了 该应用程序将生成在哪个路 径之中 通过这些 你可以最后检查一下 应用程序的设置是否 完全 正确 如果发现不对的地方 可以按 Cancel 键取消前面的操作 用 Visual C++6.0 编程实例与技巧 405 www.BOOKOO.com.cn AppWizard 重新制作 检查后若完全正确 则执行后面的步骤 (25)单击 OK 按钮 AppWizard 完成应用程序的自动生成工作 在指 定的目录下生成应用程 序框架所必须的全部文件 9.4 9.4 9.4 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 应用程序的窗口 界面 Visual C++6.0 编程实例与技巧 406 www.BOOKOO.com.cn (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 制表页中 Visual C++6.0 编程实例与技巧 407 www.BOOKOO.com.cn 加上垂直滚动条和 水平滚动条 应如图 9.18 所示 按表 9.1 中所列的内容 在对话框中制作控件 完成后的对话框应 如图 9.19 所示 现在我们已经完成了对话框窗口的设计 但是现在的对话框不能执 行任何操作 因为还没有给它添加代码 图 9.18 Content 编辑框的 Styles 制表页 Visual C++6.0 编程实例与技巧 408 www.BOOKOO.com.cn 图 9.19 设计完后的 Books 应用程序对话框 9.4.2 菜单的可视化实现菜单的可视化实现菜单的可视化实现菜单的可视化实现 Books 应用程序的框架窗口中有两个菜单 一个菜单是在应用程序 主窗口中至少有一个子窗口时显示 另一个菜单是在应用程序主窗口中 没有子窗口时显示 如何才能在 Books.rc 中看到这两个菜单呢 必须打开工作区窗口 选择 View 菜单中的 Worksp ace 项 显示出 Books 应用程序的工作区窗 口 (1)选择工作区窗口中的 Resource View 制表页 (2)单击工作区窗口中 Menu 项左边的“十”号 在下面显示出该资源 Visual C++6.0 编程实例与技巧 409 www.BOOKOO.com.cn 的 ID 号 这时就可以看到菜单资源了 如图 9.20 所示 图 9.20 Books.rc 中的菜单资源 在菜单资源中有两个菜单 IDRBOOKSTYPE 和 IDRMAINFRAME IDR MAINFRAME 是应用程序主窗口中没有子窗口时的菜单 IDRBOOKSTYPE 是应用程序主窗口中至少有一个子窗口时的菜 单 (3)双击 IDR BOOKSTYPE 则在右边显示一个可编辑的菜单条 这个菜单条中有四项 File Edit Window 和 Help 其中 Edit 项在 Books 应用程序的菜单条中是没有的 其他几项与 Books 应用程序中是 Visual C++6.0 编程实例与技巧 410 www.BOOKOO.com.cn 完全相同的 所以对菜单的设计仅仅是删除掉 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 选项 应用程序按级联的格式排列子窗口 Visual C++6.0 编程实例与技巧 411 www.BOOKOO.com.cn (7)最小化各个子窗口后 将子窗口拖到主窗口的不同位置 (8)在 Window 菜单中选择 Arrange Icons 选项 应用程序将最小化的子窗口排列到主窗口的底部 这时我们发现 还没有编写一句代码 AppWizard 生成的应用程序 就已经具有了一定的功能 下面我们要编写代码来完成其他一些功能 (9)单击窗口右上角的关闭按钮 终止 Books 应用程序 9.5 9.5 9.5 9.5 添加代码添加代码添加代码添加代码 9.5.1 声明视类的数据成员声明视类的数据成员声明视类的数据成员声明视类的数据成员 首先用 ClassWizard 给三个编辑框控件连接变量 注意要使 ClassWizard 中的类为 CBooksView 类 因为这三个变量是属于应用程序 视类中的数据成员 按照表 9.2 中所列的属性来声明变量 表 9.2 IDDBOOKSFORM 中的变量表 ID 变量名 类别 变量类型 IDCBOOKEDIT mBook Value CString IDCWRITEREDIT mWriter Value CString IDCCONTENTEDIT mContent Value CString 按照以下步骤声明这几个变量 (1)在 ClassWizard 窗口中选择 Member Variables 制表页 Visual C++6.0 编程实例与技巧 412 www.BOOKOO.com.cn (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 Visual C++6.0 编程实例与技巧 413 www.BOOKOO.com.cn 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(); Visual C++6.0 编程实例与技巧 414 www.BOOKOO.com.cn 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. Visual C++6.0 编程实例与技巧 415 www.BOOKOO.com.cn #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= ; Visual C++6.0 编程实例与技巧 416 www.BOOKOO.com.cn 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() 处 等待你定义函数的内容 Visual C++6.0 编程实例与技巧 417 www.BOOKOO.com.cn (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) Visual C++6.0 编程实例与技巧 418 www.BOOKOO.com.cn 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; } } Serialize()函数的参数 ar 表示程序将要读和写的文件 if 语句的判断条件 (ar.IsStoring()) 当往文件中写数据时为真 也就是说 当从 File 菜单中选择 Save 或 Save as 时 满足条件 而选择 File 菜单中 Open 时 则执行 else 语句后面的代码 9.5.5 实现一个文档的多个视实现一个文档的多个视实现一个文档的多个视实现一个文档的多个视 此时 如果我们编译执行 Books 应用程序 会发现当我们打开一个 文档的多个视(子窗口) 时 更新其中一个视的话 其他的视并不会随之 而更新 这是因为被更新的视的内容没有传到文档中 文档也没有把数据传 到其他的视 所以要实现一个文档的多个视 必须分两步 (1)把视的内容传到文档中 (2)把文档的数据传到其他的视 那么 怎么知道视中的内容被更新了呢 在视类中的编辑框有一个 事件 ENCHANGE 发送对象被改变的消息 我们必须在这个事件的消 息处理函数中编写代码 在文档类中有一个很重要的成员函数 UpdateAllViews() 它用于把用 Visual C++6.0 编程实例与技巧 420 www.BOOKOO.com.cn 户修改的文档相对应的视进行更新 实际上 它是调用视类的成员函数 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 按钮 Visual C++6.0 编程实例与技巧 421 www.BOOKOO.com.cn 这样 在 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); Visual C++6.0 编程实例与技巧 422 www.BOOKOO.com.cn } 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() Visual C++6.0 编程实例与技巧 423 www.BOOKOO.com.cn 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); Visual C++6.0 编程实例与技巧 424 www.BOOKOO.com.cn 调用文档类的成员函数 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 Visual C++6.0 编程实例与技巧 425 www.BOOKOO.com.cn 编写代码处 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 文件 Visual C++6.0 编程实例与技巧 426 www.BOOKOO.com.cn (4)在 Window 菜单中选择 New Window 选项 打开该文件的另一个视(子窗口) (5)在其中一个子窗口中输入数据 发现另一个子窗口中内容也自动更新 说明代码的编写是正确的 9.6 9.6 9.6 9.6 增强增强增强增强 BooksBooksBooksBooks 应用程序应用程序应用程序应用程序 9.6.1 增加分割条增加分割条增加分割条增加分割条 在本章开始时 我介绍了该应用程序中每个子窗口的水平和垂直滚 动条中有一个分割 条 图 9.21 加入分割器类 要实现这个功能 必须给应用程序增加一个分割器类 Visual C++6.0 编程实例与技巧 427 www.BOOKOO.com.cn (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), Visual C++6.0 编程实例与技巧 428 www.BOOKOO.com.cn 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 菜单中新增一项 Visual C++6.0 编程实例与技巧 429 www.BOOKOO.com.cn 新增一项的属性中 Caption 设为&Split ID 设为 IDWINDOWSPLIT 如图 9.22 所示 图 9.22 Split 菜单项的属性窗口 (3)选择 File 菜单中 Save 项 保存所做的工作 现在 在 Window 菜单中就增加了新的一项 Split 可以分割子窗口 了 9.7 9.7 9.7 9.7 执行执行执行执行 BooksBooksBooksBooks 应用程序应用程序应用程序应用程序 (1)从 Build 主菜单中选择 Build Books.exe 项 Visual C++编译和连接 Books 应用程序 (2)从 Build 主菜单中选择 Execute Books.exe 项 Visual C++执行 Books 应用程序 (3)按照本章开头的步骤测试该应用程序的功能 如果你是一步一步按前面的步骤来做的话 会与预期的结果一样 (4)选择 File 菜单的 Exit 项 退出 Books 应用程序 Visual C++6.0 编程实例与技巧 430 www.BOOKOO.com.cn 第十章第十章第十章第十章 Visual C++6.0 多媒体程序设计多媒体程序设计多媒体程序设计多媒体程序设计 Windows 9X WindowsNT 下传统的多媒体制作工具是 MCI(Media Control Interface,媒体控 制接口) 但随着对多媒体性能要求的提高 MCI 采用 GDI(Graphic Divice Interface,图形 设备接口)封装计算机媒体硬件 的做法给其自身带来了不好的影响 虽然为普通用户带来了 编程上的 方便 但却严重影响计算机的速度 特别是在游戏中 这种影响更为突 出 DirectX OpenGL ActiveMovie 等是继 MCI 之后的新一代多媒体程 序设计及游戏制作 API 随着基于 Windows 的游戏的日益盛行 用 DirectX OpenGL 编程进行多媒体制作与游戏开发已成 为编程人员的 必备技能 但无论是 MCI 还是 DirectX OpenGL,都是非常庞大的多媒 体 API 要 想在一章之内详尽介绍是不可能的 本章只就 MCI DirectX OpenGL 视频图形方面分别做一 引导性的介绍 帮助读者入门 详细内 容请参阅有关专门书籍 10.1 10.1 10.1 10.1 用用用用 MCIWndMCIWndMCIWndMCIWnd 开发视频图像开发视频图像开发视频图像开发视频图像 还有什么方式比以视频探索的方式来学习我们的 VC++多媒体开发 更合适的呢 读过本章内容后 读者便会明白 MCI 命令接口是如何用于播放视频 文件的 我们所需要发送的只是一个简单的命令字符串 然后 MCl 命令 接口打开所需求的文件 识别它的类型 并恰 当地播放 对一个视频 文件(.AVI)来说 这一过程打开一个小窗口 从头至尾播放音频 这很 容易 但它看起来很了不起——我们还想要得到更多的东西 Visual C++6.0 编程实例与技巧 431 www.BOOKOO.com.cn 足够了 也许用户会暂停播放以也许他们喜欢大一些或小一些的窗 口 快一些或慢一些播放或改变音量会怎么样呢 “太难了 程序设计 不出来” 用户可能会想“不值得费力” 那 么读者曾经有过又高兴又惊 讶的感觉吗 把这些功能增加到用户的 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 播放器 它 是一个复合元素 是 Visual C++6.0 编程实例与技巧 432 www.BOOKOO.com.cn 当用户想要提供多媒体播放时加在你的 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 显示了 Visual C++6.0 编程实例与技巧 433 www.BOOKOO.com.cn 图 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 指定 Visual C++6.0 编程实例与技巧 434 www.BOOKOO.com.cn 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...类型选项以外 MCIWnd 还有几个类型选项 也是由 dwStyle 参数设置 给一个缺省类型传输一个零值 它包括所有控件 用户可以传输定义在 VFW.H 中的各种常量 来禁止 窗口的各个方面和 控制程序显示在窗口标题中的信息 例如 MCIWnd NOPLAYBAR 指 示 MC IWnd 不带控制条 意思是说窗口必须由程序控制而不是由用户 直接控制 类型常量的完整型 列表已给出 如果需要的话 用“|”连接 Visual C++6.0 编程实例与技巧 435 www.BOOKOO.com.cn 两个或更多的类型 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...类型 我们指出文件名 和显示在标题中的当前位置 当用户运行本章的 演示程序 VIDEO. MAK 时 将会看到它的一切 注意到有一些其他的 MCIWND“类型”常量可用于指出当一个条件 发生时 如当媒体或媒体位置改变时 MCIWnd 用来通知父窗口 我们 不再讨论这些高一级的问题 2. 直接控制一个 MCIWnd 如果建立了一个带有缺省类型的 MCIWnd 用户对于它的操作就有 了很多控制 单击 play pa u se 按钮来播放或暂停当前文件 当文件播 Visual C++6.0 编程实例与技巧 436 www.BOOKOO.com.cn 放时滑尺就会移动 指示相对于文件大小的在文件 中的当前位置 在 播放或暂停时拉滑尺来改变当前位置 菜单可以通过在播放条上单击菜单按钮来显示 也可以通过在窗口 的任何位置右键单击来显示 如图 10.2 所示 可得到的菜单命令依赖 于载入的文件的类型 下面是有关这些命令的简要描述 View 选择整个 一半或双倍大小的显示窗口 Vo l u me 控制音量 Speed 控制播放速度 Open 打开一个 MCI 文件 图 10.2 可通过单击菜单按钮或右单击图形窗口来显示的 MCIWnd 菜单 Close 关闭当前文件 Copy 将当前图形 帧拷贝到裁剪板上 Configure 设置视频显示参数 Visual C++6.0 编程实例与技巧 437 www.BOOKOO.com.cn 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) 开始寻找 Visual C++6.0 编程实例与技巧 438 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 439 www.BOOKOO.com.cn LRESULTMCIWndCanEject(hwnd) 如果设备支持 eject 则为 TRUE LONGMCIWndGetEnd(hwnd) 结束的位置 (对影像来说即最后一帧的序号) LONGMCIWndGetLength(hwnd) 长度(对影像来 讲即为帧数) LONGMCIWndGetPosition(hwnd) 当前位置(对影 像来讲即帧的序号) LONGMCIwndGetVolume(hwnd) 当前音量设置 用户可以想象这些收集信息宏的很多用途 改变音量就是一个好例 子 由于没有 IncreaseVo lumeByTenPercent()函数 如果用户想要有精确 的音量控制 那么在用 MCIWndSet Volume()设置一个新的音量级之前 必须用 MCIWndGetVloume()来获得当前音量设置 (3)多功能宏 几个多功能的 MCIWnd 宏经常需要 它们在表 10.3 中给出 表 10.3 多功能操作的宏 宏 功能 目的 MCIwndClose(hwnd) 离开打开的 MCIwnd 时关闭 当前 MCI 设备 文件 MCIWndDestroy(hwnd) 破坏 MCIWnd MCIWndSendString(hwnd,sz) 给设备发送命令字符串 sz 正如我们前面提到的 还有无数其他的 MCIWnd 宏 但是大多数情 况下用户很少用它们 如果用户还需要其他信息请参阅有关 Video for Windows 的书籍 Visual C++6.0 编程实例与技巧 440 www.BOOKOO.com.cn 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 的标题条 并将它 拉到一个新的位置 在父窗口的任何地方 右单击就破坏了这个视频窗口 Visual C++6.0 编程实例与技巧 441 www.BOOKOO.com.cn 图 10.3 VIDEO 项目 程序清单 10.1 OnLButtonDown()函数 void CVIEDOView::OnLButtonDown(UINT nFlags, CPoint point) { TODO: Add your message handler code here and or call default CString filename( d: MSDN98 98VS 1033 SAMPLES VID98 content mmedia fox.avi ); If the MCIWnd doen not exist,create it. if(mvideoWnd==NULL) mvideoWnd=MCIWndCreate(this->GetSafeHwnd(), AfxGetInstanceHandle(), WSCHILD|WSCAPTION|WSVISIBLE|MCIWNDFSHOWPOS|M CIWNDFSHOWNAME, Visual C++6.0 编程实例与技巧 442 www.BOOKOO.com.cn 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 对话框 Visual C++6.0 编程实例与技巧 443 www.BOOKOO.com.cn 用户可能想知道我们为什么在这个演示项目中使用一个 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 内部已 有的文件选择功能 Visual C++6.0 编程实例与技巧 444 www.BOOKOO.com.cn 如果窗口已经存在 则程序用 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++的无限威力 那么读者的程 序的灵活 性与创建性只会被您的 Visual C++6.0 编程实例与技巧 445 www.BOOKOO.com.cn 想象力所限制 而不会被预包装的多媒体制作系统的不足所限制 下 一 节进入 OpenGL 3D 图形 API 部分 10.2 OpenGL10.2 OpenGL10.2 OpenGL10.2 OpenGL 3D3D3D3D 图形设计图形设计图形设计图形设计 APIAPIAPIAPI 10.2.1 OpenGL 概述概述概述概述 OpenGL 是个硬件和图形的软件接口 实际上就是一个三维图形和 模型库 由于它在三维真实 感图形制作中具有优秀的性能 所以 诸 如 Microsoft SGI IBM DEC SUN HP 等在计算 机市场中占主导 地位的大公司 都将它做为自己的图形标准 从而使之成为新一代的三 维图 形工业标准 OpenGL 不仅仅是一个图形库 更是一个 API(ApplicationProgrammingInterface) 它 本身是一个与硬件无关的编 程接口 可以在不同的硬件平台上得到实现 也正是因为如此 OpenGL 中没有包含处理窗口和用户输入的命令 在 OpenGL 中不提供三维造型 的高级命令 虽 然 OpenGL 也是通过基本的几何图元——点 线 多 边形来建立物体模型的 但更确切地说它 应该被称为新一代的三维图 形开发标准 现在有很多优秀的三维图形软件 如 3Dmax 等可 以方便 地建立模型 但难以对其进行控制 把这些模型转化为用 C 语言编写的 OpenGL 程序 就可以随心所欲地控制这些模型 制作 CAD 制作三 维动画 实现虚拟仿真 制作商业广告 进行影视采辑 这些都使我 们制作出的三维真实感图形更方便 更真实 OpenGL 可以制作各种各样的三维图形 方便地实现三维图形的交 互操作 但这些图形都是由 一些基本操作实现的 OpenGL 提供的操作 Visual C++6.0 编程实例与技巧 446 www.BOOKOO.com.cn 包括 1. 绘制物体(Drawing Object): 任何三维图形 三维场景都是由一些基本的图元——点 线 多边 形组成的 实际上 一个 图形系统 其性能是由它对这些基本图元的 绘制操作决定的 OpenGL 提供了丰富的基本图元 绘制命令 在第二章 中将详细地介绍 OpenGL 的基本图元绘制函数 2. 变换(Transformer) 任何复杂的图形都是物体经过一系列变换来实现的 OpenGL 提供 了一系列基本的变换 如投 影变换定义了一个视景体 几何变换可以 使物体在三维场景中平移 旋转和放缩 视点变换 可以从不同的角度 去观察物体 如果用特殊的显示硬件还可实现虚拟现实显示 裁剪变换 可 以定义除了裁剪体以外的裁剪平面 视口变换决定怎样把制作的图 形映射屏幕上 另外 Op enGL 提供了一系列矩阵操作函数 利用这些 函数 用户可以根据具体的需要 定义具体应用 中的变换 这样有利 于具体问题具体分析 消除了系统对应用的局限性 读者可以参考有关 的专门书籍详细地了解这些变换的应用以及矩阵操作函数 3. 着色(Rendering) OpenGL 提供了两种颜色模式 RGBA 模式和颜色索引表模式和两种 具体的物体着色方式 如果显 示硬件允许的话 OpenGL 提供 16(位) 种颜色 基本上是自然界所有的颜色 读者可以参考有 关的专门书籍 详细地了解 OpenGL 提供的颜色模式和具体的着色模式 4. 光照(lighting) 光是真实感图形的必要组成部分 OpenGL 中认为光是由四部分组 成的 这四部分是环境光 漫反射光 辐射光和镜面光 在定义光源 Visual C++6.0 编程实例与技巧 447 www.BOOKOO.com.cn 的时候要分别定义这四部分 用户可以在应用中定 义光源的属性 改 变光源的位置 在 OpenGL 中 光源相当于一个几何体 用户可以像控 制几 何体一样地控制光源的位置 控制光照物体表面的属性以及可以 定义聚光 有关书籍将具体 地介绍光照 5. 反走样(Antialiasing) 走样是计算机绘制图形过程中常见的问题之一 在 OpenGL 中 提 供了点 线和多边形的反走 样技术 有关书籍将会做具体地介绍 6. 混合(Blending) 在一些逼真的高质量三维图形制作过程中 经常会遇到透明物体和 半透明物体的处理 在 Op enGL 中 这种处理是通过混合技术来实现的 缺省状态下 所有的物体都是不透明的 物体 的不透明度是由表示物 体颜色的第四个分量 Alpha 来决定 OpenGL 提供了控制比例的混合的 函数 用户可以根据自己的需要 选择在实际应用中最合适的混合函数 这部分内容有关书籍将会做具体地介绍 7. 雾(Fog) 在制作真实感图形的过程中 经常要有一些烟雾等特殊效果的制作 要求 在 OpenGL 中用雾 来实现这种自然现象 可以使所制作的三维 图形更真实 这部分内容有关书籍将会做具体地 介绍 8. 位图和图像(Bitmap and Image) OpenGL 提供了两种特殊的数据类型——位图和图像 OpenGL 提供 了一系列的函数来实现位图 操作 例如可以和显示列表结合 方便地 实现英文字符的制作和显示 图像是图形制作中的 一个重要方面 OpenGL 提供了一系列的函数来实现图像操作 如图像绘制 拷贝 放 缩以及 图像数据的转换 映射和存储 这部分内容有关书籍也将会做 Visual C++6.0 编程实例与技巧 448 www.BOOKOO.com.cn 具体地介绍 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 程序 Visual C++6.0 编程实例与技巧 449 www.BOOKOO.com.cn 在微机版本中 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 也提供了一些相关的函数 库 但在这里不做详细的说 明 Visual C++6.0 编程实例与技巧 450 www.BOOKOO.com.cn 10.2.3 用用用用 Visual C++ 6.0 MFC 编写编写编写编写 OpenGL 程序程序程序程序 这一节主要是通过用 MFC 编写一个 OpenGL 程序 来说明在 Windows 和 Visual C++环 境下编写 OpenGL 多媒体应用程序是多么简单快捷 程序运行结果 是在一个 SGI 应用程序的客户区窗口显示一些辐射线条 当然 使用 MFC 编写程序要启动 Visual C++ 6.0 (1) 使用 Appwizard 建立一个叫 myopengl 的应用程序: 1)打开菜单下面的编辑框中填 写 myopengl 在左边的 项目类型列表中选择 MFC AppWizard exe ,然后单击 OK 按钮 见图 10.6 4)选择 Single Document> 单击 Next 见图 10.7 5)使用缺省选项 单击 Next 6)选择mhDC,hglrc); drawwithopengl(); wglMakeCurrent(pDC->mhDC,NULL); SwapBuffers(pDC->mhDC); (6) 在文件 myopengView.h 中加入以下说明 #include #include #include #include 在 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); Visual C++6.0 编程实例与技巧 459 www.BOOKOO.com.cn 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); glLoadIdentity(); 不难发现 这些代码与普通 VC++编程代码基本类同,只要掌握了基 本的多媒体常识知识,即便是改用 OpenGL 编程也并不需要特别地学习 (8) 在 project 中加入静态库 opengl32.lib glu32.lib 和 glaux.lib 打开菜单击中 settings 出现项目设置对话框 选择 Link 选 项卡,在 Object Libr ary Modules 编辑框中输入 opengl32.lib glu32.lib 和 glaux.lib 单击,见图 10.10 Visual C++6.0 编程实例与技巧 460 www.BOOKOO.com.cn 图 10.10 项目设置对话框 (9) 运行这个程序 见图 10.11 Visual C++6.0 编程实例与技巧 461 www.BOOKOO.com.cn 图 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 Visual C++6.0 编程实例与技巧 462 www.BOOKOO.com.cn #if !defined(AFXMYOPENGLVIEWHC461966D8D2211D2B2A0EE05 DF303FE6INCLUDED ) #define AFXMYOPENGLVIEWHC461966D8D2 211D2B2A0EE05DF303FE6INCLUDED #if MSCVER > 1000 #pragma once #endif MSCVER > 1000 #include #include #include #include class CMyOpenglView : public CView { protected: create from serialization only CMyOpenglView(); DECLAREDYNCREATE(CMyOpenglView) Attributes public: CMyOpenglDoc* GetDocument(); Operations public: Visual C++6.0 编程实例与技巧 463 www.BOOKOO.com.cn 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(); Visual C++6.0 编程实例与技巧 464 www.BOOKOO.com.cn 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. #endif !defined(AFXMYOPENGLVIEWHC461966 D 8D2211D2B2A0EE05DF303FE6IN CLUDED) myopenglView.cpp MyOpenglView.cpp : implementation of the CMyOpenglView class #include stdafx.h #include MyOpengl.h #include MyOpenglDoc.h Visual C++6.0 编程实例与技巧 465 www.BOOKOO.com.cn #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() Visual C++6.0 编程实例与技巧 466 www.BOOKOO.com.cn 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); Visual C++6.0 编程实例与技巧 467 www.BOOKOO.com.cn drawwithopengl(); wglMakeCurrent(pDC->mhDC,NULL); SwapBuffers(pDC->mhDC); } CMyOpenglView printing BOOL CMyOpenglView::OnPreparePrinting(CPrintInfo* pInfo) { default preparation return DoPreparePrinting(pInfo); } void CMyOpenglView::OnBeginPrinting(CDC* *pDC* , CPrintInfo* *pInfo* ) { TODO: add extra initialization before printing } void CMyOpenglView::OnEndPrinting(CDC* *pDC* , CPrintInfo* *pInfo* ) { TODO: add cleanup after printing } Visual C++6.0 编程实例与技巧 468 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 469 www.BOOKOO.com.cn 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; Visual C++6.0 编程实例与技巧 470 www.BOOKOO.com.cn } 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); Visual C++6.0 编程实例与技巧 471 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 472 www.BOOKOO.com.cn #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 Visual C++6.0 编程实例与技巧 473 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 474 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 475 www.BOOKOO.com.cn 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() { Visual C++6.0 编程实例与技巧 476 www.BOOKOO.com.cn } 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( Failed to create toolbar n ); return -1; fail to create } if (!mwndStatusBar.Create(this) || !mwndStatusBar.SetIndicators(indicators, sizeof(indicators) sizeof(UINT))) { TRACE0( Failed to create status bar n ); return -1; 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); Visual C++6.0 编程实例与技巧 477 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 478 www.BOOKOO.com.cn 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: Visual C++6.0 编程实例与技巧 479 www.BOOKOO.com.cn 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}} Visual C++6.0 编程实例与技巧 480 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 481 www.BOOKOO.com.cn 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 } Visual C++6.0 编程实例与技巧 482 www.BOOKOO.com.cn 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 Enable3dControls(); Call this when using MFC in a shared DL L #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. Visual C++6.0 编程实例与技巧 483 www.BOOKOO.com.cn 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; } Visual C++6.0 编程实例与技巧 484 www.BOOKOO.com.cn 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() }; Visual C++6.0 编程实例与技巧 485 www.BOOKOO.com.cn 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(); } Visual C++6.0 编程实例与技巧 486 www.BOOKOO.com.cn 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: Visual C++6.0 编程实例与技巧 487 www.BOOKOO.com.cn 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() Visual C++6.0 编程实例与技巧 488 www.BOOKOO.com.cn }; {{AFXINSERTLOCATION}} Microsoft Visual C++ will insert additional declarations immediate ly before the previous line. #endif !defined(AFXMYOPENGLDOCHC461966B 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 Visual C++6.0 编程实例与技巧 489 www.BOOKOO.com.cn 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; Visual C++6.0 编程实例与技巧 490 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 491 www.BOOKOO.com.cn { CDocument::AssertValid(); } void CMyOpenglDoc::Dump(CDumpContext& dc) const { CDocument::Dump(dc); } #endif DEBUG CMyOpenglDoc commands 10.3 DirectX10.3 DirectX10.3 DirectX10.3 DirectX 新一代媒体大师新一代媒体大师新一代媒体大师新一代媒体大师 Microsoft DirectX 是微软公司提供的一个格调良好的应用程序开发 接口(API),能提供设计 高速实时应用程序所需要的资源 帮助您创建新 一代的电脑游戏和多媒体应用程序,目前已达到 DirectX 6.0 版本 包括 五个组成部分: DirectDraw: 这是 Game SDK 的图像部分,它是目前为止最大及最显 著的部分 提供了对硬件加速的透明通道,以及屏幕外视频内存里位图的 直接通道,并支持硬件的页面转换特性 DirectSound: 这提供了播音及实时混合能力,如果有合适的驱动器, 可往声卡上实现,否则在软件上实现 DirectPlay: 这提供了一套建立多人游戏的功能,可以用 Modem 来操 作,或可以在网络上来操作 Visual C++6.0 编程实例与技巧 492 www.BOOKOO.com.cn 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 当前所使 用的分辨率和比特 深度 窗口程序有一个主表面 但只在进行真实页面翻转时才显现 而且 Visual C++6.0 编程实例与技巧 493 www.BOOKOO.com.cn 主表面并不代表窗口的客户区域(该区域在窗口边界内) 主表面还代表 整个桌面 这就是说 你的程序必须追踪 窗口的位置和大小 以便在 窗口内正确显示可见的输出 换言之 利用窗口化的应用程序 可以在 整个桌面上进行绘图 如果不允许页面翻转 那么图像就必须从离屏缓冲区中 blt 到主表面 上 这就增加了图像撕裂的可能性 因为 blt 比页面翻转速度慢 为了 避免图像撕裂 blt 操作可以与监视器的刷新 率保持同步 如果与窗口客户区域同样大小的离屏缓冲区被创建到显示 RAM 中 窗口应用程序就可以很好地运行 这样 窗口的内容可利用离屏表面合 成 然后离屏表面可以通过硬件加速很快地到 主表面上 由于显示存储器的缺乏而不得不将离屏缓冲区创建到系统 RAM 中 时 会严重影响性能 不幸的是 这种情况常常发生 尤其是在只有 2MB 显示卡的时候 这是因为人们总希望为自己 Win dows 的桌面设置高分 辨率的显示模式 例如 采用 1024x768xl6 显示模式的主表面自己就要 占用 2MB 的 RAM 在一个 2MB 显示卡上 几乎没有显示 RAM 留给 离屏表面 窗口应用程序的另一个问题是剪裁 一个性能良好的应用程序必须 有一个连接到主表面的剪裁器对象 这是有损性能的 原因在于为了检 查剪裁器的剪裁清单内容 blt 操作只能在一 个小的矩形部分内进行 而且 不能使用优化的 BltFast()函数 Bltting 必须用较慢的( 而且更笨 重的)Blt()函数 最后要讲的是 窗口应用程序不允许全调色板控制 由于 Windows 保留了 20 个调色板项 所以在 256 种颜色中只有 236 种颜色可被设定 被 Windows 保留的颜色只用系统调色板的前 10 个 项和后 10 个项 因 Visual C++6.0 编程实例与技巧 494 www.BOOKOO.com.cn 此在给图像上色时 只能使用中间的 236 个调色板项 10.3.1.2 全屏应用程序全屏应用程序全屏应用程序全屏应用程序 包含对显示设备进行专有存取的 DirectDraw 应用程序就是全屏应用 程序 这种应用程序可以任意选取显示卡所支持的显示模式 并享有全 调色板控制 另外 全屏应用程序还可进行页 面翻转 因此同窗口应 用程序相比 全屏应用程序速度更快 灵活性更好 典型的全屏应用程序首先检查所支持的显示模式 并激活其中一个 然后创建具有一个或更多后备缓冲区的可翻转主表面 剩下的显示 RAM 用于创建离屏表面 当显示 RAM 耗尽时 就启 用系统 RAM 屏 幕被在后备缓冲区中合成的第一个场景更新 然后进行页面翻转 即使 主表 面占用了所有的可用的显示 RAM 全屏应用程序还可输出执行其 窗口化的副本 这是因为全 屏应用程序可进行真实页面翻转 由于全屏应用程序不一定非得使用 Windows 的当前显示模式 所以 显示 RAM 的可用与否不存在多少问题 如果只检测到 2MB 的显示 RAM 就可使用低分辨率显示模式来保留内存 如果检测到 4MB 的显 示 RAM 应用程序就可使用要求的显示模式并仍能保持良好性能 全调色板控制也是个有利因素 这样可以使用所有 256 个调色板项 而无需根据 Windows 保留的 20 种颜色重新分配位图 10.3.1.3 混合应用程序混合应用程序混合应用程序混合应用程序 混合应用程序既可以在全屏方式下运行也可在窗口方式下运行 混 合应用程序内部非常复杂 但却可以提供良好的性能 用户可在窗口方 式下运行 如果太慢 则可切换到全屏方式下运行 Visual C++6.0 编程实例与技巧 495 www.BOOKOO.com.cn 编写混合应用程序最好的方法是编写一个定制库 该库包括那些与 应用程序使用全屏方式还是窗口方式无关的函数 本小节的目的是教授 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 运行期部分 这 Visual C++6.0 编程实例与技巧 496 www.BOOKOO.com.cn 些 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 按 钮 就会出现一份成份清单 V4.02 版本的 成份是 DirectX l 的成份 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 包括示例和帮助文件 但这些都是可选资源 必需的文件是头文件 Visual C++6.0 编程实例与技巧 497 www.BOOKOO.com.cn (.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 显示了增加头文件路径 设置后出现的 对话框 Visual C++6.0 编程实例与技巧 498 www.BOOKOO.com.cn 图 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 Visual C++6.0 编程实例与技巧 499 www.BOOKOO.com.cn ...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 秒钟 然后保存先前的显示模式 随后出现一个对话框 询问新 设置是否正常工作 Visual C++6.0 编程实例与技巧 500 www.BOOKOO.com.cn 商业化的 Windows 9X 也采用了这种型式的安全机构 毕竟无法保 证显视器是被正确设置过的 如果将错误的显视器型号提供给 Windows 9X 那么检测到的显示模式很有可能不被支持 10.3.3 视频回放视频回放视频回放视频回放——DirectDraw 开发例程开发例程开发例程开发例程 虽然视频回放在几年前己用于游戏 但它仅用于简单的显示以及各 层之间的过渡序列 然而 也有些游戏内容采用了视频 例如在 Sierra OnLine sPhantasmagoria 中 就使用了把 带有真实演员的图片加到计 算机产生的背景环境中的视频技术 结果是产生了一个令人惊叹 又令 人信服的组合 从这种意义上讲 视频回放将使得某种程度的真实成为 可能 这种真实 由真实的演员是不可能办到的 在本节 我们将学习如何在 DirectDraw 表面上读和写 AVI 文件 本 章中的材料虽然仅是针对于简单的视频回放 但它提供了一个用 DirectDraw 视频回放的良好开端 另外 本节的内容 以 AviPlay 示例 一个基于 DirectDraw 的 AVI 播放器的形式提供 10.3.3.1 开始启动开始启动开始启动开始启动 在开始讨论细节之前 先让我们复习几个重要的与视频回放有关的 术语和概念 如果你曾经与视频回放打过交道 那么这一部分也可作为 回顾之用 一个视频文件是一组位图序列 其中的每一幅在一个给定时间内描 绘一幅场景 所有的位图将具有统一的尺寸 并以一定的速率播放 通 常这些位图将伴随着声音数据 如叙述 对话 或音乐 一个视频序列的图形和可选声音部分通常称为流(Stream) 这个术语 Visual C++6.0 编程实例与技巧 501 www.BOOKOO.com.cn 暗示着组成视频序列 (位图 也即视频数据)的各个部分是相关联的 并 以一定的顺序排列 我们将在下面的讨 论中经常用到“流”这个术语 10.3.3.2 AVI 文件文件文件文件 Microsoft 公司为了满足视频存储和回放的需要 开发了 AV I 文件格 式 一个 AVI 文件 除了存储一序列的位图外 还包含一个或多个与视 频序列在一起的声音轨迹 我们将在本节中广 泛涉及到 AVI 文件 视频为何能如此特殊 以至于必须有它独特的格式 为什么不用一 系列的位图和一个声音文 件来代表视频 这要牵涉到许多问题 但其 中最重要的一点是压缩 即便对于一个普通的很 短的视频图像 用非 压缩的文件格式(例如 BMP 文件格式)来存储一个视频序列中的某一帧 也需要大量的存储空间 例如 一个 1 分钟的视频图像 它的分辨率是 320 240 以每秒 3 0 帧的速率回放 就需要多于 100MB 的存储空间 很显然 视频图像用于远程播放时 进行压缩是必要的 剩下的问 题就是使用哪种压缩方法 现在有许多各种各样的压缩算法 各有优 点 也各有缺点 单个算法是不可能满足每个人的需要的 因此 AVI 文件提供了广泛的压缩支持 你也可以想像 这将使得 AVI 文件必须在一个开发得很成功的 API 帮助下才可读 幸运的是 Windows 提供了这样的 API: Windows API 视频 我们很快就会看到它的内容 10.3.3.3 视频数据视频数据视频数据视频数据 视频文件格式 例如 AVI 文件 实际上使用了两种压缩形式 除了 对视频序列中的每一帧进 行压缩的各种算法外 一些图像不再被整幅 传送 而只传送变化的部分 Visual C++6.0 编程实例与技巧 502 www.BOOKOO.com.cn 这可能是由于一个典型的视频序列是由相似的图像组成 例如 如 果一个视频显示了一个人走过一间屋子 那么 场景中代表人走动的那 一部分将逐帧变化 但其他的部分很可能不改 变 因此这个视频将由 第 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”开头 另一些与压缩有关的 Visual C++6.0 编程实例与技巧 503 www.BOOKOO.com.cn 函数 以“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() Visual C++6.0 编程实例与技巧 504 www.BOOKOO.com.cn 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() Visual C++6.0 编程实例与技巧 505 www.BOOKOO.com.cn 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() Visual C++6.0 编程实例与技巧 506 www.BOOKOO.com.cn 当然 视频回放并不需要所有的函数 但很可能要用到其中的一些 因此你必须学会修改或扩展本节函数的功能 下面让我们看一些特殊的 函数 提示 在项目中加入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; Visual C++6.0 编程实例与技巧 507 www.BOOKOO.com.cn 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; Visual C++6.0 编程实例与技巧 508 www.BOOKOO.com.cn 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()函数提取 该 函数应用我们所要检索的指定的帧的流句柄和 索引 并将原始帧数据 Visual C++6.0 编程实例与技巧 509 www.BOOKOO.com.cn 存入给定的缓冲区 该原始帧数据是经过压缩的 在播放之前 首先 要 进行解压缩 使用 ICDecompress()函数进行解压缩 2 个缓冲区传给该函数 一个 用于存放压缩数据 另一个用于存放解压后的数据 该函数还需要一个 解压程序句柄 以便处理帧数据时用 解压程序句柄由 ICDecompressOpen()函数检索 当给出视频数据的 描述后 ICDecompress Open()函数就查找一个所需要的解压算法的解压 程序 一旦使用完一个流 AVIStreamRelease()函数将被调用关闭该流 当 该函数被调用之后 代表流的句柄将无效(除非这个流被重新打开) 最 后 当你的程序终止时 AVI F il e Ex i t ()函数将被调用释放 VFW 模块所 使用的所有资源 10.3.3.6 AviPlay——DirectDraw 视频播放编程示例视频播放编程示例视频播放编程示例视频播放编程示例 AviPlay 示例是在一个 DirectDraw 表面上使用 VFW 打开并播放 AVI 文件 该示例允许进行 AVI 文件选择 并为用于播放的示例指定显示模 式 该程序运行时显示 AVI 文件选择对话框如图 10. 13 所示 在AVI 文件选择对话框中选择正在使用的分辨率工作模式(亦可调整 以适应之) 然后按按钮 弹出文件选择对话框 选择一个(.avi) 文件 按,返回 AVI 文件选择对话 框 按钮 则程序接管 整个屏幕的控制权 在全屏幕方式下播放所选定的 AVI 文件 图 10.14 为正在播放关于地球的(.avi)文件 Visual C++6.0 编程实例与技巧 510 www.BOOKOO.com.cn 图 10.13 AviPlay 示例 AVI 选择对话框 图 10.14 AviPlay 全屏幕播放 AVI 文件 如果你还未曾使用过 AviPlay 示例 也未曾察觉 我们必须指出 该 示例是有一定限制的 一方面 它不能用以播放可能也在 AVI 文件表中 列出的音频流——它只提取并播放文件中的图形部分 另一方面 它也不能对所播放的视频实行速率控制 它只能对各帧 尽可能快地提取并播放 如果该 AVI 文件播放器是高性能的 那么它可 对 AVI 文件中的时间信息进行利用 并使用这些数据 AviPlay 示例可支持 16 位和 24 位显示模式 这是一种很有用的特性 不考虑所播放的视频的位深度 该示例可支持 8 位显示模式 Visual C++6.0 编程实例与技巧 511 www.BOOKOO.com.cn 现在我们开始动手编程 (1)启动 Visual C++ 6.0,选择菜单命令 在弹出的 New 对话框中输入项目名 AviPlay, 在项目类型列表中选择 DirectDraw AppWizard,如图 10.15 所示 按 OK 图 10.15 创建 AviPlay 项目 (2)在 DirectDraw AppWizard 对话框中是一些介绍信息 如图 10.16, 按钮 Visual C++6.0 编程实例与技巧 512 www.BOOKOO.com.cn 图 10.16 DirectDraw AppWizard 向导框 (3)在 Application Type 框中选择应用程序类型 全屏幕型与视窗型 选,如图 10.17,按钮 Visual C++6.0 编程实例与技巧 513 www.BOOKOO.com.cn 图 10.17 选择应用程序类型 (4)在 Initial Setting 框中选择适合的工作模式 如图 10.18 按 钮 Visual C++6.0 编程实例与技巧 514 www.BOOKOO.com.cn 图 10.18 初始化设置 (5)在 Application Content 框中选,因为我们并不是来显示位 图或制作动画的 如图 10.19, 按钮 Visual C++6.0 编程实例与技巧 515 www.BOOKOO.com.cn 图 10.19 应用程序内容框 (6)在 Class Name 框中列出了 DirectDraw AppWizard 自动为你创 建的应用程序类名 可以在编辑框中进行修改 缺省时为*App 和*Win 两个类 其中*代表项目名 如图 10.20, 按钮 Visual C++6.0 编程实例与技巧 516 www.BOOKOO.com.cn 图 10.20 应用程序类名列表 (7)最后为新建项目的总结信息 此时是返回修改设置的最后一次机 会 如果对前述设置不满意 可以按钮返回重新设置 如果是 无要修改的地方 按,如图 10.21 Visual C++6.0 编程实例与技巧 517 www.BOOKOO.com.cn 图 10.21 项目总结信息框 项目建立起来后 即可编译运行 但由于尚未添加任何代码 故此 时的项目是一个空项目 运行结果为全黑的屏幕(这也正是我们想要的效 果 我们有了对全屏幕的控制权 )此时 若展开项目工作区窗口 (Workspace Window)中的资源视图(Resource View) 则会发现 A ppwizard 已自动加入了一个 Dialog 资源 如图 10.22 删除 Cancel 钮 保留其他 控件 按表 10.4 添加其他控件并同时设置各控件及对话框的属性和 ID 值 结果应如图 10.23 所示 Visual C++6.0 编程实例与技巧 518 www.BOOKOO.com.cn 图 10.22 缺省对话框资源 图 10.23 修改后的对话框资源 利用前面章节的 VC++ Classwizard 知识 按照后面的源程序清单添 加各种消息处理函数和成员变量 或修改已有函数的代码,由于这不是新 的内容 故不再详述具体过程 但要注意的是 别忘了按照前边介绍的 方法把 vfw32.lib 与应用程序连接 并把 vfw.h 及其他几个头文件(.h)嵌 入项目文件 对于其中几个重要函数的解释请参阅后面的程序说明部 分 表 10.4 各控件的对话框的属性和 ID 值 类别 ID 值 属 性 对话框 IDDAVIDIALOG 标题 DirectDraw AVI Player Visual C++6.0 编程实例与技巧 519 www.BOOKOO.com.cn 风格 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 标题 AVI ,Display Modes 静态文本框(四个) IDCSTATIC 标题 Name,Locator,Dimensio ns,Frames 附 源程序清单 AviPlayWin.h #ifndef AVIPLAYWINH #define AVIPLAYWINH #include DirectDrawWin.h Visual C++6.0 编程实例与技巧 520 www.BOOKOO.com.cn #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; Visual C++6.0 编程实例与技巧 521 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 522 www.BOOKOO.com.cn #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; Visual C++6.0 编程实例与技巧 523 www.BOOKOO.com.cn 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: Visual C++6.0 编程实例与技巧 524 www.BOOKOO.com.cn TRACE( FILEREAD n ); break; } } r=ICDecompress( decomp, 0, srcfmt, rawdata, dstfmt, finaldata); if (r != ICERROK) TRACE( ICDecompress: failed (error code %d) n , r); curframe=(curframeBltFast( 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) Visual C++6.0 编程实例与技巧 525 www.BOOKOO.com.cn 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; yUnlock( 0 ); } return TRUE; } void AviPlayWin::RestoreSurfaces() { avisurf->Restore(); } int AviPlayWin::SelectInitialDisplayMode() { Visual C++6.0 编程实例与技巧 526 www.BOOKOO.com.cn GetSystemPalette(); int i, nummodes=GetNumDisplayModes(); DWORD w,h,d; for (i=0;iSetDisplayMode( 640, 480, curdepth, 0, 0 ); for (i=0;iSetDisplayMode( 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 ); Visual C++6.0 编程实例与技巧 528 www.BOOKOO.com.cn } 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; Visual C++6.0 编程实例与技巧 529 www.BOOKOO.com.cn CWaitCursor cur; if (avistream) AVIStreamRelease( avistream ), avistream=0; r=AVIStreamOpenFromFile( &avistream, filename, streamtypeVIDEO, 0, OFREAD | OFSHAREEXCLUSIVE, 0 ); TRACE( AVIStreamOpenFromFile: %s n , r==0 ? OK : failed ); r=AVIStreamFormatSize( avistream, 0, &fmtlen ); TRACE( AVIStreamFormatSize: %s n , r==0 ? OK : failed ); 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( AVIStreamReadFormat: %s n , r==0 ? OK : failed ); TRACE( --- %s --- n , filename); TRACE( biSize: %d n , srcfmt->biSize ); TRACE( biWidth x biHeight: %dx%d n , srcfmt->biWidth, Visual C++6.0 编程实例与技巧 530 www.BOOKOO.com.cn 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 n , comp ); TRACE( biSizeImage: %d n , srcfmt->biSizeImage ); TRACE( ------------------ n ); memcpy( dstfmt, srcfmt, fmtlen ); dstfmt->biBitCount = 8; dstfmt->biCompression = BIRGB; Visual C++6.0 编程实例与技巧 531 www.BOOKOO.com.cn 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 ? OK : failed ); if (rawdata) { TRACE( delete rawdata... n ); delete rawdata; } Visual C++6.0 编程实例与技巧 532 www.BOOKOO.com.cn rawdata = new BYTE buflen ; if (finaldata) { TRACE( delete finaldata... n ); delete finaldata; } finaldata = new BYTE finalbuflen ; 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 i .rgbRed; pe i .peGreen = info->bmiColors i .rgbGreen; pe i .peBlue= info->bmiColors i .rgbBlue; pe i .peFlags = 0; } if (avipal) avipal->Release(); ddraw2->CreatePalette( DDPCAPS8BIT, pe, &avipal, 0 ); primsurf->SetPalette( avipal ); return TRUE; Visual C++6.0 编程实例与技巧 533 www.BOOKOO.com.cn } 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) delete dstfmt, dstfmt=0; if (rawdata) { TRACE( delete rawdata... n ); delete rawdata, rawdata=0; } if (finaldata) { TRACE( delete finaldata.. n ); delete finaldata, finaldata=0;; Visual C++6.0 编程实例与技巧 534 www.BOOKOO.com.cn } 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 Visual C++6.0 编程实例与技巧 535 www.BOOKOO.com.cn #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() Visual C++6.0 编程实例与技巧 536 www.BOOKOO.com.cn 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 DisplayModeArray; Visual C++6.0 编程实例与技巧 537 www.BOOKOO.com.cn 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 Visual C++6.0 编程实例与技巧 538 www.BOOKOO.com.cn {{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 Visual C++6.0 编程实例与技巧 539 www.BOOKOO.com.cn #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) Visual C++6.0 编程实例与技巧 540 www.BOOKOO.com.cn { 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() { Visual C++6.0 编程实例与技巧 541 www.BOOKOO.com.cn 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) Visual C++6.0 编程实例与技巧 542 www.BOOKOO.com.cn 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( AVIStreamOpenFromFile: %s n , r==0 ? OK : failed ); long fmtlen; r=AVIStreamFormatSize( avistream, 0, &fmtlen ); TRACE( AVIStreamFormatSize: %s n , r==0 ? OK : failed ); LPBITMAPINFOHEADER format; format = (LPBITMAPINFOHEADER)new BYTE fmtlen ; ZeroMemory( format, fmtlen ); r=AVIStreamReadFormat( avistream, 0, format, &fmtlen ); TRACE( AVIStreamReadFormat: %s n , r==0 ? OK : failed ); *w=format->biWidth; *h=format->biHeight; *d=format->biBitCount; TRACE( Dialog: w=%d h=%d d=%d n , *w, *h, *d); Visual C++6.0 编程实例与技巧 543 www.BOOKOO.com.cn *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;iGetAt(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 ); Visual C++6.0 编程实例与技巧 544 www.BOOKOO.com.cn aviframes.Format( %d , frames ); if (index!=-1 && modelist.GetCount()>0) { modelist.SetCurSel( index ); GetDlgItem(IDOK)->EnableWindow( TRUE ); } return TRUE; } Headers.h #define VCEXTRALEAN #include #include #include #include #include #include #include Headers.cpp #include Headers.h 10.3.3.7 AviPlay 应用程序说明应用程序说明应用程序说明应用程序说明 1. AviPlayWin 类 Visual C++6.0 编程实例与技巧 545 www.BOOKOO.com.cn 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(); Visual C++6.0 编程实例与技巧 546 www.BOOKOO.com.cn 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; }; Visual C++6.0 编程实例与技巧 547 www.BOOKOO.com.cn 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 中的表面分配 该示例中这 个函数是普通的 可以在以后看到 Visual C++6.0 编程实例与技巧 548 www.BOOKOO.com.cn 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 选择对话框 Visual C++6.0 编程实例与技巧 549 www.BOOKOO.com.cn 并等待用户输入 然而 在看 ShowDialog()函数之前 必须先看看 SelectiInitiaIDisplayMode()函数 该函 数是在调用 DirectDrawWin 版本 的 OnCreate()时由 DirectDrawWin 类调用的 3. SelectInitialDisplayMode()函数 如上所述 SelectInitialDisplayMode()函数要完成 3 个任务 该函数 如下所列举 int AviPlayWin::SelectInitialDisplayMode() { GetSystemPalette(); int i, nummodes=GetNumDisplayModes(); DWORD w,h,d; for (i=0;iSetDisplayMode( 640, 480, curdepth, 0, 0 ); for (i=0;iSetDisplayMode( 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; Visual C++6.0 编程实例与技巧 552 www.BOOKOO.com.cn 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 对话框会弹出在可见的表面 而不是在 Visual C++6.0 编程实例与技巧 553 www.BOOKOO.com.cn 后备缓冲区中 当然 鼠标也是可见的( 否则用户将无法按下对话框按 钮进行选择) 如果先前调用 ShowDialog()函数没有生成 AviDialog 类 那么将生成 一个 AviDialog 类 注意 当对话框被建立时 先前由 SelectInitialDisplayMode()函数生成的 8 位显示模式数 组将被传递给新 的对话框 然后存在的 AVI 流将被关闭 这样做是因为 AviDialog 类保留有它 自身的 AVI 文件支持 用以显示所选 AV I 文件的尺寸和帧数 如果不关 闭己被打开的文件 当同一文件又被打开时 对话框将无法提取这个消 息 接下来用于对话的 DoModa1()函数被调用 将显示对话框并允许用 户进行选择 当用户按 Cancel 按钮后 WMCLOSE 消息将被传递 否 则 将提取所选文件名(以 3 种不同 形式) 同时提取的还有显示模式的 索引(显示模示必须在开始播放前选择) 然后是从 displaymod e 数组取 得所选显示模式的尺寸 并做为参数传给 SetDisplayMode()函数 LoadAvi 函数被调用 我们将看到 LoadAvi()函数并没有装入全部的 视频 它打开文件并取得一些视频信息 例如帧的尺寸和数量 接下来 调用的是 CreateAviSurface()函数 按视频的尺寸创建一个存储视频流的 每一帧的表面 InstallPalette()函数用于从 AVI 文件中提取调色板数据 并建立最适 合于 AVI 文件的 DirectDraw 调色板 最后用于索引视频帧的 curframe 数据成员被 startframe 成员初始化 5. LoadAvi()函数 下面让我们看看实际用于打开 AVI 文件的函数 LoadAvi()函数如程 Visual C++6.0 编程实例与技巧 554 www.BOOKOO.com.cn 序 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( AVIStreamOpenFromFile: %s n , r==0 ? OK : failed ); r=AVIStreamFormatSize( avistream, 0, &fmtlen ); TRACE( AVIStreamFormatSize: %s n , r==0 ? OK : failed ); 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 ); Visual C++6.0 编程实例与技巧 555 www.BOOKOO.com.cn TRACE( AVIStreamReadFormat: %s n , r==0 ? OK : failed ); TRACE( --- %s --- n , filename); TRACE( biSize: %d n , srcfmt->biSize ); TRACE( biWidth x biHeight: %dx%d 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; } Visual C++6.0 编程实例与技巧 556 www.BOOKOO.com.cn TRACE( biCompression: %s n , comp ); TRACE( biSizeImage: %d n , srcfmt->biSizeImage ); TRACE( ------------------ 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 ? OK : Visual C++6.0 编程实例与技巧 557 www.BOOKOO.com.cn failed ); if (rawdata) { TRACE( delete rawdata... n ); delete rawdata; } rawdata = new BYTE buflen ; if (finaldata) { TRACE( delete finaldata... n ); delete finaldata; } finaldata = new BYTE finalbuflen ; return TRUE; } LoadAvi()函数使用 AVIStreamRelease()函数以关闭先前打开的 AVI 文件流 接着 AVIStrea mOpenFromFile()函数打开新的 AVI 文件 注意 AVIStreamOpenFromFile()函数接受一个标志作为它的第 3 个 参数 该标志说明可以打开的文件流的类型 在我们看来 streamtypeVIDEO 用以表示视频流,但 AVIStreamOpenFromF ile()函数通 过保留的 3 个标志(streamtypeAUDIO streamtypeMIDI 和 streamtypeTEXT )可用于打开非视频流 接下来 流格式信息由 AVIStreamReadFormat() 检索( 在 AVIStreamFormatSize()函数的 协助下) 这里已经将 TRACE( )宏留在了 Visual C++6.0 编程实例与技巧 558 www.BOOKOO.com.cn 描述有关 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() 函数将利用 Visual C++6.0 编程实例与技巧 559 www.BOOKOO.com.cn DirectDraw Createsurface( ) 函数创建一个同样尺寸的表面 同时 CreateAviSurface()函数还将初始化 x 和 y 这两个数 据成员 x y 将决 定后备缓冲区中 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 i .rgbRed; pe i .peGreen = info->bmiColors i .rgbGreen; pe i .peBlue= info->bmiColors i .rgbBlue; pe i .peFlags = 0; } if (avipal) avipal->Release(); ddraw2->CreatePalette( DDPCAPS8BIT, pe, &avipal, 0 ); primsurf->SetPalette( avipal ); Visual C++6.0 编程实例与技巧 560 www.BOOKOO.com.cn 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: Visual C++6.0 编程实例与技巧 561 www.BOOKOO.com.cn TRACE( FILEREAD n ); break; } } r=ICDecompress( decomp, 0, srcfmt, rawdata, dstfmt, finaldata); if (r != ICERROK) TRACE( ICDecompress: failed (error code %d) n , r); curframe=(curframeBltFast( x, y, avisurf, 0, DDBLTFASTWAIT ); primsurf->Flip( 0, DDFLIPWAIT ); } DrawScene()函数利用 AVIStreamRead()函数从 AVI 流中提取一帧 并把结果帧存放在 raw data 缓冲区中 在该函数中滞留了一些 TRACE() 宏 以便调试时使用 但希望用不上 接着 通过调用 ICDecompress()函数从 LoadAvi()函数中得到解压模 块的句柄 ICDecom press()函数用两个缓冲区作参数 一个用于原始压 缩数据 另一个用于存放解压后的图 像 然后 UpdateAviSurface()函数将被调用 以把解压的帧数据拷贝到 AVI 表面 一旦 AVI 表面准备就绪 就由 DirectDrawSurfaceBltFast()函 数将其 blt 到后备缓冲区 之后 cur frame 数据成员被增值或复位 这 取决于它的值以及视频序列的帧数 最后 调用 DirectDra wSurface Flip() 函数 把最新获得的视频帧显示到屏幕上 9. UpdateAviSurface()函数 在看此函数之前 必须提醒大家注意 这个函数有点像装入 BMP 文 Visual C++6.0 编程实例与技巧 562 www.BOOKOO.com.cn 件的 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; yUnlock( 0 ); } Visual C++6.0 编程实例与技巧 563 www.BOOKOO.com.cn return TRUE; } 一旦表面被锁定 该函数就通过一个循环把 AVI 数据的每一行像素 拷贝到表面内存中 和 BMP 数据格式一样 AVI 数据格式也是倒置存 放的 所以应从 AVI 数据缓仲区的底部开始向顶部读数据 10. RestoreSurfaces()函数 我们已经完成了最困难的工作 剩余部分将很容易了 RestoreSurfaces()函数是非常易于实现的 RestoreSurfaces()函数如下所 示 void AviPlayWin::RestoreSurfaces() { avisurf->Restore(); } 记住 RestoreSurfaces()函数只在表面丢失时被调用 DirectDrawWin 类将自动恢复主表面和后备缓冲区 对于 AviPlay 示例 由于仅剩下 AVI 表面要恢复 故可以仅调用一个 Direct DrawSurface Restore()函数恢复 AVI 表面 在有些示例中 曾经使用了 RestoreSurfaces()函数来恢复表面内容和 内存 这里 由于 AV I 画面将被下一帧更新 所以有足够的内存来恢复 画面 它并没有对调用 Restore()函数恢 复未丢失的表面而产生破坏(和 用系统 RAM 创建表面情形类似) 11. 处理用户输入 在 AviPlay 示例中 用户输入只占很小的一部分 该示例只对 3 个键 有响应 并且响应是一样的 键盘输入是由 OnKeyDown()函数处理的 Visual C++6.0 编程实例与技巧 564 www.BOOKOO.com.cn 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()函数将关闭打开的 Visual C++6.0 编程实例与技巧 565 www.BOOKOO.com.cn 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) delete dstfmt, dstfmt=0; if (rawdata) { TRACE( delete rawdata... n ); delete rawdata, rawdata=0; } if (finaldata) { TRACE( delete finaldata.. n ); delete finaldata, finaldata=0;; } if (avidialog) delete avidialog, avidialog=0; AVIFileExit(); Visual C++6.0 编程实例与技巧 566 www.BOOKOO.com.cn } 注意 OnDestroy() 函数最后调用的是 AVIFileExit() 函数 AVIFileExit()将关闭 VFW 并释放它所分配的资源 10.3.3.8 小结小结小结小结 以上所述为视频回放 要想从 AviPlay 示例中做一个真正的 AVI 播 放器 工作量是很大的 除了声音和时间支持外 VFW 是一个要求有 许多实验的相当强大的 AP1 最后一点 由于未知的原因 VFW 不能用于处理用 IR32 和 IR42 压 缩的 AVI 文件(也许的压缩格式也不适宜) 但另一方面 使用 MS CRAM 和 Cinepak 的 Avi 文件却工作得很好 Visual C++6.0 编程实例与技巧 567 www.BOOKOO.com.cn 第十一章第十一章第十一章第十一章 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 类为例 来开发一个现实生活中应用特别 广泛的数据库应用程序 Visual C++6.0 编程实例与技巧 568 www.BOOKOO.com.cn 记住 MFC 是个万花筒 它可以帮你编织出多彩的世界 使你具有 非凡的能力 11.1 11.1 11.1 11.1 登录登录登录登录 ODBCODBCODBCODBC 数据源数据源数据源数据源 当今社会正处于信息爆炸的时代 数据库技术的应用己遍及各行各 业 这给数据库技术的研究和数据库应用程序的开发提供了良好的环 境 Visual C++为顺应这一发展需求 开发了 ODBC 类 以方便用户对 多种流行数据库开发应用程序 ODBC 是指开放数据库互联 是一套开 放的数据库标准接口 使用它可以构 造与数据库无关的客户服务器应 用程序 数据库应用程序当然是需要一个数据库作为后台支持 所以在建立 它之前 首先要使 用 ODBC 登录一个确定的数据库文件 假定我们将 要选择一个预先建好的用于管理学生 记录的 MicrosoftAccess 数据库文 件 STDREG32 (1) 在 Windows9X 的桌面上 用鼠标双击屏幕左下端任务栏的“开 始”按钮 从弹出的菜单中 通过“设置”命令 找到并打开“控制面板” 对话框 用鼠标双击 ODBC(32bit)图标 就 可打开 ODBC 管理对话框 如图 11.1 所示 Visual C++6.0 编程实例与技巧 569 www.BOOKOO.com.cn 图 11.1 双击控制面板中的 ODBC(32bit)图标 (2) 在打开 ODBC 的 Data Source Administrator 对话框中 UserData Sources 列表是空的 表明当前无用户数据源被登录 如图 11.2 所示 选择 Add 按钮 以确定要登录的数据库文件的 ODBC 驱动器 Visual C++6.0 编程实例与技巧 570 www.BOOKOO.com.cn 图 11.2 ODBC Data Source Adminstrator 对话框 (3) Create New Data Source 对话框打开 在该对话框的列表中 给出 了各种己有的数据源驱动器 用户可以从中选择一种所需的驱动器 用鼠标选 Microsoft Access Driver 再击“完成”按钮 如图 11.3 所示 图 11.3 Create New Data Source 对话框 Visual C++6.0 编程实例与技巧 571 www.BOOKOO.com.cn (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 完成了数据 库的登录 Visual C++6.0 编程实例与技巧 572 www.BOOKOO.com.cn 图 11.5 安装 Access 数据库文件 11.2 ODBC11.2 ODBC11.2 ODBC11.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 所示 Visual C++6.0 编程实例与技巧 573 www.BOOKOO.com.cn 选择 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 新建项目 Visual C++6.0 编程实例与技巧 574 www.BOOKOO.com.cn 图 11.7 创建单文档应用程序 Visual C++6.0 编程实例与技巧 575 www.BOOKOO.com.cn 图 11.8 应用程序向导第二步 (4) 从打开的 Select Database Tables 对话框中选择数据库的 SECTION 表名如图 11.9 所示 并击 OK 按钮 返回 Step2 对话框 并 从 Step2 对话框直至 Step5 对话框中 单击 Next 钮来接受缺省选项 在打开的 Step6 对话框中 可修改缺省生成的类和文件名 以便和 表名 Section 对应 Visual C++6.0 编程实例与技巧 576 www.BOOKOO.com.cn 图 11.9 选择数据库表 (5) 选择类 CEnrollSet 将其更名为 CSectionSet 并将对应文件更名 为 Section.h(.cpp) 选择类 CEnrollView 将其更名为 CSectionForm 并 将对应文件更名为 SectionForm.h(.cpp) 如图 11.10 所示 按 Finish 钮 并在被打开的 New Project Information 对话框中单击 OK 按钮 这样就可以使 A ppWizard 生成此项目 Visual C++6.0 编程实例与技巧 577 www.BOOKOO.com.cn 图 11.10 应用程序向导最后一步 11.2.2 查看记录集合查看记录集合查看记录集合查看记录集合 数据应用程序与文档应用程序是有很大区别的 在文档应用程序中 用户主要与文档类和视窗类打交道 而在数据应用程序中 用户主要与 记录集合和记录视类打交道 记录集合(CRecordset)对象是从数据源中选择出来的一组记录 它可 以是从一个或多个数据库表的行中所选择的一个或多个确定的列 Visual C++6.0 编程实例与技巧 578 www.BOOKOO.com.cn 图 11.11 Enroll 项目的类集合 记录视(CRecordView)是一种特定的视类 它使用对话模板资源中的 控件来查看和编辑类似对话表格中记录集合的字段 在 AppWizard 过程中 由用户指定 ODBC 数据源以及数据的表 而 AppWizard 则生成一对类 记录集合类和记录视类 (1) 当 Enroll 项目生成后 就可通过 ClassView 来查看 AppWizard 所 生成的类以及各类所包含的缺省成员函数和成员变量 在 ClassView 窗口中 扩展 Enroll 项从中可以看到己生成的类集合 很丰富 如图 11.11 所示 (2) 为查看 CSectionSet 这个新的记录集合类 可继续展开这个类的图标 如图 11.12 所示 从中可以显示出己生成好的所有成员变量 它们己包括了数据表格 Section 中每列对应的变量 Visual C++6.0 编程实例与技巧 579 www.BOOKOO.com.cn 图 11.12 展开 CSectionSet 项 (3) 用户可以通过 ClassWizard 来查看 AppWizard 是如何将数据表 Section 的列与这些成员变量相连接的 使用平台上 View 菜单的 ClassWizard 命令来打开 ClassWizard 对话框 如图 11.1 3 所示 (4) 在 ClassWizard 对话框中 选择标签 Member Variable 从类名框中选择 CSectionSet 并 从相应的列表框中 已可看到 Section 表的所有列与该类 的成员变量相连接了 如图 11.14 所 示 这些成员变量叫做“字段数据变量” 它是由 AppWizard 基于数据源 的列名自动命名的 Visual C++6.0 编程实例与技巧 580 www.BOOKOO.com.cn 图 11.13 ClassWizard 对话框 Visual C++6.0 编程实例与技巧 581 www.BOOKOO.com.cn 图 11.14 Section 表的所有列与成员变量被连接 (5) 如果用户不需要将表格的所有列与用户记录集合相连接 则可以 从 ClassWizard 选择不想要的列所对应的记录集合字段数据变量并单击 Delete Variable 按钮 就可删除它 但不 要删除作为表的主关键字的任 何字段 11.2.3 查看其他类和资源查看其他类和资源查看其他类和资源查看其他类和资源 AppWizard 除生成记录集合类 CSectionSet 外 还生成有记录视类 CSectionForm 文档类 CEnr ollDoc 菜单栏资源和工具栏资源 记录视 CSectionForm 是一种特定的视类 它使用对话模板资源中的 控件来查看和编辑类似对话表格中记录集合的字段 记录视对象与一个 记录集合对象以及对话框模板资源相关联 文档类通常是用于在文档内存储数据并串行化它磁盘文件中 但在 数据库应用程序中 数据是存在数据库中的 用户通常是以记录形式查 看数据而不以文件形式 这样 文档在数据库 应用程序中通常就不是 用于串行化支持 它有着特殊的作用 (1) 为了查看记录视类 CSectionForm 的源代码 可以使用 ClassView 跳到其成员函数 OnIniti alUpdate 定义处 如图 11.15 所示 Visual C++6.0 编程实例与技巧 582 www.BOOKOO.com.cn 图 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 资源文件 Visual C++6.0 编程实例与技巧 583 www.BOOKOO.com.cn 图 11.16 查看 CEnroll Doc 文件 程序如图 11.17 所示 它们都是与项目相关的资源 图 11.17 Enroll 项目的资源文件 (4) 为查看 Enroll 的菜单栏资源 可以扩充 Menu 并双击 IDRMANFRAME 如图 11.18 所示 菜单编辑器会打开并显示 AppWizard 为 Enroll 应用程序所生成的缺 省菜单 Visual C++6.0 编程实例与技巧 584 www.BOOKOO.com.cn (5) 为查看 Enroll 的工具栏资源 可以扩充 ToolBar 并双击 IDRMAINFRAME 如图 11.19 所示 工具栏编辑器会打开并显示 AppWizard 为 Ellroll 应用程序所生成的 缺省工具栏及按钮 图 11.18 菜单栏资源 Visual C++6.0 编程实例与技巧 585 www.BOOKOO.com.cn 图 11.19 工具栏资源 11.2.4 为表格定制对话框界面为表格定制对话框界面为表格定制对话框界面为表格定制对话框界面 AppWizard 还为 Enroll 应用程序生成了一个名为IDRENROLLFORM 对话框资源 记录视类 CRecordView 的派生类 CSectionForm 用它来显 示其表格控件 由于类 CRecordView 是从类 CFormView 中派生出来的 所以记录视 的客户区是由对话框模板资源布置 该表格的布局设置是由开发者来设 计的 AppWizard 会在对话框模板资源上放置一个静态文 其控件标签 为“TODO Place form control on this dialog” 如图 11.20 所示 Visual C++6.0 编程实例与技巧 586 www.BOOKOO.com.cn 图 11.20 在此处设置表格布局 (1) 从 Resource View 窗口中扩展 Dialog 双击其内的 IDDENROLLFORM 对话框模板资源 该对话框编辑器会打开 可删除标签为“TODO Place form contro1 on this dialog”的静态文本控件 (2) 使用静态文本控件在对话框界面上布置表格的列名 先从 Edit 菜单中选择 Properties 命令来打开特性窗口并把它钉住 对 于每取一个静态控件 可在 Caption 编辑框中为它确定一个与表列名一 一对应的标签 如图 11.21 所示 Visual C++6.0 编程实例与技巧 587 www.BOOKOO.com.cn 图 11.21 使用静态文本控件布置格表的列名 (3) 当所有静态控件布置完成后 就可以为它们增加对应的编辑控件 (也可以成对地增加静 态控件和编辑控件) 对每一个编辑控件 使用特 性窗口中的 ID 框来确定基于表中列名称的 ID(如 IDCCOURSE) 这是 一种常规做法 如图 11.22 所示 Visual C++6.0 编程实例与技巧 588 www.BOOKOO.com.cn 图 11.22 增加相应的编辑控件 (4) 对于使用者不能更新的关键字列 可以通过选择性性窗口的 Styles 区并设置“ReadOn1y ”复选框使其编辑控件为只读 在这里可用此方法分别将 Course 和 Section 编辑控件设为只读型 (5) 当完成各种控件设置后(如图 11.23 所示) 可以通过 Layout 菜单 的 TabOrder 命令来 Visual C++6.0 编程实例与技巧 589 www.BOOKOO.com.cn 图 11.23 完成表格布局设计 查看各控件当前的 Tab 顺 序 通过单击在此次序中的每个控件来确 定开发者所希望的 Tab 顺序 也可以通过 Layout 菜 单的其他命令来进 行控件布局调整 如图 11.24 所示 Visual C++6.0 编程实例与技巧 590 www.BOOKOO.com.cn 图 11.24 表格中各控件的 Tab 顺序 最后保存编辑好的资源文件 11.2.5 控件与记录集合字段相连接控件与记录集合字段相连接控件与记录集合字段相连接控件与记录集合字段相连接 当表格界面设计好后 需要指明各编辑控件映射到表中的相应列 更准确地说就是各编辑控件映射到相应记录集合字段数据成员 要完成 这项工作 开发者要使用 ClassWizard 的“外 部对象”机制 通常 开发者使用 ClassWizard 来将对话框或表格中的控件与用户 CDialog 或 CFormView 派生类的成员变量相连接 但在 CRecordView 这 种情况下 开发者不是将表格的控件与记录视类 的数据成员连接 而 是与记录视相关的记录集合类的数据成员相连接 Visual C++6.0 编程实例与技巧 591 www.BOOKOO.com.cn (1) 在 CRecordView 这种情况下 其派生类 CSectionForm 有一个名 为 mpSet 的数据成员 可以通过扩展 ClassView 中的 CSectionForm 类来查看 mpSet 如图 11.25 所示 它是 一个指向 Enroll 记录集合类 CSectionSet 的指针 图 11.25 查看 m 数据成员 (2) 控件通过 mpSet 与相应的 CSectionSet 字段数据成员相连接 为 实现表格控件 与记录集合数据成员相连接 首先要将布局己设计好的 对话框资源 IDDENROLLFORM 在对话框编辑器中打开 如图 11.26 所 示 Visual C++6.0 编程实例与技巧 592 www.BOOKOO.com.cn 图 11.26 设计好的对话框资源 (3) 为将 Course 编辑控件实现这样的连接 mpSet CourseID 在对话框编辑器窗口中 按下 Ctrl 并双击 Course 编辑控件可打开 Add Member Variable 对话框 确保 Member Variable Name 框内显示为 mpSet mCourseID 如图 11.27 所示 Visual C++6.0 编程实例与技巧 593 www.BOOKOO.com.cn 图 11.27 添加成员变量 单击 OK 来接受该名字 (4) 与 Course 编辑控件的处理过程相同 分别使 Section Instructor Room Schedule 和 C apacity 编辑控件与记录集合对应的数据成员相连 接(不必为静态控件生成上述的映射关系) (5) 当实现了所有表格控件与记录集合数据成员相连接后 开发者可 以通过 ClassWizard 中类 CSectionForm 相应的 Member Variables 区内查 看完整的映射 例如 在 Control IDs 列 中出现 IDCCOURSE 则在 Member 列中可看到“ mCourseID” 如图 11.2 8 所示 Visual C++6.0 编程实例与技巧 594 www.BOOKOO.com.cn 图 11.28 所有表格控件与记录数据成员相连接 11.2.6 运行运行运行运行 ODBC 数据库应用程序数据库应用程序数据库应用程序数据库应用程序 到目前为止虽然我们并没有编写一条程序 但借助于 AppWizard MFC 的 ODBC 类和资源编辑器 通过生成起始数据库应用程序 定制 数据库表格和将表格控件与记录集合字段相连接等几 个简单步骤 己 基本建立起了一个可运行的数据库应用程序 该应用程序将具有使记录 集中 的记录滚动和更新某些字段等基本功能 尽管其功能还不够强大 但它为开发者的进一步开 发提供了良好的基础 下面 我们通过编译并运行该数据库应用程序 来测试一下该应用 程序的功能 (1) 为检查 Enroll 数据库应用程序的编程结果 可以通过 Build 菜单 中的 Build 命令来生成可 执行文件 如果 Build 过程正常通过 则就可以运行通过 Build 菜单的 Execute 命令来运行该数据库应用 程序了 如图 11.29 所示 Visual C++6.0 编程实例与技巧 595 www.BOOKOO.com.cn 图 11.29 运行 ODBC 数据库应用程序 (2) Enroll 应用程序的数据库界面显示了用户设计的控件 如图 11.30 所示 当 CSectionS et 记录集合打开时 它从数据库的表中选择记录并 使第一条记录集合成为“当前记录” 使 它呈现在界面的编辑控件中 Visual C++6.0 编程实例与技巧 596 www.BOOKOO.com.cn 图 11.30 Enroll 应用程序的数据库界面 使用者可以使用数据库的向左向右按钮去查询数据库记录 (3) AppWizard 为应用程序缺省提供了 Record 菜单 它拥有 First Record Privious Record NextRecod 和 Last Record 命令并在工具栏上 有相对应的按钮 如图 11.31 所示 通过这些命令或相应的按钮可实现记录集合内记录的滚动控制 Visual C++6.0 编程实例与技巧 597 www.BOOKOO.com.cn 图 11.31 Record 菜单选项 (4) 对于新出现的工具按钮 可通过鼠标在其上的滑动来获取简要提 示说明 如图 11.32 所示 被设置成只读型的编辑控件(如 Course 和 Section) 用灰色呈现 以 加以区别 Visual C++6.0 编程实例与技巧 598 www.BOOKOO.com.cn 图 11.32 工具按钮的提示说明 (5) 由于在应用程序时选择了“Database view without file support”选 项 所以在 File 菜单中没有了 New Open Save 和 Save As 命令 如 图 11.33 所示 如果用户选择了“Dat abase view with file support”选项 则 AppWizard 将提供那些没有的 File 命令 Visual C++6.0 编程实例与技巧 599 www.BOOKOO.com.cn 图 11.33 File 菜单选项 11.3 DAO11.3 DAO11.3 DAO11.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) 单击浏览按钮( ) 以打开“打开”对话框 通过该对话框查找到 Visual C++6.0 编程实例与技巧 600 www.BOOKOO.com.cn Microsoft Acce ss 数据库文件 STDREG32.MDB 如图 11.36 所示 图 11.34 新建 MFC 项目 图 11.35 设置数据库选项 Visual C++6.0 编程实例与技巧 601 www.BOOKOO.com.cn 图 11.36 选择 Access 数据库文件 然后单击“打开”按钮 (4) 这时 Select Database Tables 对话框将显示出来 在数据库表名列 表框中选取表 Secti on 并击 OK 钮 返回 Step2 对话框 如图 11.37 所示 从 Step2 对话框直至 Step5 对话框中 单击 Next 钮来接受缺省选项 在 打开的 Step6 对话框中 可修改缺省生成的类和文件名 以便和 表名 Section 对应 Visual C++6.0 编程实例与技巧 602 www.BOOKOO.com.cn 图 11.37 选择数据库表 (5) 选择类 CDaoEnrollDoc 将其头( 实现) 文件更名为 DENRLDoc.H(.CPP) 选择类 CDaoEnro llView 将其更名为 CSectionFom 并将对应文件更名为 SECTFORM.H(.CPP) 选择类 CDaOEnr o1lSet 将 其更名为 CSectionSet 并将对应文件更名为 SECTSET.H(.CPP),使 AppWizard 生 成此项目 如图 11.38 所示 Visual C++6.0 编程实例与技巧 603 www.BOOKOO.com.cn 图 11.38 修改类和文件名 11.3.2 查看查看查看查看 DAO 类及资源类及资源类及资源类及资源 DAO 记录集合 数据库以及记录视类等的实现代码与 ODBC 数据库 类的十分相似 这表明作为使用 ODBC 数据库类的结果 开发者可以将 从中己获取的技能和知识应用于 DAO 数据库类 当使用AppWizard生成了DAO项目后 开发者就可以使用ClassView 来查看它所包含的类 成员函数以及成员变量 也可以通过 ClassWizard 来查看 AppWizard 为开发者确定的成员变量连接 可将查看结果与 ODBC 项目作一比较 (1) 当 DaoRnroll 项目生成后 就可通过 ClassView 来查看 AppWizard 所生成的类以及各类所包含的缺省成员函数和成员变量 在 ClassView 窗口中 扩展 DaoEnroll 项目 从中可以看到己生成的 类及字段数据成员 如图 11.39 所示 Visual C++6.0 编程实例与技巧 604 www.BOOKOO.com.cn 图 11.39 Section 表的所有列与类的成员变量相连接 (2) 在 Class Wizard 对话框中 选择标签 Member Variable 如图 11.40 所示 从类名框中选择 CSectionSet 并 从相应的列表框中 已可看到 Section 表的所有列与该类的成员变量相连接了 这些成员变量叫做“字 段数据变量” 它是由 AppWizard 基于数据源的列名自动命名的 (3) 为了查看记录视类 CSectionForm 的源代码 可以使用 ClassView 跳到其成员函数 OnInita lUpdate 定义处 如图 11.41 所示 Visual C++6.0 编程实例与技巧 605 www.BOOKOO.com.cn 图 11.40 DaoEnroll 项目的集合 Visual C++6.0 编程实例与技巧 606 www.BOOKOO.com.cn 图 11.41 CSeeTionForm 类的 OnInitialUpdate 函数定义 如图所示 基类框架函数 CdaoRecordView::OnInitialUpdate 在数据库 未打开时就已打开它和记录集合 并通过调用 CformView::OnInitialUpdate 来初始化表格 (4) 为了查看文档类 CDaoEnrollDoc 的头文件 可以在 Class View 中 鼠标双击类 CDaoEnrollD oc 的图标以打开 CdaoEnrollDoc.h 文件 从图 11.42 中可以看出 DaoEnroll 应用程序中文档类的作用是去拥有记录集 合 记录集合对象 msectionSet 嵌在文档对象中 它随文档 对象构造而 自动构造 随文档对象删除而自动删除 Visual C++6.0 编程实例与技巧 607 www.BOOKOO.com.cn 图 11.42 查看文档类 CDao Enroll Doc 的头文件 (5) 为查看 DaoEnroll 的菜单资源 可以扩展 Menu 并双击 IDRMAINFRAME 菜单编辑器会打开并显示 AppWizard 该应用程序所 生成的菜单 如图 11.43 所示 在 Record 菜单 它 Visual C++6.0 编程实例与技巧 608 www.BOOKOO.com.cn 图 11.43 Dao Enroll 的菜单资源 拥有 First Record Previou s Record Next Record 和 Last Record 命 令并在工具栏上有相对应的按钮 它们可实现记录 集合内记录的滚动 控制 11.3.3 完成完成完成完成 DAO 数数数数据库应用程序据库应用程序据库应用程序据库应用程序 在生成一个 DA0 数据库应用程序之后 要建立该应用程序还需要以 下三个基本操作 (1) 为数据库应用程序的表格定制对话框界面 (2)将对话框表的编辑控件与记录集合字段相连接 (3)建立并运行该数据库应用程序 Visual C++6.0 编程实例与技巧 609 www.BOOKOO.com.cn 这些操作过程基本与前述的 ODBC 数据库应用程序的实现过程相 同 而且通过这两种方法所建立的数据库应用程序具有相同的功能 (1) 从 Resource View 窗口中扩展 Dialog 并双击其内的 IDDDAOENROLFORM 对话框模板资源 该对话框编辑器会打开(如图 11.44 所示) 可删除标签为“TODo Place form control on t his dialog”的静态文本控件 图 11.44 Dao Enroll 的对话框模板资源 (2) 同前一样 在对话框模板上设置好静态控件 编辑控件并对只读 型编辑控件设置 RedOn1y 复选框 如图 11.45 所示 Visual C++6.0 编程实例与技巧 610 www.BOOKOO.com.cn 图 11.45 在对话框模板上设置表格布局 通过 Layout 菜单命令为控件确定好 Tab 顺序并布局调整 测试并保 存最后的工作 (3) 控件通过 mpSet 与相应的 CSectionSet 字段数据成员相连接 为 将 Course 编辑 控件实现这样的连接 mpSet CourseID 在对话框编辑器窗口中 按下 Ctl 并双击 Course 编辑控件可打开 Add Member Variable 对话框 确保 Member Variable Name 框内显示为 mpSet mCourselID 如图 11. 46 所示 Visual C++6.0 编程实例与技巧 611 www.BOOKOO.com.cn 图 11.46 添加成员变量 单击 OK 来接受该名字 (4) 与 Course 编辑控件相同 分别使 Section Instructor Room Schedule 和 Capacity 编辑控件与记录集合对应的数据成员相连接 当实现了所有表格控件与记录集合数据成员相连接后 可以通过 ClassWizard 中类 CSe ctionForm 相应的 Member Variables 区内查看完整 的映射 如图 11.47 所示 (5) 为检查 DaoEnroll 数据库应用程序的编程结果 可以通过 Build 菜单中的 Build 命令来生成可执行文件并通过 Execute 命令来运行该数 据库应用程序 Visual C++6.0 编程实例与技巧 612 www.BOOKOO.com.cn 图 11.47 所有表格控件与记录集合数据成员的完整映射 可通过该应用程序对话框上与 Record 有关的命令或按钮来检查该应 用程序的运行情况 如图 11.48 所示 Visual C++6.0 编程实例与技巧 613 www.BOOKOO.com.cn 图 11.48 运行 Dao Enroll 数据库应用程序
还剩615页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

hyqfxlt

贡献于2013-05-14

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf