arcgis engine中文开发指南清晰版


环境系统研究所公司(美国) 380 New York Street Redlands, CA 92373-8100, USA Copyright © 2004 环境系统研究所公司(美国) 版权所有,翻录必究 环境系统研究所公司(美国)保留本书全部内容的所 有版权。本书受美国版权法及其他国际版权条约和公 约的保护。未经环境系统研究所公司(美国)的书面 许可,不得以任何形式或手段复制、传播,或以任何 电子或文本方式翻印、转载本书的任何部分。如有疑 问,请与环境系统研究所公司(美国)联系:380 New York Street, Redlands, CA 92373-8100, USA。 本书内容的更改将不另行通知。 本书由 ESRI 公司授权,ESRI 中国(北京)有限公司 组织翻译、出版。 著 作 者 Euan Cameron, Chris Davies, Rob Elkins, Kylie Evans, Anne Frankland, Shelly Gill, Sean Jones, Allan Laframboise, Glenn Meister, Dan O’Neill, Rohit Singh, Steve Van Esch, Zhiqian Yu. 美国政府的受限 / 有限权利 以下所列的任何软件、文档和 / 或数据均受“许可 协议”的制约。美国政府在任何条件下都不能获得大 于受限 / 有限权利中所规定的权利。最基本的原则 是,美国政府使用、复制或公开数据要受到以下条例 相关内容的制约: FAR §52.227-14 Alternates I,II和 III (1987年6月);FAR §52.227-19 (1987年6月) 和 / 或FAR §12.211/12.212 (商业技术数据 / 计算机软 件); 以及DFARS §252.227-7015 (1995年11月)(技 术数据)和 / 或DFARS §227.7202 (计算机软件)。合 约方 / 制造商是环境系统研究所公司(美国), 380 New York Street, Redlands, California 92373-8100, USA。 ESRI、ArcView、ArcIMS、SDE、ESRI地球标志、 ArcObjects、ArcGIS、ArcMap、ArcCatalog、ArcScene、 ArcInfo、 ArcEditor、ArcGlobe、StreetMap、ArcReader、 ArcToolbox、3D Analyst、ArcSDE、GIS by ESRI、 ArcGIS标志、www.esri.com以及@esri.com均是环境系 统研究所公司(美国)在美国、欧盟和某些其他司法 登辖区的商标,注册商标或服务标志。 本书涉及到的其他公司和产品是属于其各自商标拥 有人的商标或注册商标。 iii 目 录 第一章 ArcGIS 引擎简介 1 ArcGIS 9 概览 2 ArcGIS 引擎概览 6 ArcGIS 引擎用户 10 ArcGIS 引擎功能 12 如何开始 16 使用本书 18 开发资源 19 第二章 ArcGIS 软件体系结构 21 ArcGIS 软件体系结构 22 ArcGIS 应用程序接口 27 ArcGIS 引擎类库 30 第三章 使用 ArcGIS 控件进行开发 39 什么是 ArcGIS 控件? 40 使用 ArcGIS 控件 41 MapControl 和 PageLayoutControl 44 GlobalControl 和 SceneControl 45 ReaderControl 46 TOCControl 和 ToolbarControl 47 用 ToolbarControl 建立应用程序 48 建立不带 ToolbarControl 应用程序 53 第四章 开发环境 55 微软组件对象模型 56 使用 ArcObjects 进行开发 68 Visual Basic 环境 77 Visual Basic 开发环境 90 Visual C++ 97 .NET 应用程序接口 139 Java 应用程序接口 175 C++应用程序接口 189 第五章 许可与部署 213 ArcGIS 许可选项 214 独立可执行许可的初始化 215 部署 ArcGIS 引擎运行时 225 为 ArcGIS 引擎应用程序授权 236 第六章 开发情景 239 用 ActiveX 建立应用程序 240 用 Visual JavaBeans 建立应用程序 262 用 Windows 控件建立应用程序 288 建立命令行 Java 应用程序 314 建立命令行 C++应用程序 331 附录 A:理解对象模型图 345 解释对象模型图 346 附录 B:ArcGIS 开发资源 349 ArcGIS 软件开发工具包 350 ArcGIS 开发在线 353 术语表 355 ·ArcGIS Engine 开发指南 iv 1 ArcGIS Engine 简介 ArcGIS Engine 简介 ESRI®的ArcGIS® Engine是一个用于建立自定义独立地理信息 系统(GIS)应用程序的平台,支持多种应用程序接口(APIs), 拥有许多高级GIS功能,而且构建在工业标准基础之上。 本章将向开发人员介绍 ArcGIS Engine 开发工具包和 ArcGIS Engine 运行时软件,讨论如何使用 ArcGIS Engine 及其不同组 件等问题。 本章涵盖以下主题: ● ArcGIS 9 概览 ● ArcGIS Engine 概览 ● ArcGIS Engine 用户 ● ArcGIS Engine 功能 ● 本书的描述 ArcGIS 9 概览 ArcGIS 9 概览 需要定制 ArcGIS 桌面应用程序或 操作 ArcGIS 服务器的开发人员应 参考《ArcGIS Desktop 开发指南》 和《ArcGIS Server 管理员和开发 指南》。 ArcGIS 为实现单用户或多用户桌面和服务器 GIS 系统提供了一个可伸 缩的框架。本书集中阐述使用 ArcGIS Engine 建立和配置自定义应用 程序的方法。本书对那些需要将制图和 GIS 功能嵌入到自定义应用程 序的开发人员而言非常有用。本书概览了 ArcGIS Engine 及其组件, 以及 ArcGIS Engine 为开发人员建立和配置自定义 GIS 应用程序所能 提供的解决方案。此外,本书还提供了几个情景描述,通过代码示例 展示了 ArcGIS Engine 可以开发的应用程序类型。 ArcGIS 9 概览 ArcGIS 9 是建立完整 GIS 的一个 GIS 软件产品集成体系。该体系建立 在 ArcObjects 这个共享的 GIS 软件组件公用库基础之上。ArcGIS 9 由四个关键部分组成: ArcGIS Desktop—高级 GIS 应用程序的一个集成套件。 ArcGIS Engine—通过多种应用程序接口建立自定义应用程序的 嵌入式 GIS 组件库。 ArcGIS Server—为企业和 Web 计算框架建立服务器端 GIS 应用程 序的一个平台,可用于建立 Web 服务和 Web 应用程序。 ArcIMS—通过开放 Internet 协议发布地图、数据和元数据的 GIS Web 服务器。 2·ArcGIS Engine 开发指南 ArcGIS 9 概览 GIS 框架中的每个部分都包括 ArcSDE 网关—管理存储在关系数据库管 理系统(RDBMS)中的地理数据库的一个接口。 ArcGIS 是一个建立地理信息系统的平台。ArcGIS 9 在原有版本的基础 上扩展了一些新功能,主要包括地理处理、三维可视化和开发工具等 几个方面。在这个版本中新增了两个产品,即 ArcGIS Engine 和 ArcGIS Server,使 ArcGIS 成为了应用程序和服务器开发的完整体系。 ArcGIS 的开发途径多种多样,开发人员可以通过以下几种方法进行开 发: 配置/定制诸如 ArcMap 和 ArcCatalog 等 ArcGIS 应用程序。 扩展 ArcGIS 的结构和数据模型。 用 ArcGIS Engine 将地图和 GIS 功能嵌入到其他应用程序中。 用 ArcGIS Engine 建立和部署自定义桌面应用程序。 用 ArcGIS Server 建立 Web 服务和 Web 应用程序。 用 ArcObjects 这个软件组件可以建立和扩展 ArcGIS 系统。ArcObjects 包括各种各样的可编程组件,从细粒度对象,如几何对象,到粗粒度 对象,如可用于与现有 ArcMap 文档进行交互的地图对象。这些组件集 成了开发人员所需的全部 GIS 功能。 ArcGIS 9 的开发可以跨所有产品 ( Engine、Server 和 Desktop)。开 发人员可以用标准编程框架操作 ArcObjects 来扩展 ArcGIS Desktop, 用 ArcGIS Engine 建立自定义应用程 序,也可以用 ArcGIS Server 实现企 业级 GIS 应用程序。 如前所述,本书集中阐述使用 ArcGIS Engine 建立和部署自定义应用程序的 方法。如果要定制 ArcGIS Desktop 应 用程序或使用 ArcGIS Server,请参阅 《 ArcGIS Desktop 开 发 指 南 》 和 《ArcGIS Server 管理员 和 开发指 南》。 第一章·ArcGIS Engine 简介·3 ArcGIS 9 概览 ArcGIS 系统可以通过多种编程框架进行开发,包括:C++、COM、.NET 和 Java。 用 ArcObjects 建立的 ArcGIS 产品体系结构的每个部分代表了不同的 应用程序开发容器,包括桌面、嵌入式 Engine 和服务器。 ArcGIS Desktop 包括一系列具有用户界面组件的 Windows 桌面应用程 序框架(如地图、目录、工具箱和 Globe 等)。ArcGIS Desktop 有三个 功能层次(ArcView、ArcEditor 和 ArcInfo),而且可以使用 ArcGIS Desktop 开发工具包进行定制和扩展。 ArcGIS Desktop 的软件开发工具包(SDK)包含在 ArcView、ArcEditor 和 ArcInfo 中,而且支持 COM 和.NET 编程框架。许多开发人员应用 ArcGIS Desktop 的软件开发工具包来增加扩展功能、添加新的 GIS 工 具、自定义用户接口,甚至对 ArcGIS Desktop 应用程序进行完全扩展 以提高专业 GIS 的生产能力。 ArcGIS Server 定义和实现了一系列标准的 GIS Web 服务(如地图、数 据访问、地理编码等服务),并支持基于服务器 ArcObejcts 的企业级 应用程序开发。 4·ArcGIS Engine 开发指南 ArcGIS 9 概览 ArcGIS Engine 及其开发工具包将 在本章及本书的后续部分详细讨 论。 开发人员可以利用 ArcGIS Server 开发工具包建立中央服务器,以驻 留 GIS 功能并允许多用户访问、在 GIS 大型中央数据库上执行后台 (back office)处理、建立和发布 GIS Web 应用程序以及执行分布式 GIS 计算。 本指南的焦点—ArcGIS Engine,是一个简单的、独立于应用程序的 ArcObjects 编程环境。其 SDK 提供了一系列嵌入式、应用于 ArcGIS Desktop 应用程序框架之外的 ArcGIS 组件(例如,地图对象作为 ArcGIS Engine 的一个部分进行管理,而不是在 ArcMap 中管理)。为了给广大 的用户群部署 GIS 系统,开发人员可以使用 ArcGIS Engine 开发工具 包建立具有简单接口的集中式 GIS 解决方案,以访问现有用户应用程 序中的任何 GIS 功能集或嵌入式 GIS 逻辑。 第一章·ArcGIS Engine 简介·5 ArcGIS Engine 概述 ArcGIS Engine 概览 ArcGIS Engine 开发工具包和运行 时(Runtime)可用于建立和部署用 户自定义解决方案。 6·ArcGIS Engine 开发指南 ArcGIS Engine 的组件 ArcGIS Engine 是开发人员用于建立自定义应用程序的嵌入式 GIS 组件 的一个完整类库。开发人员可以使用 ArcGIS Engine 将 GIS 功能嵌入 到现有的应用程序中,包括 Microsoft Office 的 Word 和 Excel 等产 品,也可以建立能分发给众多用户的自定义 高级 GIS 系统应用程序。ArcGIS Engine 由 一个软件开发工具包和一个可以重新分发 的、为所有 ArcGIS 应用程序提供平台的运 行时(runtime)组成。 ArcGIS Engine 的五个组成部分概括如下: 1. 基本服务—由 GIS 核心 ArcObjects 构 成,几乎所有 GIS 应用程序都需要, 如要素几何体和显示。 2. 数据存取—ArcGIS Engine 可以对许 多栅格和矢量格式进行存取,包括强大而灵活的地理数据库。 3. 地图表达—包括用于创建和显示带有符号体系和标注功能的地 图的 ArcObjects,及包括创建自定义应用程序的专题制图功能的 ArcObjects。 4. 开发组件—用于快速应用程序开发的高级用户接口控件和用于 高效开发的一个综合帮助系统。 5. 运行时选项—ArcGIS Engine 运行时可以与标准功能或其他高级 功能一起部署。 除运行时选项外,这些部分都可以通过 ArcGIS Engine 的软件开发工 具包获得。ArcGIS Engine 运行时及其选项虽然也是自定义 GIS 应用程 序开发不可或缺的一部分,但涉及到特定的应用程序部署,因此被认 为是一个独立的组成部分。 ArcGIS Engine 软件开发工具包 ArcGIS Engine 开发工具包是一个基于组件的软件开发产品,用于建立 和部署自定义 GIS 和制图应用程序。ArcGIS Engine 开发工具包不是一 个终端用户产品,而是一个应用程序开发人员的工具包。可以用 ArcGIS Engine 开发工具包建立基本的地图浏览器或综合、动态的 GIS 编辑工 具。使用 ArcGIS Engine 开发工具包,开发人员在建立定制的地图接 口方面具有前所未有的灵活性。开发人员可以使用几个 API 中的任何 一个来建立独一无二的应用程序,或者将 ArcGIS Engine 组件与其他 软件组件组合起来实现地图与用户管理信息之间的协同关系。 ArcGIS Engine 概览 第三章,“使用 ArcGIS 控件进行开 发”,将详细讨论这些可视化组件的 使用。 使用 ArcGIS Engine,地图本身可以是应用程序的一个次要元素或核心 部分。例如,如果应用程序的重点是有关商业信息的数据库,当应用 程序的用户执行一个数据库查询时,ArcGIS Engine 可以使该应用程序 显示一幅高亮显示感兴趣商业区位置的地图。 ArcGIS Engine 开发工具包可以访问 GIS 组件或 ArcObjects 的大型集 合,这些 GIS 组件或 ArcObjects 分别属于前面讨论过的类目—基本服 务、数据存取和地图表达。ArcGIS Engine 的第四个部分—开发组件也 包含在 ArcGIS Engine 开发工具包中。这些都是用于建立高质量地图 用户接口的增值开发控件。ArcGIS Engine 为辅助应用程序开发提供了 下列 ArcGIS 控件或可视化组件: MapControl PageLayoutControl SceneControl GlobeControl ToolbarControl TOCControl ReaderControl ToolbarControl 中使用的命令、工具和菜单集合 一个基于控件的 ArcGIS Engine 应用程序 第一章·ArcGIS Engine 简介·7 ArcGIS Engine 概览 8·ArcGIS Engine 开发指南 ArcGIS Engine 运行时部署选项 不同级别功能的可用性由一个软件 授权文件控制,该软件授权文件可 以由终端用户或应用程序开发人员 进行配置。要了解有关部署和配置 ArcGIS Engine 运行时方面更详细 的信息,请参考第五章,“许可与部 署”。 ArcGIS Engine 运行时(Runtime) ArcGIS Engine 的最后一个组件就是其运行时选件。用 ArcGIS Engine 软件开发工具包建立的所有应用程序为成功执行都需要合适级别许可 的ArcGIS Engine运行时。ArcGIS Engine运行时是建立ArcGIS Desktop 的平台,因此,在 ArcGIS Engine 应用程序开发人员允许的情况下, ArcGIS Desktop 应用程序的用户可以执行基于 Engine 的自定义应用 程序。ArcGIS Engine 运行时有多种选项,从标准版本一直到企业版本。 标准 ArcGIS Engine 功能 标准 Engine 运行时提供所有 ArcGIS 应用程序的核 心功能。这个级别的 ArcGIS Engine 运行时可以操 作几种不同的栅格和矢量格式、进行地图表达和创 建以及通过执行各种空间或属性查询查找要素。这 个级别的 ArcGIS Engine 运行时还可以进行基本数 据创建、编辑 Shapefile 和简单的个人地理数据库 及 GIS 分析。 地理数据库更新选项 ArcGIS Engine 运行时地理数据库更新选项增加了 创建和更新多用户企业地理数据库的功能,包括操 作程式(schema)和版本地理数据库。地理数据库 更新选项开放了 ArcGIS Engine 运行时运行自定义 方案所需的几个必要的 ArcObjects。这些方案包括 GIS 数据自动化和 编译及地理数据库要素的创建和维护。地理数据库更新选项提供了建 立诸如拓扑、子类和几何网络等地理数据库行为的程序实现。 其他 ArcGIS Engine 运行时选项 空间分析 3D 分析 StreetMap USA 地理数据库更新运行时选项 数据创建 数据管理 ArcGIS Engine 标准功能 地图交互 地图创建 地图分析 数据创建(shapefile 和个人地理数据库) 开发控件 开发技术 通过 ArcSDE 访问 RDBMS 的 ArcGIS Engine 开发人员可以为安装和配置 了 ArcGIS Engine 运行时地理数据库更新选项的终端用户建立和部署 多用户编辑应用程序。 ArcGIS Engine 的其他选项 ArcGIS Engine 运行时还有其他三个运行时选项: 1. 空间分析选项—ArcGIS Engine 运行时空间分析选项提供了强大 的功能集,允许应用程序创建、查询和分析基于像元的栅格数据。 这种分析允许用户生成其数据方面的信息、确定空间关系、查找 合适地址及计算从一个点到另一个点的旅行累积成本。这个运行 时选项支持的其他高级应用程序还包括坡度、坡向计算和用数字 高程模型生成等高线等功能。 ArcGIS Engine 概览 2. 3D 分析选项—ArcGIS Engine 运行时 3D 分析选项实现了数据的三 维可视化。与标准 ArcGIS Engine 相比,这个选项补充了从多个 视点观察表面和确定一个选择位置的可视区的组件。 SceneControl 和 GlobeControl 提供接口浏览用于可视化数据、创 建表面和分析表面的多层 3D 数据和全球数据。 3. StreetMap USA 选项—StreetMap USA 选项提供了美国街道制图、 地址匹配和基本路径选择功能。StreetMap 图层自动管理、标注和 绘制要素,如地方里程碑、街道、公园、水体及其他要素,生成 了美国丰富的街道网络地图。所有数据都存储在 CD-ROM 上的压缩 格式文件中。 第一章·ArcGIS Engine 简介·9 ArcGIS Engine 用户 10·ArcGIS Engine 开发指南 用 GlobeControl 建立的一个 Java 应用程序 许多用户需要对 GIS 进行专门的轻便型访问。他们需要的远少于诸如 ArcView 这样完整的 GIS 应用程序,但需要在其应用程序中访问专门的 GIS 逻辑。ArcGIS Engine 可以为这些需要定制专门 GIS 应用程序的用 户提供一个低成本的、轻便的选择。 独立应用程序开发人员 许多 GIS 应用程序的潜在用户都不是 GIS 专业人员,他们不经过高强 度的学习就不能充分利用市场上现有的综合工具。为了给这些非 GIS 专业用户提供空间解决方案,开发人员需要建立专门领域易于使用的 应用程序,将综合 GIS 系统的强大功能集成到用户界面友好的应用程 序中。这些应用程序如果从零开始建立,则需要大量的开发工作,而 且可能在时间或成本上效率很低。 用户可以使用 ArcGIS Engine 开发工具包成功地创建独立的应用程序。 使用 ArcGIS Engine 开发工具包可以创建多种类型的应用程序,从图 形用户界面(GUI)应用程序到命令行、批处理应用程序。GUI 应用程 序要大量用到开发工具包中的 ArcGIS 控件。这些控件包括创建成熟的 前端应用程序所需要的所有方法。开发人员可以选择某种 API 以便将 ArcGIS 控件与第三方组件进行集成,为自定义 ArcGIS Engine 应用程 序建立唯一的用户界面。 ArcGIS Desktop 用户 ArcGIS Desktop 应用程序 ArcMap 是创建自定义应用程序所需数据和地 图的一个非常好的软件。ArcGIS Engine 提供的 MapControl 和 PageLayoutControl 可 以 操 作 ArcMap 中 创 建 的 地 图文档。 SceneControl 和 GlobeControl 控件可以显示在 ArcScene 和 ArcGlobe ArcGIS Engine 用户 应用程序中制作的文档。使用 ArcGIS Desktop 应用程序创建和管理自 定义应用程序所需的地图可以节省大量的开发工作。ArcGIS Desktop 还提供工具建立和管理地理数据库、Shapefile 和其他形式的空间数 据。 ArcGIS Desktop 的底层组件就是组成 ArcGIS Engine 的 ArcObjects 组件。这允许每个 ArcGIS Desktop 用户运行 ArcGIS Engine 应用程序。 开发人员可以为 ArcGIS Desktop 用户开发和部署基于 Engine 的应用 程序,或者扩展 ArcToolbox,使其具有用 ArcGIS Engine 开发工具包 建立的自定义工具集。 ArcGIS Server 用户 ArcGIS Server 管理员可以为 ArcGIS Engine 应用程序提供服务器对象 和 Web 服务。这允许将桌面功能与服务器功能进行集成。 第一章·ArcGIS Engine 简介·11 ArcGIS Engine 功能 ArcGIS Engine 功能 如果部署了的话,右边列出的功能 项应包含在标准 ArcGIS Engine 运 行时功能中,不需要任何其他运行 时选项。 软件授权文件控制着各级 ArcGIS Engine 运行时功能的可用性。要了 解有关部署和配置 ArcGIS Engine 运行时方面更多的信息,请参考第 五章,“许可与部署”。 ArcGIS Engine 的性能非常强大。ArcGIS Engine 开发人员可以通过使 用其开发工具包实现下列及许多其他功能: 显示具有多个图层,如道路、河流和边界等的地图。 地图漫游和缩放。 识别地图上的要素。 搜索和查找地图上的要素。 显示字段值的文本标注。 绘制航空相片或卫星影像。 绘制描述性文本。 用线、框、区域、多边形和圆选择要素。 选择与某些要素的距离在一定范围内的要素。 用结构化查询语言(SQL)表达式查找和选择要素。 用专题方法,如 value map、class breaks 和 dot density 等为 要素着色。 动态显示实时或时间序列数据。 通过地理编码地址或街道交叉口在地图上查找位置。 转换地图数据的坐标系统。 在要素几何形状上执行几何操作以创建缓冲区、计算差异、发现 交叉、合并或反交叉。 处理要素形状或旋转地图。 创建和更新地理要素及其属性。 编辑要素 开发人员可以使用 ArcGIS Engine 开发工具包建立能在地理数据库或 Shapefile 中创建、修改和删除矢量要素的应用程序。标准 ArcGIS Engine 运行时用于运行能编辑 Shapefile 或个人地理数据库简单要素 的应用程序。然而,要使用企业地理数据库的全部功能,需要 ArcGIS Engine 运行时的地理数据库更新选项。 12·ArcGIS Engine 开发指南 ArcGIS Engine 功能 用 MapControl 开发的一个应用程 序,利用了 ArcGIS Engine 运行时 的空间选项 空间建模和分析 用户可以通过增加 ArcGIS Engine 运行时的空间选项来扩展 ArcGIS Engine 的功能。这个选项提供更广泛更强大的空间建模和分析功能。 用户可以创建、查询、制图和分析基于像元的栅格数据;执行栅格-矢 量一体化分析;从现有数据生成新的信息;跨多个数据层查询信息; 以及在自定义 ArcGIS Engine 应用程序中完全集成基于像元的栅格数 据和矢量数据。 例如,用户可以: 将要素(点、线或多边形)转换为栅格。 由要素或栅格创建基于距离或邻近性的栅格缓冲区。 由点要素生成密度图。 生成等高线、坡度、可视区、坡向和山体阴影。 执行格网(grid)分类和显示。 使用 TIFF、BIL、IMG、USGS DEM、SDTS、DTED 及其他标准格式的 数据。 第一章·ArcGIS Engine 简介·13 ArcGIS Engine 功能 基于 GlobeControl 应用程序的 Java 代码 14·ArcGIS Engine 开发指南 表面、查询表面、确定表 文档 括漫游和缩 表面要素,如建筑物等 ) 最陡路径。 三维可视化及其他 ArcGIS Engine 运行时 3D 选项进一步扩展了 ArcGIS Engine 的功能, 使开发人员可以用 Scene 和 Globe 控件建立高效可视化和分析表面和 全球(Globe)数据的应用程序。开发人员可以创建能从多个视点查看 面上被选位置的可视区域、通过在表面上叠 加栅格和矢量数据显示逼真的透视影像。 例如,开发人员可以: 显示 Scene 和 Globe 执行交互式透视图查看,包 放、旋转、倾斜和飞行模拟以便表达和分 析。 显示现实世界 执行可视区分析和视线(line-of-sight 分析、现场高度插值、剖面图制作及确定 显示了一个基于 SceneControl 的应用程序 ArcGIS Engine 功能 街道级数据 StreetMap USA 提供了全美详细的街道数据。使用 StreetMap USA 运行 时选项,用户可以在其 ArcGIS Engine 应用程序中使用这些数据。使 用这个选项,用户可以象使用任何其他要素类数据集一样使用 StreetMap 数据源。StreetMap USA 选项扩展了 ArcObjects 内的底层 地理数据库对象模型,这样用户应用程序可以将 StreetMap 数据源作 为地理数据库对象无缝地使用。 StreetMap USA 运行时选项提供下列功能: 全国性的地址匹配 街道和里程碑数据库 StreetMap 图层组,可以在不同比例尺上显示不同的细节层次 基本的街道路线选择 第一章·ArcGIS Engine 简介·15 ArcGIS Engine 概述 如何开始 第六章“开发情景”提供了一些 ArcGIS Engine 应用程序的示例。 ArcGIS 开发帮助系统中也包括一 些其他示例。 16·ArcGIS Engine 开发指南 websphere studio visual studio.net 一旦安装了 ArcGIS Engine 开发工具包,就可以开始开发 ArcGIS Engine 应用程序。然而,好的应用程序需要仔细地规划;使用 ArcObjects 进行开发也不例外。在开始进行开发前,通读并使用(必 要的话)本节中的讨论和列出的可选清单。这些信息可以帮助用户做 出规划并确保用户处于正确的起点上。 确定应用程序类型 使用 ArcGIS Engine 可以开发多种应用程序。这些应用程序可以从执 行诸如数据库编辑和分析等简单的控制台到包含用于用户交互和地理 数据显示的控件和可视化组件的复杂 Windows 应用程序。一般地,有 三类 ArcGIS Engine 应用程序: 1. 独立的非可视化应用程序,如控制台和实用工具应用程序。 2. 独立的可视化应用程序,如 Windows 和基于控件的应用程序。 3. 嵌入式应用程序,如嵌入到现有应用程序中的组件。 开发的应用程序的类型最终将取决于当前项目的功能需求。 清单: 要开发的是何种类型的应用程序?是非可视化、可视化、还是嵌 入式应用程序? 用户是否打算将开发的功能移植到 ArcGIS Desktop 或 ArcGIS Server 产品中? 现在和未来用户希望支持何种平台?Windows?UNIX?还是两者 都支持? 选择 API 和开发环境 ArcGIS Engine 提供四种开发 APIs—COM、.NET、Java 和 C++,开发人 员可以使用任何支持这些 API 的开发环境进行应用程序开发。对于非 可视化应用程序,常用的语言选择包括 C++和 Java。对于可视化应用 程序,则有许多可用的 Windows 语言,如 Visual Basic(VB)6、C#.NET、 Java 和 Visual C++等。下面列出了适合 ArcGIS Engine 的一些可能的 APIs、开发环境和语言。 COM—Visual Studio 6.0(VB,VC++) .NET—Visual Studio.NET(VB.NET,C#) Java—Eclipse,WebsphereStudio,Intelli J,Jbuilder 等 C++—Visual Studio 6.0,C++ Builder 如何开始 本书的大部分地方使用 VB6 作为展 示多数代码概念的语言,而且也往 往是开始时最容易掌握的语言。第 四章“开发环境”阐述了 VB 及 ArcGIS Engine APIs 支持的其他一 些开发环境的编程方针。 为成功编译和运行应用程序,使用 的所有 ArcObjects 功能组或库都 要在开发环境中引用。ArcGIS Engine 中可用的各种类库将在第 二章“ArcGIS 软件体系结构”中讨 论。 “ArcGIS开发在线”可以从这个网 址访问: http://arcgisdeveloperonline.e sri.com。 第五章“许可与部署”将讨论这些 清单的各个方面。 开发环境的选择最终取决于开发人员熟悉的编程语言、希望提供给终 端用户的功能以及是否要与其他现有应用程序或技术集成。 清单: 开发人员最熟悉何种开发环境与语言? 打算使用何种 ArcGIS Engine API? 何种开发环境与语言最适合于待进行的开发类型? 开发应用程序 此时,假设已经制定好了一个合适的项目开发计划,开发人员准备着 手研究 ArcGIS Engine 软件开发工具包并开始开发应用程序。开发人 员可能希望开始查找应用程序功能必需的类库和对象。这个过程可以 使用开发帮助资源,包括 ArcGIS 开发帮助系统、开发指南系列、帮助 系统中的示例以及 ArcGIS 开发在线网站资源等。 清单: 查找需要的 ArcObjects 功能。 需要引用何种 ArcGIS Engine 类库? 运行应用程序需要何种 ArcGIS 许可? 是否需要 ArcGIS Engine 运行时选项? 计划如何部署应用程序? 是否实现了正确的许可检出代码? 部署应用程序 应该早在开始应用程序开发之前考虑应用程序的部署问题。ArcGIS Engine 应用程序的部署方式有多种,也可能有多种终端用户软件和许 可配置方式。因此,开发人员需要考虑许多问题。 清单: 是否会安装 ArcGIS Desktop 或 ArcGIS Engine 运行时或者两者都 安装?应用程序将采用何种许可? 终端用户在其系统中有何种 ArcGIS 许可?ArcInfo,ArcEditor 还是 ArcView ? 开发人员如何打包和部署应用程序? 将来是否需要提供新的版本? 如何分发开发的应用程序? 第一章·ArcGIS Engine 简介·17 使用本书 本书《ArcGIS Engine 开发指南》是一本介绍 GIS 独立应用程序开发的 入门教程。本书将介绍 ArcGIS Engine 开发工具包的所有组件、讨论 建立应用程序的相关方面、介绍支持的 APIs 并提供建立实际 GIS 应用 程序的开发情景,以帮助开发人员熟悉 ArcGIS Engine 组件模型。 为服务于大多数开发人员,本书中提供的代码示例大多数采用 COM Visual Basic 6 API。然而,开发情景涵盖了所有支持的 APIs 并有一 章专门用于阐述 API 的使用问题。 本书的前两章概览了 ArcGIS Engine 及其功能,包括其体系结构和组 件。其他几章集中阐述 ArcGIS Engine 支持的各种 API 在开发应用程 序中的使用。 章节安排 第一章,“ArcGIS Engine 简介”,向开发人员概述了 ArcGIS Engine 产品、功能及相关资源。 第二章,“ArcGIS 软件体系结构”,描述了 ArcGIS Engine 的体系结构 及软件组件在系统内的交互方式。 第三章,“使用 ArcGIS 控件”,描述了各种控件并提出了在应用程序开 发中使用它们时应考虑的问题。 第四章,“开发环境”介绍了 ArcGIS Engine 支持的多种 APIs。本章逐 一介绍了各种 API 从基本到高级的使用问题。 第五章,“许可与部署”详细介绍了各种许可选项,讨论了应用程序的 部署策略,包括初始化和许可校验。 第六章,“开发情景”指导开发人员利用每种支持的 API 进行几种类型 的独立应用程序的创建和部署。 本书还包含几个附录,提供了有关 ArcGIS 开发帮助系统及其他开发资 源中对象模型图的详细信息。 18·ArcGIS Engine 开发指南 ArcGIS Engine 概述 典型安装中安装的是 VB6 版本的 ArcGIS 开发帮助。按照自定义安装 过程可以安装其 C++、Java 或.NET 版本。 开发资源 下面描述了 ArcGIS 开发人员可用的一些资源。有关开发人员可用资源 的更深入讨论列在附录 B 中。 ArcGIS 开发帮助系统 ArcGIS 开发帮助系统是 ArcObjects 开发新手和熟练者的一个基本资 源。该系统包含使用 ArcObjects 进行开发的一些信息,包括示例代码、 技术文档和对象模型图。此外该系统还是 ArcObjects 中各个对象相关 信息的参考指南。Visual Basic、.NET、Java 和 C++的开发人员都可 以获得该帮助系统。可以从 Windows“开始”按钮下的 ArcGIS 程序组 中启动 ArcGIS 开发帮助系统。 ArcGIS 开发系列 本书是 ArcGIS 开发指南丛书中的一本。 《ArcGIS Desktop 开发指南》用于指导开发人员定制或扩展 ArcGIS Desktop 应用程序,如 ArcMap 或 ArcCatalog。开发人员可以使用 Visual Basic for Application(VBA)定制或用 Visual Basic、Visual C++ 或.NET 扩展应用程序。 《ArcGIS Server 管理员与开发指南》用于指导开发人员使用 ArcGIS Server 建立自定义服务器应用程序。服务器开发人员可以建立用于简 单制图或包含高级 GIS 功能的 Web 服务和 Web 应用程序。具有代码示 例的几个情景展示了应用多个 ArcGIS Server 开发工具包之一可以开 发的应用程序的不同类型。该书也可以作为 ArcGIS Server 管理员指 南。 第一章·ArcGIS Engine 简介·19 开发资源 20·ArcGIS Engine 开发指南 http://arcgisdeveloperonline.e sri.com上的ArcGIS开发在线网站 http://support.esri.com网站上 的ESRI技术支持中心 http://campus.esri.com上的ESRI 虚拟校园网站 ArcGIS 开发在线网站 ArcGIS 开发在线是网络版的 ArcGIS 开发帮助系统,可以通过下面的 URL 访问: http://arcgisdeveloperonline.esri.com。 在线网站的优点之一就是可以通过 Web 浏览器和一个到 Internet 的连 接来访问。该网站在不断更新,使开发者可以获得最新的帮助信息。 ESRI 技术支持中心 ESRI技术支持中心网站http://support.esri.com上包含所有ArcGIS 产品的软件信息、技术文档、示例、论坛和知识库。 ArcGIS 开发人员可以充分利用论坛、知识库和示例部分来辅助其 ArcGIS 应用程序的开发。 培训 ESRI 为 ArcGIS 开发人员提供了许多课堂讲授和基于网络的培训课程。 这些课程包括从简单的 VBA 入门到更高级的 ArcGIS Desktop、 Engine 和 Server 组件开发。 要了解更多信息,访问http://www.esr.com并选择“培训和事件 (Training and Events)”选项卡。 ESRI虚拟校园可以直接通过http://campus.esri.com找到。 ArcGIS 软件体系结构 2 ArcGIS 软件已经在前几个版本的基础上演化为模块化、可伸缩 和跨平台的体系结构,该体系结构通过一个称为 ArcObjects 的 软件组件集来实现。 本章的核心内容是介绍 ArcGIS 9 的主要进展,并向读者介绍组 成 ArcGIS 系统的各种类库。 ArcGIS 软件体系结构 ArcGIS 软件体系结构 要了解有关 COM 的详细解释,请参 阅第四章“开发环境”的 COM 部分。 22·ArcGIS Engine 开发指南 ArcGIS Engine ArcGIS 软件已经在前几个版本的基础上演化为模块化、可伸缩和跨平 台的体系结构,它通过一个称为 ArcObjects 的软件组件集来实现。 本节的核心内容是介绍 ArcGIS9 的主要进展,并向读者介绍组成 ArcGIS 系统的各种类库。 ArcGIS 软件体系结构支持许多产品,每个产品都有自己独特的需求。 作为构成 ArcGIS 的组件,ArcObjects 就是为支持这些需求而设计和建 立的。本章将介绍 ArcObjects。 ArcObjects 是用 C++ 语言编写的独立于平台的一套软件组件,提供支 持桌面系统胖客户端和瘦客户端 GIS 应用及服务器 GIS 应用服务。 如上所述,ArcObjects 开发采用了 C++语言;此外,ArcObjects 还采 用了微软的组件对象模型(COM)。人们通常认为 COM 只是简单地规定 了对象在内存中如何实现和建立以及组件之间如何交流。虽然这一点 是正确的,但 COM 还在操作系统层次上提供了一个坚实的基础设施以 支持使用 COM 构造任何系统。在微软 Windows 操作系统中,COM 基础设 施直接构建到操作系统中。对于其他非微软 Windows 的操作系统而言, 必须为 ArcObjects 提供 COM 基础设施。 并非所有 ArcObjects 组件都以同样的方式创建。除了基本功能之外, 对特定对象的需求随该对象的最终用途而变化。这种最终用途广义上 属于三个 ArcGIS 产品家族中的一个: ● ArcGIS Engine—对象在自定义应用程序中使用。Engine 中的 对象必须支持各种应用;简单的地图对话框、多线程服务器和 复杂的 Windows 桌面应用程序都有可能使用 Engine 对象。必须 很好地理解 Engine 内对象的依赖性。必须仔细考虑添加外部依 赖性到 ArcObjects 中的影响,因为新的依赖性会把不合需要的 复杂性引入到构建于 Engine 之上的应用程序安装中。 ● ArcGIS Server-对象在服务器框架中使用,通常对象的客户大 多数为远程客户。客户的远近程度可以从本地(可能在同一计 算机或网络上)到很远的地方(客户可能在互联网上)。服务器 内运行的对象必须是可伸缩的,且线程必须是安全的,以允许 在多线程环境下执行。 ● ArcGIS Desktop-对象在 ArcGIS 桌面应用程序中使用。许多用 户都具有丰富的 ArcGIS 桌面应用程序使用经验,其中包含大量 的对话框和属性页,使用户可以有效的使用对象的功能。包含 应用程序的用户可以修改其属性的对象应该具创建这些属性的 属性页。并非所有的对象都需要属性页。 ArcGIS 软件体系结构 第二章·ArcGIS 软件体系结构·23 ArcGIS Server ArcGIS Desktop 许多构成 ArcGIS 的 ArcObjects 同时在这三个 ArcGIS 产品中都有使用。 这两页上的产品图显示了这三个产品中都包含的基础服务、数据访问、 地图分析和地图表达组件类目中的对象。这四个类目包括了 ArcGIS 提 供给开发者和使用者的主要 GIS 功能。 对于开发者来说理解 ArcGIS 所有产品之间的公共功能是很重要的,因 为这意味着当使用特定的类目进行工作时,大量开发工作可以在 ArcGIS 产品之间移植而只需作少量更改。归根结底,这正是 ArcGIS 体系结构开发的方式。代码重用是构建模块化体系结构的主要好处, 但代码重用并非仅指以模块化方式创建组件。 ArcGIS 体系为开发者提供了丰富功能,但它并非一个封闭系统。ESRI 之外的开发者可以对 ArcGIS 体系进行扩展。开发者已经对 ArcGIS 体 系进行了多年的扩展;而 ArcGIS9 也不例外,同样可以进行扩展。但 是,ArcGIS 9 引入了许多使用 ESRI 和用户创建的对象的新方法。要实 现这些新方法,这些组件必须满足其他一些要求,以确保这些组件能 够在这个新的、显著改进的 ArcGIS 系统中运作。在外观上,ArcGIS 8 到 ArcGIS 9 的一些变化似乎很肤浅,其中一个例子就是原来的类库被 划分为更小的类库。但 ArcGIS 8.3 对象的方法和属性在 ArcGIS 9 中仍 然可用的事实表明,ArcObjects 的内部结构已经进行了大的更改。 ArcGIS 9体系的变化主要集中在以下四个关键概念上: ● 模块化-在模块化系统中,组件间的依赖性可以在一个灵活的 系统中很好地定义。 ● 伸缩性—ArcObjects 必须在所有预期操作环境中运行良好,包 括从单用户桌面应用程序到多用户/多线程服务器应用程序。 ● 多平台支持-ArcGIS Engine 和 Server 的 ArcObjects 能在多种 计算机平台上运行。 ● 兼容性-ArcObjects 9.0 的功能和编程方法应与 ArcGIS 8.3 保持相同。 模块化 作为 ArcGIS 8.3 的一部分,esriCore 对象库有效地把所有 ArcObjects 打包为一个大的 GIS 功能块,在其中间组件间没有区别。ArcObjects 组件分成许多小的组件群,这些组件群被打包在动态连接库(DLL)中。 这个大的类库尽管简化了外部开发人员的开发任务,但阻碍了软件的 模块化。将类型信息添加到所有的 DLL 中虽然是可能的,但会大大增 加外部开发者的负担,因此不是一个好的选择。此外,DLL 的结构并不 总是反映基于功能和依赖性的软件组件的最佳模块化方案。 ArcGIS 软件体系结构 通过特性和功能分析与用户需求和 基于三个 ArcGIS 产品家族的部署 选项匹配, ESRI 已经开发了 ArcGIS 9 的模块化体系结构。鼓励已经用 自定义组件对 ArcGIS 8 体系结构进 行了扩展的开发人员进行同样的过 程,重新把他们的源代码构建到类 似的模块化结构中。 要做的一个明显功能区分就是用户 界面和无用户界面代码。UI 类库趋 向于只包括在 ArcGIS Desktop 产品 中。 在这里,线程安全是指对多线程并 发对象的访问。 在考虑体系结构的模块化时,总要在性能和易管理性之间维持一种平 衡。对于每个标准,要考虑终端用户及支持终端用户所需的模块化。 例如,系统可以分解为许多仅仅包含很少对象的 DLLs。虽然这样可以 为部署选项提供很大的灵活性,需要最小的内存,但由于大量 DLLs 的载入和载出会影响系统的性能。相反地,包含所有对象的大 DLL 也 不是最合适的解决方案。了解对组件的需求可以将它们有效的打包到 DLLs 中。 ArcGIS 9 体系结构被分解为许多的类库。一个类库中可以包含任意数 量的 DLLs 和 EXEs。组件包含到某个类库中必须满足的要求要有明确 的定义。例如,诸如 esriGeometry 这样的类库(来自基本服务模块集) 要求线程安全、可伸缩、没有用户界面组件和能够在多种平台上部署 等条件。这些要求就与 esriArcMap(来自应用程序类目)不同,后者 的确需要用户界面组件并且只能包含在 Windows 类库中。 类库中的所有组件都共享放置在类库上的相同要求的集合中。不能将 一个类库拆分为更小的单元来分发。类库为所有的组件定义了命名空 间,并以适合用户所选 API 的形式出现。 ● 类型库(Type Library) -COM ● .NET 互操作套件(Interop Assembly)-.NET ● Java 包-Java ● 头文件-C++ 伸缩性 ArcGIS Engine 和 ArcGIS Server 中的 ArcObjects 组件必须是可伸缩的。 Engine 对象是可伸缩的,因为它们可被用在许多不同类型的应用程序 中;这些应用程序有的需要伸缩性而有的不需要。Server 对象也要求 是可伸缩的,以确保服务器可以处理许多用户的连接,并保证随着服 务器配置的增长,在服务器上运行的 ArcObjects 组件性能也能提高。 系统的伸缩性通过许多变量,包括系统的硬件和软件来实现。在这点 上,ArcObjects 通过有效地使用对象中的内存和在多线程过程中执行对 象的能力来支持伸缩性。 在讨论多线程应用程序时有两点考虑:线程安全和伸缩性。所有对象 的线程安全非常重要,但是仅有线程安全的对象并不是自动地意味着 创建多线程应用程序是非常简单的事情,或者所创建的应用程序在性 能上的确会有极大的改进。 24·ArcGIS Engine 开发指南 ArcGIS 软件体系结构 经典的每过程独生模型指应用线程 的所有线程仍会访问驻留了独生对 象的主线程。这样可以有效地将应 用程序变为单线程应用程序。 微软的 Windows 是一个小端格式的 平台,而 Sun 公司的 Solaris 是一 个大端格式的平台。 尽管 ArcGIS 版本的目的是限制 API 中的变化,但开发者还是应该在以 后的版本中彻底测试自己的软件。 包含在基础服务、数据访问、地图分析和地图表达类目中的 ArcObjects 组件都是线程安全的。这就意味着应用程序开发人员可以在多线程应 用程序中使用它们;但是,程序员仍然必须以这样一种方式编写多线 程代码,以避免由于死锁等情况造成的应用程序失败。 除了 ArcGIS9 中的 ArcObjects 组件需要线程安全之外,对 ArcObjects 使用的单元线程模型进行了分析,以确保 ArcOjects 可以在多线程进程 中有效地运行。一个称为“Threads in Isolation”的模型被用来保证 ArcObjects 体系能被有效地使用。 这个模型的工作方式是:将跨线程通信变为一个绝对最小化的通信, 或者最好完全移除该通信。为了使这种工作方式生效,ArcGIS 9 中的 独生对象更改为每线程独生和每过程非独生。在一个过程中驻留多个 独生对象的资源开销要小于停止跨线程通信获得的性能,在跨线程通 信中,在某个线程(一般是主 STA)中创建一个独生对象,而在另一 个线程中访问该独生对象。 ArcGIS 是可扩展系统,而为了使“Threads in Isolation”模型生效,所 有的独生对象必须遵循这个规则。如果用户在开发中创建了独生对象, 必须保证这些对象遵循这个规则。 多平台支持 如前所述,ArcObjects 组件都是 C++对象,意味着具有 C++编译器的 任何计算机平台都是 ArcObjects 的潜在平台。除 C++编译器外,平台 还必须支持 ArcObjects 需要的一些基本服务。 尽管许多平台差异不会影响 ArcObjects 组件的开发方式,但是有些差 异还是会影响代码开发的方式。不同计算体系结构的字节顺序在小端 格式和大端格式之间会发生变化。这最容易在对象向磁盘读写数据时 看到。用某种计算平台写入的数据如果用另一种计算平台读取会不兼 容,除非执行一些解码。所有的 ArcGIS Engine 和 ArcGIS Server 对象 都支持这种多平台保存模型。ArcObjects 组件总是使用小端格式模型保 存自己。当对象读取保存后的数据时,就会将其转换为合适的本地字 节顺序。除了字节顺序差异外,平台之间还存在其他一些功能上的差 异;例如,Windows 和 Unix 的目录结构使用不同的分隔符,分别为“\” 和“/”。另一个例子就是与具体平台有关的功能,如 OleDB 等。 兼容性 维护 ArcGIS 系统不同版本之间的兼容性非常重要,可以保证外部开发 人员不用修改其代码以在最新版本中工作。维护对象级别的兼容性是 第二章·ArcGIS 软件体系结构·25 ArcGIS 软件体系结构 ArcGIS9 开发工作的一个基本目标。尽管对象级别的兼容性得到了维 护,但 ArcGIS 8 和 ArcGIS 9 体系结构之间还是有一些变化会影响开 发人员,主要是软件编译方面的问题。 尽管用在 ArcGIS 8 中的软件只需很小的改变就可以用在 ArcGIS 9 中, 但理解这一点很重要:为了在 ArcGIS 9 上实现 ArcObjects 体系结构 中的任何已有投入,用户都必须审查有关 ArcGIS Engine 、ArcGIS Server 和 ArcGIS Desktop 的开发。 ESRI 理解统一软件架构的重要性,并且已经对 ArcGIS 9 进行了重大调 整,以便在 ArcObjects 上投资可以在多个产品中实现。如果用户已经 为 ArcGIS 8 体系结构创建了一些组件,就应该考虑新的 ArcGIS 9 体 系结构会如何影响用户开发的组件的实现方式。 26·ArcGIS Engine 开发指南 ArcGIS 应用程序接口 ArcGIS 应用程序接口 重要的是不要混淆 VC++支持的 COM API 与本地 C++ API。 由于 ArcObjects 是用 C++开发的, 有的情况下出于性能考虑使用了一 些与 C++兼容的数据类型。这些性 能考虑大多数会影响 ArcObjects 的内部 ,因此,使用一般接口不应 该对性能或 ArcObjects 开发产生 不利的影响。 ArcObjects 的功能可以通过四个应用程序接口(APIs)来 访 问 。选 择 使 用哪个 API 并不是一个简单的问题,而要取决于许多因素,包括:要 开发的 ArcGIS 产品、要开发的最终用户功能以及使用特定开发语言的 经验。ArcGIS Engine 支持的四种 API 是: ● COM-—任何 COM 生成语言(Visual Basic、Visual C++、Delphi 等)都可以使用这个 API。 ● .NET-—这个 API 支持 Visual Basic.NET 和 C#。 ● Java——Sun 公司的 Java2 平台标准编辑器。 ● C++ ——微软 VC++6.0 、微软 VC++.NET2003 、Sun Solaris Forte6 Update2、Linux GCC3.2 支持此 API。 操作 ArcObjects 时开发者可以使用 ArcObjects 提供的功能或用自己的 组件扩展 ArcObjects 的功能。在引用这些 APIs 时,使用和扩展 ArcObjects 架构之间是存在差别的。 使用 API 四个 APIs 全都支持使用 ArcObjects 的功能;但是,并不是所有 ArcObjects 实现的接口都在所有平台上支持。有些情况下,接口使用的 数据类型与某种 API 不兼容。在这种情况下,提供了接口的其他实现 以便开发者使用。接口后缀为“GEN”的命名习惯被用来表示这种接 口;IFoo 将有 IFooGEN 接口。所有 APIs 都支持这种接口;但是,如 果 API 支持非 GEN 接口,则可以继续使用特定 API 接口。 扩展 API 扩展 ArcObjects 就是指创建用户自己创造的对象并将它们添加到 ArcObjects 架构中。ArcObjects 几乎在所有方面都可以进行扩展。对 ArcObjects 体系结构扩展的支持随 API 不同而变化,而且有时也随 API 语言的不同而变化。 COM API 提供了最强大的系统扩展能力。这个 API 的限制就是对 Visual Basic 语言的限制。Visual Basic 不支持具有以下特征或特征之一的 接口的实现: ● 不是从 IUnknown 和 IDispatch 继承而来的接口。从 IGeometry 接口继承而来的 Icurve 接口就是由于这个原因不能在 VB 中实 现。 ● 名字以下划线开头的接口方法。用户在 ArcObjects 中找不到以 “_”开头的函数。 ● 方法中参数使用的数据类型不被 VB 支持。由于这个原因 IActiveView 不能在 VB 中实现。 第二章·ArcGIS 软件体系结构·27 ArcGIS 应用程序接口 28·ArcGIS Engine 开发指南 AO 支持的 API 之间的主要区别在数 据类型上。所有的 API 完全支持右 边显示的所有自适应数据类型。非 OLE 自适应数据间会产生的差异。 除了 VB 支持的接口限制外,COM 集合的二进制重用技术也不被 VB 支持。这就意味着 ArcObjects 体系中有部分组件是不能扩展的;自定 义特性(Custom Features)就是这样一个例子。实际上,上述 VB 限制 对绝大多数开发者没有多大的影响,因为受影响的 ArcObjects 所占百 分比很小,而且对于这么小百分比的部分而言,开发者似乎没有必要 扩展 ArcObjects 体系。其他 COM 语言如 Visual C++等就没有任何限制。 除了使用非 OLE 自适应数据类型的接口外,.NET API 支持 ArcObjects 的完全扩展(下表列出了所有 OLE 自适应数据类型)。 类型 描述 Boolean 值为 True 或 False 的数据项 unsigned char 8 位无符号数据项 double 64 位 IEEE 浮点数 float 32 位 IEEE 浮点数 int 有符号整型,大小与系统有关 long 32 位有符号整型 short 16 位有符号整型 BSTR 以长度为前缀的字符串 CURRENCY 8 字节固定点数 DATE 64 位,从 1899 年 12 月 30 日起的日期浮点分数 SCODE 用于 16 位系统,与 VT_ERROR 相应的内嵌错误 Typed enum myenum 有符号整型,大小与系统有关 Interface IDispatch 指向 IDispatch 接口的指针 Interface IUnkown 指向不是源自 IDispatch 的接口的指针 dispinterface Typename * 指向源自 IDispatch 的接口的指针 Coclass Typename * 指向一个 Coclass 名称(VT_UNKNOWN)的指针 [oleautomation] interface Typename* 指向源自 IDispatch 的接口的指针 SAFEARRAY (TypeName) TypeName 为上面任一种类型,这些类型的数组 TypeName* TypeName 为上面任一种类型,指向某个类型的指针 Decimal 96 位无符号二进制整型,以 10 的变量幂作为位制。 一个十进制数据类型提供一个数的大小和位制(如 坐标)。 OLE 自适应数据类型 Java 和 C++ APIs 在扩展 ArcObjects 方面有类似的限制。使用这些 APIs 的开发者只能创建自定义命令和工具。这些命令和工具可以在 ToolbarControl 中使用。这看起来是一种严厉的限制,但尽管存在这 种限制,这些 APIs 仍然对开发者具有很强的吸引力。ToolbarControl ArcGIS 应用程序接口 和其他 ArcGIS 控件一起提供了丰富的开发环境。ArcGIS Desktop 应用 程序是具有大量功能的专业 GIS 应用程序,但是如果简单来看,这些 应用程序可以分解为一系列工具条、一个 TOC 和地图浏览区。通过添 加命令和工具可以扩展桌面应用程序。通过类似的方法,开发者可以 使用四种 ArcGIS Engine APIs 中的任何一种构建具有丰富功能的应用 程序。 COM 和.NET APIs 只在微软 Windows 平台上被支持,而 Java 和 C++ APIs 可以在 ArcGIS Engine 支持的所有平台上都支持。 第二章·ArcGIS 软件体系结构·29 ArcGIS Engine 类库 30·ArcGIS Engine 开发指南 ArcGIS Engine 类库 ArcGIS Engine 类库 要了解每个类库的全部讨论,请参 考 ArcGIS 开发帮助系统中类库参 考部分的类库概览。 了解类库的依赖关系顺序非常重 要,因为它会影响到开发者在软件 开发时与类库的交互方式。例如, C++程序员必须根据类库依赖关系 顺序依次包含类库,以保证能正确 编译。对依赖关系的理解也有助于 部署开发人员开发的应用程序。 下面将概述 ArcGIS Engine 中的各个类库。本节中的各个模型图展示了 ArcGIS Engine 的类库架构。理解类库结构、它们的依赖关系和基本功 能将有助于开发人员了解 ArcGIS Engine 的组件。 下面根据依赖关系的顺序对类库进行讨论。模型图中在每个类库框的 右上角显示了其序列号。例如,作为 ArcGIS 体系结构基础的 System 类库,其编号为 1,而编号为 7 的 GeoDatabase 类库依赖于模型图中其 前面的 6 个类库—SyStem、SystemUI、Geometry、Display、Server 和 Output。 System 类库 System 类库是 ArcGIS 体系结构中最底层的类库。System 类库包含给 构成 ArcGIS 的其他类库提供服务的组件。System 类库中定义了大量开 发者可以实现的接口。AoInitializer 对象就是在 System 类库中定义的, 所有的开发者必须使用这个对象来初始化 ArcGIS Engine 和解除 ArcGIS Engine 的初始化。开发者不能扩展这个类库,但可以通过实现 这个类库中包含的接口来扩展 ArcGIS 系统。 SystemUI 类库 SystemUI 类库包含用户界面组件接口定义,这些用户界面组件可以在 ArcGIS Engine 中进行扩展。包含 ICommand、ITool 和 IToolControl 接 口。开发者用这些接口来扩展 UI 组件,ArcGIS Engine 开发人员自己 的组件将使用这些 UI 组件。这个类库中包含的对象是一些实用工具对 象,开发人员可以通过使用这些对象简化用户界面的开发。开发者不 能扩展这个类库,但可以通过实现这个类库中包含的接口来扩展 ArcGIS 系统。 Geometry 类库 Geometry 类库处理存储在要素类中的要素几何图形或形状或其他图形 元素。大多数用户会遇到的基本几何图形对象为 Point、MultiPoint、 Polyline 和 Polygon 。除了这些顶级实体外,就是作为多义线和多边 形的组成部分的几何图形,是组成几何图形的子要素。它们是 Segement、Path 和 Ring。Polyline 和 Polygon 由一系相连接的、构成 列 Path 的片段组成.一个片段由两个不同的点,即起始点和结束点, 以及一个定义这两点之间弯曲度的元素类型组成。片段的类型有 CircularArc、Line、EllipticArc 和 BezierCurve。所有几何图形对 象都可以有与其顶点相关联的 Z、M 和 IDs。所有的基本几何图形对象 都支持诸如 Buffer,Clip 等几何操作。几何子要素不可以由开发者扩 展。 第二章·ArcGIS 软件体系结构·31 ArcGIS Engine 类库 GIS 中的实体指的是现实世界中的要素;现实世界中要素的位置由一个 带有空间参考的几何图形来定义。空间参考对象,包括投影坐标和地 理坐标系统,都包括在 Geometry 类库中。开发者可以通过添加新的空 间参考和投影来扩展空间参考系统。 Display 类库 Display 类库包含用于显示 GIS 数据的对象。除了负责实际输出图像的 主要显示对象外,这个类库还包含表示符号和颜色的对象,它们用来 控制在显示上绘制时实体的属性。Display 类库还包含在与显示交互时 提供给用户可视化反馈的对象。开发者与 Display 最常用的交互方式 就是类似于 Map 对象或 PageLayout 对象提供的视图。Display 类库的 所有部分都能进行扩展;通常扩展的对象包括符号、颜色和显示反馈。 Server 类库 Server 类库包含允许用户连接并操作 ArcGIS Server 的对象。开发人 员 用 GISServerConnection 对 象 来 访 问 ArcGIS Server。通过 GISServerConnection 可以访问 ServerObjectsMananger 对象。用这个 对象,开发人员可以操作 ServerContext 对象,以处理运行于服务器 上的 ArcObjects。开发人员还可以用 GISClient 类库与 ArcGIS Server 进行交互。 Output 类库 Output 类库用于创建图形输出到诸如打印机和绘图仪等设备及诸如增 强型元文件和栅格图像格式(JPG、BMP 等)等硬拷贝格式。开发人员 用这个类库中的对象及 ArcGIS 系统的其他部分创建图形输出。通常是 Display 和 Carto 类库中的对象。开发者可以为自定义设备和输出格式 扩展 Output 类库。 GeoDatabase 类库 Geodatabase 类库为地理数据库提供了编程 API。地理数据库是建立在 标准工业关系型和对象关系数据库技术之上的地理数据仓库。 Geodatabase 类库中的对象为 ArcGIS 支持的所有数据源提供了一个统 一编程模型。GeoDatabase 类库定义了许多由 ArcObjects 架构中更高 级的数据源提供者实现的接口。开发者可以扩展地理数据库,以支持 特定类型的数据对象(要素、类等);此外,Geodatabase 类库还有用 PlugInDataSource 对象添加的自定义矢量数据源。地理数据库支持的 本地数据类型不能扩展。 GISClient 类库 GISClient 类库允许开发者使用 Web 服务;这些 Web 服务可以由 ArcIMS 和 ArcGIS Server 提供。GISClient 类库中包含用于连接 GIS 服务器以 32·ArcGIS Engine 开发指南 ArcGIS Engine 类库 栅格数据对象(RDO)是一个 COM API,提供支持基于文件的栅格数据 的显示和分析功能。 使用 Web 服务的对象。该类库支持 ArcIMS 的图像和要素服务。 GISClient 类库提供以无态方式直接或通过 Web 服务目录操作 ArcGIS Server 对象的通用编程模型。在 ArcGIS Server 上运行的 ArcObjects 组件不能通过 GISClient 接口来访问。要直接获得访问在服务器上运 行的 ArcObjects,开发人员应使用 Server 类库中的功能。 DataSourcesFile 类库 DataSourcesFile 类库包含用于基于文件数据源的 GeoDatabase API 实现。这些基于文件的数据源包括 shapefile、coverage、TIN、CAD、 SDC、StreetMap 和 VPF。开发者不能扩展 DtaSourcesFile 类库。 DataSourcesGDB 类库 DataSourcesGDB 类库包含用于数据库数据源的 GeoDatabase API 实现。 这些数据源包括 Microsoft Access 和 ArcSDE 支持的关系型数据库管 理系统—IBM、DB2、Informix、Microsoft SQL Server 和 Oracle。开 发者不能扩展 DataSourcesGDB 类库。 DataSourcesOleDB 类库 DataSourcesOleDB 类 库 包 含 用 于 Microsoft OLE DB 数据源的 GeoDatabase API 实现。此类库只能用在 Microsoft Windows 操作系统 上。这些数据源包括支持数据提供者和文本文件工作空间的所有 OLE DB。开发者不能扩展 DataSourcesOleDB 类库。 DataSourcesRaster 类库 DataSourcesRaste 类库包含用于栅格数据源的 GeoDatabase API 实现。 这些数据源包括 ArcSDE 支持的关系型数据库管理系统—IBM、DB2、 Informix、Microsoft SQL Server 和 Oracle,以及其支持的 RDO 栅格 文件格式。当需要支持新的栅格格式时,开发者不扩展这个类库,而 是扩展 RDO。开发者不能扩展 DataSourcesRaster 类库。 GeoDatabaseDistributed 类库 GeoDatabaseDistributed 类库通过提供地理数据库数据导入和导出工 具,可以支持对企业级地理数据库的分布式访问。开发者不能扩展 GeoDatabaseDistribute 类库。 Carto 类库 Carto 类库支持地图的创建和显示;这些地图可以在一幅地图或由许多 地图及其地图元素组成的页面中包含数据。PageLayout 对象是驻留一 幅或多幅地图及其底土元素的容器。地图元素包括指北针、图例、比 例尺等。Map 对象包括地图上所有图层都有的属性—空间参考、地图比 例尺等,以及操作地图图层的方法。可以将许多不同类型的图层加载 到地图中。 第二章·ArcGIS 软件体系结构·33 ArcGIS Engine 类库 34·ArcGIS Engine 开发指南 ArcGIS Engine 类库 ArcGIS Server 为其 MapServer 使 用 MapServer 对象。 不同的数据源通常有相应的图层负责数据在地图上的显示,矢量要素 由 FeatureLayer 对象处理,栅格数据由 RasterLayer 对象处理,TIN 数据由 TinLayer 对象处理,等等。必要的话,图层可以处理与之相关 数据的所有绘图操作,但通常图层都是一个相关的 Renderer 对象。 Renderer 对象的属性控制着数据在地图中的显示方式。Renderers 通 常用 Display 类库中的符号来进行实际绘制,而 Renderer 只是将特定 符号与待绘实体的属性相匹配。Map 对象和 PageLayout 对象可以包含 元素。元素用其几何图形定义其在地图或页面上的位置,用行为控制 元素的显示。包括用于基本形状、文字标注和复杂标注等的元素。Carto 类库还支持地图注释和动态标注。 尽管开发者可以在其应用程序中直接使用 Map 和 PageLayout 对象,但 通常来说开发者更经常使用更高级的对象,如 MapControl 、 PageLayoutControl 或 ArcGIS 应用程序。这些高级对象简化了一些任 务,尽管它们也提供对更低级别的 Map 和 PageLayout 对象的访问,允 许开发者更好的控制对象。 Map 和 Pagelayout 对象并不是 Carto 类库中提供地图和页面绘制的仅 有对象。MxdServer 和 MapServer 对象都支持地图和页面的绘制,但不 是绘制到窗口中,而是直接绘制到文件中。 开发者可以用 MapDocument 对象保存地图和地图文档(.mxd)中页面 布局的状态,以便在 ArcMap 或 ArcGIS 控件中使用。 Carto 类库通常可以在许多方面进行扩展。自定义 Renderer、Layer 等都很普遍。自定义图层通常是向地图应用程序中加载自定义数据最 简单的方法。 Location 类库 Location 类库包含支持地理编码和操作路径事件的对象。地理编码功 能可以通过细粒度对象来完全控制访问,或通过 GeocodeServer 对象 提供的简化 API 来访问。开发者可以创建自己的地理编码对象。线性 参考功能提供对象用于向线性要素添加事件,用各种绘制方法来绘制 这些事件。开发者可以扩展线性参考功能。 NetworkAnalysis 类库 NetworkAnalysis 类库提供用于在地理数据库中加载网络数据的对象 并提供对象用于分析加载到地理数据库中的网络。开发者开以扩展 NetworkAnalysis 类库以便支持自定义网络追踪。这个类库目的在于操 作公共网络:供气管线、电力供应线网等。 第二章·ArcGIS 软件体系结构·35 ArcGIS Engine 类库 36·ArcGIS Engine 开发指南 和 PageLayout 控件的内容可以 RenderControl 只支持发布地图文 件(PMF)。 ArcGIS Engine 带有 150 多个命 令。 开发者用 Controls 类库来构建或扩展具有 ArcGIS 功能的应用程序。 ArcGIS Controls 通过封装 ArcObjects 并提供粗粒度的 API 简化了开 发过程。尽管这些控件封装了细粒度的 ArcObjects,但是并不限制对 这些细粒度的 ArcObjects 的访问。MapControl 和 PageLayoutControl 分别封装了 Carto 类库的的 Map 和 PageLayout 对象。ReaderControl 同时封装了 Map 和 PageLayout 对象,且在操作控件时提供了简化的 API。如果授权了地图发布程序,开发者可以以访问 Map 和 PageLayout 控件类似的方式访问内部对象。Controls 类库还包含实现一个目录表 的 TOCControl 及驻留操作合适控件的命令和工具的 ToolbarControl。 开发者通过创建自己的用于操作控件的命令和工具来扩展 Controls 类 库。为此 Controls 类库提供 HookHelper 对象。这个对象使得创建一 个操作任何控件及操作诸如 ArcMap 这样的 ArcGIS 应用程序的命令变 得非常简单。 GeoAnalyst 类库 发者可以通过创建新类 型的栅格操作来扩展 GeoAnalyst 类库。为使用这个类库中的对象,需 要 ArcGIS Spatial Analyst 或 3D Analyst 扩展模块许可,或者 ArcGIS Engine 运行时空间分析或 3D 分析选项许可。 3DAnalyst 类库 3Danalyst 类库包含操作 3D 场景的对象,其方式与 Carto 类库包含操作 2D 地图的对象类似。Scene 对象是 3Danalyst 类库中主要对象之一,因 象一样,是数据的容器。Camera 和 Target 对象规定 在考虑要素位置与观察者关系时场景如何浏览。一个场景由一个和多 个图层组成;这些图层规定了场景中包含的数据及这些数据如何显示。 开发者很少扩展 3Danalyst 类库。为使用这个类库中的对象,需要 ArcGIS 3D Analyst 扩展模块许可或 ArcGIS Engine 运行时 3D 分析选 项许可。 GlobeCore 类库 GlobeCore 类库包含操作 globe 数据的对象,其方式与 Carto 类库包含 的数据及这些数据如何 Map 通过程序指定,或者加载到 MapDocument 中。. Controls 类库 GeoAnalyst 类库包含支持核心空间分析功能的对象。这些功能用在 SpatialAnalyst 和 3Danalyst 两个类库中。开 为该对象与 Map 对 操作 2D 地图的对象类似。Globe 对象是 GlobeCore 类库中主要对象之 一,因为该对象与 Map 对象一样,是数据的容器。GlobeCamera 对象 规定在考虑 globe 位置与观察者关系时 golbe 如何浏览。一个 golbe 有 一个和多个图层;这些图层规定了 golbe 中包含 ArcGIS Engine 类库 显示。 GlobeCore 类库中有一个开发控件及与其一起使用的命令和工具。该开 发控件可以与 Controls 类库中的对象协同使用。 SpatialAnalyst 类库 开发者很少扩展 GlobeCore 类库。为使用这个类库中的对象,需要 ArcGIS 3D Analyst 扩展模块许可或 ArcGIS Engine 运行时 3D 分析选 项许可。 SpatialAnalyst 类库包含在栅格数据和矢量数据上执行空间分析的对 象。开发者通常使用这个类库中的对象,而不扩展这个类库。为使用 这个类库中的对象,需要 ArcGIS 空间分析扩展模块许可或 ArcGIS Engine 运行时空间分析选项许可。 第二章·ArcGIS 软件体系结构·37 使用 ArcGIS 控件进行开发 3 ArcGIS Engine 提供大量高级开发控件,使开发人员可以建立 或扩展 GIS 应用程序和创建高质量的地图用户界面。这些控件 包 括 MapControl 、 PageLayoutControl 、 ReaderControl 、 TOCCONTROL和 ToolbarControl。GlobeControl和 SceneControl 也可以获得,但使用这两个控件的应用程序必须具有 ArcGIS Engine 的 3D 分析选项授权。 本章的主要内容包括: ● 概览各个控件 ● 讨论各个 ArcGIS 控件共同的主题和概念 ● 考虑建立带或不带 ToolbarControl 的应用程序 什么是 ArcGIS 控件? 什么是 ArcGIS 控件? 部署用 GlobeControl 或 SceneControl 建立的应用程序需要 ArcGIS Engine 的 3D 分析选项。 ArcGIS 控件是高级开发组件,首先它们允许开发人员建立和扩展 GIS 应用程序,其次它们提供了图形用户界面(GUI)。 下列所有 ArcGIS 控件都可以作为 ActiveX 控件、.NET Windows 控件 和 Visual JavaBean 获得: MapControl—类似于“数据(data)视图” PageLayoutControl ToolbarControl TOCControl(内容表控件) SceneControl GlobeControl ReaderControl ArcGIS 控件可以通过两种方式建立应用程序:首先,ArcGIS 控件可以 嵌入到现有的应用程序中以增加制图功能;其次,ArcGIS 控件可用于 创建新的独立应用程序。在前一种情况中,单个的 ArcGIS 控件可以嵌 入到应用程序中,在后一种情况中,TOCControl 和 ToolbarControl 可 以与其他 ArcGIS 控件结合起来组成应用程序框架的组成部分。 40·ArcGIS Engine 开发指南 使用 ArcGIS 控件 使用 ArcGIS 控件 ArcGIS 41 通过属性页可以访问的所有属性都 可以由开发人员用代码设置。 为了使用 ArcGIS 控件有效地建立应用程序,应该理解所有 ArcGIS 控 件共同的一些主题和概念。 嵌入式组件 每个 ArcGIS 控件都是一个嵌入式组件,可以拖放到可视化设计环境提 供的容器窗体或对话框中。一旦放在容器内,就可以改变 ArcGIS 控件的大小和位置,以便与其他嵌入式组件,如命令按钮和组合 框等一起为应用程序提供用户界面。 属性页 一旦 ArcGIS 控件嵌入到容器中,通过右键点击该控件并选择弹出 式菜单中的“属性”菜单项,就可以在大多数可视化设计环境中 访问各个 ArcGIS 控件的属性页集 合。这些属性页提供了选择控件属 性和方法的快捷方式,使开发人员 只需写很少代码甚至不用写代码就 可以建立应用程序。 ArcObjects 每个 ArcGIS 控件都封装了粗粒度 ArcObjects,同时又提供对细粒度 ArcObjects 的访问,这样简化了开 发过程。例如,PageLayoutControl 封装了布局(PageLayout)对象。 布局(PageLayout)对象至少包含一个 MapFrame 元素,该 MapFrame 元素包含一个地图(Map)对象,而地图(Map)对象又可以包含多个 栅格、矢量或自定义图层(Layer)对象。每个 ArcGIS 控件为其封装 的 ArcObjects 上频繁使用的属性和方法提供了快捷方式。例如, MapControl 有一个 SpatialReference 属性,该属性就是地图(Map)对 象的 SpatialReference 属性的一个快捷方式。每个 ArcGIS 控件还有一 些执行公共任务的帮助方法。例如,MapControl 有一个 AddShapeFile 方法。ArcGIS 控件一般都是开发应用程序的起始点,不仅因为这些控 件提供了用户界面,而且还因为通过它们可以直接进入对象模型。 事件 每个 ArcGIS 控件都会响应终端用户的键盘和鼠标交互而触发事件。响 应发生在控件内的行为会触发一些其他事件。例如,当装载一个地图 文档到 MapControl 时,就会触发 OnMapReplaced 事件;或者当通过拖 放将一个对象在 MapControl 上拖动时,就会触发 OnOleDrop 事件。 第三章·使用 控件进行开发· 使用 ArcGIS 控件 要了解控件如何协同工作方面更详 细的信息,请参阅下面的 “TOCControl”和 “ToolbarControl”部分。 伙伴控件 ToolbarControl 和 TOCControl 都要与另一个“伙伴控件”协同工作。“伙 伴控件”一般是 MapControl、PageLayoutControl、ReaderControl、 SceneControl 或 GlobeControl。“伙伴控件”可以在设计时通过控件的 属性页设置(在支持属性页功能的开发环境中可以这样设置),或者用 SetBuddyControl 方法编程设置。 控件命令 ArcGIS Engine 提供了一系列命令、工具和菜单与 ArcGIS 控件协同工 作。例如,MapControl 和 PageLayoutControl 有一套地图导航、要素选 择和图形元素命令与其协同工作。类似的,SceneControl、GlobeControl 和 ReaderControl 也有一套命令与其协同工作。对于使用单个控件的应 用程序,这些命令可以通过编程创建命令的一个新例程并将该控件传 递给命令的 OnCreate 事件而直接操作控件。对于使用 ToolbarControl 及其“伙伴控件”的应用程序,这些命令可以通过设计阶段设置属性 页或通过编程设置,或者如果 ToolbarControl 处于定制模式可以在运行 时由终端用户设置。 开发人员也可以通过创建他们自己的自定义命令、工具和菜单来扩展 ArcGIS Engine 提供的命令集。HookHelper、GlobeHookHelper 和 SceneHookHelper 对象可用于简化这种开发。要了解有关如何使用 HookHelper 对象建立自定义命令方面的信息,请参考第六章“开发情 景”中的“建立应用程序”情景。 地图制作 ArcGIS Desktop 应用程序可用于预制作能被装载到 ArcGIS 控件中的文 档,以便快速生产高质量的地图。例如,可以用 ArcMap 制作能被装载 到 MapControl 和 PageLayoutControl 中的地图文档。预先制作文档可以 节省大量时间,因为它不必从零开始编程建立地图及其符号体系。一 旦文档被装载到 ArcGIS 控件中,如果随后需要改变其外观,就可以通 过对象模型编程访问任何图层、元素和符号。 42·ArcGIS Engine 开发指南 使用 ArcGIS 控件 下表总结了可以被装载到各个 ArcGIS 控件中的文档类型。 发布地图文件(*.pmf) 地图 文档 ( *. mxd, *.mxt ) 图层文件 (*.lyr) Scene 文档 ( *.sx d,*.sxt ) Globe 文档 (*.3d d,*.sdt ) 不允许装载到 定制应用程序 中(只能装载 到 ArcReader 应用程序) 允许装 载到定 制应用 程序中 允许装载 到定制应 用程序中 且其内容 的访问不 受限制 MapControl 是 是 否 否 否 否 是 PageLayoutControl 是 **是 否 否 否 否 是 SceneControl 否 **是 是 否 否 否 否 GlobeControl 否 **是 否 是 否 否 否 ReaderControl 否 否 否 否 否 是 是 *ArcReaderControl 否 否 否 否 否 是 是 *ArcReaderControl 只能用 ArcGIS Publisher 扩展模块获得。然而,这里 列出来是由于其与 ReaderControl 的类似性。 **ArcGIS 控件上没有可用的属性直接装载图层文件(*.lyr)。但是它 们可以间接地通过 MapDocument 对象装载。 ArcGIS 43第三章·使用 控件进行开发· MapControl 和 PageLayoutControl MAPCONTROL 与PAGELAYOUTCONTROL 分别用 MapControl 和 PageLayoutControl 建立的应用程 序。 MapControl 和 PageLayoutControl 对应于 ArcMap 桌面应用程序的“数 据”和“布局”视图。MapControl 封装了 Map 对象,而 PageLayoutControl 封装了 PageLayout 对象。用 ArcMap 创作的地图文档可以装载到 MapControl 和 PageLayoutControl 中,以节省开发人员编程创作地图的 时间。 地图文档可以在设计时通过 MapControl 和 PageLayoutControl 的属性页 (在支持属性页功能的开发环境中可以这样做)设置,控件可以被设 置为“链接”或“包含”地图文档。若为“链接”控件,则每当“链 接”控件被创建时都会读取地图文档并显示该地图文档的最新更新。 而如果为“包含”控件,则“包含”控件会将地图文档的内容复制到 控件中,但不会显示该地图文档后来所作的任何更新。另外,地图文 档页可以使用 LoadMxFile 方法通过编程装载到控件中。 MapControl 和 PageLayoutControl 不仅可以读取地图文档,它们还可以 写入地图文档(*.mxd)。这两个控件都实现了 IMxContents 接口,使地 图文档(MapDocument)对象可以将 MapControl 和 PageLayoutControl 的内容写到一个新的地图文档中。 MapControl 上存在诸如 TrackRectangle、TrackPolygon、TrackLine 和 TrackCircle 等帮助方法,用于追踪或“橡皮圈住(rubber banding)”显示上的几何图形(Shape)。VisibleRegion 属性 可用于更改 MapControl 显示区内的几何图形。 PageLayoutControl 上存在诸如 FindElementByName 和 LocateFrontElement 等帮助方法,以帮助开发人员管理元素, 而 Printer 和 PrinterPageCount 属性与 PrintPageLayout 方法则 共同完成打 印任务。 44·ArcGIS Engine 开发指南 GlobeControl 和 SceneControl GLOBECONTROL 与SCENECONTROL 部署用 GlobeControl 或 SceneControl 建立的应用程序需 要 ArcGIS Engine 的 3D 分析运行时 选项。 第三章·使用 ArcGIS 控件进行开发·45 分别用 GlobeControl 和 SceneControl 建立的应用程序 GlobeControl 和 SceneControl 对应于 ArcGlobe 和 ArcScene 桌面应用程 序的三维视图。GlobeControl 封装了 GlobeViewer 对象,而 SceneControl 封装了 SceneViewer 对象。用 ArcGlobe 和 ArcScene 应用程序创作的 Globe 和 Scene 文档可以分别装载到 GlobeControl 和 SceneControl 中, 以节省开发人员编程创作这两种地图的时间。 GlobeControl 和 SceneControl 都具有内置的导航功能,允许终端用户移 动三维视图和可视化三维数据,而不必使用控件命令或自定义命令。 要使用内置的导航工具,必须通过属性页或者编程设置 Navigate 属性。 终端用户可以用鼠标左键对显示进行前后左右的移动,使用鼠标右键 放大和缩小显示。 ReaderControl READERCONTROL ArcReader可以下载。要获得其免费 的拷贝,请访问 http://www.esri.com/software/ar cgis/arcreader。 ReaderControl 对应于 ArcReader 桌面应用程序的“数据”和“布局” 视图及其内容表(TOC)。ReaderControl 还包含 ArcReader 桌面应用程 序使用的内部窗口和工具,如 Find 窗口和 Identify 工具等。用 ArcMap 桌面应用程序创作并用 ArcGIS Publisher 扩展模块发布的发布地图文 件( PMF)如果在发布时设置为允许装载到定制的 ArcReader 应用程序 中,则可以装载到 ReaderControl 中。 ReaderControl 中有一个简单的自包含对象模型,提供 ArcReader 桌面 应用程序的所有功能而不需要访问 ArcObjects。这样,用 ReaderControl 开发应用程序就不需要有任何使用 ArcObjects 开发程序的经验。但是, 如果发布地图文件在发布时设置为不受限制地访问其内容,开发人员 就可以访问底层的ArcObjects,并以与MapControl和PageLayoutControl 相似的方式使用 ReaderControl 进行开发。 但 ArcReaderControl 并不能通过 ArcGIS Engine 获得,在这里提出来是 因为该控件与 ReaderControl 很相似;ArcReaderControl 也有与 ReaderControl 相同的简单自包含对象模型。但是,ArcReaderControl 不能用作“伙伴控件”以便与 TOCControl 或 ToolbarControl 协同工作, 而且开发人员也不能访问其底层的 ArcObjects。使用 ArcReaderControl 进行开发需要 ArcGIS Publisher 扩展模块,而且 ArcReaderControl 可以 部署到有 ArcReader 桌面应用程序的任何机器上。 一个 ReaderControl 的应用程序 46·ArcGIS Desktop Engine 开发指南 TOCControl 和 ToolbarControl TOCCONTROL 与TOOLBARCONTROL 第三章·使用 ArcGIS 控件进行开发·47 一个 TOCControl 应用程序 用 ToolbarControl 和 GlobeControl 建立的一个应用程 序 TOCControl TOCControl 要与一个“伙伴控件”协同工作。“伙伴控件”可以是 MapControl 、 PageLayoutControl 、 ReaderControl 、 SceneControl 或 GlobeControl。“伙伴控件”可以在设计时通过 TOCControl 属性页设置(在支持属性页功能的开发环境中可以这样做) 或在驻留 TOCControl 的容器被显示时用 SetBuddyControl 方法通过编程设置。TOCControl 的每个“伙伴控件”都实 现了 ITOCBuddy 接口。TOCControl 用“伙伴控件”来显示 其地图、图层和符号体系内容的一个交互树视图并保持其 内容与“伙伴控件”同步。例如,如果 TOCControl 的“伙 伴控件”是一个 MapControl,而且从该 MapControl 中删除 了一个图层,则该图层也会从 TOCControl 中删除。同样地, 如果终端用户与 TOCControl 交互并取消了某个图层的 Visibility 复选框,则该图层在 MapControl 中不再可见。 ToolbarControl ToolbarControl 要与一个“伙伴控件”协同工作。“伙伴控件” 可以是 一个 MapControl、PageLayoutControl、ReaderControl、SceneControl 或 GlobeControl。“伙伴控件”可以在设计时通过 ToolbarControl 属性页设 置(在支持属性页功能的开发环境中可以这样做)或在驻留 ToolbarControl 的容器被显示时用 SetBuddyControl 方法通过编程设置。 ToolbarControl 可以驻留操作其“伙伴控件”显示的命令、工具和菜单。 ToolbarControl 的每个“伙伴控件”都实现了 IToolbarBuddy 接口。这 个接口用于设置“伙伴控件”的 CurrentTool 属性。例如,设想驻留了 一个“页面放大”工具并有一个 PageLayoutControl 作为其“伙伴控件” 的 ToolbarControl。当终端用户单击 该 ToolbarControl 上的“页面放大” 工具时,该“页面放大”工具就会成 为 PageLayoutControl 的 CurrentTool。 “页面放大”工具的实现会查询 ToolbarControl 以访问其“伙伴控件” ( PageLayoutControl )并取回该 PageLayoutControl 。然后该 PageLayoutControl 提供实现显示终 端用户拉动鼠标所画的框并改变布 局的范围。 使用 ToolbarControl 建立应用程序 用TOOLBARCONTROL 建立应用程序 ToolbarControl 一般要与一个“伙伴控件”协同工作,并有一个控件命 令选择集,以便快速提供功能强大的 GIS 应用程序。ToolbarControl 不 仅提供了部分用户界面,而且还提供了部分应用程序框架。ArcGIS Desktop 应用程序,如 ArcMap、ArcGlobe 和 ArcScene 等具有强大而灵 活的框架,包括诸如工具条、命令、菜单、泊靠窗口和状态条等用户 界面组件。这种框架使终端用户可以通过改变位置、添加和删除这些 用户界面组件来定制应用程序。 许多开发环境提供简单对话框、窗体和多泊靠界面(MDI)应用程序 形式的框架。它们也提供普通的用户界面组件,如按钮、状态条和列 表框。然而,仍需要为驻留命令的工具条和菜单提供大量的代码,特 别是如果这些命令需要由终端用户定制的话。 ToolbarControl 及其类库中的对象可以提供类似于 ArcGIS Desktop 应用 程序框架那样的框架组件。用 ToolbarControl 建立应用程序时,开发人 员可以使用这些框架组件的一部分或全部。 命令 ArcGIS Engine 提供了几套使用 ArcGIS 控件的控件命令,以便执行某 种特定动作。开发人员可以通过创建他们自己的执行特定任务的定制 命令来扩展这套控件命令。所有的命令对象都实现了 ICommand 接口, ToolbarControl 在适当的时候要使用该接口来调用方法和访问属性。 在命令对象被驻留到 ToolbarControl 后就会立即调用 ICommand::OnCreate 方法。这个方法将一个句柄(handle)或“钩子 (hook)”传递给该命令操作的应用程序。命令的实现一般都要测试以 查看该“钩子(hook)”对象是否被支持(也就是说,命令要测试以查 看该“钩子(hook)”是不是该命令可以操作的一个对象)。如果不支 持该“钩子(hook)”,命令自动失效。如果支持该“钩子(hook)”, 则命令存储该“钩子(hook)”以便以后使用。例如,如果“打开地图 文档”命令要操作 MapControl 或 PageLayoutControl,而且它们作为“钩 子(hook)”被传递给 OnCreate 方法,则该命令会存储该“钩子(hook)” 以便以后使用。如果 ToolbarControl 作为钩子(hook)”被传递给 OnCreate 事件,则命令一般会通过 Buddy 属性检查与该工具条协同使 用的“伙伴控件”的类型。例如,如果驻留在 ToolbarControl 上的一个 命令只能操作 ReaderControl,而该 ToolbarControl 的“伙伴”是一个 MapControl,则该命令应自动失效。 48·ArcGIS Engine 开发指南 使用 ToolbarControl 建立应用程序 HookHelper、GlobeHookHelper 和 SceneHookHelper 可以帮助开发人员 创建自定义命令以操作 ArcGIS 控件和 ArcGIS Desktop 应用程序。 HookHelper 用于帮助开发人员创建能操作 MapControl 、 PageLayoutControl、ToolbarControl 和 ArcMap 桌面应用程序的自 定义命令。 SceneHookHelper 用于帮助开发人员创建能操作 SceneControl、 ToolbarControl 和 ArcScene 桌面应用程序的自定义命令。 GlobeHookHelper 用于帮助开发人员创建能操作 GlobeControl、 ToolbarControl 和 ArcGlobe 桌面应用程序的自定义命令。 并不是由开发人员向命令的 OnCreate 方法中添加代码以确定传递给该 命令的“钩子(hook)”的类型,而是由 helper 对象来处理这个任务。 helper 对象用于控制“钩子(hook)”并返回 ActiveView 对象、PageLayout 对象、Map 对象、Globe 对象和 Scene 对象(取决于 helper 对象的类型), 而不管被传递的是何种类型的“钩子(hook)”。参考第六章“开发情 景”中的“建立应用程序”情景,以了解如何使用操作 MapControl、 PageLayoutControl 和 ToolbarControl 的 HookHelper 对象建立自定义命 令。 当终端用户单击工具条上的某个命令项时,ICommand::OnClick 方法被 调用。取决于命令的类型,一般会使用“钩子(hook)”访问来自“伙 伴控件”的所需对象来完成某些工作。有三类命令: 实现了响应单击事件的 ICommand 接口的单击命令。用户单击事 件会导致对 ICommand::OnClick 方法的调用,并执行某种动作。 通过改变 ICommand::Checked 属性的值,简单命令项的行为就象 开关那样。单击命令是可以驻留在菜单中的唯一命令类型。 实现了 ICommand 接口和 ITool 接口、需要终端用户与“伙伴控 件”的显示进行交互的命令项或工具。ToolbarControl 维护着一个 CurrentTool 属性。当终端用户单击 ToolbarControl 上的工具时, 该工具就成为 CurrentTool,而前一个工具就处于非活动状态。 ToolbarControl 会设置“伙伴控件”的 CurrentTool 属性。当某个 工具为 CurrentTool 时,该工具会从“伙伴控件”收到鼠标和键盘 事件。 实现了 ICommand 接口和 IToolControl 接口的命令项或工具控件。 这通常是用户界面组件,象 ToolbarControl 上的列表框或组合框。 ToolbarControl 驻留了来自 IToolControl::hWnd 属性窗口句柄提供 的一个小窗口。只能向 ToolbarControl 添加特定工具控件的一个 例程。 第三章·使用 ArcGIS 控件进行开发·49 使用 ToolbarControl 建立应用程序 50·ArcGIS Engine 开发指南 可以用两种方法将命令添加到 ToolbarControl 中,第一种方法是指定唯 一识别命令的一个 UID 对象(使用 GUID),第二种方法是给 AddItem 方法提供某个现有命令对象的一个例程。可能的话命令应通过指定一 个 UID 添加到 ToolbarControl 中。如果提供了一个 UID,就可以确定 这个命令是否已经被添加到 ToolbarControl 上,如果已经添加了该命 令,就可以重用该命令的前一个例程。当命令没有唯一识别符并将命 令的一个现有例程添加到 ToolbarControl 时,就可以在 ToolbarControl 上添加相同命令的多个例程。 工具条命令项(TOOLBARITEM) 工具条命令项就是驻留在 ToolbarControl 或工具条菜单上的单个命令 或菜单。IToolbarItem 接口的属性决定工具条命令项的外观。例如,工 具条命令项是否在其左侧有一条垂直线表示是否开始一个命令组 (Group),及命令项的样式是否有一个位图、标题或两者都有。 Command 和 Menu 属性返回工具条命令项代表的实际命令或菜单。 更新命令 默认情况下,ToolbarControl 每半秒钟自动更新其自身一次,以确保驻 留在 ToolbarControl 上的每个工具条命令项的外观与其底层命令的 Enabled、Bitmap 和 Caption 属性同步。改变 UpdateInterval 属性可以更 改更新的频率。UpdateInterval 为 0 会停止任何自动发生的更新,开发 人员必须编程调用 Update 方法以刷新每个工具条命令项的状态。 在应用程序中首次调用 Update 方法时,ToolbarControl 会检查每个工具 条命令项的底层命令的 ICommand::OnCreate 方法来检查是否已经被调 用过。如果还没有调用过该方法,则该 ToolbarControl 作为“钩子 (hook)”被自动传递给 ICommand::OnCreate 方法。 使用 ToolbarControl 建立应用程序 第三章·使用 ArcGIS 控件进行开发·51 作为弹出式菜单驻留工具条菜单 工具条菜单 ToolbarControl 可以驻留下拉菜单项。工具条菜单(ToolbarMenu)表 示 单击命令项的一个垂直列表。用户必须选择工具条菜单上的一个命令 项,或单击工具条菜单之外的地方使其消失。工具条菜单 只能驻留命令项(不允许驻留工具或工具控件)。工具条菜 单本身可以驻留在 ToolbarControl 上,作为“子菜单”驻留 在另一个工具条菜单上,或者作为右键单击“弹出式菜单” 出现。要了解如何建立驻留操作 PageLayoutControl 的控件 命令的“弹出式菜单”,请参考第六章“开发情景”中的“建 立应用程序”情景。 命令池 每个 ToolbarControl 和工具条菜单都有一个命令池 (CommandPool),用于管理其使用的命令对象集。一般来 说,开发人员不会与命令池(CommandPool)进行交互。 当通过 ToolbarControl 属性页或编程将命令添加到 ToolbarControl 中时,该命令自动添加到命令池(CommandPool)中。 命令对象要么作为唯一识别该命令的一个 UID 对象(使用 GUID)、要 么作为命令对象的一个现有例程被添加到命令池(CommandPool)中。 如果命令对象的一个现有例程被添加,并且该命令没有一个唯一识别 符,则命令池中可以有同一命令的多个例程存在。如果提供了一个 UID 对象,命令池可以确定该命令是否已经存在于命令池中,而且如果存 在的话就可以重用该命令的前一个例程。命令池通过追踪是否已经调 用过命令的 OnCreate 方法来完成这个工作。如果已经调用过 OnCreate 方法,则将重用该命令并增加其使用次数(UsageCount)。 例如,如果向某个 ToolbarControl 中添加两次“放大”工具并提供 UID, 则当 ToolbarControl 上的一个“放大”工具被选择并显示“按下 (pressed)”时,另一个“放大”工具也会显示“按下(pressed)”状 态,因为它们使用同一个命令对象。当应用程序包含多个 ToolbarControl 或工具条菜单时,开发人员应确保每个 ToolbarControl 和工具条菜单使 用相同的命令池(CommandPool),以保证在应用程序中只创建了命令 的一个例程。 定制 ToolbarControl 有一个 Customize 属性,可以设置该属性以使 ToolbarControl 处于定制模式。这会改变 ToolbarControl 的行为,并允 许终端用户重新安排、删除和添加命令项以及改变这些命令项的外观。 使用 ToolbarControl 建立应用程序 用鼠标左键选择 ToolbarControl 上的命令项,拖动选中的命令项到 一个新的位置,或者拖动并将选中的命令项拖到 ToolbarControl 外以删除该命令项。 用鼠标右键选择 ToolbarControl 上的命令项并显示定制菜单。定 制菜单可用于删除命令项或改变命令项的样式(位图、标题或两 者)以及分组工具条命令项。 当 ToolbarControl 处于定制模式时,开发人员可以编程启动非模态定制 对话框(CustomizeDialog)。定制对话框列出了所有的控件命令以及任 何自定义命令、工具集和菜单。通过读取“ESRI 控件命令”、“ESRI 控件工具条”和“ESRI 控件菜单”组件类目的条目来完成这个工作。 如果需要的话,开发人员可以改变定制对话框(CustomizeDialog)以 使用其他组件类目。终端用户可以向 ToolbarControl 中添加这些命令、 工具集和菜单,方法是拖动并将它们放到 ToolbarControl 上或双击这些 命令、工具集和菜单。 定制对话框(CustomizeDialog)是非模态对话框,以允许用户与 ToolbarControl 进行交互。当用 StartDialog 方法弹出定制对话框时,该 方法调用立即返回而定制对话框仍然在屏幕上打开。为了保持当定制 对话框处于打开状态时对其的引用,将定制对话框存储为一个类级别 的变量并侦听其 ICustomizeDialogEvents 是明智的做法。要了解当 ToolbarControl 处于定制模式时如何显示定制对话框 (CustomizeDialog),请参考第六章“开发情景”中的“建立应用程序” 情景。 操作栈(Operation Stack) ToolbarControl 有一个操作栈,用于管理“撤消(undo)”和“重做(redo)” 功能。由每个工具条命令项的底层命令将操作添加到操作栈中,以便 可以根据需要将操作前滚和后滚。例如,当删除一个图形元素时,可 以通过移动该图形回到其原来的位置而撤消该操作。命令是否可以利 用操作栈取决于该命令的实现。一般地,开发人员为应用程序创建一 个单独的控件操作栈(ControlsOperationStack)(默认情况下操作栈属 性为 Nothing),并将其设置给每个 ToolbarControl。撤消和重做命令可 以添加到进入了操作栈的 ToolbarControl 中。 52·ArcGIS Engine 开发指南 建立不带 ToolbarControl 的应用程序 建立不带 TOOLBARCONTROL 的应用程序 虽然建立带有 ToolbarControl 的应用程序可以快速提供类似于 ArcGIS Desktop 应用程序框架那样的框架组成部分,但有的时候应用程序可能 并不需要 ToolbarControl: ToolbarControl 的可视化外观可能不符合应用程序的需要。 不需要实现 ToolbarControl 的命令(Command)对象。 应用程序中已经有一个现有的应用程序框架。 ToolbarControl 及其驻留的命令不易于跨多个“伙伴控件”使用。 在这些情况下,开发人员必须直接操作 MapControl 、 PageLayoutControl、SceneControl、GlobeControl 和 ReaderControl。应 用程序需要其他用户界面组件,如命令按钮、状态条和列表框等,可 能要由开发环境提供。 例如,将地图导航功能建立到 MapControl 应用程序中可以如下实现: 在 MapControl 的 OnMouseDown 事件中将 IMapControl2::TrackRectangle 方法的结果“封装边界(Envelope)” 设置给 IMapControl2::Extent 属性,以创建“放大”功能。 将 IMapControl2::FullExtent 属性的“封装边界(Envelope)”设置 给 IMapControl2:: Extent 属性,以创建“全图显示”功能。代码 可以放在开发环境提供的命令按钮的单击(Click)事件中。 作为选择,ArcGIS Engine 提供的控件命令或者使用 HookHelper、 SceneHookHelper 或 GlobeHookHelper 对象的任何自定义命令将直接操 作单个的 ArcGIS 控件。然而,开发人员要负责在适当的时候调用 ICommand::OnCreate 和 ICommand::OnClick 方法并读取 ICommand 接 口上的属性以建立用户界面,方法如下: 通过编程创建命令的一个新例程,并将单个 ArcGIS 控件传递给 OnCreate 事件。例如,如果 3D 的“放大到全图(Zoom FullExtent)” 命令要操作 GlobeControl,则 GlobeControl 必须作为“钩子(hook)” 传递给 OnCreate 方法。 开发人员可以使用脱离 ToolbarControl 的命令池(CommandPool) 对象,以便管理应用程序所使用的命令。命令池(CommandPool) 支持基于命令的“钩子(hook)”属性调用各个命令的 OnCreate 方法。 第三章·使用 ArcGIS 控件进行开发·53 建立不带 ToolbarControl 的应用程序 如果命令只实现了 ICommand 接口,开发人员可以在适当的时候 调用 OnClick 方法以执行特定行动。如果命令为实现了 ICommand 接口和 ITool 接口的工具命令,开发人员必须将该工具设置为 ArcGIS 控件中的 CurrentTool。ArcGIS 控件会将任何键盘和鼠标 事件传送给该工具。 命令的 Enabled、Caption 和 Bitmap 属性可以被读写到开发环境提 供的命令按钮的属性中,以建立应用程序的用户界面。 虽然这种建立应用程序的方法需要开发人员编写更多的程序代码,但 从零开始建立应用程序的确具有更大的灵活性。 54·ArcGIS Engine 开发指南 开发环境 4 ArcObjects 基于微软组件对象模型(COM)之上。ArcGIS 应用 程序的最终用户不必理解 COM,但打算基于 AO 开发应用程序或 使用 AO 扩展已有 ArcGIS 应用程序的开发者必须理解 COM,即 使打算用 C++、Java、.Net APIs 而不是用 COM 开发也要理解 COM。需要理解的层次取决于定制和开发的深度。至少需要浏览 组件对象模型和使用 ArcObjects 开发部分,然后继续所选的特 定 API 部分。 每个特定的 API 部分介绍了支持语言的编程技术并详细阐述了 有关使用 ArcObjetcs 进行开发的高级特性。 本章的主题包括: ● 微软组件对象模型 ● 使用 ArcObjects 进行开发 ● Visual Basic 平台和开发环境 ● Visual C++ ● .NET API ● Java API ● C++ API 微软组件对象模型 微软组件对象模型 ESRI 选择 COM 作为 ArcGIS 的组件 技术的原因是 COM 是一项成熟的技 术,能提供很好的性能,目前有很 多开发工具支持,而且有很多第三 方组件可用于扩展 ArcObjects 的 功能。 组件成功的关键是它们以一种可行 的方式实现了目前软件工程中普遍 接受的许多面向对象原理。组件方 便了软件的重用,因为它们是能很 容易组配成更大系统的自包含构建 块。 在明确讨论 COM 之前,有必要先一般性地思考一下软件组件使用的广 泛性。在软件组件背后有许多的驱使因素,但最基本的就是软件开发 是个昂贵和耗时的风险行为。 在理想的世界里,假如曾经写了一段代码,然后通过不同的开发工具 一次一次的反复使用它,这种情况是可能的,即便是在最初的开发者 不能预见的环境下也能使用。理想情况下,对最初开发者生成的函数 功能的改变能在不需要目前用户改变和重编译他们的代码情况下实 现。 早期重用代码块的尝试就是创建类库的进展,这些类库通常用 C++开 发。早期的尝试有很多的限制,较为明显的是:共享系统组件的困难 (共享二进制 C++组件是非常困难的——大部分的尝试是共享源代 码);不重新编译就很难保存和更新 C++组件的问题;缺乏好的建模语 言和工具以及属性接口和定制工具。 为了解决这样那样的一些问题,许多软件工程师开始采用基于组件的 方法进行系统开发。一个软件组件就是一个可重用代码的二进制单元。 已经出现了几个不同但相互重叠的开发和共享组件标准。微软的 COM 已成为建立交互式桌面应用程序事实上的标准。在互联网上,JavaBean 是可行的技术。对象管理组(OMG)制定了粗粒度层次上适用于应用程 序互操作的公用对象请求代理架构(CORBA)。 为了理解 COM(以及所有基于 COM 的技术),认识到其不是一种面向对 象语言而是一个协议或标准是非常重要的。COM 不仅仅是一种技术,还 是软件开发的一种方法论。COM 定义了一种连接软件组件或模型的协 议。通过使用这种协议,可以建立能在分布式系统中动态交互的可重 用软件组件。 COM 还定义了一种称作基于接口的编程模式。对象封装了能体现定义良 好的接口后每个实例化对象特征的操作方法和数据。这增强了系统开 发的结构化和安全性,因为一个对象的客户并不知道某个特定方法如 何实现的任何细节。COM 不规定应如何构建一个应用程序。语言、结构 和实现细节都由使用 COM 的应用程序开发人员决定。 COM 的确规定了一个对象模型和使 COM 对象能与其他 COM 对象交互的编 程需求。这些对象可以位于单个过程中,在不同的过程中,或者甚至 在远程机器上。它们可以用其他语言编写,也可以用完全不同的方式 开发。这就是 COM 被认为是二进制规范或标准的原因—它是一个在程 序被编译为二进制机器代码后才应用的标准。 56·ArcGIS Engine 开发指南 微软组件对象模型 对象是为客户提供服务的 COM 类的 实例。因此一般说客户端和对象而 不是客户和服务端。这些对象经常 指 COM 对象和组件对象。本书将把 它们简单地指为对象。 COM 允许对象在二进制水平上重用,意味着第三方开发者即使从最底层 扩展系统也不需要访问这些对象的源代码、头文件或对象库。 组件、对象、客户和服务器 不同的文件中组件(components)、对象(objects)、客户(clients) 和服务器(servers)表示不同的意思(各种文件用这些术语指代相同 的事物,使得术语使用更加混乱)。因此,定义这本书所要使用的术语 是非常有必要的。 COM 是一种客户/服务器(C/S)架构。服务器(或对象)提供某种功能, 而客户使用这种功能。COM 使客户和对象之间的交流变得简单。一个对 象可以同时是一个客户的服务器和其他对象服务的客户。 客户及其服务器可以存在于同一个进程中或不同的进程中。入进程服 务被打包成动态链接库(DLL)格式,当客户第一次访问这些服务时会 加载这些 DLL 到客户的地址空间中。出进程服务被编译为可执行文件 并在它们自己的地址空间中运行。COM 这种差异对客户透明。 在创建 COM 对象时,开发者必须了解对象驻留在什么类型的服务器中, 但如果对象的创建者正确地实现了它们,打包并不会影响客户对对象 的使用。 这两种对称相反的打包方法各有优缺点。DLLs 能更快速的装载到内存 中,且调用一个 DLL 函数速度较快。另一方面,EXEs(可执行文件) 能够提供更健壮的解决方案(如果服务器失败,客户端不会崩溃),同 时安全性能更好,因为服务器有其自己的安全保障。 在一个分布式系统中,EXEs 更灵活,即使服务器与客户端有不同的字 节顺序也不会有什么问题。大多数 ArcObjects 服务被打包为入进程服 务(DLLs)。稍后,用户将看到使用入进程服务的性能优势。 在一个 COM 系统中,客户端或功能的使用者与功能的提供者,对象完 全相分离。客户端需要知道所有知识就是可用的功能;有了这些知识, 客户端就可以调用对象中的方法并期望对象作出响应。这样,COM 可以 认为是客户端和服务器之间的一种协议。如果对象违反了这种协议, 那么系统的行为就不能预知。这样,COM 开发是基于功能实现者和功能 使用者间相互信任基础上的。 第四章·开发环境·57 Client VBApp.exe Server / Client ArcMap.exe Server Map.dll Client and server COM+ server MyComputer process space YourComputer process space Objects inside an out-of-process server are accessed by COM-supplied proxy objects which make access transparent to the client. The COM run-time handles the remoting layer COM objects yourEXE server myEXE client Objects inside an out-of-process server are accessed by COM-supplied proxy objects which make access transparent to the client MyComputer process space process space COM out-of-process server COM object s myEXE client proxy object yourEXE server proxy object MyComputer process space Objects inside an in-process server are accessed directly by their clients. COM in-process server myDLL COM objects client yourDLL server 微软组件对象模型 58·ArcGIS Engine 开发指南 服务是一个二进制文件,包含一个 或多个 COM 类需要的所有代码。这 些代码既包括用 COM 将对象实例化 到内存的代码,也包括执行服务中 对象支持的各种方法的代码。 GUIDEN.EXE 是微软 Visual Studio 随带的一个实用工具,并提供生成 GUID 的一个易用用户接口。该工具 可以在 VS 安装目录下 \Common\tools 文件夹中找到。 缩写的 GUID 通常读作“gwid”。 在 ArcGIS 应用程序中,许多对象通过其接口提供了几千个属性和方法。 当使用 ESRI 对象库时,可以假定所有这些属性和方法是完全实现了的, 如果它们列在对象图中,就可以使用它们。 类工厂(CLASS FACTORY) 在每个服务器中都有一个称为“类工厂”的对象,COM 在运行时与它交 互作用以便实例化某个特定类的对象。每个 COM 类都有一个相应的类 工厂。通常,当客户端向服务器请求一个对象时,相应的类工厂就会 创建一个新对象并将该对象传给客户端。 Class factory A IClassFactory IUnknown COM object A InterfaceA IUnknown COM object B InterfaceB IUnknown COM object B InterfaceB IUnknown Class factory B IClassFactory IUnknown 独生对象(SINGLETON OBJECTS) 虽然这是通常的实现方法,但不是仅有的实现方法。类工厂也可以在 第一次创建一个对象实例,在随后的调用中将同一个对象传给客户端。 这类实现创建了所谓的“独生对象”,因为每个进程只有一个对象实例。 全局唯一标识符(GLOBALLY UNIQUE IDENTIERS) 一个分布式系统可能具有成千上万个接口、类和服务,在运行时定位 和绑定客户端和对象都需要引用它们。毫无疑问,使用人们易读名称 可能会导致潜在的冲突,因此 COM 使用全局唯一标识符(GUIDs),它 是一个 128 位的数字,实际上能保证其在世界上的唯一性。在公元 5770 年以前可以每秒产生 1000 万个 GUIDs。 COM API 定义了用于产生 GUIDs 的函数;此外,所有适用于 COM 开发的 工具都会自动分配适当的 GUIDs。与通用唯一标识符(UUIDs)一样, GUIDs 也由开放集群分布式计算环境(DCE)规范定义。下面就是一个 注册表格式的 GUID 例子。 {E6BDAA76-4D35-11D0-98BE-00805F7CED21} COM 类和接口(COM CLASSES AND INTERFACES) 使用 COM 开发就意味着用接口开发,即所谓的基于接口的编程模式。 对象间的所有通信都是通过其接口实现的。COM 接口是抽象的,意味着 没有与接口相关联的实现;与接口关联的代码都来自类的实现。接口 规定了对选择用于实现接口的对象可以作什么请求。 接口的实现方法随对象不同而不同。这样对象继承了接口类型而不是 接口的实现,这称为类型继承。功能用接口来抽象模拟并在类的实现 中实现。类和接口通常被称为 COM 的“什么”和“怎么”。接口定义对 象能做什么,而类定义怎么做。 COM 类提供了一个或多个接口的代码,从而在类中完全封装了其功能。 微软组件对象模型 这是 Geodatabase 对象模型的一个 简化图,展示了抽象类和组件对象 类之间的类型继承及类的实例化。 第四章·开发环境·59 这个模型图展示了表达为接口的公 共行为如何在多个对象(本例中是 动物)间共享,以支持多态。 两个类可以具有相同的接口,但它们实现这些接口的方法可能完全不 同。通过这种方式实现接口,COM 展示了经典的面向对象的多态行为。 COM 不支持多重继承概念,但由于单个类能实现多个接口,所以这不是 一个缺陷。关于多态行为可以看左边的模型图。 ArcObjects 中开发者必须理解的类有三种类型:抽象类(abstract Classes)、组件对象类(coclasses)和类(classes)。抽象类不能创 建对象,只是对其子类的实例化进行了规定(通过类型继承)。 ArcObjects 的 Dataset 或 Geometry 类就是抽象类的例子。不能创建 Geometry 类型的对象,但可以创建 Polyline 类型的对象。Polyline 对象实现了在 Geometry 基类中定义的接口,因此在基于对象类中定义 的任何接口都可以从组件对象类访问。 组件对象类是公开可创建类。换言之,COM 可以创建组件对象类的实例 并把实例对象提供给客户端,从而使客户端能使用这个类接口定义的 服务。类不能公开创建,但类的对象可以用 ArcObjects 中其它对象来 创建并提供给客户端使用。 左边的模型图解释了在实现接口时 COM 类中展现的多态行为。注意 Human 和 Parrot 类都实现了 ITalk 接口。ITalk 接口定义了一些方法 和属性,如 StartTalking、StopTalking、Language 等,但显然这两 个类的实现是不同的。 内部接口(INSIDE INTERFACES) Human IBirth ITalk IWalk IDeath Parrot IBirth ITalk IWalk IFly IDeath Dog IBirth IWalk IDeath Classes Interfaces IBirth ITalk IWalk IFly IDeath COM 接口规定 COM 对象之间如何相互通信。在操作 COM 对象时,开发者 从不直接操作 COM 对象,而是通过对象的一个接口来访问对象。COM 接口被设计为逻辑相关的一组功能。实际功能由客户端调用并由服务 器实现;这样,对象接口就成为客户端和服务器之间的协议。对象的 客户端拥有指向该对象的接口指针。这个指针被称为一个不透明的指 针,因为客户端不能获得对象内的任何实现细节或直接访问对象的状 态数据。客户端必须通过接口的成员函数来通信。这就允许 COM 提供 二进制标准,通过这个标准所有的对象都能有效地通信。 接口允许开发者抽象的定义功能。VC++开发者把接口看作纯虚函数的 集合,而 VB 开发者将接口看作属性、函数和子过程的集合。 接口概念是 COM 的基本概念。在讨论 COM 接口时,COM 规范(微软,1995) 着重强调以下四点: 1. 接口不是类。接口不能实例化自身,因为其上没有实现。 2. 接口不是对象。接口是一个相关功能组,是客户和对象之间通信 的二进制标准。 微软组件对象模型 接口的永久性不是简单的限于其方 法署名,而是也扩展到其语义行为。 例如,某个接口定义了两个方法 A 和 B,没有限制它们的使用方法。 如果后来的版本中方法 A 要求 B 先 被执行,则违反了 COM 协议。这种 变化可能会迫使客户端重新编译。 IUnknown 名称来自 1988 微软内部 称为对象架构一文:处理动态可扩 展类库中的 Unknow 或类型安全 (Dealing with the Unknow -or –Type Safety in a Dynamically Extensible Class Library)。 3. 接口具有很强的类型性。每个接口都有自己的接口标识符,因此 消除了具有人们易读相同名字接口间可能的冲突。 4. 接口是不变的。接口从来没有版本。一旦接口被定义和公布就不 能改变。 一旦接口被公布,接口的外部标记就不可以改变。可以在任何时候改 变提供接口的对象的实现细节。这种改变可能是细微错误的修改或底 层算法的完全重写;接口的客户端不必担心,因为接口对于他们来说 似乎没变。这就意味着当对服务器的升级以新的 DLLs 和 EXEs 格式部 署时,现有客户端不需要重新编译以使用新的功能。如果接口的外部 标识不能再容纳新功能,那么可以创建一个新的接口来提供新功能。 旧的或没有什么作用的接口不会从类中删除,以保证已有客户端应用 程序能继续与新升级后的服务器通信。新的客户可以选择使用新的或 旧的接口。 IUNKNOW 接口 所有 COM 接口都源于 IUnkonow 接口,而且所有 COM 对象都必须实现这 个接口。IUnknow 接口执行两个任务:控制对象的生命周期和提供运行 时类型支持。客户端在使用对象时通过 IUnknow 接口维护对该对象的 引用,而把实际生命周期管理留给对象自身。 对象的生命周期用两个方法 AddRef 与 Release 以及一个内部引用计数 来控制。每个对象都必须实现 IUnknow 接口以便控制其自身的生命周 期。当创建或复制接口指针时,就会调用 AddRef 方法;当客户端不再 需要这个指针时,就会调用相应的 Release 方法。当引用计数为 0 时, 对象自动销毁。 客户也可以使用 IUnknow 来获得对象的其他接口。QueryInterface 方 法是当对象其他接口被请求时的一种客户调用。当一个客户调用 QueryInterface 时,对象提供一个接口并调用 AddRef。实际上,返回 接口的任何 COM 方法有责任为调用者增加对象引用计数。当接口不再 需要时,客户端必须调用 Release 方法。只有在复制接口时客户端才 显式调用 AddRef 方法。 开发 COM 对象时,开发者必须遵循 QueryInterface 规则。这些规则规 定对象接口是对称的、可传递的和自反的,并且在对象生命周期中一 直可用。对客户而言,这意味着根据一个有效的对象接口,通过调用 QueryInterface 来请求对象的其他接口(包括该接口自身)总是有效 的。不能因为时间或安全限制而先支持一个接口,后来又拒绝访问该 接口。其他机制必须用于提供这个层次的功能。有些类支持可选接口 60·ArcGIS Engine 开发指南 微软组件对象模型 第四章·开发环境·61 QueryInterface 规则规定对象接 口是自反的、对称的和可传递的。 控制对象上的一个有效接口指针就 总有可能获取该对象上的其它接 口。 QueryInterface 常简写为 QI。 由于 IUnknow 是所有 COM 对象的基 础,一般在 AO 文档和类模型图中都 不引用该接口。 聪明指针是基于类的聪明类型,本 章后面将详细讨论。 MIDL 通常简化地称为 IDL。 IDL 定义开发者在操作 ArcObjects 时使用的公共接口,在编译时,IDL 会创建一个类型库。 概念。取决于组件对象类,可以选择性的实现一个接口;这不会违反 这个规则,因为这个接口在类上要么总是可用,要么总是不可用。 在请求一个特定接口时,QueryInterface 方法返回一个已经为该请求 接口分配好的内存块,或分配并返回一个新的内存块。当 IUnknow 接 口被请求时才会出现必须还回相同内存块的情况。当比较两个接口指 针以查看它们是否指向同一个对象时,很重要的是不能执行简单比较。 为正确比较两个接口指针以查看它们是否指向同一个对象,都必须请 求它们的 IUnknow,而比较必须在 Iunknow 指针上进行。这样,IUnknow 接口被认为可以定义 COM 对象的标识符。 在 VB 中通过将接口赋值为 Nothing 调用 release 来释放其控制的任何 资源是好的习惯。即使没有调用 release,VB 也会在不再需要该对象 时,即跳出其运行范围时自动调用 release。对于全局变量,必须显式 调用 release。在 VB 中,系统会为用户执行所有这些引用计数操作, 使 COM 对象的使用变得相对简单。 然而,在 C ++中,必须增加和减少引用计数以便使对象正确控制自己 的生命周期。同样,在请求其他接口时必须使用 QueryInterface 方法。 C++中聪明(smart)指针的使用可以简化这些操作。这些聪明指针是 基于类的,因此具有适当的构造器、销毁器和过载操作符以自动化多 数引用计数和查询接口操作。 接口定义语言(INTERFACE DEFINITION LANGUAGE) 微软接口定义语言(MIDL)可用于描述包括其接口的 COM 对象。MIDL 是分布式计算环境(DCE)定义的接口定义语言(IDL)的一个扩展, 其中 IDL 用于定义客户端和服务器间的远程调用。MIDL 扩展包括大部 分对象定义语言(ODL)声明和属性。ODL 早期用于创建 OLE 自动化类 型库。 类型库(TYPE LIBRARY) 类型库最好被看作 IDL 文件的二进制版本。它包含了所有组件对象类、 接口、方法和一个或多个服务中包含的类型的二进制描述。 微软提供了几个能操作类型库的 COM 接口。ITypeInfo 和 ITypelib 就 是其中两个接口。通过使用这些标准的 COM 接口,各种开发工具和编 译器可以获得特定类型库支持的组件对象类和接口方面的信息。 为了支持独立于语言的组件开发思想,涉及 ArcObjects 库的所有相关 数据都被加载进类型库中。类型库中没有头文件,源文件或是外部 微软组件对象模型 62·ArcGIS Engine 开发指南 在本书中的模型图和 AO 对象模型 图中,出接口用实心圈表示。 Iunknown 之所以作为缺省接口的 原因是因为 VB 对象浏览器隐藏了 缺省接口信息,其隐藏 Iunknown 的事实对 VB 开发者并不重要。 开发者提供或需要的对象文件。 入接口(INBOUD) 和出接口(OUTBOUND) 接口可以是入接口或出接口。入接口是最常见的接口类型,客户端调 用包含在对象上的入接口的功能。出接口则是对象调用客户的接口, 这是一种与传统的回调机制类似的技术。 这两种接口的实现方式是不同的。入接口的实现程序必须实现该接口 的所有功能,否则就违反了 COM 协议。出接口也一样。如果使用 VB, 不必实现接口上的所有功能,因为 VB 为那些没有实现的方法提供了存 根(stub)方法。另一方面,如果使用 C++,则必须实现所有的纯虚函 数以编译类。 连接点是操作 COM 出接口时的一个特殊方法。连接点架构定义了对象 间的通信是怎样建立和撤消的。连接点不是初始化双向对象通信的最 有效方法,但是一种常用方法,因为许多开发工具和环境支持它们。 Dispatch 事件接口(Dispatch event interface) server class outbound interface inbound interface client class interfaceinterface interface ArcObjects 中有些对象支持两个出接口事件接口,看起来类似于它们 支持的方法。IDocumentEvents 和 IDocumentEventsDisp 就是这样两个 接口的例子。后缀“Disp”指示是个纯 Dispatch 接口。VBA 在处理某 些应用程序事件时,如载入文档等,会使用这些 Dispatch 接口。VBA 程序员使用 Dispatch 接口,而使用其他开发语言的程序员使用非纯粹 的 Dispatch 接口。因为这些 Dispatch 事件接口是针对特定应用程序 的,所以细节问题将在本书应用程序章节中讨论,而不在框架章节中 讨论。 缺省接口(Default interfaces) 每个 COM 对象都有一个缺省接口,当创建对象时如果没有指定其他接 口就会返回缺省接口。除了少数例外,ESRI 对象库中的所有对象都使 用 IUnknow 作为其缺省接口。 ArcCatalog 和 ArcMap 的 Application 对象的缺省接口都是 IApplication 接口。使用非 IUnknow 的缺省接口是 VBA 的要求,并且 在 ArcMap 和 ArcCatalog 的应用程序层次的对象上才可以发现。 这意味着保持接口指针的变量必须以某种方式声明。要了解更多有关 这方面的细节,请参考本章后面的代码部分。当创建 COM 对象时,可 以请求对象支持的任何接口。 微软组件对象模型 绑定这个术语指给定一个对象指针 匹配函数位置的进程。 第四章·开发环境·63 绑定类型 入进程 DLL 出进程 DLL 延迟绑定 22,250 5,000 自定义 vTable 绑定 825,000 20,000 这个表显示了在典型的 Pentium III 机器上每秒可以调用的函数数 量。 这些模型图总结了 AO 中两个类的 自定义和 Idispatch 接口。vTable 的布局展示了差异。也说明了实现 这些方法的重要性。如果丢失了一 个方法,vTable 的布局将出错,因 此错误的函数指针将被返回给客户 端,导致系统崩溃。 IDispatch 接口 COM 支持 3 类绑定: 1. 延迟绑定(Late)。这种绑定中直到运行时才发现类型。客户端调 用但被对象没有实现的方法在执行时会失败。 2. ID 绑定。方法的 IDs 在编译时被保存,但这个方法的执行仍通过 更高级的功能来执行。 3. 自定义 vTable(早期)绑定。在编译时进行绑定。客户可以直接 调用对象的方法。 IDispatch 接口支持延迟(late-)和 ID-绑定语言。IDispatch 接 口具有允许客户询问对象支持什么方法的方法。 假定支持所需的方法,客户端通过调用 IDispatch::Invoke 方法来执 行该方法。调用完这个方法反过来又调用所需方法并将状态和任何参 数返回给客户端。 很明显,这不是调用 COM 对象最有效的方式。延迟绑定请求一个对象 调用以获得方法 IDs 的列表;客户端必须构建 Invoke 方法调用并调用 它。Invoke 方法必须取得参数并调用功能。 vTable vTable GetTypeInfoCount GetTypeInfo GetIDsOfNames Invoke Name Document StatusBar QueryInterface AddRef Release QueryInterface AddRef Release Name Description AreaOfInterest Custom - Map Dual - Application IUnknown IMap IUnknown IDispatch IApplication 所有这些步骤显著增加了执行该方法所消耗的时间开销。此外,每个 对象都必须具有 Idispatch 的一个实现,使所有对象更大而且增加了 其开发时间。 ID 绑定比延迟绑定稍微有所改进,方法 IDs 在编译时被调入高速缓存, 意味着不需要获取 IDs 的初始调用。然而,仍有很显著的调用开销, 因为为了执行对象上所需的方法,仍调用了 IDispatch::Invoke 方法。 早期绑定,通常也称为自定义 vTabel 绑定,不使用 IDispstch 接口。 相反地,一个类型库提供编译时需要的信息以允许客户端了解服务器 的布局。在运行时,客户端直接调用对象的方法。这是调用对象方法 的最快方法,也对编译时的类型检查有益。 支持 IDispatch 和自定义 vTable 的对象被称为双重接口对象。ESRI 对象库中的对象类没有实现 IDsipatch 接口;意味着这些对象库不能 使用延迟绑定脚本语言,如 JavaScript 和 VBScript,因为这些语言 要求 COM 服务器能支持 IDispatch 接口。 仔细观察 ArcGIS 类模型图,可以看到 Application 对象支持 IDispatch 接口,因为 VBA 要求 IDispatch 接口。 微软组件对象模型 64·ArcGIS Engine 开发指南 直接从非 IUnknown 接口继承的接 口在 VB 中不能实现。 所有 ActiveX 控件都支持 IDispatch。这意味着可以使用 ArcObjects 中的各种 ActiveX 控件从脚本环境中访问其功能。 接口继承(INTERFACE INHERITANCEN) 接口由一组方法和属性组成。如果一个接口继承自另一个接口,则父 接口中的所有方法和属性在子对象中可以直接获得。 这里最基本的原则是接口继承,而不是可以在诸如 SmallTalk 和 C++ 语言中看到的实现继承。在实现继承中,对象实际上是从父对象那里 继承了代码。在接口继承中,传递的是对象方法的定义。实现了接口 的组件对象类必须提供所有继承接口的实现。 异构开发环境中不支持实现继承,因为这需要访问源文件和头文件。 为重用代码,COM 使用集成(aggregation)和包含(containment)原 理。两者都是二进制重用技术。 COM aggregation class interface1 method3 method4 interface2 IUnknown IUnknown class method1 method2 COM containment feature interface2 method7 method8 interface4 IUnknown IUnknown class method1 method2 method5 method6 interface3 method3 method4 interface1 Custom feature class interface1 method3 method4 interface2 IUnknown (inner) IUnknown (controlling) class method1 method2 child class parent class 聚合和包容(AGGREGATION 和 CONTAINMENT) 第三方开发者通过聚合或包容来使用现有的对象,唯一的一个要求就 是驻留待聚合或包容对象的服务要安装在开发者和终端用户机器上。 并不是所有开发语言都支持集成。 最简单的二进制重用形式是包容。包容允许修改原始对象的方法行为, 但是不能改变其签名。通过包容,被包容对象(内部的)并不知道自 己被包容在其他对象(外部的)中。外部对象必须实现内部对象支持 的所有接口。当请求这些接口时,外部对象简单地将请求委托给内部 对象。为支持新功能,外部对象可以在没有传递调用的情况下实现一 个接口,或者实现除内部对象接口外的一个全新接口。 COM 聚合涉及一个外部对象,这个外部对象控制从内部对象中选择提供 哪些接口。聚合方法不能修改原始对象的方法行为。内部对象知道自 己被聚合到另一个对象中,并促进 QueryInterface 调用外部(控制) 对象,以便对象作为一个整体服从 COM 法则。 对使用聚合的对象客户来说,没有方法区分外部对象实现了哪些接口, 内部对象实现了哪些接口。 自定义要素同时使用聚合和包容。开发者聚合那些不需要定制的接口 并包容那些需要定制的接口。被包容接口上的单个方法可以在定制类 中实现,这样可以提供自定义功能,或者这个方法的调用可以传递给 被包容接口的合适方法。 微软组件对象模型 虽然对单元和线程的理解不是使用 ArcObjects 所必须的,但基础知识 将有助于理解本章后面重点介绍的 某种开发环境中的一些暗示。 第四章·开发环境·65 将 SCM(发音为 scum)想象为 COM 运行时环境。SCM 与对象、服务器 和操作系统交互并在客户与其操作 的对象间提供透明。 聚合在这种情况下非常重要,因为要素中定义了一些隐藏接口,它们 不能被包容。要了解有关更多自定义要素方面的信息,请参阅第二卷 第 8 章“访问地理数据库”。 VB6 不支持聚合,所以不能用于创建自定义要素。 线程、单元和调度(Threads Apartments and Marshaling) 线程是贯穿应用程序的进程流。Windows 应用程序中有许多潜在的线 程。单元是在进程中操作语境的线程组。对于 COM+,语境属于一个单元。 语境有许多潜在的类型;安全就是一个语境类型的一个例子。在对象 间成功地相互通信前,对象必须有兼容的语境。 COM 支持两类单元:单线程单元和多线程单元(MTA)。COM+还支持线程 中立单元(TNA)。一个进程可以有很多的 STA;每个进程创建一个 STA 来调用主单元。作为单元创建的线程放置在一个 STA 中。所有的用户 接口代码都被放在一个 STA 中,以防止死锁状态。一个进程只能有一 个 MTA。作为多线程启动的一个线程被放置在 MTA 中。TNA 没有与其永 久关联的线程;而是在适当的时候线程进入和离开单元。 入进程对象在注册表中有一个入口—ThreadingModal—来通知 COM 服 务器控制管理器(SCM)将对象放置到哪个单元中。如果对象请求的单 元和创建的单元兼容,那么对象就被放置到该单元中。否则,SCM 将另 找一个或是新建一个合适的单元。如果没有定义线程模型,对象将被 放置在进程的主单元中。ThreadingModel 注册表入口可能有以下几个 值: Apartments process space Thread neutral apartment Single threaded apartment (main apartment) Single threaded apartment Single threaded apartment Multi-threaded apartment 1. Apartment. 对象必须在 STA 中执行。UI 对象一般使用这个值。 2. Free. 对象必须在 MTA 中执行。创建线程的对象一般被放在 MTA 中。 3. Both. 对象与所有的单元类型兼容。对象将在与创建者相同的单 元中创建。 4. Neutral. 对象必须在 TNA 中执行。对象使用这个值来确保当从其 他单元调用时线程没有切换。Neutral 值只在 COM+下可用。 调度使客户端可以透明地调用其他单元中对象的接口功能。调度可以 发生在不同机器上的 COM 单元之间、不同进程空间的 COM 单元之间及 同一进程空间(如 STA 和 MTA)的 COM 单元之间。COM 提供了一个标准 的调度器,来处理使用自适应数据类型的函数调用(见下表)。只要生 成了代理存根代码,非自适应数据类型也可以由标准调度器处理;否 则,就需要编写自定义调度代码。 微软组件对象模型 66·ArcGIS Engine 开发指南 Windows 系统注册表中的 ESRI 键 类型 描述 (Boolean 具有 True 或 False 值的数据项 unsigned char 8 位无符号数据项 double 64 位 IEEE 浮点数 float 32 位 IEEE 浮点数 int 有符号整数,长度取决于系统 long 32 位有符号整数 short 16 位有符号整数 BSTR 以长度为前缀的字符 CURRENCY 8 位,小数点固定的数值 DATE 64 位,自 1899 年 12 月 30 日以来的浮点分数日 期 SCODE 16 位系统中,对应于 VT_ERROR 的内置错误 Typedef enum myenum 有符号整型,长度取决于系统 Interface IDispatch 指向 IDispatch 接口的指针 Interface IUnkown 指向非源自 IDispatch 的接口的指针 Dispinterface Typename 指向源自 IDispatch 的接口的指针 Coclass Typename 指向组件对象类名的指针(VT_UNKNOWN) [oleautomation]interface Typename 指向源自 IDispatch 的接口的指针 SAFEARRARY(TypeName) TypeName 是上述类型中的任何一种。这些类型的 数组 TypeName TypeName 是上述类型中的任何一种。指向一种类 型的指针 Decimal 以 10 的变量次幂为比例因子的 96 位无符号二进 制整数。一种提供了大小和比例因子的十进制数 据类型 组件类目 客户应用程序用组件类目来有效查找安装在系统中某个特定类型的所 有 COM 类。例如,一个客户应用程序可能支持一个能指定输出格式的 数据输出函数,在这种情况下,组件类目可被用于为各种数据格式查 找数据输出类。如果不使用组件类目,应用程序必须实例化每个对象, 并询问该对象以查看它是否支持所需的功能,这不是一个可行的方法。 通过允许客户应用程序的开发者创建和操作属于特定类目的类,组件 类目支持 COM 的扩展。如果后来向组件类目中添加了一个新的类,客 户应用程序不需更改就可以使用这个新类;在下次读取组件类目时会 自动加载该新类。 COM 与注册表 COM 使用 Windows 系统注册表来存储组成 COM 系统的各 个部分的信息。类、接口、DLLs、EXEs、类型库等都分 配了唯一的识别符(GUID),引用这些组件时 SCM 使用 它们。运行 regedit,然后打开 HKEY_CLASSES_ROOT 就 可以查看这样的一个例子。这样打开了在系统中注册的 所有类的列表。 COM 使用注册表来完成许多日常管理任务,但最重要和最容易理解的是 微软组件对象模型 函数 DLLGetClassObjects 是使 DLL 成为 COM DLL 的一个函数。其他的 函数,如 DLLRegisterServer 和 DllUnregisterServer 等,有的话 当然很好,但不是使 DLL 成为 COM DLL 所必须的。 将 COM 对象实例化到内存中时注册表的使用。最简单的情况就是入进 程服务,其步骤如下: 1. 客户端请求 COM 对象服务。 2. SCM 通过查询类 ID(一个 GUID)寻找被请求对象的注册表入口。 3. 定位DLL并将其导入内存。SCM调用DLL中称为DllGetClassOjects 的函数,将想要得到的类作为第一个参数传递。 4. 类对象正常实现 IClassFactory 接口。SCM 调用该接口上的 CreateInstance 方法以将适合的对象实例化到内存中。 5. 最后,SCM 为客户端请求的接口请求新创建的对象并将该接口回传 给客户端。此时 SCM 退出,客户端和对象直接进行通信。 从上面的步骤序列中很容易想到对象打包方式的变化(DLL 对 EXE)对 对象的客户产生的区别是如何的细微。COM 会处理这些区别。 自动化 自动化是单个对象或整个应用程序通过延迟绑定语言来访问其封装功 能的技术。通常,自动化被看作写入宏,这些宏为了完成任务可以访 问许多应用程序。如前所述,ArcObjects 不支持 IDispatch 接口;因 此,自动化控制器不能单独使用。 通过创建文档对象并通过该文档对象或与其连接的对象之一调用 ArcMap 的方法来实例化一个 ArcMap 例程是可能的。但是,这种方法存 在许多问题,因为自动化控制器例程和 ArcMap 例程运行在不同的进程 中。包含在 ArcObjects 中的许多对象是依赖于进程的,因此简单的自 动化不起作用。 第四章·开发环境·67 使用 ARCOBJECTS 进行开发 使用 ARCOBJECTS 进行开发 为了简单起见,有些示例没有遵循 编码标准。例如,建议在用Visual Basic编码时,在ESRI对象库中定义 的所有类型都要以库名为前缀,如 esriGeometry.IPolyline。只有在 名称会冲突的情况下才会这样做。 忽略这部分内容使得ArcObjects的 新开发人员更容易理解代码。 ArcGIS应用程序是用ArcObjects构建的,并且可以通过许多APIs来开 发。这些APIs包括COM (VB, VC++, Delphi, MainWin),.NET (VB .NET 和C#), Java和 C++。有些APIs比其它API更合适于某些特定应用程序 的开发。后面会简要地讨论这方面的内容,但也应该阅读适合所使用 的产品的开发指南,以获得关于使用何种API的更多信息和建议。 本章后面几节讲述不考虑API而用ArcObjects进行开发时的一般性指 导方针和要考虑的问题。每种比较通用的API语言都有一节专门描述开 发环境、编程技巧、资源和其它用ArcObjects进行开发时必须考虑的 问题。 编码标准 与特定语言相关的每节都以这种语言的编码标准部分开始。这些标准 在ESRI内部使用,且软件随带的示例也遵循这些标准。 要理解标准和指导方针之所以重要的原因,设想在大型软件开发工程 中有许多由团队成员代表的背景。每个程序员对代码风格和编写方法 都有自己的主张。如果每个程序员都以不同的方式设计代码,会极大 地增加共享工作和想法的难度。在一个成功的团队中,每个开发人员 都采用团体共同建立的编码方式。这往往意味着调整自己的代码以匹 配系统中现有代码的风格。 这样做最初这看起来似乎是负担,但采用一种统一的编程风格和一套 不变的技术可以提高了软件的质量。当一个工程中所有的代码遵从一 套标准的格式和约定时,浪费在学习单个程序员独特的句法遁词的时 间大大减少了,可以将更多的时间花费在校对、调试和扩充代码上。 甚至在社会层次上,统一风格鼓励了团队精神而不是个人主义,其前 景将是更大的团队统一性、生产力,最终生产更好的软件。 一般编码技巧和资源 本节关于一般编码技巧的内容对所有使用ArcObjects的开发人员都是 有益的,不管他们使用什么语言。不过,示例代码是用VBA编写。 类图 获取对象模型的帮助是成功使用ArcObjects的基础。附录“理解对象 模型图”通过对象介绍了类图并展示了许多常用的方法。早期学习阶 段查看打印的类图最有用。通过模型图,开发人员可以理解ArcObjects 实现的对象模型的总体结构。当能够轻松地理解总体结构时,就可以 更有效地使用随软件一块发布的PDF文件。这些PDF文件是可搜索的; 可在Adobe® Acrobat® Reader®中用Search对话框快速找到类和接口。 68·ArcGIS Engine 开发指南 使用 ARCOBJECTS 进行开发 第四章·开发环境·69 这个曲线图显示了使用一个与元素 索引相反的计数器访问集合的性能 优势。正如所期望的那样,该曲线 为一个经典的指数趋势线(y=cxb)。 对象浏览器 除了类图的PDF文件外,许多依赖于开发平台的对象浏览器可以用来查 看类型库信息。 Visual Basic和.NET都有内置的对象浏览器;OLEView(Microsoft的 免费应用程序)也可以显示类型库信息。在这种环境中最好的对象浏 览器是ESRI的对象浏览器。使用对象浏览器可以查看所引用的任何类 型库的类型信息。类和接口的信息可以在Visual Basic、Visual C++ 或对象图格式中显示。对象浏览器可以用来查看组件对象类和类,但 是不能用来查看抽象类。抽象类只有在对象图中才是可见的,在这里 使用对象图只是为了简化模型。 Java和C++开发人员应参考ArcObjects JavaDoc或者ArcGIS Developer Help。 组件帮助 所有接口和组件对象类都记录在组件帮助文件中。当更好地掌握了对 象模型时,最终这些帮助是最经常要用到的。 对于Visual Basic和.NET的开发人员而言,这是一个编译过的HTML文 件,可以在这些开发环境中直接查看或使用IDE查看。当按下F1键时, 如果光标停在一个ESRI类型上,ArcGIS开发帮助系统的ArcObjects Class Help中适当的页面就会显示在编译过的HTML浏览器中。 Java和 C++的开发人员可以参考ArcObjects JavaDoc 或 ArcGIS Developer Help。 代码向导 在Visual Basic、 Visual C++和.NET中都有大量的有助于创建样板代 码的代码生成向导(Code Generation Wizards)。虽然这些向导有助 于省去一些沉闷的常规任务,但作为开发人员不能因此而不去理解潜 在于这些代码后面的原理。主要目的是通过阅读这些附带的文档并理 解这些工具的局限性。 集合索引 ArcObjects中所有类似于集合的对象的索引都是从零开始的。并不是 所有的开发环境都是如此;Visual Basic既有从零开始的索引,也有 基于1的索引。作为通用规则,如果集合基数未知,则假定集合基数为 零。这样保证了集合第一次被访问时会出现一个运行时错误(假定集 合的访问不是从零开始的)。假定集合以1为基数,那么,以零为基数 集合的第一个元素就会被错过,并且当代码被运行时,只有到达集合 末尾时,才会出现一个错误。 使用 ARCOBJECTS 进行开发 异常处理是与特定语言相关的,而 且因为COM是语言无关的,所以不支 持异常。 访问集合元素 顺序访问一个集合元素时,最好使用一个计数器接口。这提供了遍历 集合最快的方法。这样做的理由是每次按索引请求一个元素,使用内 部计数器定位元素。因此,如果以循环的方式依次获取一个元素,花 费的时间以指数增长(y=cxb)。 计数器的使用 当从一个对象请求一个计数器接口时,客户端不知道对象如何实现这 个接口。对象可能会创建一个新的计数器,或者为了效率决定返回先 前创建的一个计数器。如果先前的计数器传递至客户端,元素指针的 位置会指向最后一个被访问的元素。为了保证计数器的指针指向集合 的首个元素,客户端在使用前需要将计数器复位。 错误处理 接口的所有方法,也就是其它对象可以调用的方法,应该处理内部错 误并通过合适的HRESULT指示成功或失败。COM不支持传递接口方法调 用外的异常。COM支持COM异常的概念。COM异常使用COM错误对象,通 过在COM错误对象中增加相关的信息并返回合适的HRESULT表示失败。 客户端接收到HRESULT后就询问COM Error对象关于错误的上下文信息。 像Visual Basic这样的语言实现了自身的异常处理模板。要了解更多 信息,请参阅用于开发的合适API语言。 通知接口 ArcObjects中有许多没有方法的接口。这些接口被称作通知接口。这 些接口的目的是通知应用程序框架实现这些接口的类支持特定的功能 集。例如,应用程序框架使用这些接口确定一个菜单对象是一个根菜 单(IRootLevelMenu)还是一个弹出式菜单(IShortcutMenu)。 客户端存储 一些ArcObjects方法期望接口指针在调用方法前指向有效的对象。这 被称作客户端存储,因为客户端在调用方法前为对象分配所需的内存。 假设有一个多边形并想得到它的边界矩形。使用IPolygon上的 QueryEnvelope方法完成这一操作。如果编写下面的代码: Dim pEnv As IEnvelope pPolygon.QueryEnvelope pEnv 就会出错,因为QueryEnvelope方法期望客户端创建Envelope。这个方 法会修改所遍历的外包矩形并返回一个修改过的外包矩形给用户。正 确的代码如下: 70·ArcGIS Engine 开发指南 使用 ARCOBJECTS 进行开发 DISPIDs是为了IDispatch接口有效 使用Invoke方法调用适当方法而给 属性和方法赋与的唯一IDs。 注意“Set”的使用。 Dim pEnv As IEnvelope Set pEnv = New Envelope pPolygon.QueryEnvelope pEnv 如何知道什么时候创建和什么时候不创建?一般来说,所有以 “Query”开头的方法,例如QueryEnvelope期望客户端创建对象。如 果方法名是GetEnvelope,那么就会为用户创建一个对象。使用客户端 存储的原因就是性能。由于对象的方法在一个紧密的循环中被调用, 参数只需要创建一次且易于增加。这比每次在方法内部创建一个新的 对象要快得多。 属性的传值和引用 有时候,会看见设置为值或引用的属性,这意味着该属性既有put_XXX 方法,也有putref_XXX方法。乍一看这可能有点奇怪—为什么一个属 性会支持两种方法呢?Visual C++开发人员将这看作是,简单地给客 户一个机会传递资源的所有权给服务器(使用putref_XXX方法)。 Visual Basic则不同;当然,可能是因为Visual Basic开发人员在属 性赋值上支持引用(By Reference)和传值(By Value)。 为了说明这点,假设一个窗体上有两个文本框,Text1和Text2。使用 propput,在Visual Basic中可能会写成下面这样: Text1.text = Text2.text 也可能写成: Text1.text = Text2 或这样: Text1 = Text2 所有这些例子都利用propput方法将文本框Text2的文本字符串赋给文 本框Text1的文本字符串变量。第二和第三个例子中,因为没有专门的 属性描述,Visual Basic寻找DISPID为0的属性。 假定被操作的是文本框的文本字符属性,这些语句都是正确的。如果 Text2变量引用的实际对象被赋给变量Text1,会发生什么?如果只有 一个propput方法,这是不可能的;因此需要propputref方法。使用 propputref方法,下面的代码获得对象引用的设置。 Set Text1 = Text2 初始化出接口 初始化一个出接口时,如果变量没有监听来自于服务器对象的事件, 只初始化变量是很重要的。不遵守这条规则会导致死循环。 举个例子,假定有一个变量ViewEvents标注如下: Private WithEvents ViewEvents As Map 第四章·开发环境·71 使用 ARCOBJECTS 进行开发 为了正确地接收这个事件句柄,可以在一个UI按钮的OnClick事件中编 写如下代码: Private Sub UIButtonControl1_Click() Dim pMxDoc As IMxDocument Set pMxDoc = ThisDocument ' Check to see that the map is different than what is currently connected If (Not ViewEvents Is pMxDoc.FocusMap) Then ' Sink the event since listener has not been initialized with this map Set ViewEvents = pMxDoc.FocusMap End If End Sub 注意到上面的代码中,使用Is关键字检查对象的身份。 数据库考虑 依靠数据库编程时,必须遵循大量的规则以确保代码最优。下面是这 些规则的详细内容。 如果打算通过编程的方式编辑数据,也就是说,不需要在ArcMap中使 用编辑工具,需要遵循这些规则来确保自定义对象行为正确地被调用 以响应应用程序对数据库所做的修改。这些自定义行为包括网络拓扑 结构的维护或自定义要素定义方法的触发等。为了确保所做的修改在 多用户编辑(long transaction)框架之内,也必须遵循这些规则。 编辑会话 地理数据库的所有修改应在一个编辑会话内完成,该会话用大括号将 位于Workspace对象的IWorkspaceEdit接口上的StartEditing和 StopEditing方法调用之间的代码括住。 任何多用户更新的数据库中都要求这种行为。开始一个编辑会话就将 数据库的一个状态给应用程序,该数据库保证不会被修改,除非通过 编辑程序进行修改。 另外,开始编辑会话将打开地理数据库中这样的行为,以便数据库的 查询保证会返回一个内存中存在的对象引用如果,这个对象前面被查 询过并且仍在使用中的话。 当操作一组相关对象而同时又修改对象时,也需要这种行为以使应用 程序行正确。换言之,不在一个编辑会话内时,每次应用程序从数据 库请求一个特定对象时,数据库都会创建一个新的COM对象实例。 编辑操作 将修改归组到编辑操作中去,该操作用大括号将位于IWorkspaceEdit 接口上StartEditOperation和StopEditOperation方法调用之间的代 72·ArcGIS Engine 开发指南 使用 ARCOBJECTS 进行开发 码括起来。 如果需要这样的话,所有的修改可以在一个编辑操作中完成。编辑操 作可以撤消和重做。如果操作储存在ArcSDE中的数据,至少需要创建 一个编辑操作。创建编辑操作不会有额外的开销。 循环和非循环光标 使用非循环查询光标选择对象或读取将被更新的对象。循环光标仅可 用于只读操作,例如绘制和查询要素。 在一个编辑会话中,非循环光标只有被返回的对象在内存中不存在时, 才会创建一个新对象。 采用查询过滤读取属性 只要读取的是对象的所有属性;查询过滤器应总使用“*”。为了有效 地访问数据库,可以指定从数据库返回对象属性的数量。例如,绘制 要素只需要要素的OID和Shape属性,因此较简单的绘制方式只从数据 库获取这两列数据。这种优化加快了绘制的速度但不适合于编辑要素。 如果不读取所有的属性,那么被触发的特定对象的代码可能会找不到 方法所要求的属性。例如,当要素的几何图形改变时,自定义要素开 发人员更要编写代码来更新属性A和B。如果只恢复几何图形,那么在 OnChanged方法上会发现A、B属性丢失。这会导致OnChanged方法返回 一个错误,从而导致Store返回一个错误并使编辑操作失败。 标记修改过的对象 修改一个对象后,通过在对象上调用Store来标记对象为修改过的对象 (并保证在数据库中更新)。在对象上调用Delete来方法删除该对象。 设置这些调用的版本也存在,如果为了确保最佳的性能而在一组对象 上执行某个操作时会用到。 调用这些方法保证了内置在地理数据库中的所有必要多态性对象行为 被执行(如网络拓扑的更新,或响应ESRI提供的对象的其它列中的变 化而更新特定列)。这也保证了开发人员提供的行为被正确地触发。 更新和插入光标 永远不要使用更新光标或插入光标来更新或将对象插入到一个已经被 加载的地理数据库中具有活动行为的对象类和要素类中。 第四章·开发环境·73 更新和插入光标是在开始数据库加载时使用的大光标APIs。如果在具 有活动行为的对象类或要素类上使用,它们会回避任何与对象创建(例 如拓扑创建)和属性或几何图象更新(如自动重新计算其它的相关字 段)相关的所有特定对象行为。 使用 ARCOBJECTS 进行开发 上图清楚地显示了一个COM对象 Feature有另外一个COM对象作为其 几何图形。要素的Shape属性仅将指 向这个几何图形对象的IGeometry 接口指针向外传递给请求shape的 调用者。这意味着如果不止一个客 户请求该shape,所有客户都指向同 一个几何图形对象。因此这个几何 图形对象必须看作只读对象。不应 该在从这个属性返回的几何图形上 执行任何修改,甚至是临时修改。 任何时候要修改要素的shape属性, 必须修改ShapeCopy属性返回的几 何图形,且更新后的几何图形应随 后赋给Shape属性。 Shape 和 ShapeCopy几何图形属性 利用Feature对象的Shape和ShapeCopy属性来最优化地检索一个要素 的几何图形。为了更好地理解与要素几何图形相关的这些属性,参考 左图以理解来自于某个数据源的要素如何实例化到内存中以便在一个 应用程序中使用。 要素按照下面的顺序从数据源中被实例化: 1. 应用程序通过调用合适的地理数据库API方法调用从一个数据源 请求一个Feature对象。 2. 地理数据库请求COM创建一个期望的COM类(通常是 esriCore.Feature)的vanilla COM对象。 3. COM创建Feature COM对象。 4. 地理数据库从数据源获得属性和几何图形数据。 5. vanilla Feature对象增加适当的属性值。 6. 创建Geometry COM对象,并在Feature对象中设置一个引用。 7. Feature对象被传递到应用程序上。 8. Feature对象在应用程序中一直存在直到不再需要。 使用类型库 由于ArcObjects中的对象没有实现IDispatch,因此为了使编译器早期 绑定到正确的数据类型,有必要使用类型库。这适合于所有的开发环 境,尽管在Visual Basic、Visual C++和.NET中都有帮助设置这些参 考的向导。 ArcObjects需要的类型库位于ArcGIS的安装文件夹中。例如,COM类型 库可以在COM文件夹中找到,而.NET Interop互操作套件在DotNet文件 夹中。许多不同的文件都包含类型库信息,包括EXEs、DLLs、OLE自定 义控件(OCXs)和对象库(OLBs). COM数据类型 COM对象通过它们的接口会话,因此使用的所有数据类型必须被IDL支 持。IDL支持大量的数据类型;然而不是所有支持COM的语言都支持这 些数据类型。因此,ArcObjects没有使用IDL中可用的所有数据类型, 而是将大多数接口限制在Visual Basic支持的数据类型内。下表显示 了IDL支持的数据类型及其在不同语言中的相应的类型。 74·ArcGIS Engine 开发指南 使用 ARCOBJECTS 进行开发 开发语言 IDL Microsoft C++ Visual Basic Java boolean unsigned char unsupported char byte unsigned char unsupported char small char unsupported char short short Integer short long long Long int hyper __int64 unsupported long float float Single float double double Double double char unsigned char unsupported char wchar_t wchar_t Integer short enum enum Enum int Base类型 Interface Pointer Interface Pointer Interface Ref Interface Ref VARIANT VARIANT Variant ms.com.Variant BSTR BSTR String java.lang.String Extended 类型 VARIANT_BOOL short (-1/0) Boolean [true/false] 注意表底部的扩展数据类型:VARIANT、BSTR、和 VARIANT_BOOL。虽然 可以用 char 和 wchar_t 这些数据类型传递字符串,但是有些语言像 Visual Basic 是不支持的。Visual Basic 使用 BSTRs 作为它的文本数 据类型。BSTR 是一个以长度为前缀的宽字符串数组,数组的指针指向 对象里的文本而不是长度前缀。Visual C++将 False 和 True 的 VARIANT_BOOL 值映射为 0 和-1。这和普通映射 0 和 1 是不同的。因此, 在编写 C++代码时,一定要使用正确的宏—VARIANT_FALSE 和 VARIANT_TRUE,而不是 False 和 True。 使用组件类目 组件类目在 ArcObjects 中被广泛使用,以便开发人员不需要对 ArcObjects 代码做任何修改就可以使用新功能。 ArcObjects 以两种方式使用组件类目。第一种方式需要类任何时候都 要在各自的组件类目内注册,例如 ESRI Mx Extensions. Classes,如 果出现在组件类目中,就有一个实现了 IExtension 接口的对象,且当 ArcMap 应用程序启动时就被实例化。如果该类从组件类目中被移出, 扩展模块不会加载,即使地图文档(MXD 文件)正在引用该扩展模块。 第二种用法出现在当应用程序框架使用组件类目来定位类并将这些类 显示给用户以允许一些用户自定义操作时。与第一种方法不同,应用 程序能记住(在一个地图文档中)正在被使用的对象并顺序从地图文 档中加载它们。一个例子就是 ArcMap 中使用的命令。当自定义对话框 显示给用户时,ArcMap 读取 ESRI Mx Commands 类目。这是对该类目的 唯一一次读取。一旦用户选择了一个命令并将之加载到工具条中时, 地图文档用于确定应实例化什么命令。在本章后面讲到调试 Visual Basic 代码时,就会明白这一点的重要性。 现在已经明白了组件类目的两个用途,下面将会看到如何将类注册到 第四章·开发环境·75 使用 ARCOBJECTS 进行开发 76·ArcGIS Engine 开发指南 ArcMap和 ArcCatalog中的自定义 对话框 组件类目管理器 正确的组件类目中。开发环境对组件类目有不同级别的支持;ESRI 提 供了两种添加类到组件类目中的方法;第一种方法只能用于添加到 ArcMap 或 ArcCatalog 中的命令和命令条。在 Customize 对话框中使用 Add From File 按钮(如左图所示),可能要选择一个服务器。该服务 器中所有的类被添加到 ESRI Gx Commands 或 ESRI Mx Commands 中, 这取决于要被定制的应用程序。尽管这个工具非常有用,但也有局限 性,因为它将服务器上所有的类都添加进来。它不能移除类,并且只 支持 ArcObjects 中许多组件类目中的两个类目。 随ArcGIS应用程序一起发布了一个名为组件类目管理器(Component Category Manager)的工具程序,如左图所示。这个小的应用程序可 以任意移出和添加系统的任何组件类目,而不仅是ArcObjects类目。 展开类目可以显示类目中的类列表。然后使用Add Object按钮显示服 务器上所有类的复选清单。复选需要的类,然后这些被复选的类将被 添加到类目中。 使用这些ESRI工具并不是与组件类目交互的唯一方式。在目标用户的 机器上安装服务器时,可以用注册表脚本将相关的信息添加到注册表 中。下面是一个这样的脚本。第一行告诉Windows这个脚本打算使用 regedit的哪个版本。最后一行,以“[HKEY_LOCAL_”开始,执行注册 命令;文件中所有其它行都是注释。 REGEDIT4 ; This Registry Script enters coclasses into their appropriate Component Category ; Use this script during installation of the components ; Coclass: Exporter.ExportingExtension ; CLSID: {E233797D-020B-4AD4-935C-F659EB237065} ; Component Category: ESRI Mx Extensions [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{E233797D-020B-4AD4-935 CF659EB237065}\Implemented Categories\{B56A7C45-83D4-11D2-A2E9-080009B6 F22B}] 上面代码中最后一行在脚本中是连续的一行。 后一种方法为服务器自注册代码将服务器中相关的类添加到合适的类 目中。不是所有的开发环境允许这种设置。Visual Basic不支持组件 类目,虽然也有支持这种功能的插件。请参阅本章后面的Visual Basic 开发插件和活动模板库(ATL,Active Template Library)部分。 VISUAL BASIC 环境 第四章·开发环境·77 下表概括了 Visual Basic 工程中各 种元素的命名标准。 模块类型 前缀 Form frm Class cls Standard bas Project prj 依据模块所提供的所有功能命名模 块;不要使用任何缺省名(如 “Form1”、“Class1” 或 Module1”等)。另外,为窗体、类 和标准模块名添加三个标识其类型 的字母作为前缀,如上表所示。 控件类型 前缀 Check box chk Combo box cbo Command button cmd Common dialog cdl Frame fra Form frm Grid grd Graph gph Image img Image list iml List box lst Label lbl Map control map List view lvw Map control map Menu mnu Masked edit msk Option button opt OLE client ole Progress bar pbr Picture box pic Scroll bar srl Rich text box rtf Slider sld Status bar sbr Text box txt Tab strip tab Tool bar tbr Timer tmr Tree view tvw 与模块一样,根据控件提供的功能 命名控件;不要使用缺省名,因为 这会降低可维护性。用上表中的三 字母前缀来标识控件类型。 VISUAL BASIC 环境 本节的目标读者是VB6和VBA开发人员。本节明确地标出了不同开发环 境的差异。 用户接口标准 要考虑到预加载窗体会增加应用程序的响应时间。注意不要预加载太 多窗体(三个或四个比较好)。 操作位图文件、图标文件和相关文件时,使用资源文件(.res)而不 是外部文件。 用构造函数和析构函数设置那些只在类被加载时才设置的变量引用。 这些VB函数是:Class_Initialize()和Class_Terminate(), 或 Form_Load()和 Form_Unload()。销毁所有对象时将其设置为Nothing。 确保窗体的tab键顺序正确设置。不要将滚动条添加到tab键顺序中; 这样容易令人产生疑惑。 为标识窗体中重要控件的标签添加访问键(使用TabIndex属性)。 如果可能尽量使用系统颜色,而不是代码设定的颜色。 变量声明 始终使用Option Explicit(或者在VB Options对话框中打开 Require Variable Declaration)。这会强制所有变量在使用之 前必须声明,因而可以防止因疏忽造成的错误。 在模块作用域中用Public和Private声明变量,而在局部作用域中 用Dim声明变量。(在Module作用域中Dim和Private的意义相同; 但用Private能提供更多信息)。不再使用Global;它只在向下与 VB 3.0或更早的版本兼容时使用。 始终为变量、参数和函数声明明确的类型。否则缺省为Variant, 其效率较低。 每行只声明一个变量,除非为每个变量指定类型。 这行中count声明为Variant,这可能不是开发者的初衷。 Dim count, max As Long 这行将count和 max都声明为Long,是开发者想要的类型。 Dim count As Long, max As Long 这两行也将count和 max都声明为Long,并且可读性更强。 Dim count As Long Dim max As Long 圆括号 使用圆括号使优先运算和逻辑比较语句更容易阅读。 Result = ((x * 24) / (y / 12)) + 42 If ((Not pFoo Is Nothing) And (Counter > 200)) Then VISUAL BASIC 环境 用下面的句法命名变量和常量: [<库名.>][<作用域_>]<类型><名 称> <名称>描述变量的作用或内容。< 作用域>和<类型>部分应始终使用 小写,<名称>应该用大小写混合。 78·ArcGIS Engine 开发指南 类库名 类库 esriGeometry ESRI 对象库 stdole 标准OLE COM库 stdole 简单变量数据类型 <类库名> 前缀 变量作用域 c 窗体或类中的常量 g 类窗体或标准模块中定义的公共 变量 m 类模块或窗体模块中定义的私有 变量 局部变量 <作用域_> 前缀 数据类型 b 布尔型 by 字节或无符号字符 d 双精度型 fn 函数 h 句柄 i 整型 l 长整型 p 指针 s 字符型 <类型> 条件限定顺序 与C和C++等语言不同,Visual Basic在条件所有部分上执行条件检验, 即使条件的第一部分为False。这意味着不能在条件语句的前面部分进 行了有效性检验的对象和接口上执行条件检验。 ' The following line will raise a run-time error if pFoo is NULL. If ((Not pFoo Is Nothing) And (TypeOf pFoo.Thing Is IBar)) then End If ' The correct way to test this code is If (Not pFoo Is Nothing) Then If (TypeOf pFoo.Thing Is IBar) Then ' Perform action on IBar thing of Foo. End If End If 缩进 用两个空格或两个字符宽度的制表位来缩进。由于VB代码只有一个编 辑器,缩进格式问题不象在C++代码中那样至关重要。 缺省属性 除了最常见的例子外,避免使用缺省属性。这会降低可读性。 模块间引用 访问模块间数据或函数时,始终用模块名限定引用。这使得代码更具 可读性并且会形成更有效的运行时绑定。 多属性操作 对同一对象的不同属性进行多个操作时,使用With … End With语句。 这比每次指定对象的效率要高。 With frmHello .Caption = "Hello world" .Font = "Playbill" .Left = (Screen.Width - .Width) / 2 .Top = (Screen.Height - .Height) / 2 End With 数组 对于数组,永远不要改变Option Base为任何非零值(0为缺省值)。 用Lbound和Ubound迭代数组中所有数据项。 myArray = GetSomeArray For i = LBound(myArray) To UBound(myArray) MsgBox cstr(myArray(i)) Next I VISUAL BASIC 环境 第四章·开发环境·79 位运算符 因为And, Or, 和Not是位运算符,确保只用它们来检验所有条件中的 Boolean值(当然,除非位运算语义发生变化)。 If (Not pFoo Is Nothing) Then ' Valid Foo do something with it End If 类型后缀 禁止在变量或函数名上使用类型后缀(如myString$或 Right$(myString)),除非需要区分16位和32位数值。 模糊类型匹配 使用明确的转换运算符(如CSng、CDbl和CStr)来进行模糊类型匹配, 而不是靠VB来选择使用那一个运算符。 简单图象显示 用ImageControl而不是PictureBox来显示简单图象。这样效率更高。 恢复语 句 使用 频率 含义 Exit Sub 通常 函数失败,将控制返回给 调用者 Raise 经常 在调用者的作用域内引 发一个新错误代码 Resume 少 移除错误条件,再尝试执 行语句 Resume Next 非常 少 忽略错误并继续执行下 一条语句 错误处理 始终用On Error来保证容错代码。对于每个进行错误检测的函数,用 On Error跳至程序的单个错误句柄,该句柄处理所有可能遇到的异常 情况。错误句柄处理错误后—通常是显示一个消息—应通过执行左侧 表中显示的一个恢复语句继续运行程序。 Visual Basic中的错误处理与COM中的一般错误处理不同(请参见“操 作HRESULTs”一节)。 事件函数 避免在事件函数中放置很多行代码以防止高度断裂和没有条理的代 码。事件函数应该简单地指派其他地方的可重用函数。 内存管理 为了保证有效地利用内存资源,应该考虑以下几点: 定期卸载窗体。不要保留许多被加载但不可见的窗体,因为这会 消耗系统资源。 要明白引用一个窗体作用域的变量会加载该窗体。 设置不再使用的对象为Nothing以释放内存。 利用Class_Initialize()和Class_Terminate()来分配和释放资 源。 VISUAL BASIC 环境 在软件的早期的版本中,VBVM 称作 VB Runtime。 While Wend结构 避免使用While … Wend结构。而使用Do While … Loop或Do Until ... Loop,因为可以从这个结构中有条件分支。 pFoos.Reset Set pFoo = pFoos.Next Do While (Not pFoo Is Nothing) If (pFoo.Answer = "Done") Then Exit Loop Set pFoo = pFoos.Next Loop Visual Basic虚拟机 Visual Basic虚拟机(VBVM)包含成功执行所有Visual Basic开发代 码所需的内在Visual Basic控件和服务,如开始和结束一个Visual Basic应用程序。 VBVM被封装为一个DLL,必须要安装在要执行用Visual Basic编写的代 码的任何机器上,即使这些代码已经被编译成为本地码。如果查看任 何Visual Basic编译文件的依赖关系,就会列出msvbvm60.dll;该DLL 封装了虚拟机。 要了解有关VBVM提供的服务方面更多的信息,请参见本章的“与 IUnknown接口交互”和“操作HRESULTs”部分。 与IUnknown接口交互 COM一节中详细讨论了有关IUnknown接口以及如何以IUnknown接口为 基础构建所有COM方面的内容。Visual Basic对开发者隐藏了这个接口, 并为开发者执行所需的交互(QueryInterface、AddRef和Release 函 数调用)。由于包含在VBVM中的功能,可以实现这种交互。这为许多 开发者简化了用COM进行的开发,但要成功操作ArcObjects,必须理解 VBVM的工作原理。 Visual Basic开发人员习惯于按照下面的方式声明变量: Dim pColn as New Collection 'Create a new collection object. PColn.Add "Foo", "Bar" 'Add element to collection. 这时会发生什么是值得考虑的。快速地查看一下这些代码,看起来好 象第一行代码创建了一个集合对象并且给开发人员一个pColn形式的 对象句柄。然后开发人员调用该对象上的Add方法。在本章前面已经了 解到对象通过它们的接口会话,而永远不会通过对象自身的一个直接 句柄来进行会话。记住,对象通过接口提供它们的服务。如果这是事 实,就没有增加某些事物。 真正发生的是在表示对象和接口方式上由VBVM执行的一些“VB魔术” 和由Visual Basic编辑器执行的一些把戏。第一行代码实例化了一个 集合类实例,然后为该对象将缺省的接口,_Collection。指派给变量 pColn,在_Collection接口上定义了方法。Visual Basic隐藏了基于 80·ArcGIS Engine 开发指南 VISUAL BASIC 环境 接口编程的事实以简化开发工作。如果对象实现的所有功能都可以通 过一个接口来访问,就不存在这个问题,但是当对象上有多个接口提 供服务时,这就是一个问题了。 Visual Basic编辑器通过在IntelliSense完全列表和对象浏览器中隐 藏缺省接口来弥补这种缺陷。缺省情况下,任何以下划线开头的接口 都不会显示在对象浏览器中(要显示这些接口,打开Show Hidden Member,但这样仍不会显示缺省接口)。 已经知道大多数ArcObjects以IUnknown为缺省接口,也知道Visual Basic不提供任何IUnknown方法,即QueryInterface, AddRef和 Release。假设有一个类Foo支持三个接口,IUnknown (缺省接口)、IFoo 和IBar。这意味着如果如下面的代码那样来声明变量pFoo,pFoo变量 会指向IUnknown接口。 Dim pFoo As New Foo ' Create a new Foo object pFoo.?????? 由于Visual Basic不允许直接访问IUnknown的方法,必须间接使用QI 查询具有需要调用的方法的一个接口。因此,声明一个保存指向接口 指针的变量的正确方法如下: Dim pFoo As IFoo ' Variable will hold pointer to IFoo interface. Set pFoo = New Foo ' Create Instance of Foo object and QI for IFoo. 现在有一个指向对象接口的指针,从对象请求其它任何一个接口都是 一件很容易的事了。 Dim pBar as IBar 'Dim variable to hold pointer to interface Set pBar = pFoo 'QI for IBar interface 根据惯例,大多数类都有一个与类名相同但有“I”前缀的接口;这是 在操作对象时最常用到的一个接口。在实例化一个对象时,请求哪个 接口是不受限制的;任何支持的接口都可以被请求,因此下面的代码 是有效的。 Dim pBar as IBar Set pBar = New Foo 'CoCreate Object Set pFoo = pBar 'QI for interface 对象控制其自己的生存周期,这就要求每当一个接口指针被赋给另一 个变量而被复制时,客户端调用AddRef,当不再需要接口指针时,调 用Release。确保AddRefs和Release的数目匹配是很重要的,幸运的是,Visual Basic自动执行这些调用。这保证对象不会“泄漏”。甚至当接口指针 被重用时,Visual Basic会在将新接口赋给变量时正确地在旧接口上调用 Release。下面的代码举例说明了这些概念;注意代码执行的不同阶段对 象的引用计数。 第四章·开发环境·81 VISUAL BASIC 环境 查看磁盘上 Visual Basic Magic 示例来查找这段代码。鼓励运行示 例并逐步通过代码。该对象也使用 一个 ATL C++工程来定义 SimpleObject 及其接口;鼓励仔细 研究这段代码以学习 C++ ATL 对象 的简单实现。 Private Sub VBMagic() ' Dim a variable to the IUnknown interface on the simple object. Dim pUnk As Iunknown ' Co Create simpleobject asking for the IUnknown interface. Set pUnk = New SimpleObject 'refCount = 1 ' QI for a useful interface. ' Define the interface. Dim pMagic As IsimpleObject ' Perform the QI operation Set pMagic = punk 'refCount = 2 ' Dim another variable to hold another interface on the object. Dim pMagic2 As IanotherInterface ' QI for that interface Set pMagic2 = pMagic 'refCount = 3 ' Release the interface pointer. Set pMagic2 = Nothing 'refCount = 2 ' Release the interface. Set pMagic = Nothing 'refCount = 1 ' Now reuse the pUnk variable - what will VB do for this? Set pUnk = New SimpleObject 'refCount = 1, then 0, then 1 ' Let the interface variable go out of scope and let VB tidy up. End Sub 'refCount = 0 接口通常有一些实际上是指向其它接口的指针的属性。Visual Basic 允许通过链接这些接口以一种快速(shorthand)方式访问这些属性。 例如,假设有一个指向IFoo接口的指针,而该接口有一个名为Gak的属 性,该属性是一个具有DoSomething()方法的IGak接口。可以选择如何 访问DoSomething方法。第一种方法是一个慢速(long-handed)方法。 Dim pGak as IGak Set pGak = pFoo 'Assign IGak interface to local variable. pGak.DoSomething 'Call method on IGak interface. 也可以用一行代码链接接口并完成同样的任务。 pFoo.Gak.DoSomething 'Call method on IGak interface. 当仔细研究示例代码时会看到这两种方法。通常前一种方法用在比较 简单的示例中,因为这种方法明确告诉使用的是什么接口。更复杂一 些的示例使用快速方法。 82·ArcGIS Engine 开发指南 VISUAL BASIC 环境 为找出一个 ArcObjects 在哪个库 中,查看开发帮助中的对象模型图, 或者使用开发工具包工具目录的 LibraryLocator 工具。 IID 是 Interface Identifier(一 个 GUID)的缩写。 将接口链接起来的技术常常被用来获得一个属性值,但不能始终用来 设置一个属性值。只有当链中的所有接口是通过引用设置的,才可以 使用接口链接来设置属性值。例如,下面的代码会成功执行。 Dim pMxDoc As ImxDocument Set pMxDoc = ThisDocument pMxDoc.FocusMap.Layers(0).Name = "Foo" 上面的这个例子可以正确执行,因为Map中的Layer和文档中的Map都是 以引用返回的。下面这行代码就不能正确执行,因为Extent外接矩形 在活动视图中是以值设置的。 pMxDoc.ActiveView.Extent.Width = 32 上面这样不能正常运行的原因是VBVM为了获得终端属性而扩展了接口 链。因为链中的接口是通过值来处理的,VBVM拥有的是变量自身的副 本,而不是被链接的变量。为了设置上面例子中范围外接矩形的Width 属性,VBVM必须象下面这样编写代码: Dim pActiveView as IActiveView Set pActiveView = pMxDoc.ActiveView Dim pEnv as IEnvelope Set pEnv = pActiveView.Extent ' This is a get by value, PEnv.Width = 32 ' The VBVM has set its copy of the Extent and not ' the copy inside the ActiveView 为了使上面代码正常运作,VBVM额外要求下面一行代码。 pActiveView.Extent = pEnv ' This is a set by value, 访问ArcObjects 现在将看一些涉及ArcObjects的创建实例和查询接口操作的特殊用 法。为在Visual Basic 或VBA中使用ArcGIS对象,首先必须引用包含 该对象的ESRI库。如果在ArcMap或ArcCatalog中使用VBA,大多数常用 的ESRI对象库已经被引用了。在独立的Visual Basic应用程序或组件 中,必须手工引用所需的库。 下面以识别一个简单的对象及其支持的接口作为开始。在这个例子中, 会使用一个Point对象和IPoint接口。为这个点设置坐标的一种方法就 是在IPoint接口上调用PutCoords方法并传入坐标值。 Dim pPt As IPoint Set pPt = New Point pPt.PutCoords 100, 100 这个简单代码块的第一行举例说明了持有该对象支持接口的引用的用 法。这一行代码为IPoint接口从ESRI对象库中读取IID。在接口名之前 加上库名会减少模糊性(符合编码指导方针),尤其是如果在相同的 工程中还引用了其它的对象库,例如: Dim pPt As esriCore.IPoint 第四章·开发环境·83 VISUAL BASIC 环境 Coclass 是组件对象类的缩写 由于对象的缺省接口是 IUnknown, 所以 QI 是必须的。因为 pPt 变量被 声明为 IPoin 类型,缺省的 IUnknown 接口是 IPoint 接口的 QI’d。 84·ArcGIS Engine 开发指南 当在某个接口上找不到方法或属性 时,会弹出这个编译错误消息窗口。 这样,如果碰巧在工程中还有另一个被引用的 IPoint,关于正在引用 哪一个库对象就不会出现任何歧义。 代码块中的第二行创建了对象或组件对象类的一个实例,然后为赋给 pPt的Ipoint接口执行一个QI操作。 对于象Point这样普通的组件对象类的名字,可能希望在组件对象类的 名字前加上库名,例如: Set pPt = New esriCore.Point 代码块的最后一行调用PutCoords方法。如果一个方法不能在接口上定 位,编译时会出现错误。 操作HRESULTs 目前为止已经看见了通过从方法返回的HRESULT来标识操作成功或失 败的所有COM方法;没有异常在接口之外引发。也已经知道当遇到错误 时,Visual Basic会引发异常。在Visual Basic中,HRESULTs从来不 从方法调用上返回,而当错误已经发生时Visual Basic会抛出一个异 常,这让人更加糊涂。怎么会这样呢?答案就在于Visual Basic 虚拟 机。接收HRESULT的是VBVM;如果接收到的不是S_OK,VBVM就抛出异常。 如果能从COM错误对象上检索到任何有价值的错误信息,VBVM就用该信 息填充Visual Basic Err对象。这样,VBVM控制所有从客户返回的 HRESULTs。 当在Visual Basic中实现接口时,抛出一个HRESULT错误以通知调用者 发生了错误是一个很好的编程习惯。通常,当方法还没有实现时会发 生这种异常。 ' Defined in module Const E_NOTIMPL = &H80004001 'Constant that represents HRESULT 'Added to any method not implemented On Error GoTo 0 Err.Raise E_NOTIMPL 必须编写代码处理返回的HRESULT不是S_OK的可能情况。发生这种情况 时,应调用错误处理程序并处理错误。这可能只是简单地通知用户, 或者也可能是自动处理错误并继续执行函数。如何选择取决于环境。 下面是一个简单的错误处理程序,可以捕获函数内任何的错误并报告 给用户。注意Err对象的使用将有关错误的一些描述提供给了用户。 Private Sub Test() On Error GoTo ErrorHandler ' Do something here. Exit Sub ' Must exit sub here before error handler ErrorHandler: Msgbox "Error In Application – Description " & Err.Description End Sub VISUAL BASIC 环境 操作属性 有的属性引用ESRI对象库中的特定接口,而其它属性则具有标准数据 类型的值,例如字符串、数值表达式和布尔值等。对于接口引用,声 明一个接口变量并用Set语句将接口引用赋给属性。对于其它值,声明 一个数据类型明确的变量或使用Visual Basic的Variant数据类型。然 后使用简单的赋值语句给变量赋值。 本身是接口的属性可以通过引用或者通过值来设置。通过值来设置的 属性不需要Set语句。 Dim pEnv As IEnvelope Set pEnv = pActiveView.Extent 'Get extent property of view. pEnv.Expand 0.5, 0.5, True 'Shrink envelope. pActiveView.Extent = pEnv 'Set By Value extent back on IActiveView. Dim pFeatureLayer as IfeatureLayer Set pFeatureLayer = New FeatureLayer 'Create New Layer. Set pFeatureLayer.FeatureClass = pClass 'Set ByRef a class into layer. 正如期望的那样,有些属性是只读的,有些属性是只写的,而且还有 一些属性是可读写的。所有对象浏览器和ArcObjects Class Help(在 ArcGIS 开发帮助系统中可以找到)都提供这方面的信息。如果试图使 用一个属性,并且忘记或错用了Set关键字,Visual Basic对源代码的 编译将失败,返回一个“方法或数据成员没有找到”的错误消息。这 个错误看起来很奇怪,因为可能解释为试图给一个只读的属性赋值。 产生这个消息的原因是Visual Basic试图在类型库中找到一个映射属 性名的方法。在上面的例子中,在类型库中的底层方法调用是 put_Extent和putref_FeatureClass。 操作方法 方法执行某种动作,并且可能返回值或不返回值。在有些情况下,方 法返回的值是一个接口;例如,在下面这段代码中,EditSelection返 回一个枚举型要素接口: Dim pApp As IApplication Dim pEditor As IEditor Dim pEnumFeat As IEnumFeature 'Holds the selection Dim pID As New UID 'Get a handle to the Editor extension pID = "esriEditor.Editor" Set pApp = Application Set pEditor = pApp.FindExtensionByCLSID(pID) 'Get the selection Set pEnumFeat = pEditor.EditSelection 在其他情况下,方法返回一个反映操作成功与否的布尔值,或将数据 写入一个参数中;例如,如果发生一个选择,GxDialog的DoModalOpen 第四章·开发环境·85 VISUAL BASIC 环境 方法返回一个 True 值并将该选择写到一个 IEnumGxObject 参数中。注 意不要混淆 Visual Basic 从一个方法调用返回值这个概念和所有 COM 方法必须返回一个 HRESULT 这个概念。VBVM 可以读取类型库信息并将 VB 方法调用的返回值设置为 COM 方法的合适参数。 操作事件 事件让用户知道什么时候会发生某件事。可以添加代码以响应一个事 件。例如,命令按钮有一个Click事件。可以添加代码以便当用户单击 这个控件时执行某个动作。也可以添加某些对象产生的事件。VBA和 Visual Basic允许用关键字WithEvents声明一个变量。WithEvents告 诉开发环境该对象变量会被用来响应对象事件。这有时候也称作一个 “事件汇”。WithEvents必须在类模块或窗体中声明。下面是在 Declarations部分声明变量和暴露对象事件的方法。 Private WithEvents m_pViewEvents as Map Visual Basic中每个组建对象类只支持一个出接口(在IDL中标记为缺 省出接口)。为克服这个缺陷,实现多个出接口的组件对象类有一个 相关联的伪(dummy)组件对象类,允许访问第二个出接口。这些伪组 件对象类的名字与它们包含的出接口相同,只是没有前缀I。 Private WithEvents m_pMapEvents as MapEvents 一旦声明了变量,就可以在Code窗口左上方的Object组合框中查找其 名字。然后在Code窗口右上角的Procedure/Events组合框中检查可以 添加代码的事件列表。 不是所有事件出接口的方法都需要存根,如同Visual Basic会为任何 没有实现的方法存根一样。这一点与入接口不同,它的所有方法就必 须存根以进行编译。 在调用方法之前,必须建立事件源和汇之间的连接。可以通过设置表 示事件汇到事件源的变量来完成这个任务。 Set m_pMapEvents = pMxDoc.FocusMap 指向作为参数的有效对象的指针 有些ArcGIS方法需要接口作为它们的参数。在方法调用前或方法调用 完成之后,被传递的接口指针可以指向一个实例化的对象。 例如,如果想查找一个多边形(pPolygon)的中心点,可以象下面这样 编写代码: Dim pArea As IArea Dim pPt As IPoint Set pArea = pPolygon ' QI for IArea on pPolygon Set pPt = pArea.Center 86·ArcGIS Engine 开发指南 VISUAL BASIC 环境 开发人员不需要创建 pPt,因为 Center 方法会创建了一个 Point 对象 并通过其 IPoint 接口传回一个对该对象的引用。只有使用客户端存储 的方法才需要在方法调用之前创建对象。 在模块间传递数据 当在模块间传递数据时,最好使用处理特定私有成员变量的存取函数 和赋值函数(accessor和mutator functions)。这提供了数据封装, 既面向对象编程的一种基本技术。永远不要使用公有变量。 例如,可能已经将一个变量的有效范围设为1-100。如果允许其他开发 人员直接访问这个变量,他们可能会为该变量设置非法值。处理这些 非法值的唯一的方法就是在使用前检验它们。这在编程上既容易出错, 也十分烦琐。声明所有变量为类模块的私有成员变量并为处理这些变 量提供存取函数和赋值函数可以解决这个问题。 在下面的例子中,这些属性被添加到类的缺省接口。注意用来给客户 抛出错误的技术。 Private m_lPercentage As Long Public Property Get Percentage() As Long Percentage = m_lPercentage End Property Public Property Let Percentage(ByVal lNewValue As Long) If (lNewValue >= 0) And (lNewValue <= 100) Then m_lPercentage = lNewValue Else Err.Raise vbObjectError + 29566, "MyProj.MyObject", "Invalid Percentage Value. Valid values (0 -> 100)" End If End Property 当编写代码从一个窗体、类或模块将对象引用传递到另一个窗体、类 或模块时,例如: Private Property Set PointCoord(ByRef pPt As IPoint) Set m_pPoint = pPt End Property 这些代码传递了指向IPoint接口实例的一个指针。这意味着只是传递 对接口的引用,而不是接口本身;如果添加ByVal关键字(像下面这样), 接口通过值来传递。 Private Property Let PointCoord(ByVal pPt As IPoint) Set m_pPoint = pPt End Property 在这两个例子中,接口指向的对象总是通过引用来传递。为了通过值 传递对象,必须创建对象的一个副本并传递这个副本。 第四章·开发环境·87 VISUAL BASIC 环境 使用TypeOf关键字 可以用Visual Basic的TypeOf关键字来检查一个对象是否支持某个接 口。例如,给定ArcMap内容列表中所选的一个数据项,可以用下面的 代码检测该数据项是否为一个FeatureLayer: Dim pDoc As IMxDocument Dim pUnk As IUnknown Dim pFeatLyr As IGeoFeatureLayer Set pDoc = ThisDocument Set pUnk = pDoc.SelectedItem If TypeOf pUnk Is IGeoFeatureLayer Then ' can we QI for IGeoFeatureLayer ? Set pFeatLyr = pUnk ' actually QI happens here ' Do something with pFeatLyr. End If 使用Is操作符 如果代码要求比较两个接口引用变量,可以使用Is操作符来完成比较。 一般地,可以在以下情况中使用Is操作符: 检测是否有一个有效接口。 Dim pPt As IPoint Set pPt = New Point If (Not pPt Is Nothing) Then 'a valid pointer? ... ' do something with pPt End If 检查两个变量是否引用同一个实际对象。比如说有两个IPoint类型 的接口变量pPt1和pPt2。它们是否指向相同的对象?如果是,则pPt1 Is pPt2。 Is关键字使用一个对象的COM身份。下面一个例子说明了Is关键字的用 法。该例子检验接口的某个方法返回的是对象的副本还是真实对象的 引用。 在下面的例子中,地图(IMap)的Extent属性返回一个副本,而文档 (IMxDocument)的ActiveView属性总返回一个对真实对象的引用。 Dim pDoc As IMxDocument Dim pEnv1 As IEnvelope, pEnv2 as IEnvelope Dim pActView1 As IActiveView Dim pActView2 as IActiveView Set pDoc = ThisDocument Set pEnv1 = pDoc.ActiveView.Extent Set pEnv2 = pDoc.ActiveView.Extent Set pActView1 = pDoc.ActiveView Set pActView2 = pDoc.ActiveView ' Extent returns a copy, ' so pEnv1 Is pEnv2 returns False Debug.Print pEnv1 Is pEnv2 ' ActiveView returns a reference, ' so pActView1 Is pActView2 Debug.Print pActView1 Is pActView2 88·ArcGIS Engine 开发指南 VISUAL BASIC 环境 计数器可以支持其它方法,但所有 计数器中都有这两个方法。 在集合中迭代 在操作ArcMap和ArcCatalog时,会发现许多情况下要操作集合。可以 用一个计数器在这些集合中迭代。计数器是一个提供遍历元素列表方 法的接口。计数器接口一般以IEnum开始并有两个方法:Next和Reset。 Next返回集合中的下一个元素并向前移动内部指针,Reset复位内部指 针到集合的开始。 这里是一些VBA代码,在一个地图中选中的要素(IEnumFeature)上执 行循环操作。要试验这些代码,加载States示例图层到地图中并用 Select工具选择多个要素(用鼠标拖出一个矩形框进行选择)。将代 码添加到一个VBA宏中,然后执行该宏。每个选中的州的名字会打印在 调试窗口中。 Dim pDoc As IMxDocument Dim pEnumFeat As IEnumFeature Dim pFeat As IFeature Set pDoc = ThisDocument Set pEnumFeat = pDoc.FocusMap.FeatureSelection Set pFeat = pEnumFeat.Next Do While (Not pFeat Is Nothing) Debug.Print pFeat.Value(pFeat.Fields.FindField("state_name")) Set pFeat = pEnumFeat.Next Loop 有些集合对象,Visual Basic Collection就是一个,实现了一个名为 _NewEnum的特殊接口。因为有“_”前缀,这个接口会被隐藏,但是Visual Basic开发人员仍然可以用它来简化集合中的迭代。Visual Basic For Each结构使用这个接口,以在一个集合中执行Reset和Next步骤。 Dim pColn as Collection Set pColn = GetCollection()' Collection returned from some function Dim thing as Variant ' VB uses methods on _NewEnum to step through For Each thing in pColn ' an enumerator. MsgBox Cstr(thing) Next 第四章·开发环境·89 VISUAL BASIC 开发环境 VISUAL BASIC 开发环境 可以使用ESRI VB Add-In接口实现 工具自动执行步骤3和4。 本章前一节基本上集中讲述如何在ArcGIS Desktop应用程序内嵌的VBA 开发环境下编写代码。这一节重点讲述有关创建可以添加到应用程序 中的ActiveX DLLs和使用Visual Basic开发环境编写外部独立应用程 序方面的内容。 创建COM组件 大多数开发者使用Visual Basic来创建操作ArcMap或ArcCatalog的COM 组件。在本章前面已经知道因为ESRI应用程序是COM客户端—它们的体 系结构支持使用遵从COM规程的软件组件—因而可以用包括Visual Basic在内的不同语言来构建组件。然后这些组件就可以轻松地添加到 应用程序中。要了解关于打包和部署用Visual Basic创建的COM组件方 面的更多信息,请参见本章最后一节。 本节并不想作为一个Visual Basic教程;而是重点介绍使用ArcObjects 时应该了解的Visual Basic知识。 在Visual Basic中,可以通过创建一个ActiveX DLL来构建一个可以操 作ArcMap或ArcCatalog 的COM组件。这里回顾一些涉及到的基本步骤。 注意这些步骤不是什么都包括的。具体的工程可能涉及其它需求。 1. 启动Visual Basic。在New Project对话框中创建一个ActiveX DLL Project。 2. 在Properties窗口中,确认最初的类模块和添加到Project中的任 何其它类模块的Instancing属性设置为5—MultiUse。 3. 引用所需的ESRI Object Libraries。 4. 实现需要的接口。在一个类模块中实现一个接口时,该类提供了 该接口的类型库中指定的所有公共过程在该类自己上的版本。除 了提供接口原型和过程之间的映射外,Implements语句使得类接 受调用专门接口ID的COM QueryInterface。必须包含所有涉及的 公共过程。接口或类的实现中如果缺少一个成员就会导致错误。 如果没有在所实现的类中的过程中输入代码,会引发适当的错误 (Const E_NOTIMPL = &H80004001)。那样,如果其他人使用这 个类,就会明白一个成员没有被实现。 5. 添加所需的任何其他代码。 6. 确定Project Name和其它属性来识别组件。在Project Properties 对话框中,指定的工程名会被作为组件类型库名。可以将工程名 和类名结合起来提供唯一的类名(也叫做ProgIDs)。这些名称出 现在Component Category Manager中。保存工程。 90·ArcGIS Engine 开发指南 VISUAL BASIC 开发环境 Visual Basic为类、接口和库自动 产生必要的GUIDs。设置二进制兼容 性强制VB从一个先前编译的DLL复 用GUIDs。这样做是必要的,因为 ArcMap在文档中储存了命令的 GUIDs以备下次加载。 7. 编译DLL。 8. 设置组件的Version Compatibility为二进制。因为代码需要不断 进化,所以将组件设置为Binary Compatibility是一个很好的习 惯,这样,如果修改了一个组件,会被警告破坏了兼容性。要了 解更多其他信息,请参见Visual Basic在线帮助中的“二进制兼 容模式”帮助主题。 9. 保存工程。 10. 使组件对应用程序可用。通过在Customize对话框中的Commands选 项卡上单击Add from file按钮添加组件到一个文档或模板中。另 外,还可以在Component Category Manager中注册一个组件。 实现接口 Visual Basic中接口实现的方式是不同的,取决于它们是入接口还是 出接口。Visual Basic将出接口被看作事件源,并通过WithEvents关 键字来支持。要在Visual Basic中操作出接口IactiveViewEvents(Map 类缺省出接口),使用WithEvent关键字并提供适当的函数来处理事件。 Private WithEvents ViewEvents As Map Private Sub ViewEvents_SelectionChanged() ' User changed feature selection update my feature list form UpdateMyFeatureForm End Sub 入接口用Implements关键字来支持。然而,与出接口不同,定义在入 接口上的所有方法必须存根。这保证对象实例化时形成正确的vTable。 不是所有的方法都必须完全编码的,但是函数必须存根。如果实现是 空白的,应有适当的返回代码通知用户方法未被实现(参见“操作 HRESULTs”一节)。为实现IExtension接口,需要类似于下面的代码。 注意到所有的方法都被实现。 Private m_pApp As IApplication Implements IExtension Private Property Get IExtension_Name() As String IExtension_Name = "Sample Extension" End Property Private Sub IExtension_Startup(ByRef initializationData As Variant) Set m_pApp = initializationData End Sub Private Sub IExtension_Shutdown() Set m_pApp = Nothing End Sub 第四章·开发环境·91 VISUAL BASIC 开发环境 92·ArcGIS Engine 开发指南 ESRI Object Library被引用后,包 含在其内的所有类型都可通过 Visual Basic获得。IntelliSense 也会操作对象库的内容。 设置ESRI对象库引用 使用内嵌于应用程序内的VBA开发环境和Visual Basic的主要差异就在 于后者要求加载所有合适的对象库,这样所声明的任何对象变量都可 以被找到。如果不添加引用,会出现左侧的错误消息。另外全局变量 ThisDocument和 Application对后者用户不可用。 添加对一个对象库的引用 取决于想要代码做的事情,可能会需要添加几个ESRI的核心对象和扩 展库。可通过在开发者帮助中查看对象模型图,或使用位于开发工具 包工具目录中的LibraryLocator工具确定一个对象属于哪个库。 选择Visual Basic Project菜单中的References来显示References对 话框,以便在其中设置所需的引用。 在通过复选名字右侧的复选框设置了一个对象库的引用后,可以在对 象浏览器中找到特定的对象及其方法和属性。 如果没有使用任何被引用库中的对象,应该清除该引用的复选框使 Visual Basic必须处理的对象引用最少,这样会减少工程编译花费的 时间。一定不要移除工程中正被使用的引用。 不 能 移 除 “Visual Basic for Applications” 和 “Visual Basic objects and procedures”引用,因为它们是运行Visual Basic所必 须的。 引用文档 每个VBA工程(Normal, Project, TemplateProject)都有一个叫做 ThisDocument的类,它表示一个文档对象。在VBA中任何地方编写代码 都可以用ThisDocument来引用文档。而且,如果在ThisDocument Code 窗口中编写代码,可以直接访问IDocument中的所有方法和属性。在 Visual Basic中这是不行的。必须先获得一个对Application的引用, 然后得到一个对文档的引用。添加扩展模块和命令到ArcGIS应用程序 中时,提供了一个指向Application接口的指针。 Implements IExtension Private m_pApp As IApplication Private Sub IExtension_Startup(ByRef initializationData As Variant) Set m_pApp = initializationData ' Assign IApplication. End Sub Implements ICommand Private m_pApp As IApplication Private Sub ICommand_OnCreate(ByVal hook As Object) Set m_pApp = hook ' QI for IApplication End Sub VISUAL BASIC 开发环境 Singletons是只支持一个例程的对 象。这些对象有一个类工厂来确保 一个对象被请求时,返回一个已有 对象的指针。 , 在Visual Basic中,不可能确定用 来启动程序的命令行。磁盘上有一 个例子提供了这种功能。在\samples\COM Techniques\ Command Line中可以 找到。 由于对应用程序的引用在一个IApplication指针成员变量中,因此文 档和所有其它对象可以从类中的任何方法上访问。 Dim pDoc as IDocument Set pDoc = m_pApp.Document MsgBox pDoc.Name 获取对象 上一个例子中,在ArcMap中浏览一个对象是一个直截了当的过程,因 为指向Application对象(它是大多数ArcGIS应用程序对象的根对象) 的指针通过它的一个接口被传递到对象上。然而,ArcObjects应用程 序框架内执行的所有接口不象上面这样。当实现存在于框架中的一个 对象时,从这个对象上遍历对象的层次结构是不可能的。这是因为只 有少数几个对象支持对其父对象的引用(IDocument接口有一个名为 Parent的引用IApplication接口的属性)。为了让开发人员访问应用 程序对象,有一个singleton对象提供一个指向正在运行的应用程序对 象的指针。下面的代码说明了它的用法。 Dim pAppRef As New AppRef Dim pApp as IApplication Set pApp = pAppRef 要注意确保这个对象只在ArcMap和ArcCatalog中运行的实现中才使 用。例如,在一个自定义要素中使用此功能不是一个好主意,因为这 样会限制查看要素类所用的应用程序。 用命令行参数运行ARCMAP 可以从命令行启动ArcMap并给它传递一个参数,这个参数或者是文档 (.mxd)的路径名或者是模板(.mxt)的路径名。在前一个这个例子中, ArcMap会打开一个文档;在后一个的例子中,ArcMap会基于指定的模 板创建一个新的文档。 也可以传递一个参数并通过给Win32 API的ShellExecute函数或Visual Basic的Shell函数提供一个变量来创建一个ArcMap实例,如下所示: Dim ret As Variant ret = Shell("d:\arcgis\bin\arcmap.exe _ d:\arcgis\bin\templates\LetterPortrait.mxt", vbNormalFocus) 缺省情况下,Shell异步运行其它的程序。这意味着在Shell函数后面 的语句执行之前ArcMap可能不会结束执行。 必须调用三个Win32API函数来执行一个程序并等待直到它终止。首先 调用CreateProcessA函数来加载和执行ArcMap。其次调用 WaitForSingleObject函数,强制操作系统等待直到ArcMap终止。最后, 当用户终止应用程序时,调用CloseHandle函数来释放系统池中应用程 序的32位标识符。 第四章·开发环境·93 VISUAL BASIC 开发环境 94·ArcGIS Engine 开发指南 调试VISUAL BASIC代码 Visual Basic有一个集成在开发环境中的调试器。调试Visual Basic 代码时,它在很多情况下都是一个有效的工具;然而在有的情况下不 可能使用VB调试器。调试器的使用及这些特殊的情况在下面的内容中 会详细讨论。 在应用程序中运行代码 可能会使用Visual Basic调试器调试基于ArcObjects的源代码,甚至 当ActiveX DLLs为目标服务器时。DLL的宿主应用程序必须设置为Debug 应用程序。要完成这个任务,选择合适的应用程序,例如ArcMap.exe, 并 在 Project Properties 的 Debugging Options中设置它为Start Program。 使用Debug工具条上的命令可以启动ArcMap并加载和调试DLL。可以设 置断点以跳过的行,跳入的函数和选中的变量。移动左边的行指示器 也可以设置当前执行行。 Visual Basic调试问题 在许多情况下,Visual Basic调试器会正常工作;然而,在使用Visual Basic 6提供的调试器时会出现两个问题。这两个问题存在的原因都是 因为Visual Basic实现其调试器的方式问题。 通常在ArcMap中运行一个工具时,DLL被加载到ArcMap地址空间中,并 且调用直接进入到DLL中。调试时就不是这样。Visual Basic会修改注 册表,以使DLL的类识别符(CLSID)不是指向用户的DLL,而是指向 Visual Basic Debug DLL(VB6debug.dll)。Debug DLL必须支持所有 被用户类动态实现的接口。当VB Debug DLL加载到ArcMap中时,DLL上 的任何方法调用被转寄给Visual Basic,也就是调试的代码要执行的 位置。这两个问题就是由于对注册表的修改和跨进程空间方法调用而 导致的。第一次遇到这些限制时,可能会感到糊涂,因为对象在调试 器外操作或者至少直到遇到错误代码区域时是这样。 因为从ArcMap到自定义工具的方法调用是跨单元的,所以需要接口被 调度。这种调度在某些情况下会导致错误。大多数数据类型可以被系 统自动调度,但是有一些需要自定义代码因为标准调度法不支持这种 数据类型。如果这些类型的数据在自定义工具中被接口使用且没有自 定义调度代码,调试器会以“Interface not supported error”失败。 注册表操作也会破坏对组件类目的支持。每当有一个对组件类目的请 求时,COM中的类目管理器就不能找到用户的组件,因为不是COM询问 用户的DLL是否属于组件类目,而是询问VB调试器DLL是否属于组件类 目,而结果显然是不属于。这意味着每当组件类目用来自动加载一个 VISUAL BASIC 开发环境 第四章·开发环境·95 在Project Properties对话框的 Symbo Compile选项卡中使用Create lic Debug info选项来创建调 试符号信息。 DLL时,这个DLL不能用Visual Basic调试器进行调试。 这显然对于许多扩展框架的方法都会导致一些问题。扩展框架最常用 的方法是添加一个命令或工具。前面讨论过这种情况下如何使用组件 类目。记住组件类目只是用来建立对话框中的命令列表。这意味着如 果被调试的命令已经出现在一个工具条上,就可以使用Visual Basic 调试器。因此,调试实现了ICommand接口的Visual Basic对象的过程 是为了确保当ArcMap单独执行时命令被添加到工具条中,并且在保存 文档之后,通过调试器加载ArcMap。 有些情况下,如扩展模块和属性页面等,是不可能用Visual Basic调 试器的。如果已经有了Visual C++调试器,可以选择下面概括的选项 之一。幸运的是,有许多ESRI Visual Basic Add-ins使得快速有效地 跟踪错误成为可能。这些插件在ArcGIS 开发帮助中的‘Visual Basic Developer Add-Ins’部分描述过,它们提供包括行和模块细节的错误 日志信息。下面给出了一个错误日志的输出例子;注意与行号在一起 的调用栈信息。 Error Log saved on : 8/28/2000 - 10:39:04 AM Record Call Stack Sequence - Bottom line is error line. chkVisible_MouseUp C:\Source\MapControl\Commands\frmLayer.frm Line : 196 RefreshMap C:\Source\MapControl\Commands\frmLayer.frm Line : 20 Description Object variable or With block variable not set Visual Basic调试器的替代选择 如果Visual Basic调试器和插件不能提供足够的信息,可以使用Visual C++调试器,或者单独使用,或者与C++ ATL包装类一块使用。Visual C++ 调试器不运行在ArcMap进程以外被调试的对象,这意味着上面的问题 都不适合。在“Developer Studio中的调试技巧”一节给出了常用的 调试命令。下面的两个技术都需要Visual Basic工程被编译为带有调 式符号信息。 Visual C++调试器可以操作这些符号调试信息和源文件。 Visual C++调试器 可以通过加入一个运行中的、拥有被加载的Visual Basic待调试对象 的进程并在Visual Basic文件中设置断点而直接使用Visual C++调试 器。到达断点行代码后,调试器将停止执行并跳入源文件的正确行。 所需的步骤如下所示。 VISUAL BASIC 开发环境 1. 启动一个合适的应用程序,如ArcMap.exe。 2. 启动Microsoft Visual C++。 3. 用菜单选项Build > Start Debug > Attach to process来加入 ArcMap进程。 4. 加载合适的Visual Basic Source文件到Visual C++调试器中并设 置断点。 5. 在ArcMap中调用方法 在调试器中不能对源代码作任何修改,而且变量也不能被查看,但是 可以查看和改变代码执行。这样常常足以确定什么错了,尤其是与逻 辑相关的问题。 ATL包装类 使用ATL可以创建一个实现与Visual Basic类接口相同的类。在创建ATL 对象时,也就创建了Visual Basic对象。所有方法调用都被传递给 Visual Basic对象来执行。通过在合适的C++包装方法中设置断点来调 试所包含的对象,并且当代码到达断点时,调试器跳入Visual Basic 代码中。要了解有关这个技术的更多信息,请查看ArcGIS 开发帮助系 统中开发示例的ATL Debugger示例。 96·ArcGIS Engine 开发指南 VISUAL C++ VISUAL C++ VC7中ATL增加了许多功能。本章后 面的”ATL与Visual C++.NET”一 节讨论了其中一些相关的变化。 在Visual C++中开发是一个庞大而复杂的题目,因为与其它开发环境 比较,Visual C++提供了与底层的Windows APIs 和COM APIs更低级别 的交互。 尽管这对于快速程序开发来说可能是一个阻碍,但它是最灵活的方法。 大量的设计模式,例如COM聚合和单元素集合,在Visual C++中是可能 的,但在Visual Basic 6是不可能的。通过使用标准类库,如Active Template Library,复杂的COM管件代码可能会被隐藏。然而,彻底地 了解ATL COM运行的底层仍然十分重要。 这部分文档是基于Microsoft Visual C++ 6编制的,为这种环境下的 ArcGIS开发提供了一些指导。随着Visual Studio C++ .NET(也称作 VC7)的发布,C++的开发人员有了一些新的东西。VC7可以操作.NET环 境,也可以操作ArcGIS .NET API,这只会增加访问底层ArcGIS COM对 象的额外操作。因此在VC7中的ArcGIS开发,建议以传统方式工作—也 就是说,直接使用ArcGIS COM接口和对象。 随着Visual C# .NET语言的加入,移植Visual C++代码到这种环境中 并使用ArcGIS .NET API是值得的。C#与C++的语法不一样,但结果代 码通常更加简单和协调。 这部分内容主要服务于两个目的。 1. 熟悉的Visual C++编码风格并调试,开始关于ATL的讨论。 2. 详细介绍在Visual C++中操作ArcObjects编程平台的需求和建议。 操作ATL 这部分不能覆盖使用ATL的开发人员应该有效了解的所有问题,但可作 为ATL的一个介绍。ATL帮助实现COM对象并节省了输入,但这不能因此 就不需要知道C++和如何开发COM对象。 ATL是实现COM对象的推荐框架。ATL可以与Microsoft Foundation Class Library (MFC)代码结合起来,这样为编写应用程序提供了更多 的支持。MFC的可供选择的替代项是Windows Template Library (WTL), WTL建立在ATL模板方法的基础上,并为windows类和其它支持ATL的应 用程序提供了许多包装。WTL可以从Microsoft上下载;编写本书时7.1 版本是最新的并且可以与Visual C++ 6和Visual C++ .NET一块使用。 ATL简介 ATL是C++模板类的集合,被设计为小巧、快速并且可扩展,松散地建 立在Standard Template Library (STL)的基础上。STL为C++对象提供 了普通的模板类,例如向量、堆栈和队列。ATL也提供了扩展Visual 第四章·开发环境·97 VISUAL C++ 98·ArcGIS Engine 开发指南 ATL层次结构 关于Direct-To-COM更为详细的讨 论,参见后面Direct-To-COM聪明类 型一节。 Studio开发环境的向导的集合。这些向导自动生成ATL工程必须的单调 的管件代码。这些向导包括但不限于下面列出的项。 Application—用于初始化ATL C++工程。 Object—用于创建COM对象。生成C++和IDL代码,和一些运行时支 持创建对象的合适的代码。 Property—用于添加接口的属性。 Method—用于添加方法到接口上;Property和Method向导都要求 知道IDL语法。 Interface Implementation—用于实现现有接口的存根函数。 Connection Point Implement—用于实现出站事件接口。 典型情况下通过在Visual Studio Workspace/Class View中右键单击 一个工程、类或接口访问这些向导。 ATL为实现COM对象和实现一些通用COM接口,包括IUnknown、IDispatch 和IClassFactory提供了基类。也提供一些支持ActiveX控件和它们的 容器的类。 ATL提供了暴露ATL-based COM对象所需的一些服务,包括注册、服务 器生命周期和类对象。 这些模板类创建了包含我们的类的层次结构。这些类继承关系在左侧 图中说明。CComxxxThreadModel类支持对全局变量、实例和静态数据 的线程安全访问。CComObjectRootEx为IUnknown的方法提供了行为。 处于第二层的接口表示类要实现的接口,这种接口有两种:IXxxImpl 接口包含了一些ATL支持包含实现的接口;另一接口有一些必须在类内 完全实现的纯虚函数。CComObject类继承了用户的类,这个类与对象 实例化和生命周期控件一起提供了IUnknown方法的实现。 ATL和DTC Direct-To-COM (DTC)与智能类型(在本章后面讨论)一块提供了一些 有用的编译器扩展信息,创建ATL-based对象时会用到这些。函数 __declspec和__uuidof就是两个这样的函数,但最有用的是#import命 令。 COM接口在IDL中定义,然后被Microsoft IDL编译器(MIDL.exe)编译。 这样创建了一个类型库和头文件。当编译引用这些接口的软件时,工 程自动使用这些文件。这个方法的局限性在于,操作接口时,必需访 问IDL文件。作为一个ArcGIS的开发人员,我们只能访问包含在.olb 和.ocx文件中的ArcGIS类型库信息。尽管可以从一个类型库中设计一 VISUAL C++ 虽然Visual C++支持异常机制 (try ... catch),建议不要和COM 代码混合使用这些机制。如果异常 出现在COM接口外,不能保证用户可 以捕获这个异常,并极有可能导致 崩溃。 个头文件,但这是一个单调乏味的过程。#import命令自动创建编译器 所需的文件。因为该命令是支持DTC的,当使用它导入ArcGIS类型库时, 需要传递大量的参数,这样正确地完成导入。关于这个过程更多的信 息,见后面“导入ArcGIS类型库”。 使用ATL处理错误 可能只是在一个方法内返回一个E_FAIL 的HRESULT代码表明错误;然 而这不会给调用者关于错误本质的任何提示。有许多标准的Windows HRESULTs,例 如 E_INVALIDARG (一个或多个变量无效)和E_POINTER (无 效指针)。这些出错代码在Windows头文件winerror.h中列出。不是所 有的开发环境都全面地支持HRESULT;Visual Basic客户常常将错误结 果看作是“Automation Error – Unspecified Error”。 ATL提供了 操纵错误信息对象的简单机制,可以提供出错代码和一个出错信息描 述。 创建一个ATL对象时, Object向导有一个支持ISupportErrorInfo的选 项。如果触发了这个选项,向导结束时,用户的对象会执行接口 ISupportErrorInfo,并且会添加像下面这样一个方法: STDMETHODIMP MyClass::InterfaceSupportsErrorInfo(REFIID riid) { static const IID* arr[] = { &IID_IMyClass, }; for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { if (InlineIsEqualGUID(*arr[i], riid)) return S_OK; } return S_FALSE; } 现在就可以通过调用一个ATL出错函数返回丰富的出错信息。这些函数 甚至会操作源文件以保证信息串的国际化过程简单。 // Return a simple string AtlReportError(CLSID_MyClass, _T("No connection to Database."), IID_IMyClass, E_FAIL); // Get the Error Text from a resource string AtlReportError(CLSID_MyClass, IDS_DBERROR, IID_IMyClass, E_FAIL, _Module.m_hInstResource); 为了从错误的方法提取出错字符,使用Windows函数GetErrorInfo。这个 函数提取当前线程的最后一个IErrorInfo对象,并清除当前出错状态。 第四章·开发环境·99 VISUAL C++ 100·ArcGIS Engine 开发指南 链接ATL代码 ATL的一个主要目的是支持创建小巧快速的对象。因此,当编译和链接 源文件时,ATL给了开发人员大量的选择项。必须选择如何链接或动态 访问C运行时(CRT)库、注册代码、和各种ATL应用程序功能。如果CRT 没有被代码调用,会从链接上移除。如果CRT被调用并且链接器开关 _ATL_MIN_CRT没有从链接行移出,在创建过程中会产生下面错误: LIBCMT.lib(crt0.obj) : error LNK2001: unresolved external symbol _main ReleaseMinSize/History.dll : fatal error LNK1120: 1 unresolved externals Error executing link.exe. 当编译一个debug版本时,可能不会有什么问题;然而,当编译一个 release版本时,可能会出现一些问题,这些取决于所编写的代码。如 果遇到这类错误,要么移出CRT调用,要么修改链接器开关。 如果应用程序代码在运行时被动态加载,必须保证适当的DLL (ATL.DLL) 被安装并且在用户系统上注册。安装ArcGIS 9运行时(runtime)会安 装ATL.dll。下面的表列出了不同的选择和相关的链接器开关。 Symbols CRT Utilities Registrar Debug 是 静态 动态 RelMinDepend _ATL_MIN_CRT _ATL_STATIC_REGISTRY 否 静态 静态 RelMinSize _ATL_MIN_CRT _ATL_DLL 否 动态 动态 有一些缺省条件下ANSI和Unicode构建的构建配置。通过ANSI编译的组 件会在Windows 9.x上运行;然而考虑到只有unicode操作系统(Windows NT、Windows 2000、和Windows XP)支持ArcGIS,因此这些配置是多余 的。在Visual Studio中删除配置,单击Build / Configurations ..., 然后删除Win32 Debug、Win32 Release MinSize、和Win32 Release MinDependency。 COM组件注册 ATL工程向导为注册生成了标准Windows入口点。这些代码会在DLL的类 型库中注册并为DLL内每个COM对象执行一个注册脚本文件(.rgs)。执 行其它注册任务的附加的C++代码可以插入到这些函数中。 STDAPI DllRegisterServer(void) { // registers object in .rgs, typelib and all interfaces in typelib // TRUE instructs the type library to be registered return _Module.RegisterServer(TRUE); } STDAPI DllUnregisterServer(void) { return _Module.UnregisterServer(TRUE); } VISUAL C++ ATL提供了一种文本文件格式.rgs,当注册和取消注册一个DLL时,ATL 的注册器组件解析这个文件。.rgs文件是作为一个自定义资源嵌入到 DLL中。可编辑这个文件来添加其他的注册项目、包含的ProgID、 ClassID和要放置在注册表中的组件类别入口。语法描述了从注册表中 添加和删除的键、值、名称和子键。这种格式总结如下: [NoRemove | ForceRemove | val] Name | [ = s 'Value' | d 'Value' | b 'Value' ] { .. optional subkeys for the registry } NoRemove表示注册表键不应该通过取消注册移除。ForceRemove确保在 注册新键前移除键和子键。s、d 和 b 值分别表示字符型(包括省略 号),双精度型(32位整型值),和二进制型注册表值。下面是一个典型 的注册脚本。 HKCR { SimpleObject.SimpleCOMObject.1 = s 'SimpleCOMObject Class' { CLSID = s '{2AFFC10E-ECFB-4697-8B3D-0405650B7CFB}' } SimpleObject.SimpleCOMObject = s 'SimpleCOMObject Class' { CLSID = s '{2AFFC10E-ECFB-4697-8B3D-0405650B7CFB}' CurVer = s 'SimpleObject.SimpleCOMObject.1' } NoRemove CLSID { ForceRemove {2AFFC10E-ECFB-4697-8B3D-0405650B7CFB} = s 'SimpleCOMObject Class' { ProgID = s 'SimpleObject.SimpleCOMObject.1' VersionIndependentProgID = s 'SimpleObject.SimpleCOMObject' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } 'TypeLib' = s '{855DD226-5938-489D-986E-149600FEDD63}' 'Implemented Categories' { {7DD95801-9882-11CF-9FA9-00AA006C42C4} } } } } NoRemove CLSID保证注册键CLSID永远不会被移除。所有的COM对象都 使用该子键注册它们的ProgIDs和GUIDs,因此,删除这个子键会导致 第四章·开发环境·101 VISUAL C++ 如果一个组件的GUID在开发期间改 变或者类型库名称改变,那么保 持.rgs内容与这些变动的一致非常 重要,否则注册将不正确,并且对 象创建失败。 严重的注册表崩溃。InprocServer32是将一个组件的GUID与DLL文件关 联的标准COM机制;ATL会使用%MODULE%变量插入正确的模块名称。GUID 下的其它项目指定ProgID、线程模型和类型库来使用这个组件。 有两种方式可以将COM组件对象类注册到一个组件类别中。推荐的方法 上面已经说明:将组件类别的GUIDs放置在Implemented Categories键 下面,该键依次返回组件对象类的GUID。另一个方法是在一个对象的 头文件中使用ATL宏:BEGIN_CATEGORY_MAP, IMPLEMENTED_CATEGORY, 和END_CATEGORY_MAP。不过,像在MSDN文章Q279459 BUG:Component CategoryRegistry Entries Not Removed in ATL Component中解释的 一样,这些宏不能正确地移除注册表项目。头文件中提供了ArcGIS使 用的所有组件类别的GUIDs ;在\Program Files\ArcGIS\include \CatIDs\ArcCATIDs.h中可以找到这个头文件。 调试ATL代码 除了Visual Studio标准工具外,ATL提供了大量专门调试COM对象的调 试选项。这些调试选项的输出显示在Visual C++ Output窗口中。可通 过设置符号_ATL_DEBUG_QI来调试QueryInterface调用,AddRef和 Release 调用可使用符号_ATL_DEBUG_INTERFACES 来调试,当 _ATL_DEBUG_INTERFACES符号被定义时,就可以在终止时通过监视泄漏 接口列表跟踪被泄漏的对象。泄漏接口列表有像下面这样的条目: INTERFACE LEAK: RefCount = 1, MaxRefCount = 3, {Allocation = 10} 不过这只能告诉我们一个对象被泄漏了,因为接口指针没有被释放。 不过当服务器启动时通过设置CComModule的m_nIndexBreaktA成员获 得接口时,Allocation数允许自动暂停。接着调用DebugBreak()函数 强制代码执行停止在调试器相关的位置。不过只有程序流程相同时才 可这样。 extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { _Module.Init(ObjectMap, hInstance, &LIBID_HISTORYLib); DisableThreadLibraryCalls(hInstance); _Module.m_nIndexBreakAt = 10; } else if (dwReason == DLL_PROCESS_DETACH) { _Module.Term(); } return TRUE; } 102·ArcGIS Engine 开发指南 VISUAL C++ 布尔类型 历史上,ANSI C没有Boolean数据类型,而是用整数值代替,0表示假, 非零表示真。不过,布尔数据类型现在成为ANSI C++的一部分。COM APIs 是独立于开发语言的并定义了一种不同的布尔类型,VARIANT_BOOL。 另外,Win32 API使用不同的布尔类型。在合适的时候使用正确的类型 是十分重要的。下面的表概括了它们的用法: 类型 真值 假值 哪里定义 何时使用 bool true (1) false (0) 编译器定义 编译器固有类型,使用潜力很大。可 提升为整型值。表达式(e.g., i!=0) 返回一个布尔类型值。特别使用在类 成员变量和局域变量中。 BOOL (int) TRUE (1) FALSE (0) Windows 数 据类型(在 windef.h 中定义) 与windows API函数一块使用,经常 作为一个指示成功失败的返回值 VARIANT_B OOL (16 bit short) VARIANT_TR UE (-1) VARIANT_FA LSE (0) COM Boolean values wtypes.h) 使 用 在 COM APIs 的 布尔值。也在 VARIANT类型中使用;如果VARIANT 类型是VT_BOOL,那么VARIANT赋上一 个VARIANT_BOOL值。将布尔类成员变 量转换为正确的VARIANT_BOOL值时 一定要小心。经常使用条件检验 "hook - colon"操作符。例如,如果 bRes被定义为一个布尔类型,那么设 置一个结果类型:*pVal = bRes ? VARIANT_TRUE :VARIANT_FALSE; 字符类型 虽说字符串是简单的概念,但不幸地是它是C++中成为最复杂和容易混 淆的主题。两个主要原因是,在相同的代码中支持变长字符串,并要 符合支持ANSI和Unicode字符集的要求,这是C++所缺乏的。因为ArcGIS 只在Unicode平台下可用,所以可以简化开发过程来移除ANSI的需求。 C++对字符串的规定是一个以0结束的字符数组。当计算较大字符串的 长度时,通常没有很好的性能。为支持变长字符串,字符数组在堆栈 中是动态分配和释放的,典型的是使用malloc和 free或new和delete。 因此大量的封装类提供这种支持;在MFC和WTL中定义的CString是应用 最广泛的。另外,为使用COM,定义了BSTR类型的用法,并且ATL封装 类CComBSTR也是可用的。 考虑到国际字符集,Microsoft Windows从8位ANSI字符表示(8位字符) (在Windows 95、Windows 98、和Windows Me平台上可以见到)到移 植到16位Unicode字符(16位无符号短整型)中。Unicode与宽字符 (wchar_t)是同义的。OLECHAR是在COM APIs中使用的类型,在Windows 上被定义为wchar_t。Windows操作系统例如Windows NT, Windows 2000 和Windows XP,本来就支持Unicode字符。为了在ANSI和Unicode平台 下编译相同的C++代码,采用编译转换器修改Windows API函数为ANSI 版本(SetWindowTextA)或Unicode版本(SetWindowTextW)。此外, 引 入字符独立类型(在tchar.h中定义的TCHAR)来表示字符:在一个ANSI 第四章·开发环境·103 VISUAL C++ 检验两个字符串是否不同,不要使 用不相等(“!=”)运算符。“==” 运算符进行两个字符串内容区分大 小写的比较;然而,“!=”比较的 是指针值,而不是字符串内容,通 常返回假。 构建上被定义为char,而在Unicode构建上为wchar_t,一个typedef定 义为短整型。要执行标准C字符操作,相同的函数通常有三种不同的定 义;例如,对于一个区分大小写的比较,strcmp提供了ANSI版本,wcscmp 提供了Unicode版本,_tcscmp提供了TCHAR版本。也有第四种版本, _mbscmp,是一个八字节版本的变种,在八字节字符内解释多字符序列 (MBCS)。 // Initialize some fixed length strings char* pNameANSI = "Bill"; // 5 bytes (4 characters plus a terminator) wchar_t* pNameUNICODE = L"Bill"; // 10 bytes (4 16-bit characters plus a 16-bit terminator) TCHAR* pNameTCHAR = _T("Bill"); // either 5 or 10 depending on compiler settings COM APIs使用BSTR来表示变长字符串;这是一个指向一个OLECHAR字符 序列的指针,被定义为Unicode字符串,和wchar_t相同。必须使用 SysAllocString和SysFreeString windows函数来分配和释放BSTR。与 C字符串不一样,它们可包含嵌入零的字符串,虽然这种情况很少见。 BSTR也有一个计数值,它在BSTR指针地址前以四个字节储存。CComBSTR 包装经常被用来管理字符串的生命周期。 不要传递一个指向C风格的Unicode字符串(OLECHAR或wchar_t)数组的 指针给一个期望得到BSTR的函数。如果类型相同,编译器不会引发错 误。然而,接收BSTR的函数不能正确运转或当访问该字符串长度时会 崩溃,该字符串会是随机的内存值。 ipFoo->put_WindowTitle(L"Hello"); // This is bad! ipFoo->put_WindowTitle(CComBSTR(L"Hello")); // This correctly initializes and passes a BSTR ATL提供了在ANSI (A)、TCHAR(T)、Unicode (W)、和OLECHAR (OLE)之 间转换字符串的转换宏。此外,这些类型会有一个常量修饰词(C)。这 些宏以缩写形式显示在括号中,宏与宏之间以“2”隔开。例如,要在 OLECHAR(例如,一个输入的BSTR)到常量TCHAR (在一个Windows函 数中使用)之间转换,使用OLE2CT转换宏。转换ANSI到Unicode,使用 A2W。这些宏要求USES_CONVERSION宏被放置在一个方法的最上部;这 样会创建一些转换宏所使用的局部变量。当源字符和目标字符集不同 时并且目标类型不是BSTR,宏在调用堆栈中分配目标字符(使用 _alloca 运行时函数)。意识到这点是非常重要的,尤其是当在一个 循环语句中使用这些宏时;否则,堆栈会越来越大并用完堆栈空间。 STDMETHODIMP CFoo::put_WindowTitle(BSTR bstrTitle) { USES_CONVERSION; if (::SysStringLen(bstrTitle) == 0) return E_INVALIDARG; ::SetWindowText(m_hWnd, OLE2CT(bstrTitle)); return S_OK; } 104·ArcGIS Engine 开发指南 VISUAL C++ 实现非创建类 非创建类是不能被CoCreateInstance创建的COM对象。而是在一个不同 对象的方法调用中创建对象,并且返回一个指向非创建类的接口指针。 这种对象在地理数据库模型中会大量见到。例如,FeatureClass是不 可创建的并只能通过调用一个方法成员来获得;一个例子是 IFeatureWorkspace::OpenFeatureClass方法。 非创建类的一个好处就是使用没有在COM API中暴露的方法调用来以私 有数据初始化。下面是一个返回不可创建对象的简化例子: // Foo is a cocreatable object. IFooPtr ipFoo; HRESULT hr = ipFoo.CreateInstance(CLSID_Foo); // Bar is a noncreatable object,cannot use ipBar.CreateInstance(CLSID_Bar). IBarPtr ipBar; // Use a method on Foo to create a new Bar object. hr = ipFoo->CreateBar(&ipBar); ipBar->DoSomething(); 将一个可创建的ATL类转换为非创建类所需要的步骤如下: 1. 添加“noncreatable”到.idl文件的coclass属性。 [ uuid(DCB87952-0716-4873-852B-F56AE8F9BC42), noncreatable ] coclass Bar { [default] interface IUnknown; interface IBar; }; 2. 改变类厂实现舍弃任何的非创建类的共建例程。通过主要的DLL模 块中的ATL对象映射图完成。 BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Foo, CFoo) // Creatable object OBJECT_ENTRY_NON_CREATEABLE(CLSID_Bar, CBar) // Noncreatable object END_OBJECT_MAP() 3. 作为可选项, 可以移除注册表条目。首先,从资源(在这个例子 中是Bar.rgs)中移除对象的注册表脚本。然后改变类定义 DECLARE_REGISTRY_RESOURCEID(IDR_BAR)为DECLARE_NO_REGISTRY()。 4. 要在一个方法内创建非创建对象,使用CComObject模板补充 CreateInstance的实现。 // Get NonCreatable object Bar (implementing IBar) from COM object Foo STDMETHODIMP CFoo::CreateBar(IBar **pVal) { 第四章·开发环境·105 VISUAL C++ if (pVal==0) return E_POINTER; // Smart pointer to noncreatable object Bar IBarPtr ipBar = 0; // C++ Pointer to Bar, with ATL template to supply CreateInstance implementation CComObject* pBar = 0; HRESULT hr = CComObject::CreateInstance(&pBar); if (SUCCEEDED(hr)) { // Increment the ref count from 0 to 1 to protect the object // from being released in any initialization code. pBar->AddRef(); // Call C++ methods (not exposed to COM) to initialize the Bar object. pBar->InitialiseBar(10); // QI to IBar and hold a smart pointer reference to the object Bar. hr = pBar->QueryInterface(IID_IBar, (void**)&ipBar); pBar->Release(); } // Return IBar pointer to the caller. *pVal = ipBar.Detach(); return S_OK; } Visual C++ .NET中的ATL Visual C++ 版本6可用于此帮助的绝大部分。然而随着Visual C++ .NET 的发布,有了大量与ArcGIS ATL开发人员有关的增强和改变。下面概 括了一些: 基于属性的编程—这是在VC7中引入的最大的变化。属性被插在被方括 号围起的源代码中,例如,[ coclass ]。属性设计用来简化COM编程 和.NET框架通用语言运行时开发。当源文件包含属性时,编译器操作 提供者DLLs来插入代码或修改生成的对象文件中的代码。有一些属性 用来帮助创建.idl文件、接口、类型库和其它的COM元素。在IDE中, 通过向导和Properties窗口来支持属性。ATL向导广泛地使用的属性将 ATL样板代码插入类中。因此,VC7中典型的COM组件对象类头文件比VC6 包含更少的ATL代码。因为IDL是从属性生成的,所以通常在COM工程没 有像以前那样的.idl文件表示,并且.idl文件是在编译时生成的。 构造设置—在VC7中只有两个缺省的构造设置;即基于ANSI Debug和 Release的构造。因为ArcGIS只在Unicode平台下可以使用,所以建议 106·ArcGIS Engine 开发指南 VISUAL C++ 通过修改工程属性进行修改。工程的通用属性页有一个 “CharacterSet”选项。将该选项的“Use Multi-Byte Character Set”改为Use Unicode Character Set”。 字符转换宏—字符转换宏(USES_CONVERSION, W2A, W2CT, and so forth)有改进的备选版本。不会在堆栈上分配空间,所以在循环语句 中应用,而不会用完堆栈空间。不再需要USES_CONVERSION宏。这些宏 现在都作为类来实现,并以“C”开头—例如,CW2A和CW2CT。 安全数组支持—通过CComSafeArray和CcomSafeArrayBound类来获得。 模块级全局变量—模块级全局变量CComModule _module被拆分为许多 相关的类,例如,CAtlComModule和CAtlWinModule。为检索源模块例 程,使用下面的代码:_AtlBaseModule.GetResourceInstance(); 字符串支持—通用的变长字符支持通过ATL中的CString获得。这一点 定义在头文件atlstr.h和cstring.h中。如果ATL与MFC结合,则缺省为 以MFC的CString实现。 文件路径处理—通过定义在atlpath.h的CPath类可获得处理文件路径 组件的相关函数集。 ATLServer—这是ATL类的一个新选择,为了编写Web应用程序、XML Web 服务和其它的服务器应用程序而设计。 # import问题—使用#import时,需要做一些修改。例如,esriSystem 的#import需要一个GetObject的排斥或重命名,并且esriGeometry的 #import需要一个排斥或ISegment的重命名。 ATL参考 Microsoft Developer Network (MSDN)提供了大量与Visual Studio产 品一起安装的文档、文章和例子。Visual Studio 6的ATL参考文档在 如下目录中:MSDN Library - October 2001 / Visual Tools and Languages / Visual Studio 6.0Documentation / Visual C++ Documentation / Reference / Active Template Library 其它文档可以在MSDN Web站点上获得,如下: http://www.msdn.microsoft.com。 下面的书可能也是有用的: Grimes, Richard. ATL COM Programmer’s Reference. Chicago: Wrox Press Inc., 1988. Grimes, Richard. Professional ATL COM Programming. Chicago: Wrox Press Inc.,1988. Grimes, Richard, Reilly Stockton, Alex Stockton, and Julian Templeman. Beginning ATL 3 COM Programming. Chicago: Wrox Press Inc. 1999. 第四章·开发环境·107 VISUAL C++ King, Brad and George Shepherd. Inside ATL. Redmond, WA: Microsoft Press, 1999. Rector, Brent, Chris Sells, and Jim Springfield. ATL Internals. Reading, MA: Addison–Wesley, 1999. 智能类型 智能类型是和类型有同样行为的对象。它们是C++的类实现,封装了一 种数据类型,可以使用容易操作底层类型且更少出错的函数和操作符 来包装。当这些智能类型封装了一个接口指针时,被称作smart pointers。智能指针操作IUnknown接口以确保正确地管理资源分配和 存储单元分配。这些可以通过不同的函数、构造函数和析构函数,以 及重载运算符来实现。C++程序设计者可获得大量可用的智能类型。这 里 要 讲 到 的 两 个 智 能 类 型 是 Direct-To-COM 和 Active Template Library。 智能类型使得操作COM接口和数据类型更加容易,因为许多API调用被 移至一个类的实现中,然而,这些调用必须很谨慎地使用,并且需清 楚地理解它们如何与封装的数据类型交互。 Direct-To-COM智能类型 使用DTC支持的智能类型类被称作Compiler COM Support Classes,有 下面这些组成部分: _com_error — 这个类表示一个COM支持类的异常条件。这个对象 封装了HRESULT和IErrorInf COM异常对象。 _com_ptr_t — 这个类封装了一个COM接口指针。参见下面介绍的 一般用法。 _bstr_t — 这个类封装了BSTR数据类型。这个类的函数和操作符 不像ATL CComBSTR智能类型那么丰富,因此,它不经常用到。 _variant_t — 这个类封装了VARIANT数据类型。这个类的函数和 运算符不像ATL CcomVariant 智能类型那么丰富,因此,它不经 常用到。 可以使用宏_COM_SMARTPTR_TYPEDEF为接口定义一个聪明指针,像这 样: _COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo)); 一旦声明,就声明一个变量为接口类型和并追加Ptr到接口后。下面是 一些智能指针的通常用法,用户会在大量的C++实例中见到。 // Get a CLSID GUID constant. extern "C" const GUID __declspec(selectany) CLSID_Foo = \ {0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x00}}; 108·ArcGIS Engine 开发指南 VISUAL C++ // Declare Smart Pointers for IFoo, IBar, and IGak interfaces. _COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo)); _COM_SMARTPTR_TYPEDEF(IBar, __uuidof(IBar)); _COM_SMARTPTR_TYPEDEF(IGak, __uuidof(IGak)); STDMETHODIMP SomeClass::Do() { // Create Instance of Foo class and QueryInterface (QI) for IFoo interface. IFooPtr ipFoo; HRESULT hr = ipFoo.CreateInstance(CLSID_Foo); if (FAILED(hr)) return hr; // Call method on IFoo to get IBar. IBarPtr ipBar; hr = ipFoo->get_Bar(&ipBar); if (FAILED(hr)) return hr; // QI IBar interface for IGak interface. IGakPtr ipGak(ipBar); // Call method on IGak. hr = ipGak->DoSomething(); if (FAILED(hr)) return hr; // Explicitly call Release(). ipGak = 0; ipBar = 0; // Let destructor call IFoo's Release. return S_OK; } 使用DTC智能指针的一个主要优点就在于它们是#import编译器语句为 类型库中的所有的接口和组件对象类定义自动生成的。关于这个功能 更多的细节,参见后面一节“导入ArcGIS类型库”。 可以在一个DTC智能指针的构造函数内含蓄地创建一个对象—例如: IFooPtr ipFoo(CLSID_Foo) 然而,如果在对象创建的过程中有错误发生,会引发一个C++异常—例 如,如果包含该对象实现的DLL文件被意外地删除。这种异常通常不被 处理并导致崩溃。一个更健壮的方法是避免COM中的异常,显式调用 CreateInstance,并能够处理错误的代码,例如: IFooPtr ipFoo; HRESULT hr = ipFoo.CreateInstance(CLSID_Foo); if (FAILED(hr)) return hr; // Return object creation failure code to caller. 第四章·开发环境·109 VISUAL C++ 当使用智能指针比较时,等号运算 符(“== ”) 实现方式会有所不 同。COM规定状态对象识别是通过比 较IUnknown指针值实现的。当使用 “==”运算符时,DTC智能指针会执 行必要的QI和比较。然而,ATL智能 指针不会做这些,必须使用ATL IsEqualObject()方法。 Active Template Library智能类型 ATL定义了不同的智能类型,如下面列表中所示。可以任意地合并ATL 和DTC智能类型到代码中。然而,对于智能指针,通常情况下使用DTC, 因为它们可简单地通过导入类型库生成。对于BSTR和VARIANT类型,通 常使用CComVariant,CComBSTR的ATL版本。 ATL智能类型包括: CComPtr—通过包装IUnknown接口的AddRef和Release方法封装了 一个COM接口指针 CComQIPtr—封装了一个COM接口并支持IUnknown接口的所有三个 方法:QueryInterface、AddRef和Release CComBSTR—封装了BSTR数据类型 CComVariant—封装VARIANT了数据类型 CRegKey—为操作Windows注册表条目提供了方法 CComDispatchDriver—提供了获取和设置属性值的方法,并通过 对象的IDispatch接口调用方法。 CSecurityDescriptor—为建立和操作Discretionary Access Control List (DACL)提供方法。 这部分分析了前四种智能类型和它们的用途。下面是用ATL智能指针编 写的例子代码: // Get a CLSID GUID constant. extern "C" const GUID __declspec(selectany) CLSID_Foo = \ {0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x 00}}; STDMETHODIMP SomeClass::Do () { // Create Instance of Foo class and QI for IFoo interface. CComPtr ipFoo; HRESULT hr = CoCreateInstance(CLSID_Foo, NULL, CLSCTX_INPROC_SERVER, IID_IFoo, (void **)&ipFoo); if (FAILED(hr)) return hr; // Call method on IFoo to get IBar. CComPtr ipBar; HRESULT hr = ipFoo->get_Bar(&ipBar); if (FAILED(hr)) return hr; // IBar interface for IGak interface CComQIPtr ipGak(ipBar); // Call method on IGak. hr = ipGak->DoSomething(); if (FAILED(hr)) return hr; // Explicitly call Release(). 110·ArcGIS Engine 开发指南 ipGak = 0; VISUAL C++ CComVariant(VARIANT_TRUE)会创 建一个短整型变量(type VT_I2) 而不是所期望的Boolean变量类型 (type VT_BOOL)。可以使用 CComVariant(true)来创建一个 Boolean变量。 ipPar = 0; // Let destructor call Foo's Release. return S_OK; } 在Visual C++例子中最常见的智能指针是DTC类型。在下面的例子中, 说明了BSTR和VARIANT数据类型,使用了DTC指针。当操作CComBSTR时, 使用文本映射 “L”来声明常量OLECHAR字符串。CComVariant从VARIANT 数据类型上直接派生,这意味着它的实现不会有重载的情况,这反过 来也简化了它的使用。它包含一个丰富的构造和析构函数集合,这使 得操作VARIANTs更加直接;甚至有从数据流上读写的方法。重用变量 之前一定要调用Clear方法。 ipFoo->put_Name(CComBSTR(L"NewName")); if FAILED(hr)) return hr; // Create a VT_I4 variant (signed long). CComVariant vValue(12); // Change its data type to a string. hr = vValue.ChangeType(VT_BSTR); if (FAILED(hr)) return hr; IDL里调用的一些方法被标识为可选的并接受变量型参数。然而在 Visual C++中,仍然要提供这些参数。为了表示一个参数值没有被提 供,传递一个变量来指定错误代码或类型DISP_E_PARAMNOTFOUND: CComBSTR documentFilename(L"World.mxd"); CComVariant noPassword; noPassword.vt = VT_ERROR; noPassword.scode = DISP_E_PARAMNOTFOUND; HRESULT hr = ipMapControl->LoadMxFile(documentFilename, noPassword); 当操作CComBSTR和CComVariant时,Detach()函数释放了来自智能类型 的下层数据类型,这样当传递一个结果作为一个方法的[out]参数时它 就可以使用了。Detach方法与CComBSTR一起使用如下所示: STDMETHODIMP CFoo::get_Name(BSTR* name) { if (name==0) return E_POINTER; CComBSTR bsName(L"FooBar"); *name = bsName.Detach(); } CComVariant myVar(ipSmartPointer)会产生一个Boolean变量类型 (VT_BOOL)和带有一个对象引用的非变量类型VT_UNKNOWN) ,象期望的 一样。最好传递明确的类型到构造函数,也就是说,该类型本身不是 带有重载运算符的智能类型。 第四章·开发环境·111 VISUAL C++ // Perform QI if IUnknown. IUnknownPtr ipUnk = ipSmartPointer; // Ensure IUnknown* constructor of CComVariant is used. CComVariant myVar2(ipUnk.GetInterfacePtr()); 智能指针的一个通常应用是使用Detach()从一个方法调用返回一个对 象。当返回一个接口指针时,COM标准指向方法执行内的[out]参数的 增量引用数。当指针不再需要时,调用者负责调用Release。因此,必 须小心以避免在一个成员变量上直接调用Detach()。一个典型的模式 如下所示: STDMETHODIMP CFoo::get_Bar(IBar **pVal) { if (pVal==0) return E_POINTER; // Constructing a local smart pointer using another smart pointer // results in an AddRef (if pointer is not 0). IBarPtr ipBar(m_ipBar); // Detach will clear the local smart pointer, and the // interface is written into the output parameter. *pVal = ipBar.Detach(); // This can be combined into one line // *pVal = IBarPtr(m_ipBar).Detach(); return S_OK; } 上面的模式与下面的代码有相同的结果。注意在调用AddRef之前需要 对一个零指针进行条件测试,在一个零指针上调用AddRef (或任何方 法)会导致一个访问异常并且通常会使应用程序崩溃: STDMETHODIMP CFoo::get_Bar(IBar **pVal) { if (pVal==0) return E_POINTER; // Copy the interface pointer (no AddRef) into the output parameter. *pVal = m_ipBar; // Make sure interface pointer is nonzero before calling AddRef. if (*pVal) *pVal->AddRef(); return S_OK; } 当使用智能指针从一个方法的[out]参数上接收一个对象时,使用智 能指针“&”解除操作符。这样会导致智能指针上前一个接口指针被 释放。智能指针然后被赋予新的[out]值。方法的实现会增加对象引 用数,当智能指针超出范围时会被释放: { IFooPtr ipFoo1, ipFoo2; ipFoo1.CreateInstance(CLSID_Foo); 112·ArcGIS Engine 开发指南 VISUAL C++ ipFoo2.CreateInstance(CLSID_Foo); // Initalize ipBar Smart pointer from Foo1. IBarPtr ipBar; ipFoo1->get_Bar(&ipBar); // The "&" dereference will call Release on ipBar. // ipBar is then repopulated with a new instance of IBar. ipFoo2->get_Bar(&ipBar); } // ipBar goes out of scope, and the smart pointer destructor calls Release. 命名规则 类型名称 所有的类型名称(class、struct、enum和typedef)都以大写字母开 头,剩下的部分大小写混合: class Foo : public CObject { . . .}; struct Bar { . . .}; enum ShapeType { . . . }; typedef int* FooInt; 函数指针(callbacks)的Typedefs添加Proc到它们的名称后面。 typedef void (*FooProgressProc)(int step); 枚举数据值都以标识工程的小写字符串开始;对于ArcObjects为esri, 每一个字符串出现在单独的行中: typedef enum esriQuuxness { esriQLow, esriQMedium, esriQHigh } esriQuuxness; 函数名称 命名函数采用下面的规则: 对于简单的accessor 和 mutator函数,采用Get 和 Set: int GetSize(); void SetSize(int size); 如果用户为结果提供存储,使用Query: void QuerySize(int& size); 第四章·开发环境·113 VISUAL C++ 这里有一些命名规则的建议。这些 帮助可以识别变量的用途和类型, 并减少编码错误。这是一个删减了 的匈牙利表示法: [_] 114·ArcGIS Engine 开发指南 前缀 变量作用域 m 例程类成员 c 静态成员(包括常量) g 全局静态变量 局部变量或结构或公共类成 员 前缀 数据类型 b Boolean by 字节或无符号字符 cx/cy 用做大小的短整型 d 双精度型 dw DWORD双精度字或无符号长整 型 f 浮点型 fn 函数 h 句柄 I Int(整型) IP 聪明指针 L 长整型 p 指针 s 字符串 sz ASCIIZ空值终止的字符串 w WORD无符号整型 x,y 用作坐标的短整型 描述了如何使用变量或变量 里包含了什么。 部分通常应是小写的,并且 应该使用混合大小写: 变量名 描述 m_hWnd HWND手柄 ipEnvelope 指向COM接口的聪明指 针 m_pUnkOuter 指向对象的指针 m_pUnkOuter 静态类成员 g_pWindowList 指向对象的全局指针 对于状态函数,使用Set和Is或Can: bool IsFileDirty(); void SetFileDirty(bool dirty); bool CanConnect(); 一个操作的语义从变量类型来看是明显的地方,把类型名留在函 数名的外面。 不使用: AddDatabase(Database& db); 考虑使用: Add(Database& db); 不使用: ConvertFoo2Bar(Foo* foo, Bar* bar); 考虑使用: Convert(Foo* foo, Bar* bar) 如果客户端放弃对象某些数据的所有权,使用Give。 如果对象放弃客户端某些数据的所有权,使用Take: void GiveGraphic(Graphic* graphic); Graphic* TakeGraphic(int itemNum); 当特定的操作处理不同的自变量类型时使用函数重载: void Append(const Cstring& text); void Append(int number); 自变量命名 在函数声明中使用描述性的自变量名称时,自变量的名称应该清楚地 表明自变量的用途: bool Send(int messageID, const char* address, const char* message); Developer Studio中的调试技巧 Visual C++提供了特色丰富的调试器。这些提示会帮助我们从调试过 程中获得最多的信息。 故障时的后退 当一个函数调用失败时,并且想知道出错原因(通过跳入函数),不 必重新启动应用程序。使用Set Next Statement命令重新设置程序光 标位置回到出错的语句上(右键单击该语句打开调试弹出式菜单)。 然后跳入该函数。 编辑和继续 Visual Studio 6允许在调试期间修改源代码。这些修改可以被重新编 译并合并到可执行代码中,并不需要停止调试器。对于可修改的类型, 有一些限制,在这种情况下,必须重新启动调试任务。缺省条件下 VISUAL C++ 这个特征是激活的。在工程菜单的Settings命令中可以设置这些:单 击C/C++选项卡,然后从Category下拉列表中选择General。在Debug info下拉列表中,单击Program Database for Edit and Continue。 Unicode字符串显示 要设置调试器选项来显示Unicode字符串,单击Tools菜单,单击 Options,单击Debug,然后选中Display Unicode Strings复选框。 变量值显示 将光标停留在源代码中一个变量名上查看它的当前值。如果该变量是 一个结构,单击该变量,弹出QuickWatch对话框(单击Eyeglasses图 标或按下Shift+F9)或将之拖到Watch窗口中。 不停靠窗口 如果Output窗口(或者是任何停靠的窗口)看起来太小,试着通过右 键单击该窗口并触发Docking View项目来不停靠它以使它成为一个真 窗口。 有条件的断点 当满足某种条件时需要在断点上停留时,采用有条件的断点—例如, 当一个for循环语句达到某一特定计数器值时。为达到这一目的,设置 正常断点,然后调出Breakpoints窗口(Ctrl+B or Alt+F9)。选择设置 的断点,并单击Condition按钮显示一个对话框,在其中设定断点条件。 预先加载DLLs 在执行程序之前可以预先加载要调试的DLLs。这样就使得你可以从一 开始设置断点而不是一直等到程序执行中DLL被加载之后。操作如下, 单击Project,单击Settings,单击Debug,单击Category,然后单击 Additional DLLs。然后单击列表区域来添加想要预先加载的任意DLLs。 改变显示格式 可以在QuickWatch对话框中改变变量的显示格式,或在Watch窗口中使 用下表中的格式符号。 符号 格式 值 显示 d, i 有正负号十进制整型 0xF000F065 -268373915 u 无正负号十进制整型 0x0065 101 o 无正负号八进制整型 0xF065 0170145 x, X 十六进制整型 61541 61541 l, h d, I, u, o, x, X的长整型或 短整型前缀 00406042, hx 0x0C22 f 有正负号的浮点数 f3./2. 1.500000 e 有正负号的科学计数 3./2. 1.500000e+00 g e或 f中更短的一个 3./2. 1.5 c 单个字符 0x0012FDE8 "Hello" su Unicode字符串 "Hello" hr 字符串 0 S_OK 第四章·开发环境·115 VISUAL C++ 116·ArcGIS Engine 开发指南 栏中输入“hr,hr”,以人们可读的格式 可以用下表中的格式化符号格式化存储位置的内容。 使用格式符号,输入变量名后面带有逗号和适合的符号。例如,如果 变量有一个0x0065值,并且想要以字符形式看这个值,在Watch窗口的 列表中的Name栏输入“var,c”。当按下时Enter时,字符格式值出现: var,c = ‘e’。同样地,假定hr是一个拥有HRESULTs的变量,在Name 查看HRESULT。 符号 格式 值 ma 64位ASCII字符 .0...".0W&.. 0x0012ffac .4.. .....1W&.0.:W..1 ...."..1.JO&.1.2 .."..1...0y....1 数,加16 ASCII字 符 0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4.. 16字 数,加16 ASCII字 符 0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4 8位词 0x0012ffac 34B3 00CB 3084 8094 22FF 308A 2657 0000 0x0012ffac 00CB34B3 80943084 308A22FF 00002657 (Unicode) 8478 77f4 ffff ffff 0000 0000 00 格式化符号 何值,或计算 m 16字节的十六进制 .0....".0W&.. mb 节的十六进制 ...0...".0W&.. mw md 四位双词 mu 2字节字符 0x0012fc60 00 0000 使用存储定位 ,可以输入任 一个位置值的表 mb 值,或一存储点的值,使用BY、WO或DW操作符: 操作符后面跟上变量、存储器或常量。如果BY、WO或DW操作符后面跟 达式。要显示一个字符数组为字符串,在数组名前加一个“&”,如 &yourname。格式化字符也可遵循一个表达式: rep+1,x alps[0], xloc,g count,d 查看某个地址的 BY 字节指向的内容 WO 返回字指向的内容 DW 返回双字指向的内容 上一个变量,那么环境监视包含该变量地址的字节、字或双字。 VISUAL C++ 也可以使用上下文操作符{ }显示任何位置的内容。 使用su格式区分符在Watch窗口或QuickWatch对话框中显示Unicode字 符串。使用mu格式区分符在Watch窗口或QuickWatch对话框中用 Unicode字符显示数据字节。 键盘快捷键 大量的键盘快捷键会使对Visual Studio Editor的操作更加快捷。下 面列出一些比较有用的键盘快捷键。 文本编辑器使用许多Windows应用程序(例如Word)使用的标准快捷键。 下面列出了一些详细的源代码编辑快捷键。 快捷键 功能 Alt+F8 正确缩进用环绕线选中的代码 Ctrl+] 查找匹配的大括号 Ctrl+J 显示成员列表 Ctrl+Spacebar 一旦输入的字母数使得编辑者可以识别,完成字。当完成函数和变 量名时使用full。 Tab 右缩进一个制表位 Shift+Tab 左缩进一个制表位 下面是在调试器中使用的通用键盘快捷键表。 快捷键 功能 F9 从当前行插入或删除断点。 Ctrl+Shift+F9 删除所有断点。 Ctrl+F9 禁止断点。 Ctrl+Alt+A 显示auto窗口,移动光标到里面。 Ctrl+Alt+C 显示调用栈,移动光标到里面。 Ctrl+Alt+L 显示本地窗口,移动光标到里面。 Ctrl+Alt+A 显示auto窗口,移动光标到里面。 Shift+F5 终止调试任务。 F11 一次执行代码一条语句,跳入函数。 F10 一次执行代码一条语句,跳过函数。 Ctrl+Shift+F5 重新启动调试任务。 Ctrl+F10 从当前语句恢复到选中语句。 F5 运行应用程序。 Ctrl+F5 不使用调试器运行应用程序。 Ctrl+Shift+F10 设置下一条语句。 Ctrl+Break 停止执行。 第四章·开发环境·117 VISUAL C++ 118·ArcGIS Engine 开发指南 环境的效率。 加载下面的快捷键会大大提高使用Visual Studio开发 快捷键 功能 放置焦点。 CTRL+SHIFT+N 创建新文件。 CTRL+N 创建新工程。 ESC 关闭一个菜单或对话框,取消正在进行的操作,或在当前文档窗口 CTRL+F6 或 CTRL+TAB 一次循环一个MDI子窗口。 CTRL+ALT+A 显示auto窗口,将光标移入。 CTRL+ALT+C 显示call stack窗口,将光标移入。 CTRL+ALT+T 显示文档概要窗口,将光标移入。 CTRL+H 显示查询窗口 CTRL+F 显示查询窗口。如果没有当前查询规则,在查询框光标下输入字 CTRL+ALT+I 显示查询窗口并移入光标。如果实在文本编辑窗口中不可用。 CTRL+ALT+L 显示本地窗口,将光标移入。 CTRL+ALT+O 显示输出窗口,将光标移入。 显示工程浏览器窗口,将光标移入。 T+P 显示属性窗口,将光 IFT+O 打开文件。 CTRL+P 打印文档的全部或部分内容。 保存所有的文件、工 选择全部。 +A 保存当前文档或选中 CTRL+ALT+J CTRL+AL 标移入。 CTRL+SH CTRL+SHIFT+S 程或文档。 CTRL+S CTRL 项或所有项。 浏览在线帮助主题 右键单击工具条的空白区域显示所有可用工具的列表。Infoviewer工 具条 按照帮助主题在内容列表中出现的 顺序来 的顺序浏览帮助主 题。 导入ARCGIS类型库 为了引用ArcG 口 导入到Visual C++ 类型 命令自动创建编译器所必要的文件。开发#import是 为了支持Direc To IS类型库时,必须传递大量的参数。 #pragma w u #pragma warning(disable : 4192) /* Ignore warnings for types that are duplicated in win32 header files. / #pragma warning(disable : 4146) /* Ignore warnings for use of minus on unsigned types. */ #import "\Program Files\ArcGIS\com\esriSystem.olb" /* Type library to generate C++ wrappers.*/ \ raw_interfaces_only, /* Don't add raw_ to method names. */ \ raw_native_types, /* Don't map to DTC smart types. */ \ no_namespace, /* Don't wrap with C++ name space. */ \ named_guids, /* Named guids and declspecs. */ \ exclude("OLE_COLOR", "OLE_HANDLE", "VARTYPE") /* Exclude conflicting types. */ #pragma warning(pop) 包含上下箭头,通过它们可以 浏览所有的主题。使用左右箭头可按照访问 IS接 、类型和对象,需要把这些定义 中。#import t- -Com。导入ArcG arning(p sh) * VISUAL C++ #import的主要用途是为接口定义和GUID常量(LIBID、CLSID和IID) 创建C++代码并定义智能指针。排除(“OLE_COLOR”、“OLE_HANDLE”、 “VARTYPE”)是必须的,因为Windows定义这些为无正负号的长整型, 与ArcGIS所定义的长整型相冲突—这一点需要支持将Visual Basic作 为ArcObjects的客户端,因为Visual Basic不支持无正负号的数据。 这样就不存在排除这些类型的问题。 可以在类型库的头文件(.tlh)中查看通过#import生成的代码,该头 文件类似于a .h文件的格式。也可以查找类型库实现(.tli)文件, 相当于一个a .cpp文件。这些文件可能很大不过只有当类型库改变时 才重新生成。 ArcGIS 9中有许多对于不同功能区域的类型库。可以开始导入那些包 含所需要定义的类型库。然而,#import不会自动包含所有导入类型库 需要的其它定义。例如,当导入类型库esriGeometry时,它会包含对 定 义 在 esriSystem中的类型的引用,所以esriSystem必须在 esriGeometry之前导入。 库依赖关系的完整列表可以在每个库的Overview主题中找到。 选择类型库的最小集有助于减少了编译时间,尽管减少通常并不显著。 下面的步骤有助于确定所需类型库的最小数: 1. 编译一次并查看从代码中生成的“missing type definition”错 误(例如,没有找到ICommand)。 2. 对需要引用的库放置一个#import语句到stdafx.h文件中。使用 LibraryLocator应用程序或组件来帮助完成这项任务。 3. 再次编译工程。 4. 编译器会发布在导入的类型库中不能解决的类型错误,这些通常 是类型定义,例如WKSPoint或继承到其它接口的接口。如果操作 几何对象(例如点),通过导入esriGeometry开始。编译器会发 布不同类型的错误信息,例如:c:\temp\sample\debug\ esrigeome -try.tlh(869):error C2061: syntax error:identifier WKSPoint 查看WKSPoint定义,会看到它是在esriSystem中定义的。在 esriGeometry之前导入esriSystem会解决这些问题。 下面是一个为操作ActiveX控件的典型导入列表。 #pragma warning(push) #pragma warning(disable : 4192) /* Ignore warnings for types that are duplicated in win32 header files. */ #pragma warning(disable : 4146) /* Ignore warnings for use of minus on unsigned types. */ 第四章·开发环境·119 VISUAL C++ #import "\Program Files\ArcGIS\com\esriSystem.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids, exclude("OLE_COLOR","OLE_HANDLE", "VARTYPE") #import "\Program Files\ArcGIS\com\esriSystemUI.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\com\esriGeometry.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\com\esriDisplay.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\com\esriOutput.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\com\esriGeoDatabase.olb" raw_interfaces_only, raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\com\esriCarto.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids // Some of the Engine controls #import "\Program Files\ArcGIS\bin\TOCControl.ocx" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\bin\ToolbarControl.ocx" raw_interfaces_only, raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\bin\MapControl.ocx" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\bin\PageLayoutControl.ocx" raw_interfaces_only, raw_native_types, no_namespace, named_guids // additionally for 3D controls #import "\Program Files\ArcGIS\com\esri3DAnalyst.olb" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\com\esriGlobeCore.olb" raw_interfaces_only, raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\bin\SceneControl.ocx" raw_interfaces_only,raw_native_types, no_namespace, named_guids #import "\Program Files\ArcGIS\bin\GlobeControl.ocx" raw_interfaces_only,raw_native_types, no_namespace, named_guids 当编写包含其它类型库定义的IDL时,会出现类似的问题。在这种情况 下,在库定义后使用importlib。例如,为编写一个ArcMap外部命令会 要求用户创建一个实现ICommand的COM对象。这个定义在esriSystemUI 中,并像下面这样导入到IDL中: library WALKTHROUGH1CPPLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); importlib("\Program Files\ArcGIS\com\esriSystemUI.olb"); coclass ZoomIn { [default] interface IUnknown; interface ICommand; } 120·ArcGIS Engine 开发指南 }; VISUAL C++ 对于ATL的一般的论述,参见前面的 部分“ATL简介”。 ATL和ACTIVEX控件 这部分包含了如何使用ATL并添加控件到对话框中的内容。尽管ATL重 点是提供COM支持,但也提供了一些有用的Windows编程封装类。一个 最有用的类是CWindow,它是一个窗口句柄的封装(HWND)。CWindow上 的方法名称相当于Win32 API函数。例如: HWND buttonHWnd = GetDlgItem( IDC_BUTTON1 ); // Get window handle of button. CWindow myButtonWindow( buttonHWnd ); // Attach window handle to CWindow class myButtonWindow.SetWindowText(_T("Button Title")); // Win32 function to change button caption. CWindow是所有窗口句柄的类封装,所以对于通用窗口控件的特定 Windows消息,例如按钮、树视或编辑框,一种方法是直接发送窗口消 息到窗口上,例如 // Set button to be checked (pushed in or checkmarked, depending on buttonstyle) myButtonWindow.SendMessage(BM_SETCHECK, BST_CHECKED); 然而,在头文件atlcontrols.h中有一些这些标准窗口通用控件的封装 类。在MSDN中,作为提供的ATL实例ATLCON的一部分被使用。参见文章 “HOWTO: Using Class Wrappers to Access Windows Common Controls in ATL”这个头文件是Windows Template Libraries (WTL)的早期版 本,从Microsoft可以下载。 Visual Studio Resource Editor可用来在对话框中设计和放置通用窗 口和ActiveX控件。为了创建和操作对话框,通常创建从CAxDialogImpl 继承而来的C++类。这个类提供信息通道来在一个窗口中创建和管理 ActiveX控件。ATL向导可以用来提供大多数的样板代码。下面讨论创 建对话框和在一个ATL工程中添加ActiveX控件的步骤。 1.单击菜单命令Insert/New ATL Object。 2.单击Miscellaneous类别,然后单击Dialog对象。 3.一个从CAxDialogImpl继承而来的对话框资源和类会添加到工程中。 4.右键单击资源窗口中的对话框并单击Insert ActiveX Control。这 样会显示一个可用的ActiveX控件的列表。 5.双击列表中的一个控件,添加该控件到对话框中。 第四章·开发环境·121 VISUAL C++ 122·ArcGIS Engine 开发指南 确认含有 ActiveX 控件的对话框从 CAxDialogImpl 而 不 是 CDialogImpl 继承而来。如果这里 出了错,对话框的 DoModal 方法只 是简单地退出且没有明显的出错原 因。 确认使用通用窗口控件的应用程序 (如树视)正确地调用 InitCommonControlsEx来加载窗口 类。否则,类不能正确运行。 确认应用程序使用COM对象调用 CoInitialize。这样初始化了应用 程序中的COM。如果没有这个调用, 任何CoCreate调用都会失败。 关于在ATL中处理事件的详细地论 述,参见后面“在ATL中处理COM事 件“一节。 6.右键单击控件,并单击 Properties 来设置控件设计时间的属性。 通过COM接口访问对话框上的控件 为获取一个包含在窗体中的控件的句柄,使用GetDlgControl,一个从 CAxDialogImpl继承而来的ATL方法,用来获得一个资源ID并返回底层 控件指针: ITOCControlPtr ipTOCControl; GetDlgControl(IDC_TOCCONTROL1, IID_ITOCControl, (void**) &ipTOCControl); ipTOCControl->AboutBox(); 监听控件事件 添加事件最简单的方法是使用类向导。简单地右键单击控件并选择 Events。然后,单击控件的资源ID,然后单击事件(例如,OnMouseDown)。 然后单击Add Handler。最后,通过添加AtlAdviseSinkMap(this,TRUE) 到OnInitDialog中来确认对话框开始监听事件。要完成监听事件,为 OnDestroy添加消息句柄并添加一个AtlAdviseSinkMap(this,FALSE) 调用。 在运行时创建控件 CAxWindow类以与其它窗口类似的方式提供创建和包含ActiveX控件的 机制。如果控件的父窗口也在运行时创建可能是合乎需要的。 AtlAxWinInit(); CAxWindow wnd; //m_hWnd is the parent window handle. //rect is the size of ActiveX control in client coordinates. //IDC_MYCTL is a unique ID to identify the controls window. RECT rect = {10,10,400,300}; wnd.Create(m_hWnd, rect, _T("esriReaderControl.ReaderControl"), WS_CHILD|WS_VISIBLE, 0, IDC_MYCTL); VISUAL C++ 设置伙伴控件属性 在对话框上,ToolbarControl和TOCControl需要与一个“伙伴”控件 相联系。该操作通常在一个对话框OnInitDialog窗口消息句柄上执行。 LRESULT CEngineControlsDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // Get the Control's interfaces into class member variables. GetDlgControl(IDC_TOOLBARCONTROL, IID_IToolbarControl, (void **) &m_ipToolbarControl); GetDlgControl(IDC_TOCCONTROL, IID_ITOCControl, (void **) m_ipTOCControl); GetDlgControl(IDC_PAGELAYOUTCONTROL, IID_IPageLayoutControl, (void **) &m_ipPageLayoutControl); // Connect to the controls. AtlAdviseSinkMap(this, TRUE); // Set buddy controls. m_ipTOCControl->SetBuddyControl(m_ipPageLayoutControl); m_ipToolbarControl->SetBuddyControl(m_ipPageLayoutControl); return TRUE; } 已知的Visual Studio C++ Resource Editor和ArcGIS ActiveX控件的局限性 属性页上无效的伙伴属性 在Visual Studio C++中不能通过General属性页设置TOCControl和 ToolbarControl的“Buddy”属性。Visual C++不支持控件在设计时查 找其它控件。然而这一步可在OnInitDialog方法的代码中可以被执行。 ToolbarControl不会调整按钮的高度 在其它的环境(Visual Basic 6, .NET)中,ToolbarControl会自动调 整按钮的高度。然而,在Visual Studio C++ 6中可以是任何大小。在 MFC和ATL中,ActiveX主机类不允许控件确定自身的大小 当显示上下文敏感的帮助时,设计时属性页消失 在设计时查看控件属性页时,右键单击并单击“What’s This?”会显 示帮助提示;然而属性页接着就关闭了。这是Visual Studio浮动窗口 与来自于HTML帮助的浮动提示窗口相结合的局限性。单击Help按钮为 整个属性页提供相同的文本。 第四章·开发环境·123 VISUAL C++ 在Visual Studio C++设计时在对话 框上插入ActiveX控件。TOCControl 和MapControl被添加到对话框中。 然后是ToolbarControl。 MFC和 ACTIVEX控件 在Visual C++中对于如何使用ArcGIS ActiveX Controls有多种选择。 第一个选择就是框架用来包含控件的方法(例如ATL或MFC)。第二个是 决定被包含控件的位置(Dialog、MDI应用程序等等)。这部分论述MFC 和在对话框中包含控件。 创建一个基于MFC对话框的应用程序 如果在应用程序和组件中没有对话框,这里是创建一个MFC对话框应用 程序的步骤。 1. 启动Visual Studio C++ 6,并单击New。 2. 单击Projects选项卡并选择MFC AppWizard (exe)。输入工程名称 和位置并单击OK。 3. 在向导的第一步:从radio按钮中,改变应用程序类型为Dialog Based。单击Next。 4. 在向导第二步:缺省工程要素是好的,虽然可以不选择AboutBox 以简化应用程序。确认支持ActiveX Controls的选项被选中。单 击Next。 5. 向导第三步:该页中缺省设置是很好的。MFC DLL被共享。单击Next。 6. 向导第四步:这展示了向导要生成的内容。单击Finish。 现在应该有一个简单的基于对话框的应用程序了。在资源视图中,会 看到“TODO: Place Dialog Controls Here”。可以在对话框中放置 按钮,列表框等等。对话框也可以容纳ActiveX控件;有两种方法可以 做这个工作,如下面所讨论的。也可以编译和运行这个应用程序。 在对话框上包含控件并使用IDispatch访问这些控件 1. 右键单击MFC对话框,并单击Insert ActiveX控件。 2. 从列表框中双击一个控件。控件会以缺省的大小出现在 对话框上。 3. 控件的大小和位置是必需的。 4. 对每个控件重复步骤1到3。 5. 可以右键单击控件并选择Properties设置控件的设计时 属性。 6. 在代码中访问控件,需要IMapControl的ArcGIS接口定 义。可以使用stdafx.h文件中的#import命令来完成这些 操作。关于如何完成这些操作,参见“导入ArcGIS类型 库”一节。 124·ArcGIS Engine 开发指南 VISUAL C++ 第四章·开发环境·125 设计环境展示了TOCControl、 MapControl和ToolbarControl已经 被添加到工具条和对话框中。 7. MFC提供了包含在对话框中的控件;这会转换Windows消息(例如 WM_SIZE)为合适的控件方法调用,。然而,为了能够在控件上调 用,必须执行从资源ID到控件接口的不多的几步。下面的代码演 示了设置TOCControl的Buddy为MapControl: // Code to set the Buddy property of the TOCControl to be the //MapControl // Get a pointer to the PageLayoutControl and TOCControl. IPageLayoutControlPtr ipPageLayoutControl; GetDlgControl(IDC_PAGELAYOUTCONTROL1, IID_IPageLayoutControl, (void**) &ipPageLayoutControl); ITOCControlPtr ipTOCControl; GetDlgControl(IDC_TOCCONTROL1, IID_ITOCControl, (void**) &ipTOCControl); // Get the IDispatch of the PageLayoutControl. IDispatchPtr ipBuddyDisp = ipPageLayoutControl; // Set the TOCControls Buddy to the map control. ipTOCControl->putref_Buddy(ipBuddyDisp); 8. 为了从控件上捕获事件,在窗体上双击控件并提供要被调用的方 法名。缺省条件下,向导会在事件句柄的开始添加额外的单词 “On”。删除这个单词以防止事件句柄名称变成 “OnOnMouseDownMapcontrol1”。这个向导然后会自动生成必要 的MFC接收映射宏来监听事件。 使用IDispatch封装添加控件到MFC对话框中 所有的控件都支持,这是添加控件到工程中的典型的方法: 1. 单击Project,单击Add,然后单击Components Controls。 2. 单击Registered ActiveX Controls。 3. 双击选择一个控件(例如,是ESRI TOCControl),然后单 击OK插入一个组件。单击OK生成封装,然后单击插入一个 组件。在Visual Studio中这样会为Controls工具条上的 控件添加一个图标。 4. 附加的源文件被添加到工程(例如toccontrol.cpp和 toccontol.h)。这些文件包含一个封装类(例如, CTOCControl)提供访问该控件的方法和属性。这个类会 通过IDispatch调用机制来调用控件。注意到当调用方法 和属性时,IDispatch不会导致任何封装参数的性能花费。封装类 从包含一个ActiveX控件的MFC CWnd类继承。 5. 重复步骤1到4来添加每个控件到工程的Controls工具条中。 VISUAL C++ 126·ArcGIS Engine 开发指南 Visual Studio C++ Class Wizard 为ActiveX控件添加成员变量到对 话框中。 不要在封装类上使用GetDispatch 方法(从MFC的CcmdTarget继承); 它倾向于为对象实现IDispatch而 不是正在调用IDispatch的封装类。 相反要获取控件的IDispatch使用 m_mapcontrol.GetControlUnknown ()和IDispatch上的 QueryInterface。参见上面设置 Buddy属性的例子。 6. 从 Controls 工具条上选择一个控件并拖到对话框中。 7. 右键单击控件并单击Properties。这样允许在控件上设置设计时 的属性。注意:在Visual Studio C++中,不能设置TOCControl和 ToolbarControl的Buddy属性。 这个环境不支持在运行时查找其它控件的控件。然而这一步可以 在代码中使用OnInitDialog方法执行。 // Note no addref performed with GetControlUnknown, so no need to //release this pointer. LPUNKNOWN pUnk = m_mapcontrol.GetControlUnknown(); LPDISPATCH pDisp =0; pUnk->QueryInterface(IID_IDispatch, (void **) &pDisp); // Set TOCControls buddy to be MapControl. m_toccontrol.SetRefBuddy(pDisp); pDisp->Release(); 8. 右键单击控件并选择 Class Wizard 来启动类向导。单击 Member Variables 选项卡,并单击与控件对应的资源 ID 来提供控件成员 变量名。对话框类成员变量现在可以用来调用控件的方法和属性。 9. 为 了捕获控件事件,单击类向导的 Message Maps 选项卡,并选择控 件的资源 ID。在消息列表中,单击事件捕获它—例如, OnBeginLabelEdit。双击这个事件并为它添加一个句柄到对话框 类中。缺省情况下,向导会在事件句柄的开头加上“On”。去掉 这个避免事件句柄名称成为 OnOnBeginLabelEditToccontrol1。 VISUAL C++ 在ATL中处理COM事件 下面是一些在Visual C++和ATL中讨论COM事件时所使用的术语总结。 入接口—这是COM对象实现一个预定义接口的常用情况。 出接口—这是一个COM对象会在不同时间激发的方法接口。例如, MapCoClass会激发一个IActiveViewEvents上的事件以响应地图中的 改动。 事件源—当某种动作发生时,源COM对象会激发事件到出站接口。例如, MapCoClass是一个IActiveViewEvents的源,并且当一个新的图层添加 到地图中来时会激发IActiveViewEvents::ItemAdded事件。源对象可 以有任意多个客户或event sink objects来监听事件。源对象也可以 有多个出接口;例如,MapCoClass也可以在IMapEvents接口上激发事 件。事件源通常情况下使用[source]标签在IDL中声明它的出接口。 事件汇—接听事件的COM对象被称作事件的“汇”。汇对象实现出接口; 这在类型库不是总被注意到的,因为汇在内部接听事件。事件汇通常 情况下使用关联点机制注册它感兴趣的源对象的事件。 关联点—作为事件源的COM对象通常情况下使用关联点机制允许汇与 一个源关联。关联点接口是标准的COM接口IconnectionPointContai -ner和IConnectionPoint。 激发事件—当源对象需要通知所有有一个特定的行为的汇时,称之为 源激发一个事件。这导致源迭代所有的汇,且在每个汇上调用相同的 方法。例如,添加一个图层到地图上时,Map组件对象类激发ItemAdded 事件。所以所有监听Map的出站IActiveViewEvents接口的对象都会在 它们的ItemAdded方法的实现上被调用。 建议和取消建议事件—开始接收事件,一个汇对象被称作建议一个需 要接受事件的源对象。当事件不再需求时,汇会对源取消建议。 ConnectionPoint机制 源对象实现IConnectionPointContainer接口,允许汇为特定的出站接 口查询一个源。执行下面的步骤开始监听一个事件。ATL用AtlAdvise 方法实现这个操作。 1. 汇将QI源对象的IConnectionPointContainer 并调用 FindConnectionPoint为出站接口提供一个接口ID。为了能够接收 事件,汇对象必须实现这个接口。 2. 源可能会实现许多出站接口并会返回一个指向特定关联点对象的 指针,该关联点对象实现IconnectionPoint来表示一个出接口。 第四章·开发环境·127 VISUAL C++ 128·ArcGIS Engine 开发指南 关联源到汇对象的关联点机制 Visual Studio C++ Class Wizard。 在一个对话框上添加ActiveX控件 的事件句柄。 向导中有一个隐含的错误:不添加 建议和取消建议代码到对话框中。 要修复这个问题,为OnDestroy添加 一个消息句柄。然后在 OnInitDialog句柄上以TRUE值作为 第二个参数调用AtlAdviseSinkMap 开始监听事件。在句柄中放置一个 相应的对AtlAdviseSinkMap的调用 (以FALSE作为第二个参数)。这个 在MSDN文章“BUG: ActiveX Control Events Are Not Fired in ATL Dialog(Q190530)”中进一步 讨论。 1. 汇调用 IConnectionPoint::Advise,传 递一个指向它自身IUnknown实现 的指针。源会将这个指针与其它 任何的正在侦听事件的汇一同储 存。如果调用Advise方法成功,汇 被给予一个标识符—一个简单的 无符号长整型值,称作cookie—在后面不再需要监听事件时返回 给源。 现在完成了关联;可以通过源在任何正在侦听的汇上调用 方法。汇通常情况下会持有一个指向源的接口指针,因此 当汇完成了监听,可以通过调用IConnectionPoint 5::Unadvise从源上释放它。这个过程用AtlUnadvise实现。 IDispatch事件与纯COM事件 出接口可以是一个纯分发接口。这意味着调用是通过 IDispatch::Invoke机制实现的,而不是源直接在汇上调用 方法。与纯COM调用相比较,IDispatch机制有封装参数的 性能开销。然而,有一些情况下必须使用IDispatch机制。 ActiveX控件必须实现它们缺省的出接口作为纯IDispatch 接口;例如,IMapControlEvents2是一个纯分发接口。第 二,Microsoft Visual Basic 6只能作为纯IDispatch事件的源。关联 点机制对于纯COM机制也是一样的,主要的不同在于事件如何被激发。 ATL提供了一些帮助监听IDispatch事件的宏语言;这一点在MSDN的 ‘Event Handling and ATL’中论述到。有两个模板可用, IDispEventImpl和IDispEventSimpleImpl,在下面的一节会讨论到。 使用IdispEventImpl侦听事件 ATL模板IDispEventImpl会使用类型库来破解IDispatch调用并将自变 量处理为C++方法调用。当添加一个ActiveX控件到对话框中时,Visual Studio Class向导自动提供这种机制。右键单击Control,单击Events。 在Class向导中选择控件的资源ID,选择事件,然后单击Add Handler。 下面的代码举例说明了如何通过向导添加代码处理事件,做了一些修 改以保证建议和取消建议被执行。 #pragma once #include "resource.h" // Main symbols #include VISUAL C++ //////////////////////////////////////////////////////////////////////////// // CMyDialog class CMyDialog : public CAxDialogImpl, public IDispEventImpl { public: enum { IDD = IDD_MYDIALOG }; BEGIN_MSG_MAP(CMyDialog) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) // Add a handler to ensure event unadvise occurs. MESSAGE_HANDLER(WM_DESTROY, OnDestroy) COMMAND_ID_HANDLER(IDOK, OnOK) COMMAND_ID_HANDLER(IDCANCEL, OnCancel) END_MSG_MAP() LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&bHandled) { // Calls IConnectionPoint::Advise() for each control on the dialog box with sink map entry AtlAdviseSinkMap(this, TRUE); return 1; // Let the system set the focus. } LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { // Calls IConnectionPoint::Unadvise() for each control on the dialog box with sink map entry AtlAdviseSinkMap(this, FALSE); return 0; } LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { EndDialog(wID); return 0; } LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) { EndDialog(wID); return 0; } 第四章·开发环境·129 VISUAL C++ 使用IdispEventImpl时下面这些使 用事件的问题在MSDN Knowledge Base中说明。对这些问题部分中ATL 代码大修改也在MSDN中加以说明; 然而通常修改和复制ATL头文件不 是让人满意的。在这种情况下,可 以使用IDispEventSimpleImpl代 替。 BUG: Events Fail in ATL Containers when Enum Used as Event Parameter (Q237771) BUG: IDispEventImpl Event Handlers May Give Strange Values for Parameters (Q241810) 参阅本附录中前面的“导入ArcGIS 类型库”一节了解#import的解释。 // ATL callback from SinkMap entry VOID __stdcall OnMouseDownMapcontrol1(LONG button, LONG shift, LONG x, LONG y, DOUBLE mapX, DOUBLE mapY) { MessageBox(_T("MouseDown!")); } BEGIN_SINK_MAP(CMyDialog) // Make sure the Event Handlers have __stdcall calling convention. // The 0x1 is the Dispatch ID of the OnMouseDown method. SINK_ENTRY(IDC_MAPCONTROL1, 0x1, OnMouseDownMapcontrol1) END_SINK_MAP() }; 使用IDispEventSimpleImpl侦听事件 从这个模板的名字上可看出,它是IDispEventImpl的简装版。不再使 用类型库将IDispatch变量转换为C++方法调用。虽然这是一个简单的 实现过程,现在需要开发人员提供一个指向描述事件参数的格式的结 构的指针。这个结构一般放置在.cpp文件中。例如,下面是一个 MapControl上描述OnMouseDown事件参数的结构。 _ATL_FUNC_INFO g_ParamInfo_MapControl_OnMouseDown = { CC_STDCALL, // Calling convention VT_EMPTY, // Return type 6, // Number of arguments {VT_I4, VT_I4, VT_I4, VT_I4, VT_R8, VT_R8} // VariantArgument types }; 头文件从IDispEventSimpleImpl继承而来并使用在SINK_MAP中不同的 宏,SINK_ENTRY_INFO也需要事件接口ID,#import可用来定义这个符 号。注意到分派接口通常以DIID为前缀而不是IID。 #pragma once #include "resource.h" // Main symbols #include // reference to structure defining event parameters extern _ATL_FUNC_INFO g_ParamInfo_MapControl_OnMouseDown; //////////////////////////////////////////////////////////////////////////// // CMyDialog2 class CMyDialog2 : public CAxDialogImpl, public IDispEventSimpleImpl { public: // Message handler code removed, it is the same as CMyDialog using IDispEventSimple 130·ArcGIS Engine 开发指南 VISUAL C++ BEGIN_SINK_MAP(CMyDialog2) // Make sure the Event Handlers have __stdcall calling convention. // The 0x1 is the Dispatch ID of the OnMouseDown method. SINK_ENTRY_INFO(IDC_MAPCONTROL1, // ID of event source DIID_IMapControlEvents2, // interface to listen to 0x1, // dispatch ID of MouseDown OnMapControlMouseDown, // method to call when event arrives &g_ParamInfo_MapControl_OnMouseDown) // parameter info for method call END_SINK_MAP() }; 在COM对象上监听多个IDispatch事件接口 如果单个COM对象需要从多个IDispatch源上接收事件,这会造成 DispEventAdvise方法不明确定义的编译问题。这不是出现在对话框中 的一个普通问题,像AtlAdviseSinkMap会处理所有的连接。可以通过 每次IDispEventSimpleImpl被继承时,引入不同的typedefs来避免歧 义。下面的例子说明了一个称作CListenCOM对象,该对象是一个从 MapControl和PageLayoutControl分发事件的汇。 #pragma once #include "resource.h" // Main symbols // This is the parameter information extern _ATL_FUNC_INFO g_ParamInfo_MapControl_OnMouseDown; extern _ATL_FUNC_INFO g_ParamInfo_PageLayoutControl_OnMouseDown; // // Define some typedefs of the dispatch template. // class CListen; // Forward definition typedef IDispEventSimpleImpl<0, CListen, &DIID_IMapControlEvents2> IDispEventSimpleImpl_MapControl; typedef IDispEventSimpleImpl<1, CListen, &DIID_IPageLayoutControlEvents> IDispEventSimpleImpl_PageLayoutControl; //////////////////////////////////////////////////////////////////////////// // Clisten class ATL_NO_VTABLE CListen : 第四章·开发环境·131 VISUAL C++ public CComObjectRootEx, public CComCoClass, public IDispEventSimpleImpl_MapControl, public IDispEventSimpleImpl_PageLayoutControl, public IListen { public: CListen() { } DECLARE_REGISTRY_RESOURCEID(IDR_LISTEN) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CListen) COM_INTERFACE_ENTRY(IListen) END_COM_MAP() // Associated source and dispatchID to a method call BEGIN_SINK_MAP(CListen) SINK_ENTRY_INFO(0, // ID of event source DIID_IMapControlEvents2, // Interface to listen to 0x1, // Dispatch ID to receive OnMapControlMouseDown, // Method to call when event arrives &g_ParamInfo_MapControl_OnMouseDown) // Parameter info for // method call SINK_ENTRY_INFO(1, DIID_IPageLayoutControlEvents, 0x1, OnPageLayoutControlMouseDown, &g_ParamInfo_PageLayoutControl_OnMouseDown) END_SINK_MAP() // IListen public: STDMETHOD(SetControls)(IUnknown* pMapControl, IUnknown* pPageLayoutControl); STDMETHOD(Clear)(); private: void __stdcall OnMapControlMouseDown(long button, long shift, long x, long y, double mapX, double mapY); void __stdcall OnPageLayoutControlMouseDown(long button, long shift, long x, long y, double pageX, double pageY); IUnknownPtr m_ipUnkMapControl; IUnknownPtr m_ipUnkPageLayoutControl; }; 132·ArcGIS Engine 开发指南 VISUAL C++ CListen的实现包含下面的代码来启动侦听控件;使用typdef避免了 DispEventAdvise实现的歧异。 // Start listening to the MapControl. IUnknownPtr ipUnk = pMapControl; HRESULT hr = IDispEventSimpleImpl_MapControl::DispEventAdvise(ipUnk); if (SUCCEEDED(hr)) m_ipUnkMapControl = ipUnk; // Store pointer to MapControl for Unadvise . // Start listening to the PageLayoutControl. ipUnk = pPageLayoutControl; hr = IDispEventSimpleImpl_PageLayoutControl::DispEventAdvise(ipUnk); if (SUCCEEDED(hr)) m_ipUnkPageLayoutControl = ipUnk; // Store pointer to PageLayoutControl // for Unadvise. CListen的实现包含下面的代码来UnAdvise和停止监听控件。 // Stop listening to the MapControl. if (m_ipUnkMapControl!=0) IDispEventSimpleImpl_MapControl::DispEventUnadvise(m_ipUnkMap Control); m_ipUnkMapControl = 0; if (m_ipUnkPageLayoutControl!=0) IDispEventSimpleImpl_PageLayoutControl::DispEventUnadvise(m_ipU nkPageLayoutControl); m_ipUnkPageLayoutControl= 0; 创建COM事件源 作为事件源的对象,需要提供一个IConnectionPointContainer的实现 和一个关于哪个汇侦听哪个IConnectionPoint接口的跟踪机制。ATL通 过IConnectionPointContainerImpl模板提供这些。另外ATL提供了一 个向导,为给定的分发事件接口的所有成员生成激发IDispatch事件的 代码。下面的步骤修改一个ATL COM组件对象类来支持关联点: 1. 首先确保用户的ATL组件对象类已经被编译了至少一次,使向导能 找到初始的类型库。 2. 在Class视图中,右键单击COM对象并单击Implement Connection Point。 3. 要么使用工程中的IDL上的事件定义,要么单击Add Type Lib浏览 其它定义。 4. 在组件对象类中选中要被实现的出接口。 第四章·开发环境·133 VISUAL C++ 134·ArcGIS Engine 开发指南 5. 单击OK会修改用户的ATL类,并在头文件中生成一个名称以CP结尾 的代理类,用来激发事件。 如果向导运行失败,使用下面的例子,这个例子说明了作为 ITOCControlEvents的源,一个纯分发接口的组件对象类。 #pragma once #include "resource.h" // Main symbols #include "TOCControlCP.h" // Include generated connection point class // for firing events. ////////////////////////////////////////////////////////////////////////// // CMyEventSource class ATL_NO_VTABLE CMyEventSource : public CComObjectRootEx, public CComCoClass, public IMyEventSource, public CProxyITOCControlEvents< CMyEventSource >, // Generated // ConnectionPoint class public IConnectionPointContainerImpl< CMyEventSource > // Implementation // of Connection point Container { public: CMyEventSource() { } DECLARE_REGISTRY_RESOURCEID(IDR_MYEVENTSOURCE) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP(CMyEventSource) COM_INTERFACE_ENTRY(IMyEventSource) COM_INTERFACE_ENTRY(IConnectionPointContainer) // Allow QI to this // interface. END_COM_MAP() VISUAL C++ // List of available connection points BEGIN_CONNECTION_POINT_MAP(CMyEventSource) CONNECTION_POINT_ENTRY(DIID_ITOCControlEvents) END_CONNECTION_POINT_MAP() }; 关联点类(在上面的类中是TOCControlEventsCP.h)包含了激发关联 点上所有汇对象事件的代码。 在这个类中对于每个事件开始都有一个“Fire_”方法。每个方法都建 立一个变量列表参数来作为一个自变量传递给分发Invoke方法。每个 汇被跌代,且指向汇的指针存储在从IConnectionPointContainerImpl 继承而来的一个m_vec成员变量中。注意到m_vec可能包含了指向零的 指针;在激发事件前必须进行检查。 template class CProxyITOCControlEvents : public IConnectionPointImpl { public: VOID Fire_OnMouseDown(LONG button, LONG shift, LONG x, LONG y) { // Package each of the parameters into an IDispatch argument list. T* pT = static_cast(this); int nConnectionIndex; CComVariant* pvars = new CComVariant[4]; int nConnections = m_vec.GetSize(); // Iterate each sink object. for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++) { pT->Lock(); CComPtr sp = m_vec.GetAt(nConnectionIndex); pT->Unlock(); IDispatch* pDispatch = reinterpret_cast(sp.p); // Note m_vec can contain 0 entries so it is important to check for this. if (pDispatch != NULL) { // Build up the argument list. pvars[3] = button; pvars[2] = shift; pvars[1] = x; pvars[0] = y; DISPPARAMS disp = { pvars, NULL, 4, 0 }; // Fire the dispatch method, 0x1 is the DispatchId for MouseDown. pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, NULL, NULL, NULL); 第四章·开发环境·135 VISUAL C++ } } delete[] pvars; // Clean up the parameter list. } VOID Fire_OnMouseUp(LONG button, LONG shift, LONG x, LONG y) { // ... Other events 从源上激发事件,只是在遇到请求时简单地调用Fire_OnMouseDown。 对于激发事件到一个纯COM(non IDispatch)接口可采用类似的方法。 向导不会产生关联点类,所以必须手工编写;下面的例子说明了一个 激发ITOCBuddyEvents::ActiveViewReplaced事件的类; ITOCBuddyEvents是一个纯COM非IDispatch接口。主要的不同是不需要 封装参数。可以直接进行方法调用。 template < class T > class CProxyTOCBuddyEvents : public IConnectionPointImpl< T, &IID_ITOCBuddyEvents, CComDynamicUnkArray > { // This class based on the ATL-generated connection point class public: void Fire_ActiveViewReplaced(IActiveView* pNewActiveView) { T* pT = static_cast< T* >(this); int nConnectionIndex; int nConnections = this->m_vec.GetSize(); for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++) { pT->Lock(); CComPtr< IUnknown > sp=this->m_vec.GetAt(nConnectionIndex); pT->Unlock(); ITOCBuddyEvents* pTOCBuddyEvents = reinterpret_cast< ITOCBuddyEvents* >(sp.p); if (pTOCBuddyEvents) pTOCBuddyEvents->ActiveViewReplaced(pNewActiveVie w); } } }; 支持事件对象的IDL声明 对象被导出到类型库时,通过使用相对接口名称的[source]标签来声 明事件接口。例如,激发ITOCBuddyEvents的对象声明为 [source] interface ITOCBuddyEvents; 如果出站接口是分发事件接口,使用dispinterface代替interface。 另外,组件对象类可能会有一个缺省的出站接口;使用[default]标签 136·ArcGIS Engine 开发指南 VISUAL C++ 来指定。缺省接口通过某些设计环境(例如Visual Basic 6)来识别。 下面是缺省出站事件接口的声明: [default, source] dispinterface IMyEvents2; 事件循环引用问题 汇执行完源上的Advise后,常常会有一个COM循环引用。出现这种情况 是因为源有一个指向汇的接口指针来激发事件,这使得汇保持继续活 动。类似地,汇对象有一个指回源的指针,因此可以在后面执行 Unadvise。这使得源保持继续活动。然而这两个对象永远不会被释放 并会导致大量的内存泄露。有多种方法可以处理这个问题: 1. 确保Advise和Unadvise在一个方法或窗口消息中执行,这样保证 成对发生,并且独立于一个对象的生命周期。例如,在一个接收 窗口消息的组件对象类上,使用Windows消息OnCreate (WM_CREATE) 和OnDestroy(WM_DESTROY)来执行Advise和Unadvise。 2. 如果ATL对话框类需要侦听事件,一种方法是使对话框为一个私有 的COM类,并直接在对话框上实现事件接口。ATL不需要额外的代 码就可完成这些。这种方法在下面举例说明。对话框类创建一个 CustomizeDialog组件对象类,并监听ICustomizeDialogEvents。 方法OnInitDialog和OnDestroy用来在CustomizeDialog上建议和 取消建议。 class CEngineControlsDlg : public CAxDialogImpl, public CComObjectRoot, // Make Dialog Class a COM Object as well. public ICustomizeDialogEvents // Implement this interface directly on this object. CEngineControlsDlg() : m_dwCustDlgCookie(0) {} // initialize cookie for event listening // ... Event handlers and other standard dialog code has been removed ... BEGIN_COM_MAP(CEngineControlsDlg) COM_INTERFACE_ENTRY(ICustomizeDialogEvents) // Make sure QI works for // this event interface. END_COM_MAP() // ICustomizeDialogEvents implementation to receive events on this // dialog box. STDMETHOD(OnStartDialog)(); STDMETHOD(OnCloseDialog)(); ICustomizeDialogPtr m_ipCustomizeDialog; // The source of events DWORD m_dwCustDlgCookie; // Cookie for // CustomizeDialogEvents } 第四章·开发环境·137 VISUAL C++ 对话框需要像非创建COM对象那样来创建,而不能像一个局部变量一 样在堆栈上创建。在堆栈上分配对象并允许通过COM引用计数机制释 放。 // Create dialog class on the heap using ATL CComObject template. CComObject *myDlg; CComObject::CreateInstance(&myDlg); myDlg->AddRef(); // Keep dialog box alive until you're done with it. myDlg->DoModal(); // Launch the dialog box; when method returns, dialog box has //exited. myDlg->Release(); // Typically, the refcount now goes to 0 and frees // the dialog object. 3. 通过汇为使用实现一个中间的COM对象,这种情况有时候被称作监 听者或事件帮助对象。该对象通常不包含实现,而只是简单地调 用C++方法传送事件到汇对象。监听者通过源增加引用计数,但 是汇的引用数是不受影响的。这样就打破了循环,当所有的其它 引用被释放时,允许汇的引用数达到0。当汇执行它的析构函数代 码时,指示它的监听者执行Unadvise并释放源。 使用C++指针在监听者和汇之间通信的一个备选方案是使用一个弱引 用的接口指针。也就是说,监听者包含一个指向汇的COM指针但是不增 加汇的引用数。汇负责保证汇对象被释放后这个指针不能再被访问。 138·ArcGIS Engine 开发指南 .NET 应用程序接口 .NET 应用程序接口 本节“什么是.NET框架”总结了 Microsoft对.NET Framework的概 述,作为MSDN库的部分可以在线获 得。整个文档可以在下面的网址获 得: http://www.msdn.microsoft.com/ library/default.asp?url=/libra ry/en-us/cpguide/html/cpovrint roductiontonetframeworksdk.asp 什么是.NET框架? .NET框架是一套完整的Windows组件,支持下一代应用程序和XML Web 服务的创建和运行。.NET框架是为实现下列目标而设计的: 提供一致的面向对象编程环境,而无论对象代码是在本地储存和 执行,本地执行但在互联网上分布,还是远程执行。 提供一个最小化软件部署和版本冲突的代码执行环境。 提供保证安全执行代码,包括未知或半信任的第三方创建的代码 执行环境。 提供消除脚本化或解释环境带来的性能问题的代码执行环境。 使得开发人员编程经验跨越广泛类型的应用程序取得一致性,例 如基于Windows的应用程序和基于Web的应用程序。 在工业标准之上建立所有通信确保基于.NET框架的代码可以与其 它代码集成。 .NET框架有两个主要组件:通用语言运行时和.NET框架类库。通用语 言运行时是.NET框架的基础。可以将运行时看作是在执行时管理代码 的智能体,提供诸如内存管理、线程管理和远程等核心服务,同时也 加强了严格的类型安全和其它形式的代码精度以确保安全性和健壮 性。事实上,代码管理的概念是运行时的基本原理。以运行时为目标 的代码被称作被管理代码,而不以运行时为目标的代码被称为未被管 理的代码。类库,.NET框架的另外一个主要组件,是一个全面的、面 向对象的可重用类型的集合,可以用它来开发各种应用程序,包括从 传统的命令行或图形用户界面应用程序到基于ASP.NET提供的最新技 术上的应用程序,例如Web Forms和XML Web服务。 .NET框架可以作为未被管理的组件的主机,这些组件加载通用语言运 行时到它们的进程中并初始化被管理代码的执行,因此创建了一个软 件环境既可以使用被管理的要素也可以使用未被管理的要素。.NET框 架不仅提供几个运行时主机,也支持第三方运行时主机的开发。 例如,ASP.NET包含运行时为被管理代码提供一个可缩放的、服务器端 环境。ASP.NET直接操作运行时激活ASP.NET应用程序和XML Web服务, 这两个在后面都要讨论到。 Internet Explorer是一个包含运行时的未被管理应用程序的例子(以 MIME类型扩展的形式)。使用Internet Explorer包含运行时,使得用 户可以在HTML文档中嵌入被管理的组件或Windows窗体控件。以这种方 式包含运行时,使得被管理的移动代码(类似于Microsoft ActiveX 第四章·开发环境·139 .NET 应用程序接口 控件)成为可能,并且有了很大的改善,这些只有被管理的代码能提 供,例如半信任执行和安全独立文件存储。 下面这些部分更详细地描述了.NET框架的主要组件和特性。 通用语言运行时的特性 通用语言运行时管理内存、线程执行、代码执行、代码安全性校验、 编译和其它系统服务。这些特性是运行在通用语言运行时的被管理代 码的本质属性。 关于安全性,被管理组件被授予不同等级的信任,这取决于大量的因 素,包括这些组件的来源,例如Internet,企业网或本地计算机。这 意味着被管理的组件可能或者不可能执行文件访问操作、注册表访问 操作或其它敏感的功能,即使在相同的活动应用程序中使用。 运行时加强了代码访问安全性。例如,用户可以信任嵌入在一个Web页 的可执行程序可以放映一个屏幕动画或唱一首歌,但不能访问它们的 个人数据、文件系统或网络。运行时安全特性这样使得合法的 Internet-deployed软件特征格外丰富。 运行时也通过实现一个叫做通用类型系统(CTS)的严格的 type-and-code-verification基础结构增强了代码的健壮性。CTS确保 了所有被管理代码都是自描述的。不同的Microsoft和地方语言编译器 生成符合CTS的被管理代码。这意味着被管理代码能够消耗其它的被管 理类型和例程,然而严格地加强了类型保真度和类型安全性。 此外,运行时的被管理环境除去了许多通用软件问题。例如,运行时 自动地使用对象设计和管理对象的引用,当它们不再被使用时释放它 们。这个自动内存管理解决了两个最常见的应用程序错误:内存泄漏 和无效内存引用。 运行时也提高了开发人员的生产效率。例如,程序员可以选择编写应 用程序的开发语言,仍可以利用其它的开发人员用其它语言编写的运 行时、类库和组件。任何选择以运行时为目标的编译器的卖主可以完 成这些工作。以.NET框架为目标的语言编译器使得用某种语言编写的 现有代码可以利用.NET框架的特性,这使得对现有应用程序的移植处 理变得十分的容易。 虽然运行时是为未来的软件设计的,但它也支持现在的和过去的软件。 在被管理的和未被管理的代码之间的互操作使得开发人员可以继续使 用必要的COM组件和DLLs。 运行时是为提高性能而设计的。虽然通用语言运行时提供了许多标准 运行时服务,被管理代码从来都没有被解释过。一个称为just-in-time 140·ArcGIS Engine 开发指南 .NET 应用程序接口 (JIT)编译的特性使得所有被管理的代码在本地机器语言系统上执行。 其间内存管理器消除了内存破碎化的可能性并提高了内存引用的局部 性,从而进一步提高了性能。 最后,运行时可以被高性能的服务器端应用程序使用,例如Microsoft SQL Server™和Internet Information Services (IIS)。这个结构使 得用户可以使用被管理的代码编写自己的事务逻辑,但仍可以享用支 持拥有运行时的业界最好企业服务器的出众性能。 .NET框架类库 .NET框架类库是一个与通用语言运行时紧密结合在一起的可重用类型 的集合。类库面向对象,提供用户自己的被管理代码可以从中获得功 能的类型。这不仅使得.NET框架类型易于使用,也减少了与学习.NET 框架的新特性有关的时间。此外,第三方组件可以与NET框架类无缝结 合。 例如,.NET框架集合实现了一套接口,可以使用这些接口开发自己的 集合类。自己的集合类会与.NET框架中的类无缝混合。 如同用户对面向对象类库所期望的一样,.NET框架使得用户可以完成 大量的常用编程任务,包括字符串管理、数据收集、数据库连接和文 件访问。除了这些常用的任务外,类库包括了支持大量专门化开发场 景的类型。例如,可以使用.NET框架开发下面类型的应用程序和服务: 控制台应用程序 Windows GUI应用程序(Windows Forms) ASP.NET应用程序 XML Web服务 Windows服务 例如,Windows Forms类是一个可重用类型的完整集合,它们极大地简 化了Windows GUI程序的开发。如果编写一个ASP.NET Web Form应用程 序,可以使用Windows Forms类。 客户端应用程序开发 在基于Windows的程序开发中,客户端应用程序最接近于传统风格的应 用程序。这些程序为在桌面上显示窗口或窗体使得用户可以执行一项 任务的应用程序类型。客户端应用程序包括那些文字处理和电子表格 数据,也包括顾客商业应用程序,例如数据记录和报告工具。客户端 程序通常使用窗口、菜单、按钮和其它的GUI元素,并且有可能访问本 地资源,例如文件系统和外部设备如打印机。 第四章·开发环境·141 .NET 应用程序接口 另外一种客户端应用程序是传统的ActiveX控件(现在被管理的 Windows Forms控件代替),作为一个Web页被配置在Internet上。这 个应用程序很像其它的客户端应用程序:本地执行,可访问本地资源, 包括图形元素。 在过去,开发人员使用C或C++结合Microsoft Foundation Classes或 使用快速应用程序开发(RAD)环境(例如Microsoft Visual Basic)来 创建这样的应用程序。.NET框架将这些现有产品的各个方面合并成为 一个单一的一致的开发环境,大大简化了客户端程序的开发。 包含在.NET框架中的Windows Forms类设计用于GUI开发。可以简单地 创建具有必要的灵活性的命令窗口、按钮、菜单、工具条和其它的屏 幕元素,以适应多变的商业需求。 例如,.NET框架提供了调整与窗体有关的视觉属性的简单属性。在某 些情况下底层的操作系统不支持直接修改这些属性,并且在这些情况 下.NET框架自动重建窗体,这是.NET框架结合开发人员接口使得编码 更简单和更一致的许多方法中的一个。 与ActiveX控件不同,Windows Forms控件可以半信任地访问一个用户 的计算机。这意味着二进制或本地执行代码可以访问用户的系统上的 某些资源,例如GUI元素和有限制的文件访问,而不能访问或危及其它 资源的安全。因为代码访问安全性,许多过去需要在用户系统上安装 的应用程序现在可以通过Web安全地配置。用户的应用程序配置时像展 开一个Web页一样,实现一个本机应用程序的特征。 服务器应用程序开发 服务器端应用程序在被管理的世界中通过运行时主机实现。未被管理 的应用程序拥有通用语言运行时,这允许用户的客户端管理代码控制 服务器行为。这个模型提供给用户通用语言运行时的所有特征和获得 主机服务器的性能与可伸缩性时的类库。 服务器端管理的代码 ASP.NET是开发人员使用.NET框架开发基于Web应用程序的主机环境。 然而,ASP.NET不仅是一个运行时主机;它是一个使用被管理代码开发 Web站点和互联网分布式对象的完整结构。Web Forms和XML Web服务都 使用IIS和ASP.NET作为应用程序的发布机制,两者都有一个在.NET 框 架中支持类的集合。 XML Web服务,基于Web技术中的一个重要进展,是分布式的、服务器 端的应用程序组件,它类似于通用Web站点。然而与基于Web的应用程 142·ArcGIS Engine 开发指南 .NET 应用程序接口 第四章·开发环境·143 Se Forms页。此外,代 类和工具的集合,这些类和工具有助于开发和 例如,包括.NET框架SDK的Web Services Description Language工具 有底层通 最后,像在被管理的环境中的Web Forms,用户的XML Web服务会使用 本图说明了一个基础网络模式,使 用运行在不同服务器环境下的被管 理的代码。服务器(如IIS和SQL rver)可以执行标准的操作,然 而用户应用程序的逻辑执行被管理 的代码。 序不同,XML Web服务组件没有UI并且也不是以浏览器(例如Internet Explorer和Netscape Navigator)为目标的,。相反,XML Web服务包 含可重用的软件组件,设计为可被其它的应用程序(如传统的客户端 应用程序、基于Web的应用程序,或其它的XML Web服务)使用。结果, XML Web服务技术是快速移植应用程序开发和配置到高度分布的 Internet环境的技术。 如果已经使用过ASP技术的早期版本,很快就会注意到ASP.NET和Web Forms所提供的改进。例如,可以在支持.NET框架的任何语言下开发Web 码也不再需要与HTTP文本(虽然如果你愿意,也可 继续这样做)共享相同的文件。Web Forms页在本 地机器语言上执行,因为和其它的被管理应用程序 一样,可以充分地利用运行时。与此相对,未被管 理的ASP页常常是脚本化的的和解释性的。ASP.NET 也更加快速,更多功能,并且比未被管理的ASP页 更容易开发,因为像其它的被管理应用程序一样,它们与运行时交互。 .NET框架也提供了一个 使用XML Web服务应用程序。XML Web服务建立在一些标准之上,如SOAP, 一个远程过程调用协议;XML,一个可扩展的数据格式;和WSDL,Web Services Description Language。NET框架建立在这些标准上提高与 非Microsoft解决方案互操作能力。 可以查询一个发布在Web上的XML Web服务,解析它的WSDL描述并产生 C#或Visual Basic源代码,这样,应用程序可使用这些代码成为服务 的一个XML Web客户端。这些源代码可以从类库的类创建类,这个类库 使用SOAP和XML解析操纵所有底层的通信。虽然可以使用类库直接使用 XML .NET框架 Web服务,Web Services Description Language工具和 包含在SDK中的其它工具促进了使用.NET框架开发的工作。 如果开发和发布了自己的XML Web服务,.NET框架提供了符合 信标准的一套类,这些标准有SOAP、WSDL和 XML等。使用这些类使得 用户能集中在自己服务的逻辑性上,而不关心分布式软件开发所要求 的通信基础结构。 可伸缩的IIS通信以本地机语言的速度运行。 .NET 应用程序接口 144·ArcGIS Engine 开发指南 .NET运行时提供了包装类,使得被管 向COM提供.NET组件 要遵循下面的原则以确保互操作 免使用参数化的构造函数。 与COM互操作 运行在.NET框架控件上的代码称作被管理的代码;相反,运行在.NET 框架之外的代码称作未被管理的代码。COM是一个未被管理代码的例 子。.NET框架通过一个被称作COM Interop的技术与COM交互。 为了使用COM Interop,CLR需要所有COM类型的元数据。这意味着COM 类型定义通常储存在需要转换为元数据的类型库中。使用Type Library Importer(tlbimp.exe)应用程序,用.NET框架SDK装载,很容易完成 这些任务。这个应用程序生成了包含一个类型库中所有COM定义元数据 的interop集合。一旦这些元数据可用,.NET客户就可以无缝地创建COM 类型的例程并调用该例程方法,就像本地.NET例程一样。 主要互操作(interop)集合 主要互操作集合(PIAs)是正式的,供应商提供的,为与底层的COM类 型交互而定义的.NET类型。主要互操作集合被COM库发行者强硬地命名 的,以保证唯一性。 ESRI为实现COM的所有类型库提供了主要互操作集合。如果检测到.NET 框架的1.1版本,ArcGIS .NET开发人员应该只使用这些在安装过程中 安装在Global Assembly Cache (GAC)上的主要互操作集合。ESRI仅 支 持 那 些 装 载 ArcGIS 的 主 要 互操作集合。可以通过公共键 (8FC3CC631E44AD86)识别一个有效的ESRI集合。 COM包装 理的和未被管理的客户认为它们 正在各自的环境中与对象通信。当 被管理的用户在COM对象上调用一 个方法时,运行时创建了一个运行 时可调用包装(RCW)来操纵两个 环境之间的列集。同样.NET运行时 为相反的情况创建COM可调用包 装,COM客户与.NET组件通信。上 面的图示说明了这个过程。 创建COM客户端要使用的.NET组件时, 性。 避 避免使用静态方法。 .NET 应用程序接口 用被管理的代码定义事件源接口。 在用户定义的异常中包括HRESULTs 为需要的类型提供GUIDs 期望继承差异。 要了解更多的信息,请查看MSDN帮助集中的“Interoperating with Unmanaged Code”。 性能考虑 COM Interop增加了添加一个新的开销层到应用程序,但是在COM和.NET 之间互操作的总体开销很小,所以这个开销往往是察觉不到的。然而, 创建包装和在不同环境之间列集的开销确实会累加起来;如果怀疑COM Interop是应用程序性能的瓶颈,试着创建一个COM worker类,将所有 的COM调用封装在一个可被管理代码调用的函数中。这样通过限制两个 环境之间的调度改善了性能。 COM到.NET的类型转换 通常来说,类型库导入者导入与最初在COM中名字相同的类型。所有导 入的类型都添加到一个遵循下面命名规则的命名空间中:ESRI.ArcGIS 加上库名。例如Geometry库的命名空间是ESRI.ArcGIS.Geometry。所 有类型通过它们的完整的命名空间和类型名识别。 类、接口和成员 所有的组件对象类被转换成为被管理的类;被管理的类名与起初的类 名加上‘Class’相同。例如,Point组件对象类的名称是PointClass。 所有类也有一个和组件对象类相同名称的接口,该接口对应于组件对 象类缺省接口。例如,PointClass有一个Point接口。类型库导入者添 加这个接口,这样客户端可以注册为事件汇。 .NET类也有一个.NET支持但COM不支持的类成员。该类实现的每个接口 的每个成员被作为一个类成员添加进来。从一个类移植的任何属性和 方法都可直接从类访问而不必强制转换到一个特殊的接口。因为接口 成员命名不是唯一的,所以命名冲突要通过接口名加前缀和给每个冲 突成员加下划线解决。当成员命名冲突时,列在组件对象类中的第一 个接口不会改变。 C#中有以引用传递或多个参数的属性不支持规则的属性语法。在这种 情况下,有必要使用accessor方法。下面摘录的代码展示了一个实例。 ILayer layer = mapControl.get_Layer(0); MessageBox.Show(layer.Name); 事件 类型库导入者创建了几个类型,使得被管理的应用程序接听由COM类激 第四章·开发环境·145 .NET 应用程序接口 146·ArcGIS Engine 开发指南 件接口加上下划线加上事件 ctiveViewEvents接口上定义 下面这样定义的delegate : electionChangedEventHandler。导入者也使用 IActiveViewEvents_Event。使用事件接口设 立事件汇。 非OLE自适应类型 IEnvelopeGEN::DefineFromPoints。 Dim pointArray(1) As IPoint pointArray(1).PutCoords(100, 100) pe Dim envGEN As IEnvelopeGEN 'Doesn't work [C#] w PointClass(); pointArray[0].PutCoords(0,0); IEnv velopeClass(); pile 发的事件。第一个类型是delegate,以事 名再加上EventHandler命名。例如,在IA 的 SelectionChanged 事件有 IActiveViewEvents_S 起初接口名加后缀‘_Event’来创建一个事件接口。例如, IActiveViewEvents生成 非OLE自适应的COM类型通常不能在.NET下操作。ArcGIS包含了几个非 自适应的方法并且这些方法不能在.NET中使用。然而在大多数情况下, 追加的接口被添加进来,使得不和规定的成员被自适应地改写。例如, 当通过一个点数组定义一个外包矩形时,不能使用IEnvelope::Defi- neFromPoints,相反,必须使用 [VB.NET] pointArray(0) = New PointClass pointArray(1) = New PointClass pointArray(0).PutCoords(0, 0) Dim env As IEnvelo env = New EnvelopeClass envGEN = New EnvelopeClass 'Won't compile 'env.DefineFromPoints(2, pointArray) env.DefineFromPoints(2, pointArray(0)) 'Works envGEN.DefineFromPoints(pointArray) IPoint[] pointArray = new IPoint[2]; pointArray[0] = new PointClass(); pointArray[1] = ne pointArray[1].PutCoords(100,100); elope env = new EnvelopeClass(); IEnvelopeGEN envGEN = new En // Won't com env.DefineFromPoints(3, ref pointArray); .NET 应用程序接口 提示和技巧,提供给.NET的开发人员。 .NET使用强制转换在同一个类上从一个接口跳跃至另一个接口。在COM = point 'Implicit cast metry) 'Explicit cast 在 的强制转换是完全可接受的,因为 在 据丢失。然而,强制转换失败时, 掷 ption);为避免处理不必要的异 常,如果对象实现两种接口时,最好预先进行检验。推荐的技巧是使 用 个比较从句,检验是否一个对象源于或实现 一个特定类型,例如接口。下面的例子执行了一个从IPoint到 IG 在运行时确定Point类实现IGeometry。 im geometry As IGeometry Is IGeometry) Then nd If 如果更喜欢在使用Option Strict On语句限定隐式的转换,使用CType 函 加了一个显式的强制转换到 上面的代码例子中。 ometry) C# 在C#中,在接口之间强制转换的最好办法就是使用as操作符。使用as 操作符是一个比直接强制转换分配更好的编码策略,因为它在转换失 败时产生一个空值而不是引发一个异常。 下面代码第一行是一个直接强制转换。如果完全确定被讨论的对象实 现两种接口,这样是可接受的;如果对象不实现,用户试图得到句柄 // Doesn't work env.DefineFromPoints(3, ref pointArray[0]); // Works envGEN.DefineFromPoints(ref pointArray); .NET编程技巧和要考虑的问题 这部分包含了一些编程 接口间强制转换 中叫做QueryInterface。VB .NET和C#的强制转换不同。 VB .NET 有两种类型的强制转换,隐式的和显式的。隐式的强制转换不要求附 加语义,然而显式的强制转换需要强制转换操作符。 geometry geometry = CType(point, IGeo 接口之间强制转换时,使用隐式 数字类型间强制转换时不会有数 出异常(System.InvalidCastExce TypeOf关键字,这是一 eometry的隐式转换,只有 Dim point As New PointClass D If (TypeOf point geometry = point E 数进行显式的强制转换。下面的例子添 Dim point As New PointClass Dim geometry As IGeometry If (TypeOf point Is IGeometry) Then geometry = CType(point, IGe End If 第四章·开发环境·147 .NET 应用程序接口 148·ArcGIS Engine 开发指南 的接口,.NET抛出异常。更安全的模型是使用as操作符,如果对象不 能 geometry = point; // Straight cast s operator 下面的例子展示了如何操作返回空接口句柄的可能性。 ) 编译时保持相 同的GUID。当不设置该标记时,每一次工程被编译时,为每个类产生 一个新的GUID。这样有一个负面效应,必须在合适的组件类目中重新 GUID, 下面例子展示了GUID属性被应用到一个类中。 [V 89D-4fcd-A854-44251E925F09")> lass SampleClass 捕获事件并响应事件的对 返回期望接口的引用,返回一个空值。 IGeometry IGeometry geometry = point as IGeometry; // A IPoint point = new PointClass(); IGeometry geometry = point; IGeometry geometry = point as IGeometry; if (geometry != null { Console.WriteLine(geometry.GeometryType.ToString()); } 二进制兼容性 大多数现在的ArcGIS Visual Basic 6开发人员都熟悉二进制兼容性的 概念。Visual Basic中这个编译标记确保组件每一次被 注册组件。 为防止在.NET中出现同样的问题,可以使用GUIDAttribute类手工为类 指定一个GUID。明确指定一个GUID保证它永远不会改变。如果不指定 一个GUID,当用户第一次导出组件到COM中时,类型库导出者会自动产 生一个,并且虽然导出者的意思是在后面的导出中都使用相同的 但不能保证如此。 B.NET] )提供函数指 针 用的类。与其它的类不 同 法的引用。 为 事件句柄(事件 处 执行程序逻辑并注册事件源的事件句柄。事件 句柄必须有一个和事件delegate一样的签名。此过程被称作事件配线。 下面摘录的ArcObjects代码展示了一个自定义ArcMap命令对Map对象 OnClick事件中配线。 'default interface dEventHandler is the delegate Private m_mxDoc As IMxDocument al hook As Object) rrides Sub OnClick() p e delegate, add it to SelectionChanged event. SelectionChanged = New ctiveViewEvents_SelectionChangedEventHandler(AddressOf SelectionChanged) dHandler map.SelectionChanged, SelectionChanged 'Event handler ("Selection Changed") End Sub 它引发的事件。需要在事件源和接收者之间有一个中间件(或类似指 针的机制)。.NET框架定义了一种专门的类型 的功能。Delegate是一个持有某个方法的引 ,delegate类有一个签名,并且它只持有与签名匹配的方 这样,delegate相当于一个类型安全函数指针或一个回调函数。 了在一个应用程序中使用一个事件,必须提供一个 理方法),响应事件 选中项改变的事件的配线。为了简单,该事件在 [VB.NET] 'Can't use WithEvents because the outbound interface is not the 'IActiveViewEvents is the sink event interface. 'SelectionChanged is the name of the event. 'IActiveViewEvents_SelectionChange name. 'Declare the delegate. Private SelectionChanged As IActiveViewEvents_SelectionChangedEventHandler Public Overloads Overrides Sub OnCreate(ByV Dim app As IApplication app = hook m_mxDoc = app.Document End Sub Public Ove Dim map As Ma map = m_mxDoc.FocusMap 'Create an instance of th IA On Ad End Sub Private Sub OnSelectionChanged() MessageBox.Show 第四章·开发环境·149 .NET 应用程序接口 150·ArcGIS Engine 开发指南 private ESRI.ArcGIS.ArcMapUI.IMxDocument m_mxDoc; m_mxDoc = app.Document as IMxDocument; gate instance and add it to SelectionChanged event. m_selectionChanged = new andler(SelectionChang electionChanged; () 错误处理 在 。 这个构造可能对于Visual Basic用户比较新,但对于C++或Java用户比 较熟 结构化异常处理的实现很简单,并且同样的概念也适用于VB .NET 或 C#。 On Error GoTo语句和Err对象提供了非结构化 的异常处理,也允许向下兼容,尽管该模型在这部分不会讨论到。 异常 在Visual Studio .NET中异常是用来处理出错状态的。它提供了出错 状 异常是一个继承System.Exception基础类的类的实例。.NET框架提供 不同类型的异常类,并且可以创建用户自己的异常类。每种 型通过允许更多地访问关于所发生的特殊类型的出错信息,扩展了 System.Exception类的基本功能。 当.N Exception实例。可以通 、Catch、Finally构造来处理异常。 [C#] // IActiveViewEvents is the sink event interface. // SelectionChanged is the name of the event. // IActiveViewEvents_SelectionChangedEventHandler is the delegate name. IActiveViewEvents_SelectionChangedEventHandler m_selectionChanged; public override void OnCreate(object hook) { IApplication app = hook as IApplication; } public override void OnClick() { IMap map = m_mxDoc.FocusMap; // Create a dele IActiveViewEvents_SelectionChangedEventH ed); ((IActiveViewEvents_Event)map).SelectionChanged += m_s } // Event handler private void SelectionChanged { MessageBox.Show("Selection changed"); } Vi 处理的构造被称作结构化的异常处理sual Studio .NET中出错 悉。 VB .NET通过常见的 态的信息。 了许多种 类 ET框架遇到错误情况时,创建并引发一个 过使用Try .NET 应用程序接口 Tr 这个构造允许用户在代码中捕获抛出的错误。下面是这个概念的一个 例 [V v.PutCoords(0D, 0D, 10D, 10D) ns.Rotate(env.LowerLeft, 1D) Ca [C#] { utCoords(0D, 0D, 10D, 10D); rLeft, 1D); ); // Perform any tidy up code. ,执行点会切换到第一个Catch块中。 块处理不同种类的错误。下面所示代码首先检验是否抛出的异常是一 deByZeroException。 ... s DivideByZeroException [C#] y、Catch、Finally 子。试图旋转一个外包矩形,抛出了一个错误。 B.NET] Dim env As IEnvelope = New EnvelopeClass() en Dim trans As ITransform2D = env tra tch ex As System.Exception MessageBox.Show("Error: " + ex.Message) ' Perform any tidy up code. d Try En IEnvelope env = new EnvelopeClass(); env.P ITransform2D trans = (ITransform2D) env; trans.Rotate(env.Lowe } catch (System.Exception ex) { MessageBox.Show("Error: " + ex.Message } { } 在可能出错的代码周围设置一个Try块。如果应用程序在Try块里抛出 了一个错误 Catch块处理一个抛出的错误。当引发错误的Type与Catch块所指定的 错误Type匹配时,应用程序执行Catch块。用户可以有不止一种的Catch 个Divi [VB.NET] Catch divEx A ' Perform divide by zero error handling. Catch ex As System.Exception ' Perform general error handling. ... ... catch (DivideByZeroException divEx) 第四章·开发环境·151 .NET 应用程序接口 152·ArcGIS Engine 开发指南 // Perform divide by zero error handling. ... 如 特殊的异常,Types,应该在常见的 Sy 始终处在类型检验之后。 如果错误抛出,应用程序始终会执行Finally块,或者在Try块完成后, 或 ally块通常应该包含必须被执行的代 码,例如整理资源,例如文件句柄,或数据库关联。 Finally块。 无异 如果 .NET运行时在调用函数 中搜索Catch块,在调用堆栈一直向上直到发现一个Catch块。 如果在整个调用堆栈中没有Catch块,那么确切的结果就取决于被执行 的代 一个程序的所有进入 点至少包括一个Try、 Catch、Finally构造。 COM组件错误 结构化异常处理模型和COM使用的HRESULT模型不同。C++开发人员容易 法处理,如通过使用Try、Catch、Finally块。 因 引发错误的代码装入一个 Tr 捕获COMException。下面是改写的第 一个 [VB.NET] Dim env As IEnvelope = New EnvelopeClass() .PutCoords(0D, 0D, 10D, 10D) trans As ITransform2D = env { } catch (System.Exception ex) { // Perform general error handling. } 果有不止一个 Catch块,注意越 stem.Exception之前,这样会 者在Catch块之后。因此,Fin 如果没有任何的整理代码,不需要包含一个 常处理的代码 没有包含在Try块中的一行代码抛出错误, 码的位置和.NET运行时的构造。因此,建议 在HRESULT中忽略一个错误状态;在Visual Basic 6中,在HRESULT中 的错误状态给Err对象赋值并引发一个错误。 .NET运行时对COM组件的出错处理在某种程度上类似于在VB6中处理 COM错误的方式。如果.NET程序在COM组件(通过COM interop服务)上 调用一个函数并以HRESULT返回一个错误情形,这个HRESULT用来为 COMException类的例程赋值。这个错误由.NET运行时抛出错误,可以 用 的方通常 此,建议将所有在COM组件中将所有可能 y块中,并有相应的Catch块来 例子,从COM组件上检查错误。 env Dim trans.Rotate(env.LowerLeft, 1D) .NET 应用程序接口 Catch COMex As COMException Envelope") ("Error " + COMex.ErrorCode.ToString() + ": " + COMex.Message) Catch ex As System.Exception essageBox.Show("Error: " + ex.Message) { } { == -2147220984) MessageBox.Show("You cannot rotate an Envelope"); } 属于System.Runtime.InteropServices命名空间。通 抛出错误和异常的等级 如果正在为一个用户接口编写代码,用户可能想要试图在代码中改正 错误状态并再次尝试调用。或者可能想要报告错误给用户,让他们决 来识别问题。 然而,如果正在编写一个仅从其它代码可以调用的函数,希望这样处 理这些错误,创建一个专门的错误状态并将这个错误传送给调用者。 可以使用Throw关键字完成这个操作。 的错误到达调用者函数,像下面这样,使用Throw关键字编 写 [V Ca If (COMex.ErrorCode = -2147220984) Then MessageBox.Show("You cannot rotate an MessageBox.Show _ End If M ... [C#] IEnvelope env = new EnvelopeClass(); env.PutCoords(0D, 0D, 10D, 10D); ITransform2D trans = (ITransform2D) env; trans.Rotate(env.LowerLeft, 1D); catch (COMException COMex) if (COMex.ErrorCode MessageBox.Show ("Error " + COMex.ErrorCode.ToString() + ": " + COMex.Message); catch (System.Exception ex) { MessageBox.Show("Error: " + ex.Message); } ... COMException类 过ErrorCode属性提供了对初始HRESULT的访问,可以通过测试这个值 来查找出哪一种错误情形发生。 定应该采取什么行动;这里用户可以利用Exception类的Message属性 为了使现存 出错处理程序。 B.NET] tch ex As System.Exception 第四章·开发环境·153 .NET 应用程序接口 154·ArcGIS Engine 开发指南 [C#] catch (System.Exception ex) { thr } tion类的新例程,正确地赋值,并将这个异常返回给调用者。下 的例子使用ApplicationException构造函数设置Message属性。 [V Ca ation") tch (System.Exception ex) { plication"); } ... 常丢失。为了使全部的错误信息被传送, ception 类包括 InnerException 属性。这个属性在新的异常被抛出 之 样创建了一个错误等级。 这个例子展示了使用 ApplicationException 构造函数来设置 Catch ex As System.Exception nException = _ our application", ex);throw appEx; 误,应用程序在控制被返回到调用函数之前会执行当前函 ... ow; ... 如果希望传送一个不同的或更特殊的错误给调用者,应该创建一个 Excep 面 B.NET] tch ex As System.Exception Throw New ApplicationException _ ("You had an error in your applic ... [C#] ca throw new ApplicationException("You had an error in your ap 然而如果这样做,起初的异 Ex 前,应该被设置为与被捕获的异常相等。这 下面 InnerException 和 Message 属性。 [VB.NET] Dim appEx As System.Applicatio New ApplicationException("You had an error in y ex) Throw appEx ... [C#] catch (System.Exception ex) { System.ApplicationException appEx = new ApplicationException("You had an error in your application", } ... 这样,最终处理出错情形的函数可以访问关于错误情形和其前后关系 原因的所有信息。 如果抛出错 数的 Finally 子句。 .NET 应用程序接口 第四章·开发环境·155 操作资源 形(没有本地化) 如果当前定制不支持本地化,并且也不打算在以后支持本地化,可以 接使用字符串和图形而不需要资源文件。例如可以在代码中直接指 使用字符串: "; 图 的步骤直接嵌入到部件中: 在 Solution Explorer 中右键单击 Project,单击 Add,然后单击 。 在 Add Existing Item 对话框中,浏览图象文件并单击 Open。 下 F4 显示属性。 现在 用图象。例如,下面的代码从部件中第一个嵌入 源创建一个位图对象。 sourceNames() If itmap( _ GetType(Form1).Assembly.GetManifestResourceStream(res(0))) string[] res = GetType().Assembly.GetManifestResourceNames(); 直接使用字符串和嵌入图 直 定和 [VB.NET] Me.TextBox1.Text = "My String" [C#] this.textBox1.Text = "My String 象文件(BMPs、JPEGs、PNGs等)按照下面 1. Add Existing Item 2. 3. 在 Solution Explorer 中,选择刚添加的图形文件,然后按 4. 设置 Build Action 属性到 Embedded Resource。 可以在代码中引 的资 [VB.NET] Dim res() As String = GetType(Form1).Assembly.GetManifestRe (res.GetLength(0) > 0) Dim bmp As System.Drawing.Bitmap = New System.Drawing.B ... [C#] if (res.GetLength(0) > 0) { .NET 应用程序接口 156·ArcGIS Engine 开发指南 awing.Bitmap bmp = new System.Drawing.Bitmap( GetType().Assembly.GetManifestResourceStream(res[0])); 地化资源的情况下,也要使用资源文件而不是直 述的。 al Studio .NET工程使用基于XML的文件格式的资源。XML文件的 种类的数据(图象、光标等等),直 这 下面的选项适用于创建资源文件。每个选项在下面论述。 建资源文件 为字符串资源创建.resx文件 如果所有需要本地化的都是字符串—而不是图象或光标—可以使用 VisualStudio.NET 创建新的.resx 文件,自动编译成为嵌在主部件 的.resources 模块。 1. 在 Solution Explorer 中右键单击 Project 名称 击 Add,然后 单击 Add New Item。 2. 在 Add New Item 对话框中,单击 Assembly Resource File。 System.Dr ... 创建资源文件 在试图提供本地化资源之前,确保熟悉为.NET工程创建资源文件的过 程。甚至在不打算本 接使用图形和字符串,像上面所描 Visu 扩展名为.resx并且可以包含任何 到数据被转换成ASCII格式。RESX文件被编译成为.resources文件, 是资源数据的二进制表示。二进制文件可通过编译器嵌入到主工程部 件,或只包含资源的独立satellite部件中。 为字符串资源创建.resx文件 为图象资源创 编译a.resx文件为.resources文件 ,单 .NET 应用程序接口 第四章·开发环境·157 在Microsoft .NET Framework文档 具,其网址为http:// tutorials/html/appendix_b__res ource_tools.asp。 在Microsoft .NET Framework文档 中可找到关于ResEditor例子的附 加信息。其网址为http:// msdn.microsoft.com/library/def ault.asp?url=/library/en-us/cp tutorials/html/resource_editor __reseditor_.asp ResEditor例子是由Microsoft作为 源代码提供的。如果想要用这个工 具创建资源文件,必须先创建实例。 关于创建SDK实例的信息可以在 Visual Studio .NET安装目录的SDK 子目录下找到。 . 打开Visual Studio中的新.resx文件,并添加名字—value pairs sources 个这样的例子就是Resource 象列表框和字符串到一个资 资源。文件可以被保存为.resx或 者.resource文件。 通过编程创建资源文件 可以通过使用ResXResourceWriter类(.NET框架的一部分)编程创建 包含资源的XML.resx文件。可以通过使用ResourceWriter类(也是.NET 框架的一部分)编程创建二进制.resources文件。这些类允许更灵活 地添加所需的资源种类。 中可找到有助于操作资源的一列工 msdn.microsoft.com/library/def ault.asp?url=/library/en-us/cp 3 for the culture—应用程序中特定的字符串。 4. 当编译工程时,.resx 文件会编译成为嵌在主部件的.re 模块。 为图象资源创建资源文件 在.NET中添加图象、图标或光标到一个resources文件中的过程比创建 一个仅包含字符串值的文件要复杂,因为当前在Visual Studio .NET IDE可获得的工具只能用于添加字符串资源。 然而,使用Visual Studio .NET Framework SDK可获得大量例子工程, 这些例子会有助于操作资源文件。一 Editor (ResEditor)。 ResEditor实例可用来添加图形、图标、图 源文件。这个工具不能用来添加光标 .NET 应用程序接口 158·ArcGIS Engine 开发指南 在 ary/default.asp?url=/library/e 于ResXGen的更多信息。 如果要添加不能通过.NET Framework SDK实例和工具操作的资源(例 如光标),这些类非常有用。这两个类基本用法是类似的:首先,创建 一个新的资源写字板类指定文件名,然后使用AddResource方法逐个地 地添加资源。 下面的代码示范了如何使用类创建一个新的.resx 文件并添加一个位 图和光标到文件中。 Dim img As System.Drawing.Image = CType(New System ap("ABitmap.bmp"), System.Drawing.Image) s.Forms.Cursor("Pencil.cur") rsxw.AddResource("MyBmp_jpg", img) [C#] yBmp_jpg", img); rsxw.AddResource("Mycursor_cur", cur); rsxw.Close(); PanTool开发人员实例(Samples\Map Analysis\Tools)包含一个脚本 —MakeResources—展示了怎样使用ResXResourceWriter类,将位图、 光标文件和字符串写入一个.resx文件。该实例也展示了如何使用 ResXResourceReader类读取一个.resx文件。这个例子包括一个含有一 个位图、两个光标和三个字符串的文件。 编译.resx文件到.resources文件中 基于XML的.resx文件可以使用指南中Visual Studio IDE或ResX Generator (ResXGen)实例被编译为二进制.resources文件。 当工程创建之后,Visual Studio工程中的任何一个.resx文件都 模块。关于如何使用多个资源文件 本地化,见下面“使用本地化资源”部分。 http://msdn.microsoft.com/libr n-us/cptutorials/html/ResX_Gen erator__RESXGEN_.asp上可找到关 [VB.NET] .Drawing.Bitm Dim cur As New System.Window Dim rsxw As New System.Resources.ResXResourceWriter("en-AU.resx") rsxw.AddResource("Mycursor_cur", cur) rsxw.Close() System.Drawing.Image img = (System.Drawing.Bitmap) new System.Drawing.Bitmap("ABitmap.bmp"); System.Windows.Forms.Cursor cur = new System.Windows.Forms.Cursor("Pencil.cur"); System.Resources.ResXResourceWriter rsxw = new System.Resources.ResXResourceWriter("en-GB.resx"); rsxw.AddResource("M 会被编译成为一个.resources 可以使用.NET Framework SDK命令resgen,独立于创建过程转换一 个.resx文件为.resources文件,例如: resgen PanToolCS.resx PanToolCS.resources .NET 应用程序接口 Pan Tool开发人员实例的Visual 该实例可以在Developer Samples\ArcMap\Commands and 化,作为例子用。 Pan Tool实例中提供了一个叫做 buildResources.bat的批处理文 件,用来创建缺省的.resources 下面这部分说明如何为用户定制本地化资源。 化资源 -US”表示英语的美国方言,通过使用“fr-CH” 瑞士方言。 建一 atellite部件(使用命名规则 ly Name>.resources.dll)并被放置在主构建目录的子 。这个子目录以它们所包含的satellite部件的文化命名。例如, 文化的satellite部件中 资源。适当的文化由Windows设置决定。如果适当文化的satellite 件中)代替。 .resx和.resources文件更多 。 n属性。 LL,确保用户的应用程序 2. 文件中的每个资 Basic .NET和C#风味举例说明了如 何为German语言环境本地化资源。 Tools\Pan Tool目录中找到。严格 地说,该实例仅需要本地化的字符 串,但是图象也被转变为“de”文 文 件和特定文化的satellite部件。 使用本地化资源 如何使用本地 在.NET中,一个特定的语言和国家/地区联合在一起被叫做culture。 例如,通过字符串“en 表示法语的 如果想要工程支持不同的文化(语言和方言),应该为每种文化构 个包含特定文化字符串和图象的独立.resources文件。 创建使用资源的.NET工程时,在主部件中嵌入了缺省的.resources文 件。特定文化的.resources文件编译为s
..resx 或 程中,确保每个资源文件都有设置到 Embedded Resource的Build Action。 编译器和链接器会为每一个文化创建独立的satellite部件。 satellite部件被放置包含的主部件目录的子目录下。子目录按照 应用程序配置文件 通用 ,可以是下面 的一种: 名 应用程序可能 .Configuration文件,继承了配置文件在URL 路径中的设置。例如,给定URL www.esri.com/aaa/bbb, e 位于www.esri.com/aaa.。在子目录/bbb中的ASP.NET页使用在应 用程序级别的配置文件设置和在/bbb中的配置文件设置。 ..resources。 3. 添加新的资源文件到工 4. 创建工程。 文化命名,允许.NET运行时为应用程序运行的文化查找合适资源。 主要的(缺省的)资源文件内嵌在主部件中。 部件版本化和重定向 使用经过强硬命名的特定版本部件建立起来的应用程序,运行时要求 相同的部件。例如,如果使用ESRI.ArcGIS.System 9.0.452版本创建 一个应用程序,不能在安装了新版本ESRI.ArcGIS.System(例如 9.0.0.692)的系统上运行这个应用程序。如果安装了更新版本的 ArcGIS,可能会出现这种情况;然而使用配置文件,用户可以重定向一 个应用程序来使用一个部件的新版本。 应用程序部件重定向可以有两种选择: 机器配置文件 应用程序配置文件 应用程序配置文件包含有针对于一个应用程序的设置。该文件包含了 语言运行时读取的配置设置(例如部件绑定原则或远程对象)和 应用程序可读取的设置。 应用程序配置文件的名称和位置取决于应用程序的主机 Execuable-hosted 应用程序—可执行主机拥有的应用程序配置文 件在应与应用程序相同的目录下。配置文件的名称是应用程序 称加上一个扩展名。例如,一个叫做 myApp.exe 的 与一个叫做 myApp.exe.config 的配置文件相关联。 ASP.NET-hosted应用程序—ASP.NET配置文件在ASP.NET应用程序 中被称作Web.Config www. sri.com/aaa是Web应用程序,与应用程序想关联的配置文件 .NET 应用程序接口 使用XCOPY复制应用程序不会复制 机器配置文件的设置。 上的应用程序有一个配置文件,这个文件的位置使用下面的语 在一 的URL。这样设置了应用 配置文件必须位于与应用程序相同的Web站点。 机器 机器 个文 achine.config包 含机器级别的部件绑定,内置远程通道和的配置环境和ASP.NET。 配置系统首先在机器配置文件中查找元素和其它开发 绑定到一个部件上并使之适应新的版本。 e> mlns="urn:schemas-microsoft-com:asm.v1"> culture="neutral" /> < 使用 程序和扩展 创建一个使用 分讨论了关于使用 Internet Explorer-hosted应用程序—如果Internet Explorer 句 个标签中指定: 在这个标签中,location是一个配置文件 程序的基础。 配置文件 配置文件,Machine.config,,包含应用于整个计算机的设置。这 件位于%runtime install path%\Config目录。M 人员可能会定义的配置部分。然后查看应用程序配置文件。为了使机 器配置文件容易管理,最好在应用程序配置文件中进行设置。然而在 机器配置文件中设置可以使得系统更加可维护。例如,有一个客户和 服务器应用程序都要使用的第三方组件,在一个位置为该组件设置是 比较容易的。在这种情况子下,机器配置文件是设置的合适位置,因 此在不同的文件中不会有相同的设置。 下面设置文件展示了如何 /configuration> 使用.NET开发ARCGIS .NET,可以定制ArcGIS应用程序,创建使用ESRI类型的独立应用 ESRI的类型。例如,可以为ArcMap创建一个自定义工具, MapControl的独立应用程序或创建一个自定义层。这部 ArcGIS和.NET和开发的几个关键问题。 第四章·开发环境·161 .NET 应用程序接口 162·ArcGIS Engine 开发指南 使用自定义.NET组件扩展ArcGIS COM注册表中注册组 件并导出 有两种 方法可以执行这个任务:使用装载 的RegAsm应用 程序或有一个针对 Studio.NET 一个使用COM注册的EditTools部件。/tlb参数表明 应该生成一个类型库,/codebase选项表明部件的路径应该包含在注册 表设置中。当使用.NET组件扩展ArcGIS应用程序时,这两个参数都需 要。 regasm EditTools.dll /tlb:EditTools.tlb /codebase 。例如,所有的ArcMap命令和工具必须在ESRI Mx Co 类目内注册。在一个特定的类目内注册一个.NET组件可 以有 加 详细的内 自定义的.NET ArcGIS命令和工具可以通过Customize对话框中的Add 下,只需要简单地 浏览一下TLB并打开它 的类添 类目实 另外一个选项是使用Component Categories Manager (Categories.exe)。在这种情况下,可以在应用程序中选择组件类 适的类。 最后的也是推荐的解决方案是添加代码到.NET类中,这样每当使用COM 注册组件的时候,会在特定的组件类目中自动注册他们。.NET Framework包含两个属性类(ComRegisterFunctionAttribute和 用COM注册.NET组件 应用程序需要在 .NET部件到一个类型库(TLB)中。当开发一个组件时, .NET Framework SDK COM Interop的Register编译器标签的Visual 。 下面的例子展示了 如果为COM Interop编译器标签设置了Register,Visual Studio.NET 会自动执行相同的操作;这是在一个开发机器上执行注册的最简单方 法。要检查工程的设置,在Project菜单中单击Project Properties, 然后在Configuration Properties下查看Build属性。最后一项, Register for COM Interop,应该设置为True。 在COM组件类目内注册.NET类 许多ArcGIS的扩展性依赖于COM组件类目。事实上,大多数自定义的 ArcGIS组件必须在适合语境的组件类目内和主机应用程序要使用到功 能的函数内注册 mmands组件 不同的方法,但是在注册之前,必须使用COM注册.NET组件。更 容参见上面“使用COM注册.NET组件”的部分。 自定义对话框 From File按钮快速地添加到工具条中。在这种情况 。ArcGIS框架会自动地将从类型库中选中 加到合适的组件类目中。 用工具 目,浏览类型库,并选择合 COM注册函数 .NET 应用程序接口 ),它们允许用户指定方法,不论 什么时候注册组件或取消注册组件时都可以调用。两个方法都传递给 展示了一个自定义ArcMap命令,当它所在的.NET部件 使用COM注册时,该命令自动在MxCommands组件类目中注册自己。 ) teSubKey(regKey. ubKey(regKey.Substrin S.Utility部件简化编码过程 定义命令和工具。这些类是抽象类(在Visual 并且 都属于ESRI.ArcGIS.Utility.BaseClasses命名空间。 ICommand和ITool每个成员提供了缺省的实现,这样简 ComUnregisterFunctionAttribute 当前被注册类的CLSID。使用这个消息,可以在方法内编写代码来安排 适合的注册录入和删除。在组件类目内注册一个组件也要求用户知道 组件类目的唯一ID(CATID)。 下面摘录的代码 public sealed class AngleAngleTool: BaseTool { [ComRegisterFunction()] static void Reg(String regKey { Microsoft.Win32.Registry.ClassesRoot.Crea Substring(18)+ "\\Implemented Categories\\" + "{B56A7C42-83D4-11D2-A2E9- 080009B6F22B}"); } [ComUnregisterFunction()] static void Unreg(String regKey) { Microsoft.Win32.Registry.ClassesRoot.DeleteS g(18)+ "\\Implemented Categories\\" +"{B56A7C42-83D4-11D2-A2E9-080009B6F22B}"); } 为简化此过程,ESRI为ArcGIS展示的每个组件类目的类提供了静态函 数来注册组件和取消注册组件。每个类都知道组件类目GUID所表示的 组件类目,所以注册自定义组件大大地简化了。关于使用这些类更多 的细节,见下面“操作ESRI .NET组件类目类”部分。 使用ESRI.ArcGI 一部分ArcGIS Developer Kit包括了大量的.NET应用程序类,这些程 序利用一些.NET功能(包括对象继承和静态函数),方便了.NET开发。 操作ESRI .NET基类 ESRI提供了两个抽象基类(BaseCommand and BaseTool),有助于为 ArcGIS创建新的自 Basic .NET中标记为MustInherit),这意味着虽然这些类包含一些实 现的代码,它自身不能被直接被实例化并只能通过被另外的类继承来 使用。这两个基础类都是在ESRI.ArcGIS.Utility部件中定义的, 这些基类为每个 化了创建自定义命令和工具的过程。不用除去每个成员并提供实现代 码,只需重载自定义命令或工具所需要的成员。异常是 ICommand::OnCreate;该成员在初始的类中必须被重载。 第四章·开发环境·163 .NET 应用程序接口 164·ArcGIS Engine 开发指南 一个更快速、更简单、更少出错的创建命令和工具的方法。 使得用户可以快速地通过构造函数 B.NET] Pu ng.Bitmap _ Val caption As String _ By s String _ [C m.Drawing.Bitmap bitmap, int helpContextId, string message, [VB.NET] g _ System.Drawing.Bitmap bitmap, 在.NET语言中为ArcGIS应用程序创建命令和工具,推荐使用这些基类。 可以从第一法则创建类似的COM类;然而用户很快就会发现基类技术是 句法 两种基础类都有重载的构造函数, 参数设置命令和工具的许多属性,例如Name和Category。 重载的BaseCommand构造函数有下面的签名: [V blic Sub New( _ ByVal bitmap As System.Drawi By Val category As String _ ByVal helpContextId As Integer _ ByVal helpFile As String _ By al message AV ByVal name As String _ ByVal tooltip As String) #] public BaseCommand( Syste string caption, string category, string helpFile, string name, string toolTip, ); 重载的BaseTool构造函数有下面的签名 Public Sub New( _ ByVal bitmap As System.Drawing.Bitmap _ ByVal caption As String _ ByVal category As Strin ByVal cursor As System.Windows.Forms.Cursor _ ByVal helpContextId As Integer _ ByVal helpFile As String _ ByVal message As String _ ByVal name As String _ ByVal tooltip As String _ ) [C#] public BaseTool( string caption, string category, .NET 应用程序接口 第四章·开发环境·165 string message, name, string toolTip, 继承基类 新类时,使用这些参数化的构造函数,例如,下面显示了 继承了BaseTool类。 [V Pu ", "My Custom Tools", _ Cross, 0, "", "Pans the map.", "P En public PanTool() : base ( null,"Pan", "My Custom Tools", Sy s, 0, "","Pans the map.", "PanT "Pan" { ... } 直接设置基类成员 他们内部成员变量给继承类,每个属性一个,这样可以在 构造函数来设置Caption或 重 置在基类中声明的m_caption类成员变量。 [V Pu Sy ().Assembly.GetManifestResourceStream("Name sp indows.Forms.Cursors.Cross MyBase..m_category = "My Custom Tools" Base..m_caption = "Pan" ssage = "Pans the map." lTip = "Pan" En System.Windows.Forms.Cursor cursor, int helpContextId, string helpFile, string ); 当编写一个 称作PanTool的新类 B.NET] blic Sub New() MyBase.New( Nothing, "Pan System.Windows.Forms.Cursors. anTool", "Pan") d Sub [C#] stem.Windows.Forms.Cursors.Cros ool", ) 作为使用参数化构造函数的备选方案,可以直接设置基类的成员。 基础类暴露 起初的类上直接访问它们。例如,代替使用 载Caption函数,可以在设 B.NET] blic Sub New() MyBase.New() MyBase..m_bitmap = New stem.Drawing.Bitmap([GetType] ace.Pan.bmp")) MyBase..m_cursor = System.W My MyBase..m_me MyBase..m_name = "PanTool" MyBase..m_too d Sub .NET 应用程序接口 166·ArcGIS Engine 开发指南 [C pu { new Sy itmap(GetType().Assembly.GetManifestResourceStream("Names pa stem.Windows.Forms.Cursors.Cross; base.m_category = "My Custom Tools"; tion = "Pan"; base.m_message = "Pans the map."; 当创建继承一个基类的自定义命令和工具时,可能不仅需要重载几个 的一个成员时,会执行用户提供给该成员实现的代 执行从基类继承而来的缺省实现代码。例如,在BaseCommand lick方法没有任何的实现代码,同样的在缺省情况下OnClick不 能对于一个命 令 何成员,可以在Solution Explorer Window中右键单击基类 员,单击Add,然后单击Override将该成员作为重载存根。注意如果 单击底层的接口(ICommand或ITool)成员而不是基础类成员,重 的成员不会包含在重载关键字中,并且方法也会是代替为有阴影的。 Public Overrides Sub OnClick() End Sub public override void OnClick() nClick 重载基类的成员,在Code Window Wizard条上右侧的下拉列表中单击 缺 下面的表展示了具有明显基类实现的基类成员和该实现的描述。当基 类行为与自定义不一致时, 例如,在缺省状态下Enabled 设 只有当符合一套特定的准则时才激活, 始类中重载这个属性。 #] blic PanTool() base.m_bitmap = stem.Drawing.B ce.Pan.bmp")); base.m_cursor = Sy base.m_cap base.m_name = "PanTool"; base.m_toolTip = "Pan"; } 重载成员 成员。当重载类中 码,而不会 中OnC 会做任何事。这样对于一个工具来说是合适的,但是可 来说不适合。 要重载任 成 右键 载 [VB.NET] ' Your OnClick [C#] { // Your O } Overrides,然后从左侧下拉列表中选择想要重载的成员。这样会存根 成员为重载的。 省状态下基类会做什么 重载这些成员。 置为TRUE;如果要自定义命令 必须在初 .NET 应用程序接口 第四章·开发环境·167 操作ESRI.NET组件类目类 为了帮助在COM组件类目中注册.NET组件,ESRI提供了命名空间 .Utility.CATIDs,它包含表示每个ArcGIS组件类目的类。 代 替 Microsoft.Win32.Registry.ClassesRoot. Inherits BaseTool gisterFunction()> _ regKey As [String]) egister(regKey) _ Public Shared Sub Unreg(ByVal regKey As [String]) .Unregister(regKey) End Sub [ComRegisterFunction()] MxCommands.Register(regKey); ESRI.ArcGIS 每个类都知道自己的CATID并暴露给静态方法(Register and Unregister)来添加和移除组件。注册组件像使用合适的属性添加COM 注册方法和传递接受到的CLSID至适合的静态方法一样容易。 下面的例子展示了一个自定义Pan工具在ESRI Mx Commands组件类目中 注册自己。注意到在这个例子中使用MxCommands.Register和MxComm- ands.Unregister CreateSubKey和Microsoft.Win32.Registry.ClassesRoot.DeleteSubKey。 [VB.NET] Public NotInheritable Class PanTool MxCommands [C#] public sealed class PanTool : BaseTool { static void Reg(string regKey) { } .NET 应用程序接口 168·ArcGIS Engine 开发指南 [ComUnregisterFunction()] static void Unreg(string regKey) { MxCommands.Unregister(regKey); } } 扩展服务器 当使用.NET创建在GIS服务器上使用的COM对象时,需要遵循一些指导 方针以确保可以在一个服务器环境下使用该对象并且在那样的环境下 过添加 fa tt ut 用户的COM对象在服务器上很好地运行,该对象必须从 System.EnterpriseServices部件中。由于.NET Framework的当前 必须的。 更多的细节和用.NET编写的自定义Server COM对象的例子,参见ArcGIS Server Administrator and Developer Guide第四章 ‘Developing ArcGIS Server applications’。 释 ArcGIS Engine和ArcGIS Desktop应用程序 独立的应用程序试图关闭时,意外的崩溃可能会发生。例如, l的应用程序在退出时会崩溃。 溃的原因是COM对象挂起的时间比预期的要长。为避免崩溃,所有的 CO 卸载。为了帮助卸载COM引用,添加一个 专 .ArcGIS.Utility部件中。下面摘录的 代 [VB.NET] ivate Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing 会很好地执行。下面指导方针专门应用于创建在服务器内运行的COM对 象。 必须显式创建一个COM类实现的接口。与Visual Basic 6不一 样,.NET不会为当在一个服务器环境下创建对象时要使用的COM类 创建一个隐式的接口。 应该使用Automation列集器为用户的COM类列集。可通 AutomationProxyAttribute到带有真值的类中来指定这些。 COM类应该生成一个双重的类接口。可以通过添加 ClassInter ceA rib 到 带 有 ClassInterfaceType.AutoDual 值的类中来指定。 为确保 ServicedComponent 继承,该组件位于 COM interop的实现,这是 放COM引用 当一个 一个加载了地图文档的带有MapContro 崩 M引用在程序关闭之前必须 门的静态Shutdown函数到ESRI 码展示了正在使用的函数。 Pr .NET 应用程序接口 Support.AOUninitialize.Shutdown() En [C#] private void Form1_Closing(object sender, CancelEventArgs e) ESRI.ArcGIS.Utility.COMSupport.AOUninitialize.Shutdown(); 在独立的应用程序中,使用AOUninitialize.Shutdown函数可以处理大 多数关闭问题,但仍然会遇到要求显式释放COM对象的问题;该情况下 调用System.Runtime.InteropServices.Marshal.ReleaseComObject() 来减少引用数,允许应用程序正常地终止。StyleGallery就是一个这 样的对象,下面的例子表明了如何处理这个类的引用。 . D st leryClass Object(styleGallery) [C#] M M bject(sg); 某些 创 函数 件的 一个表上,地理数据库建立 修改模式。如果是基于文件的数 据源,例如shape文件,更新临时表在该文件上获得一个唯一的写保护 它的应用程序访问文件,进行读写操作。锁的效果 时表对象上所有 的引用被释放。 流超过最大数的效果是其它用户打不开自己的临时表来查询数据库。 (或任何其它的COM对象)的 圾集合被删除才被释放。在一个Web应用程序或一个服务多 引用会 ESRI.ArcGIS.Utility.COM d Sub { } [VB NET] im styleGallery As IStyleGallery yleGallery = New StyleGal MessageBox.Show(styleGallery.ClassCount) Marshal.ReleaseCom IStyleGallery sg = new StyleGalleryClass() as IStyleGallery; essageBox.Show(sg.ClassCount.ToString()); arshal.ReleaseComO 在ArcGIS Server中操作光标 可以在服务器环境下 建的对象可以锁定或使用仅在对象的析构 上释放的资源。例如,一个地理数据库临时表可能在一个基于文 要素类上获得一个共享模式锁,或在 在该表上或占有一个SDE流。 虽然共享模式锁在适当的位置,其它的应用程序可以继续查询或更新 表中的记录,但是不能删除要素类或 锁,这样会防止其 就是所有的数据不能被其它的应用程序使用,直到临 如果是SDE数据源,临时表拥有一个SDE流,并且如果应用程序有多个 用户,每个用户获得或拥有一个SED流,最后会消耗完最大允许流。SDE 由于上面的这些原因,确保引用任何应用程序打开的临时表被及时释 放,这是很重要的。在.NET中,对临时表 引用直到垃 个当前场景和请求的Web服务,依赖于垃圾集合来释放对对象的 导致临时表和其它的资源不能被及时地释放。 第四章·开发环境·169 .NET 应用程序接口 170·ArcGIS Engine 开发指南 称 帮助者对象。使用ManageLifetime方法添加COM对象到对 ,当WebObject被处理掉时,该对象集会显式释放。必须在一个 bject时, 何 尾被显式释放。 [VB.NET] nection = New bject jects = mapsrv Dim map As IMap = mapo.Map(mapsrv.DefaultMapName) Dim flayer As IFeatureLayer = map.Layer(0) webobj.ManageLifetime(fcursor) Do Until f Is Nothing eContext() private void doSomthing_Click(object sender, System.EventArgs e) 为确 WebObject 保当COM对象超出范围时被释放,WebControls部件包含了一个 为 象集上 Using块内使用WebObject。当限定在一个使用块中使用WebO 任 使用ManageLifetime方法添加到中的WebObject对象会在使用块 的末 下面的实例演示了这种编码模式: Private Sub doSomething_Click(ByVal sender As System.Object, ByVal e AsSystem.EventArgs) Handles doSomething.Click Dim webobj As WebObject = New WebObject Dim ctx As IServerContext = Nothing Try Dim serverConn As ServerCon ServerConnection("doug", True) Dim som As IServerObjectManager = serverConn.ServerObjectManager ctx = som.CreateServerContext("Yellowstone", "MapServer") Dim mapsrv As IMapServer = ctx.ServerO Dim mapo As IMapServerOb Dim fClass As IFeatureClass = flayer.FeatureClass Dim fcursor As IFeatureCursor = fClass.Search(Nothing, True) Dim f As IFeature = fcursor.NextFeature() ' Do something with the feature. f = fcursor.NextFeature() Loop Finally ctx.Releas webobj.Dispose() End Try End Sub [C#] { using (WebObject webobj = new WebObject()) { ServerConnection serverConn = new ServerConnection("doug",true); IServerObjectManager som = serverConn.ServerObjectManager; .NET 应用程序接口 第四章·开发环境·171 IServerContext ctx = IMapServer mapsrv = ctx.ServerObject as IMapServer; IMapServerObjects mapo = mapsrv as IMapServerObjects; IMap map = mapo.get_Map(mapsrv.DefaultMapName); IFeatureLayer flayer = map.get_Layer(0) as IFeatureLayer; IFeatureClass fclas som.CreateServerContext("Yellowstone","MapServer"); s = flayer.FeatureClass; earch(null, true); j.ManageLifetime(fcursor); // Do something with the feature. } ctx } } WebMap、 e方 法。例如 一个使用块中, 可以在使 部署.N 所有ArcGIS ArcGIS安 缓存中安装 ArcGIS Engine的license 的独立GIS应用程序,要求在所有的目标机器上安装ArcGIS Engine。 独立应用 部署独立应 Desktop客户端上涉及复制可执 行程序到客户机器上。复制可执行程序可能只是简单的使用xcopy或涉 及更多 定义安装或安装程序。注意到除ArcGIS主 要inter ork部件之外,依赖的所有文件必须也被 封装和部 IS 扩 处 理 前面讨论过的,实现 COMRegisterFunction和COMUnregisterFunctions通过提供自己的类 目注 IFeatureCursor fcursor = fclass.S webob IFeature f = null; while ((f = fcursor.NextFeature()) != null) { .ReleaseContext(); WebGeocode和WebPageLayout对象也有一个ManageLifetim ,如果正在使用一个WebMap方法,并且限定在 用块的末尾添加ManageLifetime来显式释放对象。 ET ArcGIS定制 Engine和Desktop定制需要在所有的客户机上安装ArcGIS。 装必须包括ESRI 主要的interop部件,安装程序在全局部件 它。例如,部署一个只需要一个 程序 用程序到ArcGIS Engine或 一点,如创建一个自 op部件和.NET Framew 署。 ArcG 组件 展ArcGIS应用程序的组件的部署比独立的应用程序的部署更难 ,因为在特定的组件类目内使用COM注册。像 册方便了部署,但是,只有当组件被注册时才会这样发生。 .NET 应用程序接口 172·ArcGIS Engine 开发指南 用COM注册组件有两个技巧。一个选项是运行装载.NET Framework SDK. 行的解决方 案,因为客户机不一定有这个应用程序,并且不容易自动化。第二个 也是推荐的方法是添加自动注册步骤到一个自定义安装或安装程序 中。创建一个配置和注册组件的自定义安装程序的关键是 System.Runtime.InteropServices.RegistrationService 这个类 有RegisterAssembly和UnregisterAssembly两个成员,这两个成员使 用CO 序使用的相同函 数。在一个用户自定义安装类内与安装程序一块使用这些函数是一个 完整 下面的基本步骤概括了创建一个可部署解决方案的创建。注意:这些 步骤假定用户已经开始了一个解决方案,该方案包含一个至少有一个 激活类的工程。 1. 中,添加一个新的Installer Class并相应 地命名。 置。 Sub Install(ByVal stateSaver As End Sub 的注册部件的应用程序(RegAsm.exe)。通常这不是一个可 s类。 M注册和取消注册被管理类。这些是RegAsm应用程 的解决方案。 在Visual Studio .NET 重 载 在 Installer基类中实现的Install 和 Uninstall函数并使用 RegistrationServices类的RegisterAssembly和UnregisterAssembly 方法注册组件。确保使用SetCodeBase标记;这表明部件的代码基键应 该在注册表中设 [VB.NET] Public Overrides System.Collections.IDictionary) MyBase.Install(stateSaver) Dim regsrv As New RegistrationServices regsrv.RegisterAssembly(MyBase.GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase) .NET 应用程序接口 第四章·开发环境·173 Public Overrides Sub Uninstall(ByVal savedState As System.Collections.IDictionary) MyBase.Uninstall(savedState) regsrv.UnregisterAssembly(MyBase.GetType().Assembly) End Sub End Class [C#] { base.Install (stateSa Dim regsrv As New RegistrationServices public override void Install(IDictionary stateSaver) ver); RegistrationServices regSrv = new RegistrationServices(); { base.Uninstall (savedState); RegistrationServices regSrv = new RegistrationServices(); regSrv.UnregisterAssembly(base.GetType().Assembly); } 2. 添加安装程序到用户的解决方案中。 a. 在Solution Explorer中,右键单击新工程,并单击Add > Project Output。选择要部署的工程并选择Primary output。 b. 从检测到的生成的依赖关系列表中,移出所有到ESRI主要 interop部件(例如,ESRI.ArcGIS.System)和stdole.dll 的引用。一般在列表中留下的项目是用户的TLB和从 的Primary输出,这代表了正在编 译的DLL或EXE。 c. 最后的步骤包含将在一个新安装类中部署的自定义安装步骤 与安装工程相联合。要做到这一点,在Solution Explorer regSrv.RegisterAssembly(base.GetType().Assembly, AssemblyRegistrationFlags.SetCodeBase); } public override void Uninstall(IDictionary savedState) .NET 应用程序接口 174·ArcGIS Engine 开发指南 中,右键单击安装工程并单击View Custom Actions。 d. 在结果视图中,右键单击Install文件夹并单击Add Custom Action。双击Application文件夹,然后双击Primary output from the 项。这个步骤将前边创 建的自定义安装函数与自定义安装动作相联系。 e. 为安装程序的卸载重复最后一步。 3. ArcGIS服务器的部署 使用内置的Visual Studio.NET工具来部署在一个开发服务器上开发的 Web应用程序以产生产品服务器。 1. 在Solution Explorer中,单击工程。 2. 单击Project菜单,然后单击Copy Project。 3. 在Copy Project对话框中,指定部署位置。 4. 单击OK。 除了复制工程,必须复制和注册任何包含自定义COM对象的相关DLLs到 用户的Web服务器和所有GIS服务器的服务器对象容器机器。 最后,重建整个解决方案生成安装程序可执行文件。在目标机器 运行可执行文件安装组件,并用COM注册这些组件。然后 COMRegesiterFunction程序会在适合的组件类目中注册组件。 JAVA 应用程序接口 Java 应用程序接口 设置 JAVA_HOME 变量并不是绝对必 需的;然而,有的 java IDEs 和 java 工具需要设置该变量。 添加 PATH 变量允许从系统的任何 目录中运行可执行文件(javac, java,javadoc 等等)。 ArcGIS 9.0 版本的 Java API 是一种专门为 Java 开发者设计的能与 ArcObjects 互操作的程序接口。Java 技术由 Sun MicroSystems 开发, 既是一种平台,又是一种面向对象的程序设计语言,它共有三个版本 并由两个部分组成: 三个版本为: ·Java 2 平台,标准版(J2SE) ·Java2 平台,企业版(J2EE) ·Java2 平台,精简版(J2ME) 两个组成部分为: ·Java 虚拟机(JVM)—Java 运行时和客户/服务器编译器。 ·Java 应用程序接口(API)—核心、集成和用户接口工具包套件。 Java 语言非常重要,因为它基于一个开放的标准。这种编程语言的所 有实现都必须遵守 JVM 提供的标准。这使得应用程序可以在任何安装 了 JVM 的硬件平台上运行。 平台配置 本节将描述使用 Java API 编程所需的所有必要配置,包括类路径 (classpath)和环境设置。 Java 开发工具包 为了用 Java API 进行 ArcObjects 开发,必须安装 Java2 平台标准软件 开发工具包(J2SDK)。所有 J2SDK 工具都位于安装目录中。可以显式 从该目录中调用这些工具,或者把该目录添加到 PATH 环境变量中。 将该目录添加到 PATH 环境变量中包括以下两个步骤: 1. 创建一个新的名为 JAVA_HOME 的环境变量。 JAVA_HOME=[path to JDK install directory] 例如: JAVA_HOME=c:\j2sdk 2. 编辑 PATH 变量,以包括 JAVA_HOME 的 bin 目录。 PATH=..;%JAVA_HOME%\bin 为了编译基于服务器的应用程序,如 servlets 和 EJBs,也需要安装 Java2 企业版开发工具包并将其包括在类路径中。Java 应用程序服务器一般 会提供这个工具包,或者从 Sun MicroSystems 提供的参考实现中获得。 ArcGIS Engine ArcGIS Engine 开发工具包使用标准的 Java 本地接口(JNI,Java Native Interface)访问 ArcObjects 核心组件。当编译和运行应用程序时,需要 在开发者的路径中包含一些本地动态库。必须保证包含正确的路径以 将互操作引入本地 ArcObject 中。 第四章·开发环境·175 JAVA 应用程序接口 本地*.dll 位于以下路径中: ..\ArcGIS\bin ESRI 推荐设置一个 ARCENGINEHOME 环境变量并将 bin 目录添加到 PATH 变量中。尽管这不是使用 ArcGIS Engine 开发工具包所必须的, 但所有开发范例都使用这个变量以保证类路径(classpath)设置是正确 的。 设置 ARCENGINEHOME 变量: ARCENGINEHOME=[path to ArcGIS install directory] 例如: ARCENGINEHOME=c:\ArcGIS 编辑 PATH 变量使系统可以使用 ArcGIS Engine Runtime 随带的本地资 源库。编辑 PATH 目录以包括 ARCENGINEHOM 的 jre\bin 目录。 PATH=..%ARCENGINEHOME%\java\jre\bin 类路径(Classpath) Java API 为 ArcObjects 和本地运行时库提供了 Java Archive(JAR)文 件(*.jar )。这些 JAR 文件位于磁盘上的以下目录 %ARCENGINEHOME%\java 所有用 ArcGIS 开发工具包构建的 Java 应用程序在各自的应用程序类 路径中必须引用下列的 JARs: •arcobjects.jar—在一个完整的文档中包含所有非 UI 类。 •jintegra.jar—包含处理与COM互操作的运行时库中的所有类。 另外,根据需要,应将单个的 arcgis_xxx.jar 文件加入到类路径中。例 如,支持在 ArcObjects 中包含 Java 可视化 beans 的应用程序需要将 arcgis_visualbeans.jar 文件添加到类路径中。 JRE ArcGIS Engine 和 Server 开发工具包包括一个 Java 运行时环境(JRE, Java Runtime Environment)版本。只要上面描述的必要设置在本地运 行,就可以运行任何 ArcGIS Java 应用程序。用户可以注意到 bin 目录 中的必要*.dlls 文件和库扩展目录中的必要*.jar 文件。启动这个运行时 环境需要做的全部就是确保 bin 被添加到了 PATH 环境变量中。 PATH=..\ArcGIS\java\jre\bin 176·ArcGIS Engine 开发指南 JAVA 应用程序接口 要了解 initializeEngine 方法如 何作为第一次调用来使用,请参考 ArcGIS Developer Help 中的 Java 开发示例。 Java 编程技术 本节讲述 Java 编程语言的一些基本概念。这里假定开发者掌握了一般 的编程概念,但对于 Java 却是一个新手。 Java 虚拟机的特性 Java 虚拟机(JVM)规程为执行代码提供了一个平台无关、抽象的计 算器。JVM 不知道 Java 语言的任何方面,但理解一种特定的二进制格 式,即包含字节码形式指令的类文件。Java 虚拟机规程提供了一个既 可编译又可以解释程序的环境。编译器创建一个 java 文件,产生一系 列字节码并把它们存贮在一个.class 文件中,而 Java 解释器执行存贮 在.class 文件中的字节码。 JVM 的每个实现都与特定平台相关,因为 JVM 实际上要与操作系统进 行交互。JVM 处理诸如内存分配、垃圾回收和安全监测等事务。 Java 本地接口(Java Native Interface) 即使 Java 程序设计要在多个平台上运行,也有可能标准的 Java 类库不 支持某个特定应用程序所需的平台相关特性,或者某个 Java 程序需要 实现更低层次的程序并具有调用它的 Java 程序。JNI 是一个由 Java 语 言提供的标准的跨平台编程接口。它允许编写能操作其它编程语言, 如 C 或 C++等,编写的应用程序和类库的 java 程序。这是一种用于在 ArcGIS 中沟通本地 ArcObject 和 Java API 的技术。 为了为 ArcObject 的本地使用而初始化 Java 环境,每个 ArcGIS Engine Java 应用程序必须调用 EngineInitializer 类上的静态 initializeEngine() 方法。这应该是要进行的第一个调用,甚至在 AoInitialize 之前。 public static void main(String[] args){ /* always initialize ArcGIS Engine for native usage */ EngineInitializer.initializeEngine(); ... } 用 Java 进行 ArcGIS 开发 第四章·开发环境·177 本节的目标读者是使用 Java SDK for ArcGIS Engine 的开发者。SDK 提 供了与 ArcObjects 的互操作,允许开发者象访问 Java 对象一样访问 ArcObjects。Java API 不受限于任何特定的 Java 虚拟机或平台,并使用 标准的 Java 本地接口访问 ArcObjects。Java API 通过 Java 类和接口提 示了 ArcObjects 的全部功能,允许 Java 开发者编写一次代码就可以到 处运行,并且也受益于 ArcObjects 组件重复。Java API 提供了产生于 ArcObjects 组件类型库(TLBs)中的“代理(Proxy)”类,允许与底 层所有组件进行互操作。这些代理类提供了与 ArcObjects 的属性、方 法和事件对等的 Java 对等物。 JAVA 应用程序接口 接口 本地 ArcObjects 使用基于接口的编程模式。接口概念对 ArcObjects 很 重要,并强调以下四点: 1. 接口不是类。 2. 接口不是对象。 3. 接口是强类型相关的。 4. 接口是不可改变的。 ArcObjects 接口是抽象的,即没有与接口相关的实现。对象使用类型继 承;与接口相关的代码来自类的实现。 该模型共享了 Java 接口模型的一些特性,并引入 Java 接口模型以强化 单一继承模型。在 Java 语言中,接口是一种方法的规程,对象声明了 该方法的实现。一个 Java 接口并不包含例程变量或实现代码。 Java API 为每个 ArcObjects 接口提供两个对象:一个相应接口和一个 接口代理类。接口名以 ArcObjects 样式命名,并以字母 I 为前缀。接 口代理类则在类名前加上 proxy。下面是这种映射的一个例子: interface IArea : IUnknown public interface IArea{} public class IAreaProxy implements IArea{} 代理类在 Java API 内部使用以提供各个接口的实现。由于代理类没有 实现代码,应用程序开发者从来不应使用这些类的缺省构造器。 ArcObjects 要求开发者通过接口访问对象。Java 语言并不使用这个模 式;因此,面向 ArcObjects 的 Java API 提供了两种访问对象的方法: 通过接口或通过类访问。 /* use the class implementing IPoint */ IPoint iPoint = new Point(); /* access object through class */ Point cPoint = new Point(); 但不能通过缺省的接口代理类访问对象: IPointProxy proxyPoint = new IPointProxy(); // incorrect usage 在后面的几节中将更深层次地讨论这个问题。 ArcObjects 接口是不可改变的,因此从来没有版本更新。接口一旦定义 和公布就从不改变。当接口需要增加新方法时,API 通过在接口名称后 增加版本号来定义一个新的接口,如下表所示: interface IGeometry : IUnknown public interface IGeometry{} interface IGeometry2 : IGeometry public interface IGeometry2 extends IGeometry{} interface IGeometry3 : IGeometry2 public interface IGeometry3 extends IGeometry2{} public interface IGeometry4 extends IGeometry3{} 178·ArcGIS Engine 开发指南 JAVA 应用程序接口 类 在 ArcObjects 模型中,类提供了所定义接口的实现。ArcObjects 提供三 个类型的类:抽象类、类、组件对象类。可以通过 ArcGIS 开发中帮助 中提供的对象模型图来区分这些类。在使用这三种“类”之前熟悉它 们是极为重要的。 在 ArcObjects 中,一个抽象类不能用来创建新的对象,而在 Java API 中没有抽象类。抽象类在 ArcObjects 中是通过类型继承的子类例程的 规程。抽象类列举了可以通过子类来执行的接口类型,而没有提供对 那些接口的执行。对于 ArcObjects 中的每个抽象类,都有子类提供实 现。 在 ArcObjects 中“类”不能公开创建;但是,“类”对象可以作为另一 个类的属性创建或由来自另一个类的对象实例化。在 Java API 中,没 有为 ArcObjects 类定义通常用来创建类的缺省构造器。 /* the constructor for FeatureClass() is unsupported*/ FeatureClass fc = new FeatureClass(); 下面的例子解释了在进入打开一个要素类的过程时的这种行为。 IWorkspaceFactory wf = new ShapefileWorkspaceFactory(); IFeatureWorkspace fw = new IFeatureWorkspaceProxy(wf.openFromFile("\path\to\data", 0)); /* create a Feature Class from FeatureWorkspace */ IFeatureClass fc = fw.openFeatureClass("featureclass name"); 在 ArcObjects 中,组件对象类是可以公开创建的类。这意味着可以仅 仅通过声明一个新的对象来创建一个自己的对象,如下所示: /* create an Envelope from the Envelope CoClass */ Envelope env = new Envelope(); 结构 结构定义一个新的由成员元素构成的数据类型。Java 没有作为复杂数 据类型的结构。Java 语言通过类来提供这种功能;可以用合适的例程 变量来声明一个类。对于 ArcObjects 中的每种结构,都有一个代表性 类,该类具有能匹配结构成员的公开声明的例程变量,下面给出了结 构成员的轮廓。 struct WKSPointZ public class _WKSPointZ ... { double X public double x; double Y public double y; double Z public double z; } 可以象使用 Java 中的任何其它类一样使用这些类: _WKSPointZ pt = new _WKSPointZ(); pt.x = 2.23; pt.y = -23.14; pt.z = 4.85; System.out.println(pt.x + " " + pt.y + " " + pt.z); 第四章·开发环境·179 JAVA 应用程序接口 枚举 Java 没有枚举类型。为了在 Java 中模仿枚举,必须创建一个拥有常量 的类或接口。对于本地 ArcObjects 中的每个枚举,都有一个 Java 接口, 该接口具有公开声明的代表枚举值的静态整数。 enum esri3DAxis public interface esri3DAxis { esriXAxis = 0 public static final int esriXAxis = 0; esriYAxis = 1 public static final int esriYAxis = 1; esriZAxis = 2 public static final int esriZAxis = 2; } 现在可以使用下面的符号表示引用 esriXAxis 常量: esri3DAxis.esriXAxis; Variants Variant 数据类型包含一系列子类型。通过 Variants,所有类型可以被包 含在一个单个类型的 Variant 中。Java 编程语言中的每个事物都是一个 对象。如果需要的话,甚至原始数据类型也可以封装在对象中。Java 中的每个类都对 java.lang.Object 进行了扩展;因此,Java API 中的任 何对象类型都可以传递给 ArcObjects 中以 Variant 为参数的方法。 以“variant”对象为参数调用方法 对于以 variants 为参数的方法,任何对象类型都能传递给它们,因为所 有对象都源自 java.lang.Object 。由于这被视为一种“扩展转换 (windening cast)”,不需要显式地转换为“对象”。如果在需要 variants 时想将原始数据类型作为参数传递给方法,可以使用相应的原始封装 (wrapper)类。 使用返回 variants 的方法 当使用由方法返回的 variant 对象时,要显式将那些对象“向下转换 (downcast)”为相应的封装(wrapper)类。例如,如果希望得到一个 String,则向下转换为 java.lang.String;如果希望得到一个 Short,则向 下转换为 Short 的封装对象类,即 java.lang.Short,如下列代码所示。 ICursor spCursor = spTable.ITable_search(spQueryFilter, false); /*Iterate over the rows*/ IRow spRow = spCursor.nextRow(); while (spRow != null) { Short ID = (Short) (spRow.getValue(1)); String name = (String) (spRow.getValue(2)); Short baseID = (Short) (spRow.getValue(3)); System.out.println("ID="+ ID +"\t name="+ name +"\tbaseID="+ baseID); /* Move to the next row.*/ spRow = spCursor.nextRow(); 180·ArcGIS Engine 开发指南 } JAVA 应用程序接口 IrowBuffer是Irow的一个超接口,并定义了getValue(int)方法,如下所示: public Object getValue(int index) throws IOException, AutomationException The value of the field with the specified index. Parameters: index - The index (in) Returns: return value. A Variant 返回值为一个对象,javadoc 指定其为“variant”。因此,取决于其在被 查询 geodatabase 中的类型,该值可转换为 String 或 Short。 转换(Casting) ArcObjects 遵循基于接口的编程样式。许多方法使用接口类型作为参数 并以接口为返回值。当某个方法的返回值为接口类型时,该方法返回 一个实现该接口的对象。当某个方法以接口类型为参数时,该方法可 以纳入任何实现该接口的对象。这种编程样式的优点是同一方法可以 操作许多不同的对象类型,只要它们都实现了该接口。 例如,IFeature.getShape()方法返回一个实现 Igeometry 的对象。返回的 对象可能为下列实现了 Igeometry 类中的一种:BezierCurve, CircularArc, EllipticArc, Envelope,GeometryBag, Line, MultiPatch, Multipoint, Path, Point, Polygon, Polyline, Ray,Ring, Sphere, TriangleFan, Triangles, TriangleStrip。 转换(casting)可用于类型之间的转换。Java API 开发者可以使用以下 三种转换方法: 1. 接口到具体类的转换(casting) 2. 接口之间的转换(casting) 3. 接口向下转换(downcasting) 因为 JVM 中不存在对象引用,ArcObjects 中的方法返回的对象其行为 不同于隐式定义的对象行为,理解这一点很重要。 如果有一个方法 doSomeProcessingOnPolygon(Polygon p),只能操作 Polygon 对象,而又想将 IFeature.getShape()获取的结果对象传递给该方 法,则需要一种将来自 Igeometry 的对象类型转换到 Polygon 的方法。 在 Java 中,可以使用一个类的转换操作来完成: /* incorrect usage: will give ClassCastException */ Polygon poly = (Polygon)geom; 然而,对 ArcObjects Java API 使用相同的代码将会抛出一个异常 ClassCastException。抛出该异常的原因是“geom”对象引用实际上是 对本地 ArcObjects 组件的一个引用。Java 和本地 ArcObjects 组件间的 互操作的结果使得这个对象引用到 Polygon 对象的转换逻辑驻留在 Polygon 对象构造器中,而不是在 JVM 中。 第四章·开发环境·181 JAVA 应用程序接口 Java API 中每个类都有一个以单个对象为参数的构造器。该构造器可以 通过引用 ArcObjects 组件来创建相应的对象。因此,当使用 Java API 时为获得某个类转换的对等物,要使用转换目标类的“对象构造器”。 Polygon poly = new Polygon(geom); 下面的代码展示了用于将 geom 对象“转换”到一个 Polygon 的对象构 造器: IFeature feature = featureClass.getFeature(i); IGeometry geom = feature.getShape(); if (geom.getGeometryType() == esriGeometryType.esriGeometryPolygon){ /*Note: "Polygon p = (Polygon) geom;" will give ClassCastException*/ Polygon poly = new Polygon(geom); doSomeProcessingOnPolygon(poly); } 这样构造的 Polygon 对象将实现 Polygon 类实现了的所有接口。因此, 可以调用属于 Poly 对象上任何实现了的接口的方法。 可以用单独对象构造器编写所有代码,但有的时候可能把要实现一个 特定接口的某个对象转换为该对象实现的另一个接口而不是某个类类 型更好。 前面的例子中,假设想使用 doSomeProcessingOnPolygon(Polygon p)方 法在Polygon对象或其它对象上实现IArea的对象,如Envelope和Ring。 可以编写一般的 doSomeProcessingOnPolygon(Polygon p)方法,以操作 实现了 IArea 接口的所有对象的。由于 Polygon, Envelope 和 Ring 对象 都实现了 IArea 接口,可以将这些对象传入该一般方法,从而防止为每 个对象类型编写其他方法,诸如 doSomeProcessingOnEnvelope(Envelope env) 和 doSomeProcessingOnRing(Ring ring)等。为完成这个任务,需 要将 IGeometry 类型转换到 IArea 类型。在 Java 中,这通常用接口之 间的转换来完成。 /* incorrect usage: will give ClassCastException */ IArea area = (IArea) geom ; 然而,与上面类转换中的所说的原因一样,这种转换会以异常 ClassCastException 而告失败。为了能转换为 ArcObjects 接口,需要使 用前一节中讨论的接口代理类。在 Java API 中,通过使用转换目标接 口的 InterfaceProxy 可以实现与接口之间转换等价的功能。 IArea area = new IAreaProxy(geom); 下列代码显示了 InterfaceProxy 类在将 geom 对象到 IArea 的接口间转 换的使用: IFeature feature = featureClass.getFeature(i); IGeometry geom = feature.getShape(); /*Note: "IArea area = (IArea) geom;" will give ClassCastException*/ IArea area = new IAreaProxy(geom); 182·ArcGIS Engine 开发指南 JAVA 应用程序接口 doSomeProcessingOnArea(area); 象上述代码中那样使用 IareaProxy 类允许通过其 IArea 接口访问该对 象,这样该对象就能传递给以类型 IArea 为参量的方法。因此,在这个 特定的例子中,一个方法可以处理三个不同的对象类型。然而,只有 属于 IArea 接口的方法才对 IArea 对象有效。为调用对象的其它方法, 需要用对象构造器将类转换为合适的对象类型,或使用 InterfaceProxy 类获得对其它接口的一个引用。 Instanceof Java 中的Instanceof算子使开发者可以决定其第一个运算对象是其第二 运算对象的例程。 operand1 instanceof operand2 当类型背后的逻辑存在于 Java 中时,可以在 ArcObjects—Java 中使用 Instanceof 算子。当类型存在于 ArcObjects 中,而决定一个对象是否为 某个特定类型的例程的逻辑驻留在该对象类型的构造器而不是 JVM 中 时,不能使用 Instanceof 算子。 Point point = new Point(); point.putCoords(0, 0); point.putCoords(10, 10); point.putCoords(20, 20); if(point instanceof IGeometry){ System.out.println(" point is a IGeometry"); geom = point; } if(point instanceof IClone){ System.out.println(" point is a IClone"); } 由于 Point 的类型信息存在于 Java 中,上述代码可以起作用。在构造 一个 Point 对象时,也为每个实现接口构造了一个代理类。这允许在这 些类型的任何一个上使用 Instanceof 算子。开发者可能已经访问了实现 IGeometry 或 Iclone 接口的 Point 上的任何方法。 这也可以向后兼容: if(geom instanceof Polyline){ System.out.println(" geom is a Polyline"); } else if(geom instanceof Point){ System.out.println(" geom is a Point"); pnt = (IPoint)geom; // allowable cast as the type is held in JVM } 因为直接将 geom 对象转换到了 Point 对象,geom 对象就是类型 Point, 而且可以用 Instanceof 算子来检测这个信息。然而,由于该类型信息在 进行上面的检测前就已经知道了,因此并不是非常有用。有用的应该 是将上述逻辑应用到返回超接口的对象的方法上。 第四章·开发环境·183 JAVA 应用程序接口 设想返回一个 IWorkspace 的 IWorkspaceFactory.openFromFile()方法。由 于返回的对象是一个实现了 IWorkspace 的 Java 对象,无法检测返回的 对象是否为已知的实现 IWorkspace 类中的任何一个。在这种情况下, 为了检测类型信息,应调用一个被期望的返回对象上的一个方法。如 果该方法没有抛出异常,则就是那种类型。发生这种情况是因为这个 对象的逻辑在运行时声明并存在底层 ArcObjects 组件中。 RasterWorkspaceFactory rasterWkspFactory = new RasterWorkspaceFactory(); IWorkspace wksp = rasterWkspFactory.openFromFile( aPath, 0 ); if(wksp instanceof RasterWorkspace){ /*code does not execute as logic is in ArcObjects*/ System.out.println(" wksp is a RasterWorkspace"); rasWksp = (RasterWorkspace)wksp; } else{ try{ rasWksp = (RasterWorkspace)wksp; rasWksp.openRasterDataset( aRaster ); }catch(Exception e){ /*code executes if wksp is not a RasterWorkspace*/ System.out.println(" wksp is not a RasterWorkspace"); } } 获取参数的方法 ArcObjects 提供了许多返回多个值的方法。Java API 要求传送单个元素 数组的参数到这种方法中。最基本的,要传入想返回的对象的单个元 素数组,而 ArcObjects 是那些返回值数组的第一个元素。当从方法调 用返回时,数组的第一个元素包含了在方法调用过程中已设定的值。 本节中将使用的这样的一个方法就是IARMap接口的toMapPoint方法。 查看一下这种方法的 javadoc: public void toMapPoint(int x, int y, double[] xCoord, double[] yCoord) throws IOException, AutomationException Converts a point in device coordinates (typically pixels) to coordinates in map units. Converts the x and y screen coordinates supplied in pixels to x and y map coordinates. The returned map coordinates will be in MapUnits. Parameters: x - The x (in) 184·ArcGIS Engine 开发指南 JAVA 应用程序接口 y - The y (in) xCoord - The xCoord (in/out: use single element array) yCoord - The yCoord (in/out: use single element array) 注意参数 xCoord 和 yCoord 标记为“in/out: use single element array“。 为使用这个方法,前两个参数是以像素为单位的 x 和 y 坐标。接下来 的两个参数真正用于获得从方法调用中返回的值。可以传入一维单元 素的双精度数组: double [] dXcoord = {0.0}; double [] dYcoord = {0.0}; 当方法调用完成时,可以查询 dXcoord[0] 和 dYcoord[0]的值。这些值 会被该方法修改并将实际指代以地图单位表示的 x 和 y 坐标。一个该 方法调用的实际例子就是当鼠标经过控件时用当前地图坐标更新状态 条。 public void updateStatusBar(IARControlEventsOnMouseMoveEvent params)throws IOException { /*create single dimensional array of doubles *the values of the first element of the arrays will be filled in *by the arMap.toMapPoint(...) method */ double [] dXcoord = {0.0}; double [] dYcoord = {0.0}; int screenX = params.esri_getX(); int screenY = params.esri_getY(); IARMap arMap = arControl.getARPageLayout().getFocusARMap(); arMap.toMapPoint(screenX, screenY, dXcoord, dYcoord); /*set the statusLabel*/ statusLabel.setText(“Map x,y: “ + dXcoord[0]+”, “+dYcoord[0]); } Java API 不允许开发者采用超类类型填充数组,即使已经转换到了一个 超类类型。考虑一下下面的 Java 例子: Integer[] integers = { new Integer(0), new Integer(1), new Integer(2)}; Object[] integersAsObjects = (Object[])integers; integersAsObjects[0] = new Object(); 上面这样是不允许的,并会抛出一个异常 ArrayStoreException。考虑一 下下面的 ArcObjects 例子: Polyline[] polyline = {new Polyline()}; tin.interpolateShape( breakline, polyline, null ); Polyline firstPolyLine = polyline[0]; 与前一个例子一样,上面这样做是不允许的,并会抛出相同的异常 ArrayStoreException。请阅读 Isurface 的 interpolateShape()方法并分析这 里会发生什么结果。 public void interpolateShape(IGeometry pShape, IGeometry[] ppOutShape, 第四章·开发环境·185 JAVA 应用程序接口 Object pStepSize) throws IOException, AutomationException Parameters: pShape - A reference to a com.esri.arcgis.geometry.IGeometry (in) ppOutShape - A reference to a com.esri.arcgis.geometry.IGeometry (out: use single element array) pStepSize - A Variant (in, optional, pass null if not required) Throws: IOException - If there are communications problems. AutomationException - If the remote server throws an exception. IGeometry 是 IPolyline 的一个超接口,且 Polyline 类实现了这两个接口。 在第一个尝试中试着传送一个单元素 Polyline 数组到需要一个 in/out IGeometry 参数的方法中。这会抛出一个异常 ArrayStoreException,因 为 ArcObjects 试图用 IGeometry 对象填充一个 IPolyline 数组,试图将 一个超类类型放入一个子类数组中。下面概括了使用这个方法的正确 方式: /*Set up the array and call the method*/ IGeometry[] geoArray = {new Polyline()}; tin.interpolateShape( breakline, geoArray, null ); /* "Cast" the first array element as a Polyline - this is * the equivalent of calling QueryInterface on IGeometry */ IPolyline firstPolyLine = new IPolylineProxy(geoArray[0]); 非OLE自适应类型(Non-OLE Automation Compliant Types) 不是 OLE 自适应类型的 ArcObjects 类型不能在 Java API 中起作用。 ArcObjects 包含少数几个这种方法,不幸的是,这种方法不能在 Java API 中使用。但是,在大多数情况下,已经添加了追加接口,这些追加 接口有一些方法作为自动化兼容而重写。这些新接口前面都添加了字 母“GEN”,表明它们是普通针对所有支持的 API 的。 IPoint[] points = new Point[2]; points[0] = new Point(); points[1] = new Point(); points[0].putCoords(0, 0); points[1].putCoords(10, 10); IEnvelope env = new Envelope(); IEnvelopeGEN envGEN = new Envelope(); /*not automation compatible - throws exception*/ env.defineFromPoints(2, points[0]); /*automation compatible*/ envGEN.defineFromPoints(points); 186·ArcGIS Engine 开发指南 JAVA 应用程序接口 使用可视化 beans Java API 提供了一套可重用组件,是设计用于提供图形功能的预建软件 代码块。可视化 beans 开发者只需编写代码以将它们作为“伙伴”应用 到 ArcGIS Engine 应用程序中。Beans 的使用在 Java 和 ArcGIS 提供的 ActiveX 控件之间搭建了一座桥梁。这些可视化组件是重量级 AWT 组 件,并与 JavaBeans™组件架构相一致,允许其在 JavaBeans 兼容的 IDEs 中用作拖放组件来设计 Java 用户界面。 重量级组件与轻量级组件的混合 Swing 体系的主要目标之一就是要基于现有的 AWT 体系。这允许开发 者在同一个应用程序中混合使用 Swing 和 AWT 两种组件。当使用具有 Swing 组件的 ArcObject JavaBeans 时,在混合使用重量级组件与轻量 级组件时应该十分仔细。这方面的指导方针可以参考网址 http://java.sun.com/products/jfc/tsc/articles/mixing/上的“混合重量级和轻 量级组件”一文。 如果使用 Swing 组件,在选项可用的地方,使用类似于下面的代码使 轻量级组件失效: jComboBox.setLightWeightPopupEnabled(false); jPopupMenu.setLightWeightPopupEnabled(false); 侦听事件 所有的 ArcObject JavaBeans 都可以触发事件。例如,ARControl bean 可以触发下列事件: void onAction(IARControlEventsOnActionEvent theEvent) void onAfterScreenDraw(IARControlEventsOnAfterScreen DrawEvent theEvent) void onBeforeScreenDraw(IARControlEventsOnBeforeScreen DrawEvent theEvent) void onCurrentViewChanged(IARControlEventsOnCurrentView ChangedEventtheEvent) void onDocumentLoaded(IARControlEventsOnDocumentLoa dedEvent theEvent) void onDocumentUnloaded(IARControlEventsOnDocument UnloadedEventtheEvent) void onDoubleClick(IARControlEventsOnDoubleClickEvent theEvent) void onFocusARMapChanged(IARControlEventsOnFocusARMap ChangedEvent theEvent) void onKeyDown(IARControlEventsOnKeyDownEvent theEvent) void onKeyUp(IARControlEventsOnKeyUpEvent theEvent) void onMouseDown(IARControlEventsOnMouseDownEvent theEvent) void onMouseMove(IARControlEventsOnMouseMoveEvent theEvent) void onMouseUp(IARControlEventsOnMouseUpEvent theEvent) 第四章·开发环境·187 JAVA 应用程序接口 为了给事件添加和删除侦听器,beans 提供 addXYZEventListener 和 removeXYZEventListener 方法。为了便于创建侦听器对象,提供了 Adapter 类。 public void addIARControlEventsListener(IARControlEvents theListener)throws IOException public void removeIARControlEventsListener(IARControlEvents theListener) throws IOException 下列代码显示了使用具有 IARControlEventsAdapter 的匿名内部类为 onDocumentLoaded 和 onDocumentUnloaded 事件给 arControl 对象添加 事件侦听器。 arControl = new ARControl(); ... /*wire up the events for arControl*/ arControl.addIARControlEventsListener(new IARControlEventsAdapter(){ public void onDocumentLoaded(IARControlEventsOnDocument LoadedEvent evt) throws IOException{ /*set the statusbar text to point to the currently loaded document*/ statusLabel.setText(" Document filename: "+ arControl.getDocumentFilename()); /*Determine whether permission to toggle TOC visibility*/ if (arControl.hasDocumentPermission( esriARDocumentPermissions.esriARDocumentPermissionsViewTO C)) { tocVisibilityCheckBox.setEnabled(true); tocVisibilityCheckBox.setSelected(arControl.isTOCVisible()); } else { JOptionPane.showMessageDialog((Component)arg0.getSource (),"You do not have permission toggle TOC visibility"); } } public void onDocumentUnloaded(IARControlEventsOnDocument UnloadedEventevt) throws IOException{ /*set the statusbar text to empty string*/ statusLabel.setText(""); } }); 值得注意的是,由 beans 触发的事件是自定义事件,事件侦听器作为 Java API 的一部分提供给它们。从 java.awt.event 包中加载侦听器(如 MouseListener)是没有用的,因为 Java beans 不触发那些事件。相反, 可以使用由相应的事件侦听器提供的类似事件,诸如 onMouseDown, onMouseUp 和 onMouseMove 等事件,而在 ARControl 示例中则是 IARControlEvents。 188·ArcGIS Engine 开发指南 C++应用程序接口 C++应用程序接口 C++是面向对象的编程语言,是 20 世纪 80 年代中期从 C 发展而来。C++ 语言具有许多为其提供无与伦比的表现力的特性,如面向对象的继承、 操作符重载、虚函数、模板和一个称为“标准模板库(STL, Standard Template Library)”的有用和常用必要函数库。C++语言已经被国际 标准化组织(ISO)和几个有影响的国家标准组织标准化。 开发人员可能会考虑使用 ArcGIS C++ API,与其它 API 相比,使用 C++ API 的原因如下: ● 执行速度——C++代码执行速度明显比对应的 Java、VB 和 C#代码 快。 ● 跨平台兼容——VB 和 C#当前主要用于 Windows 平台。C++和 Java 本身就可以跨更多平台。 ● 已有经验——如果组织机构中的开发人员已经具备大量使用该语 言的经验,那么 C++就是一个合理的选择。 本节打算用于两个目的: 1. 让用户熟悉一般的 C++编程风格和调试。 2. 提供 ArcGIS C++ API 入门介绍,详细阐述具体的使用需求,并就 使用 ArcObjects 编程平台提供一些建议。 C++开发技术 命名习惯 类型名 所有类型名称(class、struct、enum 和 typedef)都以一个大写字母 开头,而名称其它部分则为大小写混合: class Foo : public CObject { . . .}; struct Bar { . . .}; enum ShapeType { . . . }; typedef int* FooInt; 函数指针(回调)的 Typrdef 以 Proc 作为名称的结尾。 typedef void (*FooProgressProc)(int step); 枚举值都以一个标识该工程的小写字符串开始;在 ArcObjects 中为 esri,每个字符串都是单独一行: typedef enum esriQuuxness { esriQLow, esriQMedium, esriQHigh } esriQuuxness; 第四章·开发环境·189 C++应用程序接口 这里是命名习惯的一些建议。这有 助于识别变量的用法和类型以减少 编程错误。这是有删节的匈牙利命 名规则: [_] 190·ArcGIS Engine 开发指南 前缀 变量范围 m 例程类成员 c 静态类成员(包括常量) g 全局静态变量 无 局部变量或结构或公共类成员 前 缀 数据类型 b 布尔型 by 字节或无符号字符 cx/ cy 短整型,用作大小 d 双精度 dw DWORD,双精度字或无符号长整型 f 浮点型 fn 函数 h 句柄 i 整型 ip 聪明指针 l 长整型 p 指针 s 字符串 sz ASCIIZ 无结束符字符串 w WORD 无符号整型 x,y 短整型,用作坐标 描述变量怎样使用或它包含 的内容。部分应总 是小写,而应使用大小写混 合。 变量名 描述 m_hWnd HWND 句柄 ipEnvelope COM 接口聪明指针 m_pUnkOuter 对象指针 c_isLoaded 静态类成员 g_pWindowList 全局对象指针 函数名 用以下习惯为函数命名: 对于简单取值和赋值函数,使用 Get 和 Set: int GetSize(); void SetSize(int size); 如果客户端提供对结果的存储,使用 Query: void QuerySize(int& size); 对于状态函数,使用 Set 和 Is 或 Can: bool IsFileDirty(); void SetFileDirty(bool dirty); bool CanConnect(); 在操作语义明显来自变量类型的地方,函数名中不包含类型名。 不用: AddDatabase(Database& db); 考虑用: Add(Database& db); 不用: ConvertFoo2Bar(Foo* foo, Bar* bar); 考虑用: Convert(Foo* foo, Bar* bar) 如果客户端将某些数据的所有权交给对象,使用 Give。如 果对象将某些数据的所有权交给客户端,使用 Take: void GiveGraphic(Graphic* graphic); Graphic* TakeGraphic(int itemNum); 当某个特定的操作使用不同的参量类型时,使用函数重载: void Append(const CString& text); void Append(int number); 参数名 在函数声明中使用描述性参数名。参量名应该很清楚地指示参数的用 途:bool Send(int messageID, const char* address, const char* message); 智能类型 智能类型是行为像普通类型的对象。它们是封装了一种数据类型的 C++ 类的实现,用操作符和函数将其包含。这些操作符和函数使得操作底 层类型更容易且更少出错。当这些智能类型封装一个接口指针时,它 们就称为聪明指针。智能指针操作 IUnknown 接口以保证正确地管理资 源的分配和存储单元分配。通过不同的函数、构造和析构方法和重载 操作来实现这些功能。 C++应用程序接口 智能类型使得操作 COM 接口和数据类型的任务更简单,因为许多 API 的调用可以移植到一个类的实现中;可是使用它们必须谨慎,必须清 楚了解它们与封装的数据类型进行互操作的方法。 C++ API 支持的智能类型包括: ● _com_ptr_t —这个类封装一个创建智能指针的 COM 接口指针。 ● CComBSTR-封装 BSTR 数据类型的类。 ● CComVariant-封装 VARIANT 数据类型的类。 可以使用宏_COM_SMARTPTR_TYPEDEF 来为一个接口定义一个智能指针, 像下面一样: _COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo)); 编译器可以如下代码所示对此进行扩展: typedef _com_ptr_t< _com_IIID > IFooPtr; 一旦声明后,声明一个变量为接口类型并在接口末端追加 Ptr 是很简 单的一件事。下面是在许多 C++例子中比较常见的该类智能指针的通常 用法。 // Get a CLSID GUID constant extern “C” const GUID __declspec(selectany) CLSID_Foo = \ {0x2f3b470c,0xb01f,0x11d3,{0x83,0x8e,0x00,0x00,0x00,0x00,0x00,0x 00}}; // Declare Smart Pointers for IFoo, IBar and IGak interfaces _COM_SMARTPTR_TYPEDEF(IFoo, __uuidof(IFoo)); _COM_SMARTPTR_TYPEDEF(IBar, __uuidof(IBar)); _COM_SMARTPTR_TYPEDEF(IGak, __uuidof(IGak)); HRESULT SomeClass::Do() { // Create Instance of Foo class and QueryInterface (QI) for IFoo interface IFooPtr ipFoo; HRESULT hr = ipFoo.CreateInstance(CLSID_Foo); if (FAILED(hr)) return hr; // Call method on IFoo to get IBar IBarPtr ipBar; hr = ipFoo->get_Bar(&ipBar); if (FAILED(hr)) return hr; // QI IBar interface for IGak interface IGakPtr ipGak(ipBar); // Call method on IGak hr = ipGak->DoSomething(); if (FAILED(hr)) return hr; 第四章·开发环境·191 C++应用程序接口 如果以前使用过智能指针,可能看 到了智能类型在使用比较操作符号 (“==”)时的不同之处。COM 规定 的状态对象标识通过比较 IUnknown*指针值来进行。当使用 “==”时,智能指针将执行必要 的 QI 和比较。 omVariant 将创建一个短整型 (VT_I2 类型)变量而不是期望的 布尔型(VT_BOOL 类型)变量。可 以使用 CcomVariant(true)来创 建一个布尔变量。 CComVariant myVar(ipSmartPointer)将得到一 个布尔型(VT_BOOL)变量而不是期 望带有对象引用(VT_UNKNOWN)的 变量。最好是传递明确类型给构造 函数,如输入哪些自身不是带有重 载强制转换操作符的聪明类型的类 型。 // Perform QI it IUnknown IUnknownPtr ipUnk = ipSmartPointer; // Ensure we use IUnknown* constructor of CComVariant CComVariant myVar2(ipUnk.GetInterfacePtr()); // Explicitly call Release() ipGak = 0; ipBar = 0; // Let destructor call IFoo’s Release return S_OK; } 使用 CComBSTR 时,使用文字映射 L“”来声明 OLECHAR 字符串常量。 在命令行显示一个 CComBSTR,使用 wccerr。需要包含输入输出流来使 用 wcerr。 CComBSTR bsName(L”Matt”); std::wcerr << L”The name is “ << (BSTR) bsName << std::endl; CComVariant 直接源自于 VARIANT 数据类型,意味着它的实现没有重 载,这样反过来简化它的使用。它包含一个构造函数和函数的丰富集 合,使得操作 VARIANT 简单直接;甚至包含一些读写流的方法。重用 变量以前确认调用 Clear 方法。 ipFoo->put_Name(CComBSTR(L”NewName”)); if FAILED(hr)) return hr; // Create a VT_I4 variant (signed long) CComVariant vValue(12); // Change its data type to a string hr = vValue.ChangeType(VT_BSTR); if (FAILED(hr)) return hr; 在 IDL 中的一些方法调用被标记为可选,并占用一个变量参数。然而 在 VC++中,这些参数仍必须提供。为了表示一个参数值不被支持,会 传递一个变量来指示错误代码或类型 DISP_E_PARAMNOTFOUND: CComBSTR documentFilename(L”World.mxd”); CComVariant noPassword; noPassword.vt = VT_ERROR; noPassword.scode = DISP_E_PARAMNOTFOUND; HRESULT hr = ipMapControl->LoadMxFile(documentFilename, noPassword); 不过,如果有一个希望传递到变量的值,使用智能类型,CComVariant。 int val = 1; CComVariant smartVal(val); ipRowBuffer->put_Value(2, smartVal); 192·ArcGIS Engine 开发指南 C++应用程序接口 使用 CComBSTR 和 CComVariant 时,Detach()函数从智能类型中释放 底层数据类型,并在将结果作为方法的[out]参数传递时可以使用这个 函数。下面就是和 CComBSTR 一起使用 Detach 函数的例子: HRESULT CFoo::get_Name(BSTR* name) { if (name==0) return E_POINTER; CComBSTR bsName(L”FooBar”); *name = bsName.Detach(); } 使用智能指针的常用练习就是使用 Detach()来从一个方法调用返回 一个对象。当返回一个接口指针时,COM 标准是增加在方法实现中的 [out]参数的引用数。当不再需要指针时,调用 Release 是调用者的责 任。因此,必须注意避免在成员变量上直接调用 Detach()函数,下面 就是一个典型的模式: HRESULT CFoo::get_Bar(IBar **pVal) { if (pVal==0) return E_POINTER; // Constructing a local smart pointer using another smart pointer // results in an AddRef (if pointer is not 0). IBarPtr ipBar(m_ipBar); // Detach will clear the local smart pointer and the // interface is written into the output parameter. *pVal = ipBar.Detach(); // This can be combined into one line // *pVal = IBarPtr(m_ipBar).Detach(); return S_OK; } 上面的模式与下面的代码有相同的结果,注意在 AddRef 可被调用前, 需要 0 指针的条件测试,在一个 0 指针上调用 AddRef(或其它的方法) 将导致一个访问违例异常并且通常使应用程序崩溃: HRESULT CFoo::get_Bar(IBar **pVal) { if (pVal==0) return E_POINTER; // copy the interface pointer (no AddRef) into the output parameter *pVal = m_ipBar; // Make sure interface pointer is non 0 before calling AddRef if (*pVal) *pVal->AddRef(); return S_OK; } 第四章·开发环境·193 C++应用程序接口 当使用一个智能指针时从方法上的[out]参数获取一个对象时,使用智 能指针的“&”解除引用操作符。这将引起在智能指针上的上一个接口 指针被释放。智能指针就被载入新的[out]值。这个方法的实现将已经 增加了引用对象数。当智能指针越界时,这个值将被释放。 { IFooPtr ipFoo1, ipFoo2; ipFoo1.CreateInstance(CLSID_Foo); ipFoo2.CreateInstance(CLSID_Foo); // Initalise ipBar Smart pointer from Foo1 IBarPtr ipBar; ipFoo1->get_Bar(&ipBar); // The “&” de-reference will call Release on ipBar // ipBar is then repopulate with a new instance of IBar ipFoo2->get_Bar(&ipBar); } // ipBar goes out of scope and the smart pointer destructor calls Release 调试器 Visual C++提供了特色丰富的调试器。这些提示会帮助我们从调试任 务中获得最多的信息。 失败后的后退 当一个函数调用失败时,并且想知道出错原因(通过跳入函数),不必 重新启动应用程序。使用 Set Next Statement 命令重新设置程序光标 位置回到出错的语句上(右键单击该语句打开调试弹出式菜单)。然后 跳入该函数。 编辑和继续 Visual Studio 6 允许在调试期间修改源代码。这些修改可以被重新编 译并合并到可执行代码中,并不需要停止调试器。对于可修改的类型, 有一些限制;在这种限制情况下,必须重新启动调试任务。缺省条件 下激活这个特征;在工程菜单的 Settings 命令中可以设置这些。单击 C/C++选项卡,然后从 Category 下拉列表中选择 General。在 Debug info 下拉列表中,单击 Program Database for Edit and Continue。 Unicode 字符串显示 要设置调试器选项来显示 Unicode 字符串,单击 Tools 菜单,单击 Options,单击 Debug,然后选中 Display Unicode Strings 复选框。 194·ArcGIS Engine 开发指南 C++应用程序接口 变量值显示 将光标停留在源代码中一个变量名上查看它的当前值。如果该变量是 一个结构,单击该变量,弹出 QuickWatch 对话框(单击 Eyeglasses 图标或按下 Shift+F9)或将之拖到 Watch 窗口中。 不停靠窗口 如果 Output 窗口(对这种情形,或者是任何停靠的窗口)看起来太小, 试着通过右键单击该窗口并触发 Docking View 项目来不停靠它以使它 成为一个真窗口。 条件断点 当满足某种条件时需要在断点上停留时,采用有条件的断点—例如, 当一个 for 循环语句达到某一特定计数器值时。为达到这一目的,设 置正常断点,然后调出 Breakpoints 窗口(Ctrl+B or Alt+F9)。选择 设置的断点,并单击 Condition 按钮显示一个对话框,在其中设定断 点条件。 预先加载 DLLs 在执行程序之前可以预先加载要调试的 DLLs。这样就使得你可以从一 开始设置断点而不是一直等到程序执行中 DLL 被加载之后。操作如下, 单击 Project,单击 Settings,单击 Debug,单击 Category,然后单 击 Additional DLLs。然后单击列表区域来添加想要预先加载的任意 DLLs。 改变显示格式 可以在 QuickWatch 对话框中改变变量的显示格式,或在 Watch 窗口中 使用下表中的格式符号。 符号 格式 值 显示 d,i 有符号十进制整型 0xF000F065 -268373915 u 无符号十进制整型 0x0065 101 o 无符号八进制整型 0xF065 0170145 x,X 十六进制整型 61541 0x0000F065 l,h d,l,u,o,x,X 的长或短前缀 00406042,hx 0x0C22 f 有符号浮点型 3./2. 1.500000 e 有符号科学记数法 3./2. 1.500000e+00 g 短的 e 或 f 3./2. 1.5 c 单字符 0x0065 ‘e’ s 字符串 0x0012FDE8 “Hello” su Unicode 字符串 “Hello” hr 字符串 0 S_OK 使用格式符号,输入变量名后面带有逗号和适合的符号。例如,如果 变量有一个 0x0065 值,并且想要以字符形式看这个值,在 Watch 窗口 的列表中的 Name 栏输入“var,c”。 第四章·开发环境·195 C++应用程序接口 可以只对结构、数组、指针和对象 使用格式化符号来作为未扩展的变 量,如果扩展变量,指定的格式影 响所有成员。不能对单个成员使用 格式化符号。 196·ArcGIS Engine 开发指南 当按下时 Enter 时,字符格式值出现:var,c = ‘e’。同样地,假定 hr 是一个拥有 HRESULTs 的变量,在 Name 栏中输入“hr,hr”,以人们 可读的格式查看 HRESULT。 可以用下表中的格式化符号格式化存储位置的内容。 符号 格式 值 ma 64 位 ASCII .....1 ...."..1.JO&.1.2 .."..1...0y....1 m 16 字节十六进制,后面 接着 16 个 ASCII 字符 0 94 80 26 00 00 .4...0....".0W&.. 0x0012ffac 3B3 34 CB 00 84 57 FF 22 8A 30 16 字节十六进制,后面 接着 16 个 ASCII 字符 0x0012ffac B3 34 CB 00 84 30 94 80 FF 22 8A 30 57 26 00 00 .4...0...".0W&.. mw 8 个字 0x0012ffac 34B3 00CB 3084 8094 22FF 308A 2657 0000 0x0012ffac 00CB34B3 80943084 30 0x0012fc60 ff ffff 8478 77f4 ff 0000 0000 0000 0000 为字符串,在数组 字符 0x0012ffac W&.0.:W..1 .4...0...".0W&.. mb md 4 个双精度字 8A22FF 00002657 mu 2 字节字符(Unicode) 使用存储定位格式化符号,可以输入任何值,或计算一个位置值的表 达式。要显示一个字符数组 名前加一个“&”, 查看某个地址的值,或一存储点的值,使用 BY、WO 或 DW 操作符: BY字节指向的内容 操作符后面跟上变量、存储器或常量。如果 BY、WO 或 DW 操作符后面 跟上一个变量,那么环境监视包含该变量地址的字节、字或双字。也 &yourname。格式化字符也可遵循一个表达式: rep+1,x alps[0],mb xloc,g count,d WO返回字指向的内容 DW返回双字指向的内容 可以使用上下文操作符{ }显示任何位置的内容。 C++应用程序接口 使用 su 格式区分符在 Watch 窗口或 QuickWatch 对话框中显示 Unicode 字符串。使用 mu 格式区分符在 Watch 窗口或 QuickWatch 对话框中用 Unicode 字符显示数据字节。 键盘快捷键 大量的键盘快捷键会使对 Visual Studio Editor 的操作更加快捷。下 面列出一些比较有用的键盘快捷键。 文本编辑器使用许多 Windows 应用程序(例如 Word)使用的标准快捷 键。下面列出了一些详细的源代码编辑快捷键。 快捷键 动作 Alt+F8 基于包围线正确缩进所选代码 Ctrl+] 查找匹配的大括号(brace) Ctrl+J 显示成员列表 Ctrl+Spacebar 一旦输入的几个字母能让编辑器认识就完成单词。在完成 函数和变量名时很有用。 Tab 将所选代码向右缩进一个制表符 Shift+Tab 将所选代码向左缩进一个制表符 下面是在调试器中使用的通用键盘快捷键表。 快捷键 动作 F9 在当前行添加断点或从当前行删除断点 Ctrl+Shift+F9 删除所有断点 Ctrl+F9 使断点无效 Ctrl+Alt+A 显示 auto 窗口并将光标移入该窗口 Ctrl+Alt+C 显示调用栈窗口并将光标移入该窗口 Ctrl+Alt+L 显示局部窗口并将光标移入该窗口 Ctrl+Alt+A 显示 auto 窗口并将光标移入该窗口 Shift+F5 结束调试会话 F11 每次执行代码的一条语句,步入函数 F10 每次执行代码的一条语句,跳过函数 Ctrl+Shift+F5 重新启动一个调试会话 Ctrl+F10 从当前语句继续执行到所选语句 F5 运行应用程序 Ctrl+F5 脱离调试器运行应用程序 Ctrl+Shift+F10 设置下一条语句 Ctrl+Break 停止执行 第四章·开发环境·197 C++应用程序接口 加载下面的快捷键会大大提高使用 Visual Studio 开发环境的生产率。 快捷键 动作 ESC 关闭一个菜单或对话框,取消进程中的一个操作,或将焦 点放在当前文档窗口 CTRL+SHIFT+N 创建一个新的文件 CTRL+N 创建一个新的工程 CTRL+F6 或 CTRL+TAB 在 MDI 子窗口中循环,每次一个窗口 CTRL+ALT+A 显示 auto 窗口并将光标移入该窗口 CTRL+ALT+C 显示调用栈窗口并将光标移入该窗口 CTRL+ALT+T 显示文档轮廓窗口并将光标移入该窗口 CTRL+H 显示查找窗口 放入查找框中 CTRL+F 显示查找窗口。如果当前没有查找标准,将光标下的单词 CTRL+ALT+I 显示立即窗口并将光标移入该窗口。如果在文本编辑器窗 则此快捷键不能用 口中, +ALT+L 显示局部窗口并将光标移入该窗 +ALT+O 显示输出窗口并将光标移入该窗 +ALT+J 显示工程浏览器 CTRL+ALT+P 显示属性窗口并将光标移入该窗口 打开一个文件 RL+O 打开一个工程 CTRL+P 打印所有或部分文档 CTRL 口 CTRL 口 CTRL 窗口并将光标移入该窗口 CTRL+SHIFT+O CT CTRL+SHIFT+S 保存所有文件、工程或文档 CTRL+S 选择所有 CTRL+A 保存当前文档或所选项 使用在线帮助主题 右键单击工具条的空白区域显示所有可用工具的列表。Infoviewer 工 具条包含上下箭头,通过它们可以按照帮助主题在内容列表中出现的 顺序来浏览所有的主题。使用左右箭头可按照访问的顺序浏览帮助主 题。 使用 C+ S 将要出现的章节打算给使用 C++的开发人员介绍使用 ArcGIS Engine 开发。ArcGIS EngineDeveloper Kit 让开发人员访问 ArcObjects,并 被设计为满足 C++开发工程的需求,这些工程需要 ArcObjects 而不需 要 ArcGIS Desktop 应用程序。 理解 COM b ArcGIS 的 C++ API 让 访问用于构建 ArcGIS 家庭产品的 ArcObjects 框架用 C++编写。不必为 使用 何 为了使用 ArcObjects,对 COM 对 象如何工作有个基本理解是必要的。如果你还不太熟悉 ArcObjects 框 ,推荐阅读这章最初的两节“微软组件对象模型”和“使用 ArcObjects 开发”。 +的 ArcGI 开发 和 ArcO jects C++开发人员 组件,这些组件自身就采用 COM C++ API 编写任 的 COM 代码。 架 198·ArcGIS Engine 开发指南 C++应用程序接口 获取帮助 除了 Start Menu -> Programs 快捷 方式外,ArcGIS Developer Help 的 C++版本也能从 ArcGIS Engine 安装目录中获取到。通过浏览至 \DeveloperKit\Help\C OM 打开该目录并双击 ArcGISDevHelpVC.chm。 也可以在一个开发环境下的混合和 匹配代码,并在另一个开发环境下 编译。例如,你可以在 Visual Studio 中编写代码,但通过利用命 令行工具的脚本来编译和构建。 ArcSDK.h 和 ArcGIS Engine olb 文 件缺省情况下分别被安装到 \include\CPPAPI 和 \com 文件夹 关于在这种 用 对象的帮助(接口、类等), 参阅与 ArcGIS Engine 一起安装的 的是缺 , Menu ->Programs -> ArcGIS -> Developer > 该帮助。 支持的 ArcGIS 持 udio.NET 2003 的编译器。 开发环境 作为 Ar P 环境。不过,如果 希望使 环 Visual Studio.NET 2003( 在文本编辑器 中编写自己的代码,并从命令行编译它。如果选择这个选项,推荐使 用 Windows 示 用程序。对于开发环境的选择完全取 决于个人偏好和工具的可用性。下面几节集中在这三种选择上。 在 Visual i 进行 ArcGIS 开发 程名称和选择工程存放位置。 Porject Menu>Setting 对话框的 Catego 组合框中选择“Preprocessor”。在 , 推荐使用/GX 和/NOLOGO 编译器标识。/GX 能进行同步异常处理; 语和通知的编译器信息。通过打开工程 Project->Setting ->C/C++ tab ->Customize category , 选 中 最后,到 Project > Add a Project > New 中给工程添加一些文件; 包含用户的代码。选择想添加的文件类型并给它一个名称。 向工程中添加应用程序需要的所有文件。现在可以准备编写代码了。 不要忘了包含 ArcSDK.h 头文件。 API 中 到的 ArcGIS Developer Help for C++部分。如果接受 省安装选项 可以从 Start Help - C++ Help 中访问 编译器 C++ API 支 Visual Studio 6.0 和 Visual St cGIS C++ A I 开发人员,可以选择任意开发 用集成开发 境(IDE),Visual Studio6.0 和 7.1)都是推荐的选择。如果你不想用 IDE,可以 命令提 的 nmake 应 Stud o 6.0 中使用 C++ 建立自己的应用程序 在 Visual Srudio 6.0 中开始创建 ArcGIS Engine 应用程序,启动 Microsoft Visual C++并使用 Win32 Console Application 向导创建 一个空的工程。选择 File>New,选择 Win32 Console Application, 输入工 接下来,设置必须的工程选项。在 C/C++选项卡上,从 ry Additional Include Directories 文本框中键入 ArcSDK.h 路径。另外 输入 ArcGIS Engine olb 文件的位置。不要忘了用分号隔开两个路径。 然后,进入 Preprocessor Definition 文本框并输入“ESRI_WINDOWS” 来定义 ESRI_WINDOWS 符号。与目录相似,符号也需要用逗号隔开。 /NOLOGO 防止显示编译器启动标 Property Pages 来激活/GX,移动至 C/C++ ->C++ language,并选中 ‘ Enable exception handling’复选框。通过从菜单条 ‘Suppress Starup Banner and Information Message’选项来设置 /NOLOGO。 这些最终 第四章·开发环境·199 C++应用程序接口 200·ArcGIS Engine 开发指南 SDK.h 和 ArcGIS Engine olb 文 件缺省情况下分别被安装到 Execute YourApplication 在.NET中使用C++ API编程的最简单方法是使用C/C++ Console sole . 添加一些附加的包含目录。 Arc directory>\include\CPPAPI 和 \com 文件夹。 编译工程 要在 Visual Studio 6.0 中编译 ArcGIS Engine 应用程序,按 或选 择 Build->Build YourApplicationName.exe。 运行应用程序 前,需要设置变量 Project Menu ->Setting ->Debug 选项卡,将任意变量添加到 Program argument 文本 一旦添加了变 Name.exe,或按 Ctrl +F5 来运行应用程序。要在调试模式下运行应用 程序,选择 Build ->Strat Debug->Go,或按 F5。 在 Visual Studio 6.0.NET 中使用 C++进行 ArcGIS 开发 建立应用程序 Application向导。这个向导与Win32 控Console Project and Con Application(.NET)向导不同。要访问C/C++ Console Application向 导,你必须从http://www.msdnaa.net/Resources/display aspx?ResID=1911 安装Academic Tools for VS. NET。一旦你安装了这 个向导,选择New > Project ,单击Visual C++ Project文件夹,并选 择C/C++ Console Application。 现在你准备进行下一步的工程选择。首先, 通过选择 Project Menu ->Properties,单击 C/C++文件夹,单击 General。在 Additional Include Directories 文本框中,输入 ArcSDK.h 文件的路径,另外输入 ArcGIS Engine olb 文件的位置。记 住,用分号隔开路径。也可以单击省略号来添加新的目录。 下一步,单击 Preprocessor,在 Preprocessor Definitions 文本框中 输入“ESRI_WINDOWS”来定义 ESRI_WINDOWS 符号。你也可以单击省略 号来定义符号。 现在准备编写自己的代码了,不要忘记在开始包含 ArcSDK.h 头文件。 编译应用程序 在 Visual Studio .NET 2003 中选择 Build->Build Solution 来编译 ArcGIS Engine 应用程序。 运行程序 在 Visual Studio .NET 2003 中运行一个 ArcGIS Engine 命令行应用 程序之前,需要设置变量。变量通过定制工程的设置加载到程序中; C++应用程序接口 vcvars32.bat 文件缺省位置是 Program Files\Microsoft Visual Studio\VC98\Bin(Visual Studio 6.0). vcvars32.bat 文件的缺省位置是 Program Files\Microsoft Visual Studio\VC98\Bin(Visual Studio 6.0). Menu ->Properties ->Debugging 项,将任意变量添加到 Ctrl+F5 来运行应 在调试模式下来运行应用程序,选择 Debug->Start 命令行构建工具缺省是不可用的,所 编译 文件(称作 vcvars32.bat)就可以使它们可用。vcvar32.bat 文件在 处 一步 的文件夹并开始。Visual Studio File 并选择 Edit。 rs.bat文件中所有的文本到上面创建并打开的批处 理文件中,关闭 vcvars.bat。 m32\cmd.exe 将打开一个命令提示。 8. 当希望依赖于命令行开发,双击批处理文件。一个命令行提 文件创建一个快捷方式,并将它加到开始菜单或工具条上。 到 Project ‘Command Argument’。确认在配置组合框中选中正在使用的配置。 最后,选择 Debug->Start Without Debugging 或按 用程序。如果你想 或按 F5。 使用 NMAKE 和窗口命令提示进行 ArcGIS 开发 从命令提示中,可以选择支持的编译器;首先要做的是选择其中一个 并准备使用它。Visual Studio 以需要使用一个提供的批处理文件(vcvars32.bat)来为命令行 和运行配置 Visual Studio 编译器。 通过命令行访问 Visual Studio 6.0 编译器 Visual Studio 命令行构建工具缺省是不可用的。不过提供一个批处理 每次打开一个新的命令行提示时必须运行。可以选择创建自己的批 理文件来运行 vcvars32.bat,并打开为开发准备的命令提示。每 过程描述如下。 ● 从命令行运行 vcvars32.bat 1. 打开一个命令提示窗口,进入包含 vcvars32.bat 的文件夹。 2. 输入 vcvars32.bat 来运行该批处理文件。 3. 要进行开发,进入包含代码 命令行构建工具将在用户的命令提示中可用。 4. 要运行程序。运行带有任意必须参数的可执行文件。 ● 为用户创建一个批处理文件来运行 vcvars32.bat 1. 指向想存储批处理文件的目录。 2. 右键单击该目录,选择 New->Text Document. 3. 修改文件名为以.bat 结尾(例如 cmdpromptdevel.bat),单 击 Yea 来确定名称修改。 4. 右键单击 5. 找到 vcvars.bat,右键单击该文件选择 Edit。 6. 复制vcva 7. 将下面一行加到你的批处理文件中。 %SystemRoot%\syste 这行 示将随着已经安装的需要的环境一起打开。你可以为批处理 第四章·开发环境·201 C++应用程序接口 如果想的话,可以利用提供给 ArcGIS Engine 的 Makefile.Windows 例子。这个例子 文件的细节参看下节。 使 \include\CPPAPI and \Com 文 作为包含目录。 Makefile.Windows 模板可以在 ArcGIS Developer Help under Development Environments>C++>Makefile 下找 这里用来描述 Makefile 代码的注 释文本已经从该文件的实际注释中 修改,以反映执行的步骤。 命 令 行构建 工 具 缺省是 不 可 用的。 不 过 Visual 打开命令提示 处理文件 vcvars32.bat,使得 构 建 工 具 可 用 。 vcvars32.bat 文 件 的 缺 省 地 址 是 : Program 打开自己最喜欢的文本编辑器开始写你的代码。使用一个 Makefile 来 设置下面内容,包括目录和编译器选项。 1. 使用/I 编译器选项来添加 Program Files\ArcGIS\include \CPPAPI and Program Files\ArcGIS\Com 作为附加的包含目录。 2. Windows支持的来自ArcSDK.h中的头文件。 3. 使用/GX编译标识激活同步异常处理。 4. 使用/UOLOGO编译标识防止显示编译启动标题和通知的编译器消 息。 为了 名为 Makefile.Windows 的 Makefile 区域,为了 该文 序的,这个应用程序在一个单独的文件中编写, my_application..cpp,它占用一个单独的文件。 1. 在整个 makefile 中,更新程序名称(当前为‘basic_sample’) PRO j … my_ 如果没有安装到缺省路径,找到并 用自己的安装地址,并把它加到 件夹 到。 9. 要进行开发。进入包含你代码的文件夹并开始。Visual Studio 命令行构建工具将在用户的命令提示中可用。 10. 要运行程序。运行带有任意必须参数的可执行文件。 通过命令行访问 Visual Studio.NET 2003 编译器 Visual Studio Studio.NET 2003 包含的一个命令提示使该工具可用。要 并访问这些工具,进入 Start Menu - >All programs->Visual Studio.NET 2003->Visual Studio .NET Tools-> Visual Studio.NET 2003 Command Prompt。 如果已被打开,提示将自动运行一个批 Files\Microsoft Visual Studio .NET 2003\Vc7\bin 建立应用程序 使用/D编译选项定义ESRI_WINDOWS符号,用来指导编译器读取 定制 MakeFile 例子 方便起见,ArcGIS Engine 中包括一个 例子供用户使用。下面的步骤突出了文件的特定 件在开发过程中使用必须定制这些区域。修改是基于一个应用程 来反映用户的应用程序名称。 # Set up the program name GRAM = my_application.exe … —# Program name updates source and object file lists CPPSOURCES = my_application.cpp CPPOBJECTS = my_application.ob # Program name updates –- dependencies list application.obj: my_application.cpp my_application.h 202·ArcGIS Engine 开发指南 C++应用程序接口 管编译器选项已经在模板中设置 好了,这里包含这行以展示内置 CPPFLAGS 宏的使用。 行在第一步中也出现过,以展示 程序名称的更新。 2. 成第 # Setting up the include directories /I “C:\Program Files\ArcGIS\include\CPPAPI” \ 3. 为应用程序提供依赖关系列表。 ArcSDK.h 一旦 Makefile.Windows 准备编译应用程序,就可以通过输入‘nmake /f Makefile.Windows’从命令行进行编译。 在运行一个 # Setting up the program argument INPUT = C:\Data\inputfile 可以输入‘nmake 尽 这 上面步骤 2 到步骤中概述的编译器选项已经设置好了,但需要完 一步以便准备应用程序中使用的模板。 … INCLUDEDIRS = \ /I “C:\Program Files\ArcGIS\Com” … # Setting up the compiler options CPPFLAGS = /DESRI_WINDOWS $(INCLUDEDIRS) /nologo /GX … # Program name updates –- dependencies list my_application.obj: my_application.cpp my_application.h 准备好 Makefile 后,就可以开始写代码了。不要忘记以包含 开始编写代码。 编译应用程序 运行应用程序 ArcGIS Engine 命令行应用程序之前,必须设置命令行参 数。更新 Makefile 以包括每个输入参数变量和一个运行目标。 这些修改的一个实例如下所示: … # Setting up a run target run: $(PROGRAM) $(INPUT) 一旦 Makefile.Windows 准备在应用程序中使用,就 /f Makefile.Windows run’从命令行运行应用程序。 第四章·开发环境·203 C++应用程序接口 204·ArcGIS Engine 开发指南 为简单起见,给出的代码块并不总 是检查 HRESULT,但开发人员应该 总要这么做。 注意 IAoInitialize 是限定范围 的,因而在 AoUninitialize 被调用 前会超出范围。 下面是用 推荐 C++ API ● COM 编程中要使用 AoInitialize 函数的地方 extern 化 ArcGIS Engine 和 COM。初始化必须在使用除 s 之 前 完 成 , 其 中 。 AoUninitialize(void); 这个函数取消 ArcGIS Engine 和 COM 的初始化。 的地方使用此函数, 就像在 main()中使用 return 一样。 VOID AoExit (int number); 函数在应用程序中应如何使用: * argv[]) { LL); icensing ipInit->Initialize(esriLicenseProductCodeEngine, &status); // ArcObjects Code here ipInit->Shutdown(); } // Uninitialize ArcGIS Engine and COM ::AoUninitialize(); // Exit the application AoExit(0); } ArcObjects C++实践 ArcGIS C++ API 进行编程的一些建议。 的函数用法 为一些函数提供了其自己的实现: AoInitialize——在 使用此函数。 “C” HRESULT AoInitialize(LPVOID pvReserved); 这个函数初始 IAoInitialize 接 口 外 的 任 何 ArcObject IAoInitialize 接口处理应用程序的许可问题。 ● AoUninitialize——在 COM 编程中使用 AoUninitialize 函数的地 方使用此函数 extern “C” void ● AoExit——在非 ArcObjects 代码中使用 Exit extern “C” 在应用程序退出之前,必须调用 AoExit。这使得要求正确清理各 种 ArcGIS Engime 和 COM 元素的操作系统变得更轻便。 下面的例子说明了上面讨论的 3 个 int main (int argc, char // Initialize ArcGIS Engine and COM ::AoInitialize(NU // ArcGIS Engine l { IAoInitialize ipInit(CLSID_AoInitialize); esriLicenseStatus status; C++应用程序接口 第四章·开发环境·205 AoCreateObject——在COM编程中使用CoCreateInstance函数的 地方使用此函数。 LSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, ID *ppv); 智能指针的对象实例,如下面的代码所示: paceFactory C IID_IWorkspaceFactory, R AoAllocBSTR(const OLECHAR *sz); ● 它们 // dis her” BST esriFeatureType featType; pFea switch (featType) cas bs tureType = ::AoAllocBSTR(L”simple”); break defau bsFeatureType = ::AoAllocBSTR(L”other”); } std::wce < L”Feature Type : “ << (BSTR) bsFeatureType << std::endl; ::AoFreeBS 从 VARIA 一个要素字 被返回,要求作一些处理以获得实 际值。下面的例子循环一个要素的所有字段并打印出每个字段的要素 值。这里显示的代码只处理了某些字段类型(例如,2 字节整型、4 字 节整型和 的需要选择处理其他类 型。 // ipFeature is tr, and we assume it has already // been declared and instantiated above IFieldsPtr ipFields; extern “C” HRESULT AoCreateObject(REFC LPVO 在使用智能指针的地方不需要这个函数。但是,可以使用这函数 来创建一个没有 // Create a Workspace Factory without using smart pointers IWorks *pWorkspaceFactory; hr = ::AoCreateInstance(CLSID_ShapefileWorkspaceFactory, 0, CLSCTX_INPRO _SERVER, (void **)&pWorkspaceFactory); ● AoAllocBSTR 取代 SysAllocString extern “C” BST AoFreeBSTR 取代 SysFreeString extern “C” void AoFreeBSTR(BSTR bstr); 在使用智能类型 CComBSTR 时,上面两个函数都不需要。但是,可以用 创建和释放 BSTR,如下面的例子所示: play the feature type as “simple” or “ot R bsFeatureType; tureClass->get_FeatureType(&featType); { e esriFTSimple : Fea ; lt: rr < TR(bsFeatureType); NT 类型获取一个要素字段值 段值作为 VARIANT 类型 BSTR 字符串);也可以根据应用程序 of type IFeatureP C++应用程序接口 hr = ipFeature->get_Fields(&ipFields); fieldCount; long hr = IFieldPtr ipField; CComVariant fieldValue; for (long i=0; iget_Field(i, &ipField); h // s { case VT_I2: std::cerr << fiel cas l; case VT_BSTR: << nd ; IFooPtr ipFoo(CLSID_Foo); 可以通过下面的方式来完成。注意‘dot’成员选择操作符的使 作符: ipFields->get_FieldCount(&fieldCount); r = ipFeature->get_Value(i, &fieldValue); Get field’s value based on its type witch (fieldValue.vt) dValue.iVal << std::endl; break; e VT_I4: std::cerr << fieldValue.lVal << std::endl; break; case VT_R4: std::cerr << fieldValue.fltVal << std::endl; break; case VT_R8: std::cerr << fieldValue.dblVal << std::end break; std::wcerr << fieldValue.bstrVal std::e l; break; default: std::wcerr << “Field type not supported.\n” break; } } 在声明智能指针后协同创建一个具有智能指针引用的对象 在声明一个智能指针的同时,通常协同创建了一个类: 但是,在某些情况下,可能需要首先声明智能指针,然后再协同创建 类。这 用,与之相对的是通常用于智能指针类型的箭头成员选择操 206·ArcGIS Engine 开发指南 C++应用程序接口 注意对 AddRef 的调用。这要求保证 合适的管理资源。 d be here 接口: // ce to an instance of // // nd ITinSurface interfaces // on the ITinSurface interface ((ITinSurfacePtr) ipTin)->GetVolume(…); 函数签名中的裸指针(Raw pointers) 不在函数签名中拥有智能指针,可以考虑使用一个裸指针以节省在函 数之上调用一个智能指针构造器/销毁器的开销。仍可以将智能指针对 象传递给函数,因为每个智能指针都有一个能返回底层裸指针的重载 指针操作符。下面的例子对这进行了说明: HRESULT DoRasterOp(IRaster* pRaster); // Function dec: raw pointer IRasterPtr ipRaster; HRESULT hr = DoRasterOp(ipRaster); // Pass in smart pointer 从函 这个技巧建立在前一个技巧之上。在本案例中,裸指针用在函数声明 中,并为返回的对象使用一个间接的双精度。这允许改变所传递指针 的指向。然后,用希望返回的值初始化一个智能指针对象,并将它分 配给已传入的指针。 HR GetTinWorkspace(char* path, ITinWorkspace** ppTin { T hr = S_OK; orkspaceFactoryPtr ipWorkspaceFactory(CLSID_TinWorkspaceFactory); &ipWork); ork ITinWorkspacePtr ipTinWorkspace(ipWork); if (*ppTinWorkspace) return hr; } IFooPtr ipFoo; // more code woul ipFoo.CreateInstance(CLSID_Foo); 内嵌接口查询(QI) 可以使用内置的智能类型来 QI 已有其一个接口的组件对象类支持的其 它 ipTin is of type ITinPtr and is an interfa the Tin coclass The Tin coclass supports both the ITin a GetVolume is a method 数中返回 ArcObjects ESULT Workspace) HR IW ESUL IWorkspacePtr ipWork; hr = ipWorkspaceFactory->OpenFromFile(CComBSTR(path), 0, if (FAILED(hr) || ipWork == 0) return E_FAIL; // Initialize ipTinWorkspace with ipW *ppTinWorkspace = ipTinWorkspace; // AddRef() if the assignment worked (*ppTinWorkspace)->AddRef(); 第四章·开发环境·207 C++应用程序接口 要了解有关 GUID 的更多信息,请参 阅本章中的“微软组件对象模型” 一节。 IUID 对象是一 可以是一个 GUID,如下面例子所示,或者 是一个 ProgID。 rPtr ipEnumLayer; Layer’s GUID 9F99- _TRUE, &ipEnumLayer); ual Basic)功能 现了 Idataset 接口。如果将 IDataset 作为函数参数来传递,可以像 tr ipFeatureDataset(ipDataset); hods Dataset2 methods ES Ar m 库(用 ArcSDK.h 来包括)包含许多可以简 化 脱 Ar 尽管这里不会讨论所有 这 大简化 C++中 COM 对象 的使用。标准模板库(STL)存在的类型与这些接口中的一些很相似; 但 更简单,并已经准备好与 COM 对 象一起使用。要了解下面任何接口方面更详细的信息,请参阅 ArcGIS +开发帮助。 IUID ArcObjects 中有一些方法以 IUID 对象作为参数。一个 个全局唯一标识符对象。它 IUIDPtr ipUID(CLSID_UID); IEnumLaye // use IGeoFeature hr = ipUID->put_Value(CComVariant(L”{E156D7E5-22AF-11D3- 00C04F6BC78E}”)); hr = ipMap->get_Layers(ipUID, VARIANT 复制 instanceof(Java)或 TypeOf(Vis 一个接口指针可以指向多个组件对象类中的一个是很常见的。可以通 过尝试用 if/else 逻辑查询(QI)其它接口来获得有关组件对象类的 更多信息。例如,RasterDataset 和 FeatureDataset 组件对象类都实 下面那样来决定 IDataset 引用哪个组件对象: void Foo (IDataset *pDataset) { IFeatureDatasetP if (ipFeatureDataset != 0) { // use IFeatureDataset met } else { IRasterDataset2Ptr ipRasterDataset(ipDataset); if (ipRasterDataset!= 0) { // use IRaster } } } RI System 接口 cGIS Engine 中的 Syste 编程的接口。它包含为其它 ArcGIS 类库提供服务的组件。 离 STL 的接口中的对象组 cObjects 包含一些用于管理对象组的接口。 可以极些接口,但给出的例子说明了这些接口 这些接口通常比它们的 STL 对应物 C+ 208·ArcGIS Engine 开发指南 C++应用程序接口 IArray r 中要素的几何图形添加到一个数组中。 tureLayerPtr and ipQueryFilter is of been declared and // instantiated. rray); { IS 接口提供访问控制唯一对象简单集合的成员。例如,下面的代码 块循环地图图层并试图将没有编辑过的所有要素类工作空间添加到一 个集合中。 // ipM iously // declared and instantiated ISetPtr ipSet(CLSID_Set); ILayerP IFeature IFeature IDataset IWor tr ipWorkspace; IW spaceEditPtr ipWorkspaceEdit; long layerCount; ount(&layerCount); // layer might not be a feature layer it = ipWorkspace; IArray 接口提供访问控制简单对象数组的成员。有许多相关联的接口, 诸如 IDoubleArray 和 IVariantArray 等。下面的代码块展示了怎样将 一个 FeatureCurso // ipFeatureLayer is of type IFea // type IQueryFilterPtr. Both have already IArrayPtr ipCacheArray (CLSID_A IFeatureCursorPtr ipFeatureCursor; ipFeatureLayer->Search(ipQueryFilter, VARIANT_FALSE, &ipFeatureCursor); IFeaturePtr ipFeature; while (ipFeatureCursor->NextFeature(&ipFeature) == S_OK) IGeometryPtr ipGeom; ipFeature->get_ShapeCopy(&ipGeom); ipCacheArray->Add((IUnknownPtr) ipGeom); } ISet et ap is of type IMapPtr and was prev tr ipLayer; LayerPtr ipFeatLayer; ClassPtr ipFeatClass; ipDataset; Ptr kspaceP ork hr = ipMap->get_LayerC for (long i=0; iget_Layer(i, &ipLayer); ipFeatLayer = ipLayer; if (ipFeatLayer == 0 || FAILED(hr)) continue; hr = ipFeatLayer->get_FeatureClass(&ipFeatClass); // layer could reference bogus data if (ipFeatClass == 0 || FAILED(hr)) continue; ipDataset = ipFeatClass; hr = ipDataset->get_Workspace (&ipWorkspace); ipWorkspaceEd 第四章·开发环境·209 C++应用程序接口 可能注意到这个例子和情景没有遵 循这里概述的 Good Error Handling 惯例。这样做只是为了增 加代码的可读性,因为错误检测不 是这段代码的重点。 e data are not editable if (ipWorkspaceEdit == 0 || FAILED(hr)) continue; 间和计算资 源。许多组件对象类支持 IClone 接口。请参阅 ArcGIS 开发帮助中的 个 Point 对 : // pe IPointPtr and was previously declared // an IC onePtr ipCloned; ipClone->Clone(&ipCloned); 错误处理 Objects 进行开发’一节中已经讨论过。在用 C++ API 进 HRESULT。 HRESULT: 没有实现 返回的 HRESULT。 开一个想在其中处理数据的工作空间失败,将不能 以避免随后的崩溃。 IWorkspaceFactoryPtr orkspaceFactory); space; ry->OpenFromFile(inPath, 0, ace.” << std::endl; // som VARIANT_BOOL beingEdited; hr = ipWorkspaceEdit->IsBeingEdited(&beingEdited); if (!beingEdited) { // only adds unique workspaces hr = ipSet->Add(ipWorkspace); } } 复制对象 IClone 接口在比较和复制对象方面非常有用,可以节省时 IClone 文档以了解更详细的信息。下面的代码块复制了一 象 ipMouseClickPoint is of ty d instantiated. lonePtr ipClone (ipMouseClickPoint); ICl COM 方法返回一个 HRESULT 来指示方法调用的成功或失败,这在本章前 面的‘用 Arc 行编程时,应该检查所有 COM 对象调用返回的 下面是几个可能返回的常见 ● S_OK 表示成功 ● E_FAIL 表示失败 ● E_NOTIMPL 表示某个方法 有一些宏可以用来测试 ● bool FAILED(HRESULT) 例如,如果打 使用这些数据。此时,应退出应用程序 // Open the workspace ipWorkspaceFactory(CLSID_RasterW IWorkspacePtr ipWork HRESULT hr = ipWorkspaceFacto &ipWorkspace); if (FAILED(hr) || ipWorkspace == 0) { std::cerr << “Could not open the worksp return E_FAIL; } 210·ArcGIS Engine 开发指南 C++应用程序接口 没有同名 着打开它。 et with the desired output name. a new one with the name. taset(outFile, &ipExistsCheck); if (SUCCEEDED(hr)) std::cerr << “A dataset with the output name already exists!” << 障 开 保提供了正确的包含路径: 。如果路径正确并且仍不能运行,确保文 esriSysten.olb”——如果例子因为不能打 能运行,确保已经注册了 SoftwareAuthorization 工具,可以在 Start ArcGIS 中找到该工具,并检查当前配置。如果 按照提示运行该工具。 ● 错误:请定义 ESRI_WINDOWS 或 ESRI_UNIX——需要通知编译器要 使用哪个系列的头文件。用户忘了定义 ESRI_WINDOWS 符号。 缺陷 当使 Windows C++命令行应用程序。但是,GUI 应用 些 ActiveX 控件 GIS 功能。要了解有关这方面的细 节,请参阅本章前面的“Visual C++”一节。 bool SUCCEEDED(HRESULT) 例如,如果要创建一个新的栅格数据集,必须首先知道 的数据集存在。为了查找是否存在同名的数据集,试 如果成功,就知道不能用该名字创建一个新数据集。 // Check for existence of a datas ’// If such exists, we can t create asterDatasetPtr ipExistsCheck;IR hr = ipRastWork->OpenRasterDa { std::endl; return E_FAIL; } 解决故 ● 不能打开包含文件“ArcSDK.h”——如果例子因为不能打 ArcSDK.h 而不能编译的话,确 ArcGIS\include\CPPAPI 件在该目录中。 ● 不能打开类库文件“ 开 ESRI OLB 文件而不能编译的话,确保提供了正确的包含路径: ArcGIS\Com。如果路径正确并且仍不能运行,确保文件在该目录 中。 ● 如果例子或自己的代码在编译但不 ArcGIS Engine。使用 > All Programs > 配置没有列出“standardengine”, 用 C++ API 时,只支持 程序可以用 COM API(包括 Visual C++)来建立,而且一 向独立的 GUI 应用程序提供了 第四章·开发环境·211 5 许可与部署 ArcGIS Engine 应用程序的开发离不开最终应用程序的部署问 题。应用程序的部署涉及到三个独立的过程:应用程序许可的 初始化、ArcGIS Engine 运行时软件的安装及其使用授权。 本章将详细阐述这些过程,并分析开发人员在分发其 ArcGIS Engine 应用程序之前必须做的部署决策。 ArcGIS 的许可选项 ArcGIS 许可选项 ArcGIS Engine 开发人员要做的最重要决定之一就是确定预 期应用程序的功能需求及满足这些需求所需的最小化许可级 别。在大多数情况下,开发人员不能有效地开发需要 ArcGIS Engine 运行时 3D、空间和地理数据库选项的应用程序,正 是因为开发人员没有正确地评估客户或组织的真正需要并做 出相应的计划。 本章的这一部分详细阐述各种可能的许可选项。 214·ArcGIS Engine 开发指南 独立可执行许可的初始化 第五章·许可与部署·215 独立可执行许可的初始化 第六章“开发情景”中的各个示例 应用程序也说明了许可初始化过 程。 虽然每个独立的应用程序必须初始 化,但下列实例说明了不是独立应 用程序因而不需要进行初始化的情 况: 应用程序为一个 DLL,它将集成到 另一个能执行许可配置的应用程序 中。 应用程序是 ArcMap 或第三方应用 程序的扩展。扩展程序负责许可管 理。 ArcGIS Engine 开发人员要做的最重要决定之一就是确定预期应用程序 的功能需求及满足这些需求所需的最小化许可级别。在大多数情况下, 开发人员不能有效地开发需要 ArcGIS Engine 运行时 3D、空间和地理 数据库选项的应用程序,正是因为开发人员没有正确地评估客户或组 织的真正需要并做出相应的计划。 本章这节详细阐述用多个层次的许可初始化应用程序的各种可能选 项。首先详细讨论这些选项,然后提供许多许可初始化情况的示例。 许可初始化 用 ArcObjects 开发的每个独立应用程序必须用一个合适的许可进行初 始化,以保证在安装了该应用程序的任何机器上成功地运行应用程序。 许可初始化必须由应用程序在启动和访问任何 ArcObjects 前执行。许 可初始化失败会导致应用程序错误。 初始化应用程序时要考虑两类许可:产品许可和扩展许可(如果应用 程序使用 ArcGIS 的任何扩展模块的话)。每种许可都是 Engine 单用户 (Engine Single Use)、Desktop 多用户(Desktop Concurrent Use)和 Desktop 单用户(Desktop Single Use)许可中的一种。 Engine 单用户(Engine Single Use)—提供访问 ArcGIS Engine 或 具有地理数据库编辑功能的 Engine 许可。 Desktop 多用户(Desktop Concurrent Use)—FlexLM 技术用于提 供对 ArcGIS Desktop 产品 ArcView,ArcEditor 和 ArcInfo 及其扩 展进行同步访问的许可。多台机器可以访问该许可;许可被存储 在一个许可管理器(license manager)上,使用许可时对许可进行 核对。 Desktop 单用户(Desktop Single Use)—提供访问单用户 ArcView, ArcEditor 和 ArcInfo 的许可。与 Engine 单用户(Engine Single Use) 相似,每个许可只能由安装了该许可的机器访问。尽管与 Desktop 多用户(Desktop Concurrent Use)许可有很显著的差异,它们实 际上使用的是相同的技术。这意味着 ArcGIS Engine 开发人员无 法区分开 Desktop 单用户许可和多用户许可,因此应该将它们看 作相同的许可。 不使用 ArcGIS 扩展的应用程序开发要考虑的问题 一旦应用程序用某个许可进行了初始化,就不能再进行重新初始 化了;应用程序的许可初始化针对的是其整个生命期。在用许可 初始化应用程序时必须考虑以下问题: 独立可执行许可的初始化 对于使用 ArcGIS 控件开发的应用 程序,表 1 描述了每个控件运行时 所需的许可。 应用程序运行所需的产品许可类型。例如,企业地理数据库编辑应用 程序用 ArcGIS Engine 许可或 ArcView 许可不能运行,而用 ArcGIS Engine 的地理数据库编辑许可、ArcEditor 许可或 ArcInfo 许可则可以 运行。 应用程序可获得的产品许可类型。例如,用 ArcGIS Engine 许可 可以运行的应用程序也可以用 ArcView、ArcEditor 和 ArcInfo 许 可运行。然而,用户可能并不想为这种应用程序购买 ArcInfo 许 可。 在 ArcGIS Engine 应用程序上使用 ArcView 许可可以让用户访问标准 Engine 许可能获得的所有功能。同样地,在具有地理数据库编辑功能 的 ArcGIS Engine 应用程序上使用 ArcEditor 许可可以让用户访问具有 地理编辑选项的 Engine 许可能获得的所有功能。 使用 ArcGIS 扩展的应用程序需要考虑的其他问题 当用某个特定的产品许可初始化应用程序时,就建立了一个到许可服 务器(license server)上的连接。随后对扩展模块的所有检出和检入调 用都针对这个许可服务器。这样,用户就不能组合来自不同许可服务 器上的许可或组合单用户 Engine 许可。 如果应用程序用 Desktop 多用户许可进行初始化,则该应用程序 以后只能访问那个 Desktop 多用户许可服务器及其扩展许可。 如果应用程序用 Desktop 单用户许可进行初始化,则该应用程序 以后只能访问该 Desktop 单用户许可服务器及其扩展许可。 如果应用程序用 Engine 单用户许可进行初始化,则该应用程序以 后只能访问 Engine 单用户扩展许可。 在执行初始化之前可以查询许可服务器(Desktop 多用户许可或 Desktop 单用户许可)和 Engine 单用户许可,以查看是否可以获得用 户所需的许可。如果用户需要的所有许可都可以用 Engine 单用户许可 获得,则建议使用 Engine 单用户许可,而不是 Desktop 多用户许可和 Desktop 单用户许可。这意味着开发人员不会限制任何其他用户获得 Desktop 多用户许可。 用 Engine 单用户许可可以访问下列扩展模块: 3D Analyst Spatial Anslyst StreetMap 216·ArcGIS Engine 开发指南 独立可执行许可的初始化 第五章·许可与部署·217 对于使用 ArcGIS 控件开发的 应用程序,表 2 描述了每个控件设 计时所需的许可。 Engine 单用户设计选项( Single Use Designer Option) ArcGIS Engine 单用户 许可 带有 Engine 单用 户 3D 分析扩展 模块的 ArcGIS Engine 单用户 许可 ArcView, ArcEditor, ArcInfo 单用 户或桌面多 用户许可 带有 3D 分析扩展 模块的 ArcView, ArcEditor, ArcInfo 单用户或桌面多 用户许可 Arc Rea der ArcReaderContr ol √ √ MapControl √ √ PageLayoutCont rol √ √ ReaderControl √ √ ToolbarControl √ √ TOCControl √ √ GlobeControl √ √ SceneControl √ √ 使用 ArcObjects 开发应用程序时需要 Engine Single Use Designer 扩展。 这个许可与其它扩展的工作方式的不同之处在于: 该扩展只在设计和开发应用程序时需要,在运行时从来不需要。 该扩展不需要人为检出,它会自动检出。 ArcGIS Engine 单用户 许可 带有 Engine 单用户设计 扩展模块的 Engine 单 用户许可 带有 Engine 单 用户设计和 3D 分析扩展模块 的 Engine 单用 户许可 ArcView, ArcEditor, ArcInfo 单用 户或桌面多用 户许可 带有 Publisher 扩展 模块的 ArcView, ArcEditor, ArcInfo 单用户或桌面多用 户许可 ArcReaderControl √ MapControl √ √ PageLayoutControl √ √ ReaderControl √ ToolbarControl √ TOCControl √ √ GlobeControl √ SceneControl √ √ 用许可初始化应用程序 用许可初始化应用程序必须按如下顺序执行: 1. 检查产品许可的可用性。 2. 检查扩展许可的可用性(如果需要的话)。 3. 用产品许可初始化应用程序。 4. 如果需要的话,执行扩展模块的检出与检入。 5. 关闭应用程序。 独立可执行许可的初始化 检查产品许可的可用性 所选的产品许可决定了应用程序可以访问的功能。一旦产品许可被初 始化,在应用程序的生命期内就不能改变。 如果用户需要的产品没有得到许可,可以选择用更高级的产品许 可初始化应用程序。 如果没有可用的合适产品许可,应用程序应该将这个问题告知用 户,让用户解决许可问题,或者退出应用程序。 检查扩展许可的可用性 如果应用程序设计要使用扩展功能,在初始化应用程序前,要检查扩 展许可的可用性。扩展许可的可用性检查必须与应用程序最终用来初 始化的产品许可的检查一起完成,因为并不是每个产品许可都可以获 得各个扩展许可。 如果应用程序成功运行所需的扩展不可用,应用程序应将这个问 题通知给用户并退出应用程序。 如果扩展功能并不是应用程序运行所必需的,而且该扩展许可不 可用,则应用程序应使依赖于该扩展的功能对用户无效。 初始化应用程序 一旦已经确定了可用的合适产品和扩展许可,应用程序就可以用该产 品许可进行初始化。一旦进行了初始化,就不能对该应用程序重新进 行初始化。 扩展模块的检入与检出 当应用程序需要扩展功能时,可以检出扩展模块,一旦应用程序使用 完扩展功能就可以将其检入,或者在应用程序初始化后直接检出扩展 模块并在关闭前检入。扩展模块检入和检出的方式取决于应用程序初 始化所使用的产品许可类型。 如果应用程序用 Engine 单用户许可进行初始化,则应用程序使用 的任何扩展模块也使用 Engine 单用户许可。这样任何扩展模块可 以在应用程序初始化后直接检出而在关闭前检入。 如果应用程序用许可服务器进行初始化,而且应用程序成功运行 需要扩展模块,则扩展模块应该在应用程序初始化后直接检出并 在关闭前检入。 218·ArcGIS Engine 开发指南 独立可执行许可的初始化 如果应用程序用许可服务器进行初始化,而且扩展功能并不是应 用程序运行所必需的,则扩展模块可以在应用程序初始化后直接 检出,或者当需要扩展功能时检出。当扩展模块检入时,应该使 扩展功能无效。 关闭 在关闭应用程序前,必须关闭 AoInitialize 对象。这样可以保证使用过 的任何 ESRI 类库被卸载。 许可初始化失败 如果产品或扩展模块检出失败,许可状态会指出失败的原因。许可检 出失败的原因有: 产品没有得到许可。 许可不可用,因为该许可正被使用(只适合 Desktop 多用户许可)。 由于系统管理问题导致的不可测的许可失败。 许可已被初始化。用产品许可进行的应用程序初始化对其整个生 命期有效。可以检查应用程序用何种产品许可进行了初始化。例 如,如果包含企业地理数据库编辑功能的应用程序用具有地理数 据库编辑功能的 Engine 单用户许可或 ArcEditor 或 ArcInfo 许可, 则可以使用该编辑功能。然而,如果应用程序用 Engine 单用户许 可或 ArcView 许可,则必须使该编辑功能无效。 示例 1 应用程序需要最小化的 ArcGIS Engine 许可。如果 ArcGIS Engine 许可 不可用,应用程序可以用 ArcView 或 ArcEditor 许可,但不会用 ArcInfo 许可(ArcInfo 能提供用户需要的所有功能,但用户不想为这样简单的 应用程序购买 ArcInfo 许可)。成功运行应用程序还需要 3D 分析和空 间分析扩展功能,因此在应用程序的生命期中需要检出这两个扩展模 块。用许可初始化该应用程序需要以下步骤。 能否用 ArcGIS Engine 产品许可初始化应用程序? 1. 检查 ArcGIS Engine 产品许可是否可用。 2. 检查用 ArcGIS Engine 产品许可是否可以获得 3D 分析扩展许可? 3. 检查用 ArcGIS Engine 产品许可是否可以获得空间分析扩展许 可? 如果这些许可不可用,则应用程序不能用 ArcGIS Engine 许可初始化。 那么是否可以用 ArcView 产品许可初始化应用程序呢? 第五章·许可与部署·219 独立可执行许可的初始化 如果这些许可都可用,则初始化应用程序。 4. 通过初始化应用程序将 ArcGIS Engine 产品许可检出。 如果该许可检出失败,那么是否可以用 ArcView 产品许可初始化应用 程序呢? 如果能检出该许可,则检出扩展许可。 5. 检出 3D 分析扩展模块。 6. 检出空间分析扩展模块。 如果这两个扩展许可中的一个检出失败,则应用程序不能运行。 如果这两个扩展许可被检出,应用程序已经成功地配置好了许可。 能否用 ArcView 产品许可初始化应用程序? 1. 检查 ArcView 产品许可是否可用。 2. 检查用 ArcView 产品许可是否可以获得 3D 分析扩展许可? 3. 检查用 ArcView 产品许可是否可以获得空间分析扩展许可? 如果这些许可不可用,则应用程序不能用 ArcView 许可初始化。那么 是否可以用 ArcEditor 产品许可初始化应用程序呢? 如果这些许可都可用,则初始化应用程序。 4. 通过初始化应用程序将 ArcView 产品许可检出。 如果该许可检出失败,那么是否可以用 ArcEditor 产品许可初始化应用 程序呢? 如果能检出该许可,则检出扩展许可。 5. 检出 3D 分析扩展模块。 6. 检出空间分析扩展模块。 如果这两个扩展许可中的一个检出失败,则应用程序不能运行。在这 里检出 Desktop 多用户许可可能会失败,因为该许可在检查其可用性后 可能已被另一个应用程序检出。 如果这两个扩展许可被检出,应用程序已经成功地配置好了许可。 能否用 ArcEditor 产品许可初始化应用程序? 1. 检查 ArcEditor 产品许可是否可用。 2. 检查用 ArcEditor 产品许可是否可以获得 3D 分析扩展许可? 3. 检查用 ArcEditor 产品许可是否可以获得控件分析扩展许可? 如果这些许可不可用,则应用程序不能用 ArcEditor 许可初始化,且应 用程序不能运行。 220·ArcGIS Engine 开发指南 独立可执行许可的初始化 如果这些许可都可用,则初始化应用程序。 4. 通过初始化应用程序将 ArcEditor 产品许可检出。 如果该许可检出失败,则应用程序不能运行。 如果能检出该许可,则检出扩展许可。 5. 检出 3D 分析扩展模块。 6. 检出空间分析扩展模块。 如果这两个扩展许可中的一个检出失败,则应用程序不能运行。在这 里检出 Desktop 多用户许可可能会失败,因为该许可在检查其可用性后 可能已被另一个应用程序检出。 如果这两个扩展许可被检出,应用程序已经成功地配置好了许可。 示例 2 应用程序为一个企业地理数据库编辑应用程序,因此需要具有地理数 据库编辑许可的最小化 ArcGIS Engine。如果具有地理数据库编辑许可 的 ArcGIS Engine 不可用,应用程序可以用 ArcEditor 或 ArcInfo 许可。 成功运行应用程序需要空间分析扩展功能,因此在应用程序的生命期 内要检出该扩展模块。用许可初始化该应用程序需要以下步骤。 能否用具有地理数据库编辑功能的 ArcGIS Engine 产品许可 初始化应用程序? 1. 检查具有地理数据库编辑功能的 ArcGIS Engine 产品许可是否可 用。 2. 检查用具有地理数据库编辑功能的 ArcGIS Engine 产品许可是否 可以获得空间分析扩展许可。 如果这两个许可中的任何一个不可用,则不能用具有地理数据库编辑 功能的 ArcGIS Engine 产品许可初始化应用程序。那么能否用 ArcEditor 产品许可初始化应用程序呢? 如果这两个许可都可用,则初始化该应用程序。 3. 通过初始化应用程序检出具有地理数据库编辑功能的 ArcGIS Engine 产品许可。 如果该许可检出失败,那么能否用 ArcEditor 产品许可初始化应用程序 呢? 如果能检出该许可,则将扩展许可检出。 4. 检出空间分析扩展许可。 如果该扩展许可检出失败,应用程序不能运行。在这里检出 Desktop 多用户许可可能会失败,因为该许可在检查其可用性后可能已被另一 个应用程序检出。 第五章·许可与部署·221 独立可执行许可的初始化 如果这两个扩展许可被检出,应用程序已经成功地配置好了许可。 能否用 ArcEditor 产品许可初始化应用程序? 1. 检查 ArcEditor 产品许可是否可用。 2. 检查 ArcEditor 产品许可是否可以获得空间分析扩展许可。 如果这两个许可中的任何一个不可用,则不能用 ArcEditor 产品许可初 始化应用程序。那么能否用 ArcInfo 产品许可初始化应用程序呢? 如果这两个许可都可用,则初始化该应用程序。 3. 通过初始化应用程序检出 ArcEditor 产品许可。 如果该许可检出失败,那么能否用 ArcInfo 产品许可初始化应用程序 呢? 如果能检出该许可,则将扩展许可检出。 4. 检出空间分析扩展许可。 如果该扩展许可检出失败,应用程序不能运行。在这里检出 Desktop 多用户许可可能会失败,因为该许可在检查其可用性后可能已被另一 个应用程序检出。 如果这两个扩展许可被检出,应用程序已经成功地配置好了许可。 能否用 ArcInfo 产品许可初始化应用程序? 1. 检查 ArcInfo 产品许可是否可用。 2. 检查 ArcInfo 产品许可是否可以获得空间分析扩展许可。 如果这两个许可中的任何一个不可用,则不能用 ArcInfo 产品许可初始 化应用程序,而且应用程序不能运行。 如果这两个许可都可用,则初始化该应用程序。 3. 通过初始化应用程序检出 ArcInfo 产品许可。 如果该许可检出失败,则应用程序不能运行。 如果能检出该许可,则将扩展许可检出。 4. 检出空间分析扩展许可。 如果该扩展许可检出失败,应用程序不能运行。在这里检出 Desktop 多用户许可可能会失败,因为该许可在检查其可用性后可能已被另一 个应用程序检出。 如果这两个扩展许可被检出,应用程序已经成功地配置好了许可。 222·ArcGIS Engine 开发指南 独立可执行许可的初始化 示例 3 应用程序需要最小化的 ArcGIS Engine 许可。如果 ArcGIS Engine 许可 不可用,应用程序可以用 ArcView,ArcEditor 或 ArcInfo 许可运行。应 用程序需要 3D 分析扩展功能,但并不是应用程序成功运行所必需的, 因此需要时将检出该扩展模块。 能否用 ArcGIS Engine 产品许可初始化应用程序? 1. 检查 ArcGIS Engine 产品许可是否可用。 2. 检查用 ArcGIS Engine 产品许可是否可以获得 3D 分析扩展许可。 如果这两个许可中的任何一个不可用,则不能用 ArcGIS Engine 产品许 可初始化应用程序。那么能否用ArcView产品许可初始化应用程序呢? 如果这两个许可都可用,则初始化该应用程序。 3. 通过初始化应用程序检出 ArcGIS Engine 产品许可。 如果该许可检出失败,则应用程序不能运行。那么能否用 ArcView 产 品许可初始化应用程序呢? 如果能检出该许可,应用程序已经成功地配置好了许可,但应该使 3D 分析扩展模块功能无效。 能否用 ArcView 产品许可初始化应用程序? 1. 检查 ArcView 产品许可是否可用。 2. 检查用 ArcView 产品许可是否可以获得 3D 分析扩展许可。 如果这两个许可中的任何一个不可用,则不能用 ArcView 产品许可初 始化应用程序。那么能否用 ArcEditor 产品许可初始化应用程序呢? 如果这两个许可都可用,则初始化该应用程序。 3.通过初始化应用程序检出 ArcView 产品许可。 如果该许可检出失败,则应用程序不能运行。那么能否用 ArcEditor 产 品许可初始化应用程序呢? 如果能检出该许可,应用程序已经成功地配置好了许可,但应该使 3D 分析扩展模块功能无效。 能否用 ArcEditor 产品许可初始化应用程序? 1. 检查 ArcEditor 产品许可是否可用。 2. 检查用 ArcEditor 产品许可是否可以获得 3D 分析扩展许可。 第五章·许可与部署·223 独立可执行许可的初始化 如果这两个许可中的任何一个不可用,则不能用 ArcEditor 产品许可初 始化应用程序。那么能否用 ArcInfo 产品许可初始化应用程序呢? 如果这两个许可都可用,则初始化该应用程序。 3. 通过初始化应用程序检出 ArcEditor 产品许可。 如果该许可检出失败,则应用程序不能运行。那么能否用 ArcInfo 产品 许可初始化应用程序呢? 如果能检出该许可,应用程序已经成功地配置好了许可,但应该使 3D 分析扩展模块功能无效。 能否用 ArcInfo 产品许可初始化应用程序? 1. 检查 ArcInfo 产品许可是否可用。 2. 检查用 ArcInfo 产品许可是否可以获得 3D 分析扩展许可。 如果这两个许可中的任何一个不可用,则不能用 ArcInfo 产品许可初始 化应用程序。 如果这两个许可都可用,则初始化该应用程序。 3. 通过初始化应用程序检出 ArcInfo 产品许可。 如果该许可检出失败,则应用程序不能运行。 如果能检出该许可,应用程序已经成功地配置好了许可,但应该使 3D 分析扩展模块功能无效。 使用 3D 分析功能 1. 检查 3D 分析扩展许可是否已经被检出。 如果该许可已被检出,则应用程序可以使用 3D 分析功能。 如果该许可还没有检出该许可,则检出该许可。 2. 检出 3D 分析扩展许可。 如果检出该许可失败,则应用程序不能使用 3D 分析功能。在这里检出 Desktop 多用户许可可能会失败,因为该许可在检查其可用性后可能已 被另一个应用程序检出。 如果该许可被检出,则应用程序可以使用 3D 分析功能。 224·ArcGIS Engine 开发指南 部署 ArcGIS Engine 运行时 第五章·许可与部署·225 部署 ArcGIS Engine 运行时 Microsoft Windows 操作系统上可以对 ArcGIS Engine 运行时安装程序 进行部署。 下面首先简要介绍用于创建安装程序的技术,然后探讨 Windows 上 ArcGIS Engine 运行时安装程序的各种部署方法。 什么是 ArcGIS Engine 运行时安装程序? 使用 Microsoft Windows Installer 技术创建 ArcGIS Engine 运行时 安装程序。这个技术使用一个打包文件(.msi)和一个客户端 Installer 服务(msiexec.exe)。Windows Installer 是运行于用户操作系统上的 一个服务。这个服务使操作系统能够管理安装程序并使用包含在打包 文件内的信息安装软件。 Msiexec.exe 程序是 Windows Installer 的一个组成部分。Msiexec.exe 用动态链接库 Msi.dll 读取打包文件(.msi),应用转换程序(.mst), 并集成命令行选项。 有关 Windows Installer 的更多信息可以在 Windows Installer 软件 开发工具包(SDK)中找到。网址为: http://www.microsoft.com/msdownload/platformSDK/sdkupdate/ 部署 ArcGIS Engine 运行时 ArcGIS Engine 运行时开发的应用程序需要将 ArcGIS Engine 运行时安 装到终端用户的机器上。为确保 ArcGIS Engine 运行时安装到用户机 器上,请按以下步骤操作: 用户直接运行 ArcGIS Engine 运行时安装程序 1. 用户可以从 ArcGIS Engine 运行时 CD 上启动安装程序。 2. 在安装前,用户应先检查 ArcGIS Engine 运行时是否已经安装在 他们的机器上,方法为: I. 运行“控制面板”下的“添加/删除程序”。 II. 检查 ArcGIS Engine 运行时是否在列在程序列表中。如果在 列表中,则已经在这台机器上安装了 ArcGIS Engine 运行时。 单击“支持信息”核实产品版本号是否为 9.0。 3. 如果在机器上安装了 ArcGIS Engine 运行时,用户可以继续安装 开发人员开发的应用程序。 4. 如果启动了 ArcGIS Engine 运行时安装程序,且已经在这台机器 上安装了 ArcGIS Engine 运行时,则安装将作为维护安装来执行。 5. 在继续安装前,用户必须检查这台机器上是否有低于 9.0 版本的 ArcGIS 产品,包括 ArcIMS、ArcGIS Desktop 、ArcGIS Workstation 和 ArcReader。检查这台机器上是否有这些产品的方法为: I. 运行“控制面板”下的“添加/删除程序”。 部署 ArcGIS Engine 运行时 II. 检查程序列表以查看是否在这台机器上安装了任何 ArcGIS 产 品。如果在列表中有 ArcGIS 产品,单击“支持信息”以确定 产品的版本号。如果版本低于 9.0,在安装 ArcGIS Engine 运行时 9.0 之前应删除这些产品。 6. 如果 ArcGIS Engine 运行时没有在这台机器上安装,则在安装开 发的应用程序之前,用户必须启动 ArcGIS Engine 运行时的 Setup.exe。 7. 如果应用程序需要 Java 或.NET 安装特性,用户必须检查是否安装 了这些特性。 Java 和.NET 是可选的安装特性,在 ArcGIS Engine 运行时的典型安装中并没有安装这两个特性。要检查是否安装了 这些特性,其方法为: I. 运行“控制面板”下的“添加/删除程序”。 II. 检查 ArcGIS Engine 运行时是否列在程序列表中。如果在列 表中,则已经在这台机器上安装了 ArcGIS Engine 运行时。 III.在 ArcGIS Engine 运行时特性上单击“更改”按钮,然后选 择“修改”。“Select Feature”对话框显示了 ArcGIS Engine 运 行时安装的目前状态。硬盘图标表明在机器上安装了该特性,红 色的叉表明机器上没有安装该特性,但可以进行安装。 IV. 要安装某个特性,用户必须选择该特性,右击并选择“安装 在本地硬盘上”。 在应用程序的安装程序中包含 ArcGIS Engine 运行时安装程 序 1. 开发人员可以用许多方法将 ArcGIS Engine 运行时安装程序包含 在其开发的应用程序的安装程序中。部署 ArcGIS Engine 运行时 的方法将在本章后面讨论。 2. 在应用程序的安装程序启动 ArcGIS Engine 运行时安装程序之前, 开发人员要通过执行系统检查来确定是否已经在用户机器上安装 了 ArcGIS Engine 运行时。执行系统检查的方法将在本章的后面 讨论。 3. 如果机器上没有安装 ArcGIS Engine 运行时,开发人员通过使用 下面讨论的部署选项中的一种来安装 ArcGIS Engine 运行时。 4. 如果机器上已经安装了 ArcGIS Engine 运行时,就不用再安装 ArcGIS Engine 运行时,可以继续安装应用程序。 5.如果应用程序需要 Java 或.NET 安装特性,开发人员必须检查是否 安装了这些特性(本章后面讨论)。Java 和.NET 是可选的安装特性, 在 ArcGIS Engine 运行时的典型安装中并没有安装这两个特性。如果 没有安装这些特性,而应用程序需要其中一个或两者都需要,开发人 员必需安装这些所需的特性。 226·ArcGIS Engine 开发指南 部署 ArcGIS Engine 运行时 ArcGIS Engine 运行时安装特性 一个.msi 安装程序由若干特性组成。特性是用户可以安装的组件群。 ArcGIS Engine 运行时安装程序由下列安装特性组成: 特性 描述性特性名 描述 ArcEngine ArcGIS Engine ArcGIS Engine JavaRuntime ArcGIS Engine Java Runtime Java Archives DotNetRuntime ArcGIS Engine .NET Runtime .Net Assemblies ArcGIS Engine 运行时的系统需求 Windows NT SP6a,Windows 2000,Windows XP 专业版和 Windows 2003 Server 操作系统都支持 ArcGIS Engine 运行时。 要了解有关ArcGIS Engine运行时的系统需求方面的其他信息或更新信 息,请访问http://support.esri.com。 ArcGIS Engine 运行时安装位置 ArcGIS 9.0,ArcGIS Engine 运行时,ArcGIS Engine 开发工具包,ArcGIS Desktop,ArcReader 独立程序和 ArcGIS Server 将安装在相同的安装 目录中。首次安装的 ArcGIS 9.0 产品将决定所有后续 ArcGIS 9.0 产 品的安装位置。 注意:如果已经安装了 ArcIMS ArcMap Server 9.0,ArcGIS 9.0 产品 将默认安装到 ArcIMS 安装目录中。 例如,如果 ArcGIS Desktop 安装到 C:\Desktop 目录中,ArcGIS 9.0 的安装位置将是 C:\Desktop\ArcGIS。如果接着安装 ArcGIS Engine 运行时,用户将没有机会浏览以选择安装位置。ArcGIS Desktop 的安 装已经预先确定了所有 ArcGIS 9.0 产品的安装位置。因此,在本例中, ArcGIS Engine 运行时也将安装到 C:\Desktop\ArcGIS 目录中。 如果在安装 ArcGIS 9.0 产品时没有足够的磁盘空间,用户将需要卸载 所有的 ArcGIS 9.0 产品(如前所列),然后重新将这些产品安装到有 足够磁盘空间的目录中。ArcGIS 9.0 产品(不包括 ArcSDE,ArcIMS 和 ArcInfo Workstation)不能安装到其他目录中。 部署方法 ArcGIS Engine 运行时有两种推荐的部署方法。 1. 用户直接从 CD 中安装 ArcGIS Engine 运行时安装程序。ArcGIS Engine 运行时安装程序可以用 CD 进行重新分发。可以复制 ArcGIS Engine 运行时 CD 映像内容并创建其他 CD,或者联系 ESRI 以获得 其他 ArcGIS Engine 运行时 CD。 从 CD 中直接运行安装程序的其他要求: 用户需要检查是否已经在其机器上安装了ArcGIS Engine运行时。 如果已经安装,用户可以继续安装开发人员开发的应用程序。如 果启动了 ArcGIS Engine 运行时安装程序,而且 ArcGIS Engine 运行时已经安装在机器上,则安装将作为维护安装来执行。 第五章·许可与部署·227 部署 ArcGIS Engine 运行时 ArcGIS Engine 运行时 9.0 不能在有低于 9.0 版本的 ArcGIS 产品 的机器上安装。在安装 ArcGIS Engine 运行时 9.0 之前必须将低 于 9.0 版本的 ArcGIS 产品卸载。 如果应用程序所需的任何 ArcGIS Engine 运行时特性没有安装, 用户应安装这些特性。如果所需的 ArcGIS Engine 运行时特性已 经在机器上安装,则不必运行 ArcGIS Engine 运行时安装程序。 要获得其他ArcGIS Engine运行时CD,请与www.esri.com上的“ESRI客 户服务”联系,或者美国用户可以拨打 888-377-4574,或者与当地的 ESRI分部联系。 2. 将 ArcGIS Engine 运行时安装程序集成到应用程序的安装程序中 可以通过运行使用 Windows Installer 命令行参数的安装程序来安装 ArcGIS Engine。 ArcGIS Engine 运行时可以使用下列选项集成到应用程序的安装程序 中: a.在基于 msi 的安装程序的结尾 b.在批处理文件内 c.在脚本安装程序内 本章后面提供这些选项的示例。 集成 ArcGIS Engine 运行时安装程序的其他要求 如果应用程序所需的任何 ArcGIS Engine 运行时特性没有安装,开发 人员制作的安装程序应添加这些特性。如果所需的 ArcGIS Engine 运 行时特性已经在机器上安装,就不用运行 ArcGIS Engine 运行时的安 装程序。 在安装 ArcGIS Engine 运行时的时候,会为它创建一个注册表项,其 位置为: HKEY_LOCAL_MACHINE\Software\ESRI\ArcGIS Engine Runtime 这个注册表键的 RealVersion 值是 9.0。 可以用这个键检查用户机器上是否安装了 ArcGIS Engine 运行时。 1.为 ArcGIS Engine 运行时选项的安装特性执行系统检查 如果应用程序需要安装 ArcGIS Engine 运行时.NET 或 Java 特性,必须 检查用户系统以查看是否有合适的.NET 或 Java 安装特性及 ArcGIS Engine 运行时(参见“为 ArcGIS Engine 运行时执行系统检查”)。 228·ArcGIS Engine 开发指南 部署 ArcGIS Engine 运行时 下面的注册表项将决定 ArcGIS Engine 运行时特性的安装状态: [HKEY_CLASSES_ROOT\Installer\Features\7A1A3A9178A2BC74EB11 4EA6B5DB1C1B] “Registry”=”” “ArcEngine”=”” “DotNetRuntime”=”ArcEngine”或者“?ArcEngine” “JavaRuntime”=”ArcEngine”或者“?ArcEngine” 确定是否安装了.NET 特性的方法为: .NET ArcGIS Engine 安装特性要求在机器上安装.NET Framework1.1。 下面的注册表键可用于确定是否安装了.NET ArcGIS Engine 特性: HKEY_CLASSES_ROOT\Installer\Features\7A1A3A9178A2BC74EB114 EA6B5DB1C1B DotNetRuntime 的值代表.NET ArcGIS Engine 安装特性。 如果这个注册表键下 DotNetRuntime 的值没有显示,则表明在运 行 ArcGIS Engine 运行时安装程序时还没有在机器上安装.NET Framework1.1。 如果没有安装 DotNetRuntime 特性,则 “DotNetRuntime”=“?ArcEngine” 如果安装了 DotNetRuntime 特性,则 “DotNetRuntime”=“ArcEngine” 确定是否安装了 Java 特性的方法为: 下面的注册表键可用于确定是否安装了.Java ArcGIS Engine 特性: HKEY_CLASSES_ROOT\Installer\Features\7A1A3A9178A2BC74EB11 4EA6B5DB1C1B JavaRuntime 的值代表. Java ArcGIS Engine 安装特性。 如果没有安装 JavaRuntime 特性,则 “JavaRuntime”=“?ArcEngine” 如果安装了 JavaRuntime 特性,则 “JavaRuntime”=“ArcEngine” 如果发现所需的安装特性没有安装,则只需要在脚本中使用 ADDLOCAL Windows Installer 命令就可以安装。例如, ADDLOCAL=DotNetRuntime 或 ADDLOCAL=JavaRuntime。 如果选择只安装 DotNetRuntime 或 JavaRuntime 特性,而且 ArcEngine 特性(主要的 ArcGIS Engine 安装特性)没有安装,则 安装程序在安装过程中会包括 ArcEngine 特性。 如果在机器上没有检测到.NET Framework1.1,则 DotNetRuntime 特性会被隐藏并且不会被安装。 在将 ArcGIS Engine 运行时安装程序集成到应用程序安装程序中时, 需要考虑以下需求。 第五章·许可与部署·229 部署 ArcGIS Engine 运行时 2. ArcGIS Engine 运行时安装程序使用 Windows Installer 2.0 ArcGIS Engine 运行时安装程序使用 Windows Installer 2.0。在 使用命令行参数运行 Engine 运行时安装程序之前,必须先在目标 机器上安装并运行 Windows Installer 2.0 版本。检查机器上 Windows Installer 版本的方法为: i. 定位到 system32 文件夹中的 msiexec.exe。 ii. 右击 msiexec.exe 并选择“属性”。 iii. 在“属性”对话框中,选择“版本”选项卡检查该文件的 版本。 如果将 ArcGIS Engine 运行时安装程序包含在一个不是基于 msi 的安装程序中,终端用户将需要在他们的机器上安装 Windows Installer 2.0。ArcGIS Engine 运行时安装程序使用 Windows Installer 技术。 如果在基于 msi 的安装程序的结尾启动 Engine 运行时安装程序, 必须用 Windows Installer 2.0 或更高版本创建自己的 msi 安装 程序,以兼容 ArcGIS Engine 运行时安装程序。 ArcGIS Engine 运行时 msi 不能嵌套在另一个 msi 中。每个产品, 包括 ArcGIS Engine 运行时,都必须单独列在“添加/删除程序” 列表中。 Windows Installer 2.0 可 以 从 \Support\MSI\instmsiw.exe 中获得。 3. 为 ArcGIS Engine 运行时执行系统检查 如果要将 ArcGIS Engine 运行时安装程序包含在应用程序的安装程序 中,必须在用户机器上执行系统检查,以检测是否已经安装了 ArcGIS Engine 运行时。如果启动了 ArcGIS Engine 运行时安装程序,而且已 经在机器上安装了 ArcGIS Engine 运行时,则安装将作为维护安装来 执行。 示例脚本 下面是一个在批处理文件中实现的命令行参数示例,可用来安装 ArcGIS Engine 运行时.NET 特性: REM ############################ REM Set variables SET MSI_PATH=\\CDROM\Setup.msi REM ############################ REM Launch MSI Silently - NO UI msiexec.exe /i “%MSI_PATH%” /qn ADDLOCAL=DotNetRuntime 注释:在上例中,如果在机器上没有检测到.NET Framework 1.1,则 不会安装 DotNetRuntime 特性。 下面是一个在批处理文件中实现的命令行参数示例,可用来安装 ArcGIS Engine 运行时 Java 特性: 230·ArcGIS Engine 开发指南 部署 ArcGIS Engine 运行时 REM ############################ REM Set variables SET MSI_PATH=\\CDROM\Setup.msi REM ############################ REM Launch MSI Silently - NO UI msiexec.exe /i “%MSI_PATH%” /qn ADDLOCAL=JavaRuntime 部署选项示例 下面是 ArcGIS Engine 运行时部署选项的一些示例。 在基于 MSI 安装程序的结尾部署 ArcGIS Engine 运行时 下面的示例使用 Wise for Windows Installer MSI Authoring 程序在 另一个基于 MSI 的安装程序的结尾启动 ArcGIS Engine 运行时安装程 序。在单击“结束(Finish)”按钮后启动 ArcGIS Engine 运行时安装 程序。下面的示例说明了“结束(Finish)”按钮背后的自定义动作。 这个示例假设 ArcGIS Engine 运行时安装程序驻留在与应用程序的安 装程序相同的目录中。在这种情况下,Setup.exe 驻留在一个名为 “ArcEngine”的文件夹中。启动应用程序媒介上“ArcEngine”文件 夹中的 ArcGIS Engine 运行时 Setup.exe 的方法为: 1. 创建 MSI_PATH 和 ArcEngineExists 属性,并在“属性”表中将它 们的初始值设置为 1。 2. 在安装程序的开始处执行 ArcGIS Engine 运行时系统检查。系统 检查应搜索下列注册表键:HKEY_LOCAL_MACHINE\Software\ESRI \ArcGIS Engine Runtime,而且如果返回的 RealVersion 值为 9.0, 则将 ArcEngineExists 属性设置为“True”。 3. 使用“Execute Program from Path”自定义动作类型创建一个名 为“Lauch_Engine_MSI”的自定义动作。将这个自定义动作中的 “属性”设置为 MSI_PATH。在“属性”字段中指定的路径就是程 序从中执行的路径,在这个例子中是 MSI_PATH。启动 setup.exe 时不需要命令行。 第五章·许可与部署·231 部署 ArcGIS Engine 运行时 232·ArcGIS Engine 开发指南 4. 在应用程序安装程序的“退出(Exit)”对话框中,为“结束 (Finish)”按钮控件添加两个动作。 第一个动作将 MSI_PATH 属性设置为[SourceDir]ArcEngin e\Setup.exe。其值随 ArcGIS Engine 运行时安装程序在媒介上的位置 不同而变化。在这个例子中,Setup.exe 位于 CD 上的 ArcEngine 文件 夹中。 部署 ArcGIS Engine 运行时 第二个动作调用前面创建的 Launch_Engine_MSI。 只有在 ArcEngineExists 属性不等于 True 的情况下才能执行这两个自 定义动作。 在脚本安装程序中部署 ArcGIS Engine 运行时 脚本安装程序可以用命令行参数来安装 ArcGIS Engine 运行时(下面 的示例使用 Wise Install Master Setup Authoring 软件): 注释:在下面的示例中,MSI_PATH 会随 ArcGIS Engine 运行时安装程 序在媒介上的位置不同而变化。在这个例子中,Setup.msi 位于 CD 上 的 ArcEngine 文件夹中。 Rem Set variable Set Variable MSI_PATH to \\CDROM\ArcEngine\Setup.msi Rem Launch ArcGIS Runtime setup program silently – No UI Execute %SYS32%\msiexec.exe /i %MSI_PATH% /qn (Wait) Rem Launch ArcGIS Runtime setup program silently – No UI except for a modal dialog box displayed at the end Execute %SYS32%\msiexec.exe /i %MSI_PATH% /qn+ (Wait) 在批处理文件中部署 ArcGIS Engine 运行时 下面是在一个批处理文件中实现命令行参数的示例,可用来安装 ArcGIS Engine 运行时。 注释:在下面的示例中,MSI_PATH 会随 ArcGIS Engine 运行时安装程 序在媒介上的位置不同而变化。在这个例子中,Setup.msi 位于 CD 上 的 ArcEngine 文件夹中。 REM ############################ REM Set variables SET MSI_PATH=\\CDROM\ArcEngine\Setup.msi REM ############################ REM Launch MSI Silently - NO UI msiexec.exe /i “%MSI_PATH%” /qn REM Launch MSI Silently - Reduced UI msiexec.exe /i “%MSI_PATH%” /qb REM Launch MSI Silently - No UI except for a modal dialog box displayed at the end. msiexec.exe /i “%MSI_PATH%” /qn+ 第五章·许可与部署·233 部署 ArcGIS Engine 运行时 从 ArcGIS Engine 运行时 CD 上启动安装程序 ArcGIS Engine 运行时安装程序可以从 CD 上用 setup.exe 手工启动。 1. 复制 ArcGIS Engine 运行时 CD 映像内容并创建其他 CD,或者将 ArcGIS Engine 运行时放在应用程序的安装程序 CD 中。 2. 通知用户下列问题: a.检查系统以确定是否在机器上已经安装了ArcGIS Engine运行时 9.0。 b.检查系统以确定机器上是否安装有低于9.0版本的ArcGIS产品。 如果有,必须在安装 ArcGIS Engine 运行时 9.0 之前将它们卸载。 c.如果已经安装了 ArcGIS Engine 运行时 9.0,则继续运行开发应 用程序的安装程序。 d.如果没有安装 ArcGIS Engine 运行时 9.0,从 ArcGIS Engine 运 行时 CD 映像位置上启动 Setup.exe。一旦完成了 ArcGIS Engine 运行时的安装,就启动开发应用程序的安装程序。 如何使用安装程序的命令行参数 ArcGIS Engine 运行时安装程序可以用.msi 文件和客户端 Installer 服务(msiexec.exe)的命令行参数进行安装。下表列举了一些可用的 msiexec.exe 命令行参数。 (来源:http://msdn.microsoft.com/library/default.asp? url=/library/en-us/ msi/setup/command_line_options.asp) 选项 参数 描述 /l 程序包| 产品代码 安装或配置某个产品 /a 程序包 管理员安装选项。在网络上安装产品。 /x 程序包| 产品代码 卸载某个产品。 /q n|b|r|f 设置用户界面级别 q 无用户界面 qn 无用户界面 qb 基本用户界面。用 qb!隐藏“取消”按钮。 qr 简化用户界面,在安装的结尾不显示模态对话框。 qf 完整用户界面,而且在安装的结尾显示任何 FatalError, UserExit 或 Exit 模态对话框。 qn+ 无用户界面,在结尾显示一个模态对话框。 qb+ 基本用户界面,在结尾显示一个模态对话框。如果用户取消安 装则不显示模态对话框。用 qb+!或 qb!+ 隐藏“取消”按钮。 qb- 基本用户界面,在结尾不显示模态对话框。请注意不支持/qb+- 级别的用户界面。用 qb+!或 qb!+ 隐藏“取消”按钮。 注意!选项在 Windows Installer 2.0 版本中可用,而且只能用在基 本用户界面选项上,对完整用户界面选项无效。 /?或/h 显示 Windows Installer 的版权信息。 要了解Windows Installer命令行参数方面的更多信息,请访问 http://msdn.microsoft.com/library/default.asp?url=/l ibrary/en-us/msi/setup/command_line_options.asp。 234·ArcGIS Engine 开发指南 部署 ArcGIS Engine 运行时 示例: 用下面的命令执行无用户界面的典型安装并将程序安装到非默认位置 上: Msiexec.exe /i \setup.msi /qn InstallDir=C:\Mysetup 用下面的命令执行基本用户界面(进展条)的典型安装并将程序安装 到非默认位置上: Msiexec.exe /i /setup.msi /qb InstallDir=C:\Mysetup 用下面的命令执行无用户界面的完全安装并将程序安装到默认位置 上: Msiexec.exe /i \setup.msi /qn ADDLOCAL=All 用下面的命令执行无用户界面的自定义安装并将程序安装到默认位置 上: Msiexec.exe /i \setup.msi /qn ADDLOCAL=,,.... 需要安装的特性用 ADDLOCAL 参数指定。ArcGIS Engine 具有以下安装 特性: 特性 描述性特性名 描述 ArcEngine ArcGIS Engine ArcGIS Engine JavaRuntime ArcGIS Engine Java Runtime Java Assemblies DotNetRuntime ArcGIS Engine .Net Runtime .Net Assemblies 用下面的命令执行无用户界面、包含 ArcGIS Engine 和.NET 安装特性 的自定义安装: Msiexec.exe /i \setup.msi /qn ADDLOCAL=ArcEngine,DotNetRuntime 部署方针 在卸载开发人员开发的应用程序的过程中,一定不要卸载 ArcGIS Engine 运行时。 只有在用户知道没有第三方应用程序使用 ArcGIS Engine 运行时 的情况下,开发人员才必须推荐用户手工卸载 ArcGIS Engine 运 行时。 注释:应该用控制面板卸载 ArcGIS Engine 运行时,而不是删除 磁盘上的文件。 ArcGIS Engine 运行时不能包括在制作好的 MSI 文件中(嵌套 MSI 安装)。 如果机器上已经安装了 ArcGIS Engine 运行时,则应用程序的安 装程序不用启动 ArcGIS Engine 运行时的安装程序。 不能重新分发单个的 Engine 运行时文件;本章讨论的部署方法是 部署 ArcGIS Engine 运行时文件的唯一方法。 第五章·许可与部署·235 为 ArcGIS Engine 应用程序授权 236·ArcGIS Engine 开发指南 必须将许可初始化内建到应用程序 中。要了解更多信息,请参阅前面 的“独立可执行许可的初始化”一 节。 安装 ArcGIS Engine 开发工具包后, 就会打开软件授权向导。但是, ArcGIS Engine 运行时的安装程序 不会触发软件授权向导自动启动。 为 ArcGIS Engine 应用程序授权 开发和部署 ArcGIS Engine 应用程序的最后一步就是确保所有的客户 机器具有正确的许可配置以支持 ArcGIS Engine 应用程序。本节详细阐 述终端用户和开发人员在客户系统上授权 ArcGIS Engine 运行时组件 的各种方法。 软件授权就是解除对底层 ArcGIS Engine 运行时软件组件的锁定的过 程。开发人员在安装和设置 ArcGIS Engine 开发工具包时要进行软件授权。 一旦安装好了该软件,就会打开一个软 件授权向导。该向导要求用户定位注册 产品时获得的授权文件(.ecp)。只有在 读取并接受授权文件后,用户才可以设 计和运行用 ArcGIS Engine 组件开发的 应用程序。尽管有许多不同的 “授权” 方法,但所有要部署的应用程序都必须 以相似的方式授权。 如本章前面所述,用户建立和部署的每 个应用程序都必须首先用合适的许可 进行初始化。应用程序初始化所需的 “合适”许可存储在客户机器或网络上 任何可用的软件授权文件或关键码文件中。如果应用程序试图用一个 不包含在授权文件中的许可初始化,或者如果所需许可的所有例程都 被关闭,则应用程序不能运行。 开发人员必须预先考虑用户如何获得和访问运行应用程序所需的授权 文件或关键码文件这个问题。开发人员开发的应用程序的客户可以分 为以下三类: 具有许可的 ArcGIS Desktop 用户,可以访问应用程序使用的许可 特性。 可以直接从 ESRI 获取 ArcGIS Engine 运行时软件和/或其授权的 用户。 不与 ESRI 直接联系,而是从开发人员开发的应用程序中获得打 包的 ArcGIS Engine 运行时软件和授权的用户。 下面讨论这三类用户的软件授权过程。 为 ArcGIS Engine 应用程序授权 如果终端用户没有预先注册和接收 授权文件,软件授权向导可以指导 他们完成这个任务。 ArcGIS Desktop 用户 如果客户为具有许可的 ArcGIS Desktop 用户,开发人员及其客户安装 和运行其建立的应用程序要经过以下步骤: 1. 开发人员检查并确认应用程序的许可需求—是 ArcView,ArcEditor 还是 ArcInfo,单用户还是多用户许可,是否需要任何扩展模块。 2. 终端用户确认自己拥有应用程序所需的 ArcGIS Desktop 授权或关 键码文件。 3. 安装自定义 ArcGIS Engine 应用程序。 4. 应用程序启动后就会立即初始化并从客户端已有的授权文件或关 键码文件中检出一个可用的许可。 从 ESRI 获得 ArcGIS 运行时许可的终端用户 第二类终端用户亲自购买和/或授权 ArcGIS Engine 运行时软件。开发 人员及其客户安装和运行其建立的应用程序要经过以下步骤: 1. 开发人员检查并确认应用程序所需的许可。 2. 终端用户购买 ArcGIS Engine 运行时和任何需要的选项(3D,地 理数据库,空间分析,StreetMap 等)。 3. 终端用户到 ESRI(http://www.service.esri.com)注册 ArcGIS Engine 产品及所需的选项。 4. 终端用户从 ESRI 接收一个授权文 件(.ecp)并将其保存到自己的机 器上。 5. 终端用户安装 ArcGIS Engine 运行 时软件。 6. 一旦完成安装,终端用户就可以定 位到\ArcGIS\bin 文件夹并运行其中 的 SoftwareAuthorization.exe 文件。 当软件7. 授权向导要求输入授权文件 8. 9. 始化并 时,终端用户定位授权文件的位置。 开发人员或其客户安装自定义 ArcGIS Engine 应用程序。 应用程序启动后就会立即初 从客户端的授权文件中检出一个可用的许可。 第五章·许可与部署·237 为 ArcGIS Engine 应用程序授权 238·ArcGIS Engine 开发指南 从应用程序的安装程序中运行 SoftwareAuthorization 工具可以 使用以下参量: /LIF /S /S 参量可以触发工具使其不显示 用户界面。 参阅开发帮助以了解更多有关 IauthorizeLicense 接口方面的信 息。 不与 ESRI 直接打交道的终端用户 虽然在这里记录了授权文件在 应用程序中的重新分发,但这 种重新分发的使用是有限制 的。 如果应用程序只在组织内部 使用,则可以用这种方式重 新分发。但是,重新分发的 数目不能超过用户购买的 许可个数。 如果应用程序要买给第三 方使用,使用“重新分发” 的授权文件将违反“标准 ESRI 软件使用许可协议 (standard ESRI Software Master License Agreement)”,必须签订一 个单独的协议。联系 ESRI 商业伙伴或国际销售商以 了解有关这种许可的信息。 要了解其他有关使用和部署 ArcGIS Engine 应用程序方面 的信息,请参阅“ESRI 软件使 用许可协议”的脚注 12 和 23。 应用程序调用包含在安装程序或应用程序中的 SoftwareAuthorization.exe 或 IauthorizeLicense 对象,以解锁 ArcGIS Engine 的功能。这要求开发人员将授权关键码“硬编码”到应用程序 中。这种方法的优点就是软件可以无声的授权,而不用提示用户任何 有关注册方面的信息。在这种情况下,开发人员及其客户安装和运行 其建立的应用程序要经过以下步骤: 1. 开发人员检查并确认应用程序所需的许可。 2. 开发人员购买所需的可重新分发的 ArcGIS Engine 运行时产品及 任何所需的选项(3D,地理数据库,空间分析,StreetMap 等)。 3. 开发人员到 ESRI(http://www.service.esri.com)注册 ArcGIS Engine 产品及所需的选项。 4. 开发人员接收一个可重新分发的授权文件(.ecp)并将其特性添加 到应用程序的编码中。 5. 终端用户安装自定义建立的 ArcGIS Engine 应用程序。这样将: a. 安装 ArcGIS Engine 运行时软件; b. 自动运行\ArcGIS\bin\目录下的 SoftwareAuthorization.exe 或 使用 IauthorizeLicense 对象。 6.应用程序启动后就会立即初始化并从客户端的授权文件中检出一个 可用的许可。 6 开发情景 本书已介绍了一些编程概念和模式以及一些新的 APIs。本章将 通过一些应用程序开发情景来应用这些概念。每个情景都使用 ArcGIS Engine 中可用的工具和 APIs 建立和部署了一个应用程 序。每个情景都是 ArcGIS Engine 开发工具包中的一个 ArcGIS 开发示例,其完整代码可以从该工具包中获得。 本章的开发情景包括: ●用 ActiveX 建立应用程序 ● 用 Visual JavaBeans 建立应用 程序 ● 用 Windows 控件建立应用程序 ● 建立命令行 Java 应 用程序 ● 建立命令行 C++应用程序 用 ActiveX 建立应用程序 用 ActiveX 建立应用程序 用户可以不按照本情景进行,而可 以从示例安装路径获得完整的应用 程序。示例作为 ArcGIS 开发示例的 组成部分而安装。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 ActiveX 是 Microsoft 组件对象模 型(COM)对象的另一个术语。所有 的 ArcObjects 都基于 COM 之上,而 且 ArcGIS 控件都是 COM 对象。 本情景针对那些要用 ActiveX 建立和部署应用程序的开发人员,描述 了使用 ArcGIS 控件建立和部署应用程序的过程。 可以在 ArcGIS 安装目录下的\DeveloperKit\Samples\Developer_Guide _Scenarios\ArcGIS_Engine\Building_an_ArcGIS_Control_Application\Ma p_Viewer 中找到本示例。 工程描述 本情景的目标就是向开发人员展示并使其熟悉在 COM API 中使用 ArcGIS 控件开发和部署 GIS 应用程序所需的步骤。本情景在 Microsoft Visual Basic 6.0 开发环境中使用 MapControl、PageLayoutControl、 TOCControl 和 ToolbarControl 等 ActiveX 控件。使用 C++、Java 和.NET 的开发人员可以参考本章后面的其他情景:“建立命令行 C++应用程 序” 、“用 Visual JavaBeans 建立应用程序” 、 “建立命令行 Java 应用程序” 和“用 Windows 控件建立应用程序”。 本情景展示了创建用于浏览用 ArcGIS Desktop 应用程序 ArcMap 预制 作的地图文档的 GIS 应用程序所需的步骤。本情景涵盖下列技术: 在 Microsoft Visual Basic 6.0 中装载和嵌入 ArcGIS 控件。 将预制作的地图文档装载到 MapControl 和 PageLayoutControl 中。 设置 ToolbarControl 和 TOCControl 伙伴控件。 处理窗体调整大小事件。 向 ToolbarControl 添加控件命令和工具。 创建快捷菜单。 管理 TOCControl 中的标签编辑。 在 MapControl 上绘制几何形状。 创建自定义工具以操作 MapControl 、 PageLayoutControl 和 ToolbarControl。 定制 ToolbarControl。 将应用程序部署到 Windows 操作系统上。 概念 本情景用 Microsoft Visual Basic 6.0 开发环境实现,并使用作为 ActiveX 组件的 ArcGIS 控件。ActiveX 指的是一套能使以不同语言编写的软件 组件在网络环境中协同工作的技术。每个 ActiveX ArcGIS 控件都有事 件、属性和方法,一旦控件被嵌入到象 VB 窗体等 ActiveX 容器中就可 以访问其事件、属性和方法。这些控件中的对象和功能可以与其它的 ESRI ArcObjects 及自定义控件结合以创建定制的终端用户应用程序。 240·ArcGIS Engine 开发指南 用 ActiveX 建立应用程序 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 本情景可以用任何其他完全支持ActiveX的COM开发环境编写,包括 Microsoft Visual C++、 Borland Delphi、Sybase PowerBuilder和Microsoft Visual Basic for Applications (VBA)。虽然Visual Basic不能提供诸如 Visual C++这样的开发环境的全部功能,但由于其受更广大用户的青 睐,本情景选择Visual Basic作为示例代码的编写语言。不管使用何种 开发环境,使用ArcGIS控件进行应用程序开发的成功与否取决于开发 人员所掌握的编程环境和ArcObjects的技能。 本情景使用 MapControl 、 PageLayoutControl 、 TOCControl 和 ToolbarControl 以提供应用程序的用户界面。开发人员同时使用 ArcGIS 控件和其它 ArcObjects 及控件命令来创建 GIS 浏览应用程序。 设计 本情景的设计目的在于:首先强调 ArcGIS 控件之间的相互作用,其次 向开发人员展示各个 ArcGIS 控件对象模型的部分内容。 每个 ActiveX ArcGIS 控件都有一系列属性页,一旦控件被嵌入到 ActiveX 容器中就可以访问这些属性页。这些属性页提供了控件属性和 方法选择的快捷方式,并可以使开发人员不用写任何代码就建立应用 程序。本情景不使用属性页,而是通过程序编写建立应用程序。要了 解有关属性页方面的更多信息,请参考“ArcGIS 开发帮助”。 需求 要成功地按照本情景进行开发,需要下列条件(部署需求将在后面的 “部署”一节中阐述): 安装了 ArcGIS Engine 开发工具包,且具有能用于开发的许可文件。 安装了 Microsoft Visual Basic 6.0 开发环境并有合适的许可。 熟悉 Microsoft Windows 操作系统并具有使用 Microsoft Visual Basic 6.0的经验。虽然本情景提供了一些有关如何在Microsoft Visual Basic 6.0 中使用 ArcGIS 控件方面的信息,但并不能替代该开发环境的培 训。 尽管不需要有其他 ESRI 软件的经验,但具有 ArcObjects 经验并对 诸如 ArcMap 和 ArcCatalog 等 ArcGIS 应用程序具有基本的理解是有 益的。 可以访问本情景的示例数据和代码,其位置为安装目录下的: \DeveloperKit\Samples\Developer_Guide_Scenarios\ArcGIS_Engine\Buildi ng_an_ArcGIS_Control_Application\Map_Viewer 第六章·开发情景·241 用 ActiveX 建立应用程序 可以用“ESRI 自动引用” Visual Basic 插件来快速选择和引用在 Visual Basic 6.0 中经常使用的 ArcGIS 控件和其他 ArcGIS Engine 类库。选择“Add-Ins”菜单中的 “Add-In 管理器”并复选装载行为 复选框来装载该插件。然后从 “Add-Ins”菜单中选择“ESRI 自 动引用”以显示该插件。 本情景所使用的控件和类库包括: MapControl ● TOCControl PageLayoutControl ● ToolbarControl Carto Object Library ● System Object Library Display Object Library ● SystemUI Object Library Geometry Object Library 在 Visual Basic 中,这些控件和类库的名称都带有前缀“esri”。 实现 下面的实现过程提供了成功完成本情景所需的所有代码。本情景没有 提供在 Visual Basic 6.0 中开发应用程序的逐步指导,因为我们假设用 户已经具有 Visual Basic 6.0 开发环境的使用经验。 装载 ArcGIS 控件 在开始编写应用程序前,应用程序要使用的 ArcGIS 控件和其他 ArcGIS Engine 类库参考应装载到开发环境中。 1. 启动 Visual Basic 并从“New project”对话框中创建一个新的“标 准 EXE”工程。 2. 单击“Project”菜单并选择“Components”。 3. 在“Components”对话框中,选上“ESRI MapControl”、“ESRI PageLayoutControl”、“ESRI TOCControl”和“ESRI ToolbarControl”。 单击 OK 按钮。 现在这些控件就会显现在 Visual Basic 的工具箱中。 ESRIMapControl ESRIPageLayoutControl ESRITOCControl ESRIToolbarControl 242·ArcGIS Engine 开发指南 用 ActiveX 建立应用程序 4. 再次单击“Project”菜单并选择“References”。 5. 在“References”对话框中,选上“ESRI Carto Object Library”、“ESRI Display Object Library”、“ESRI Geometry Object Library”、“ESRI System Object Library”和“ESRI SystemUI Object Library”。单击 在 容 OK 按钮。 将 ArcGIS 控件嵌入到容器中 访问控件的属性、方法和事件之前,要将各个控件嵌入到 ActiveX 器中。一旦控件被嵌入到窗体中,就可以塑造应用程序的用户界面。 1. l Basic 窗体。 2. 双击 Visual Basic 工具箱中的 MapControl 图标以将 MapControl 加到窗体上。 PageLay ontrol、TOCControl 和 ToolbarControl 添加到窗体中。 4. 打开 Visua 添 3. 重复步骤 2 以将 outC 如下图所示改变窗体上的各个控件的大小和位置。 第六章·开发情景·243 用 ActiveX 建立应用程序 244·ArcGIS Engine 开发指南 将地图文档装载到 PageLayoutControl 和 MapControl 中 单独的数据层或用 ArcGIS Desktop 应用程序 ArcMap 预制作的地图文 档可以装载到 MapControl 和 PageLayoutControl 中。可以装载 ESRI 提 供的示例地图文档或用户自己的地图文档。可以使用添加一个对话框 以浏览选择地图文档。 1. 双击窗体,显示代码窗口。 2. 选择“Form_Load”事件并输入下列代码(如果使用用户自己的地 图文档,则用该地图文档的名称替换文件名)。 Private Sub Form_Load() 'Check and load a pre-authored map document into the PageLayoutControl using relative paths. Dim sFileName As String sFileName = "..\..\..\..\..\..\Data\ArcGIS_Engine_Developer_Guide\Gulf of St. Lawrence.mxd" If PageLayoutControl1.CheckMxFile(sFileName) Then PageLayoutControl1.LoadMxFile sFileName End If End Sub 3. 选择“PageLayoutControl_OnPageLayoutReplaced”事件并输入下 列代码,将同一个地图文档装载到MapControl中。当文档装载到 PageLayoutControl中时会触发OnPageLayoutReplaced事件。 Private Sub PageLayoutControl1_OnPageLayoutReplaced(ByVal newPageLayoutAs Variant) 'Load the same pre-authored map document into the MapControl. MapControl1.LoadMxFile PageLayoutControl1.DocumentFilename 'Set the extent of the MapControl to the full extent of the data. MapControl1.Extent = MapControl1.FullExtent End Sub 设置 TOCControl 和 ToolbarControl 的伙伴控件 由于这个应用程序的缘故,TOCControl 和 ToolbarControl 将与 PageLayoutControl 而不是 MapControl 协同工作。为此必须将 PageLayoutControl 设置为伙伴控件。TOCControl 使用伙伴控件的活动 视图(ActiveView)以存放其地图、图层和符号体系,而 ToolbarControl 上的所有命令、工具或菜单项均会与伙伴控件的显示交互。 1. 双击窗体,显示代码窗口。 用 ActiveX 建立应用程序 2. 选择“Form_Load Private Sub Form_Load() ent into the tring "..\..\..\..\..\..\D rcGIS_Engine_Developer_Guide\Gulf hen xFile sFileName dyControl PageLayoutControl1 Control PageLayoutControl1 3. 档已经被装载到 PageLayoutControl 中,而 选择和取消 闭图层的可见性。默 下,地图文档中的活动地图被装载到 MapControl 中。此时 ntrol 是空的,因为还没有向其添加命令。试着改变窗体 处理窗体调整大小事件 在运行时调整窗体大小并不会使 tControl 和 MapControl 自动 ”事件并在装载文档后输入下列代码: 'Check and load a pre-authored map docum PageLayoutControl using relative paths. Dim sFileName As S sFileName = ata\A of St. Lawrence.mxd" If PageLayoutControl1.CheckMxFile(sFileName) T PageLayoutControl1.LoadM End If 'Set buddy controls. TOCControl1.SetBud ToolbarControl1.SetBuddy End Sub 运行应用程序。地图文 TOCControl 列出了该地图文档中的数据层。使用通过 选择 TOCControl 的可见性复选框来打开和关 认情况 ToolbarCo 的大小,会注意到控件的大小并没有改变。 PageLayou 调整大小。为了调整控件大小以便它们总是填充满窗体范围,开发人 员必须对 Form_Resize 事件作出响应。如果 PageLayoutControl 或 MapControl 包含大量数据,在 Form_Resize 过程中重绘这些数据的代 价很高。为提高性能,开发人员可以禁止数据重绘,直到调整大小完 成。在调整大小过程中,将绘制一个被拉伸的位图。 245 第六章·开发情景· 用 ActiveX 建立应用程序 246·ArcGIS Engine 开发指南 2. 选择“Form_Resize”事件并输入下列代码: ble, dheight As Double, dMargin As Double CControl1.Left Control. - dMargin ht = dheight = Form1.ScaleWidth - TOCControl1.Width - (dMargin * 2) ageLayoutControl1.Width = dWidth gin > 0 Then MapControl1.Height = dheight 3. ' … 'Suppress drawing while resizing MapControl1.SuppressResizeDrawing False, Form1.hWnd PageLayoutControl1.SuppressResizeDrawing False, Form1.hWnd End Sub 4. 运行应用程序并试着调整窗体大小。 向 ToolbarControl 添加命令 ArcGIS Engine 带有 120 多个直接操作 MapControl、PageLayoutControl 和 ToolbarControl 的命令和工具。这些命令和工具为用户提供了许多经 常使用的地图导航、图形管理和要素选择等 GIS 功能。现在开发人员 将向应用程序添加部分这些命令和工具。 . 双击窗体,显示代码窗口。 . 选择“Form_Load”事件并在装载文档代码前输入下列代码: Private Sub Form_Load() Dim sProgID As String 'Add generic commands. 1. 双击窗体,显示代码窗口。 Private Sub Form_Resize() Dim dWidth As Dou 'Set the margin size. dMargin = TO 'Resize the PageLayout dheight = Form1.ScaleHeight - PageLayoutControl1.Top If dheight > 0 Then PageLayoutControl1.Heig dWidth If dWidth > 0 Then P 'Resize the MapControl. dheight = Form1.ScaleHeight - MapControl1.Top - dMar If dheight End Sub 选择“Form_Load”事件并在该程序的末尾添加下列代码: Private Sub Form_Load() Set buddy controls 1 2 用 ActiveX 建立应用程序 第六章·开发情景·247 sProgID = "esriControlTools.ControlsOpenDocCommand" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly 'Add PageLayout navigation commands. sProgID = "esriControlTools.ControlsPageZoomInTool" ToolbarControl1.AddItem sProgID, , , True, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsPageZoomOutTool" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsPagePanTool" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsPageZoomWholePageCommand" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsPageZoomPageToLast ExtentBackCommand" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsPageZoomPageToLastExten tForwardCommand" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly 'Add Map naviagtion commands. sProgID = "esriControlTools.ControlsMapZoomInTool" ToolbarControl1.AddItem sProgID, , , True, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsMapZoomOutTool" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsMapPanTool" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly sProgID = "esriControlTools.ControlsMapFullExtentCommand" ToolbarControl1.AddItem sProgID, , , False, , esriCommandStyleIconOnly 'Load a pre-authored… End Sub 3. 运行应用程序。ToolbarControl 现在包含 ArcGIS Engine 命令和工具, 用户可以用这些命令和工具导航装载到 PageLayoutControl 中的地图 文档。用“页面布局”命令集导航页面布局,用“地图”命令集导航 数据框中的数据。用“open document”命令浏览和装载其他地图文 档。 用 ActiveX 建立应用程序 248·ArcGIS Engine 开发指南 捷菜单 便操作伙伴控件一 样,开发人员也可以为控件命令集创建快捷菜单。下面将给应用程序 添加一个操作 PageLayoutControl 的快捷菜单。每当在 Page 1. 添加下列代码: O Private m_pToolbarMenu As IToolbarMenu 'The popup menu 2. rControl 添加命令代码段之后和装 P 'A 'Create a new ToolbarMenu. S w ToolbarMenu 'S Set m_pToolbarMenu.CommandPool 'A sProgID = "esriControlTools.ControlsPageZoomInFixedCommand" m_pToolbarMenu.AddItem sProgID, , , False, sProgID = "esriControlTools.ControlsPageZoomOutFixedCommand" m ProgID, , , False, esriCommandSty sProgID = "esriControlTools.ControlsPageZoomWholePageCommand" m_pToolbarMenu.AddItem , , , False, e s xtent B m_pToolbarMenu.AddIte esriCommandStyleIconAndText sProgID = "esriControlTools.ControlsPageZoomPageToLastExtent m_pToolbarMenu.AddItem sProgID, , , False, nMouseDown(ByVal button As Long, Val y As Long, ByVal pageX 'Pop If button = vbRightButton Then m_pToolbarMenu.PopupMenu x, y, PageLayoutControl1.hWnd End If End Sub 为 PageLayoutControl 创建快 与前一个步骤中向 ToolbarControl 添加控件命令以 LayoutControl 显示区域上单击鼠标右键时都会显示快捷菜单。 在窗体的一般声明区 ption Explicit 在 Form_Load 事件中向 Toolba 载文档代码之前添加下列代码: rivate Sub Form_Load() dd Map naviagtion commands… et m_pToolbarMenu = Ne hare the ToolbarControl’s command pool. = ToolbarControl1.CommandPool dd commands to the ToolbarMenu. esriCommandStyleIconAndText _pToolbarMenu.AddItem s leIconAndText sProgID sriCommandStyleIconAndText ProgID = "esriControlTools.ControlsPageZoomPageToLastE ackCommand" msProgID, , ,True, ForwardCommand" esriCommandStyleIconAndText 'Set the hook to the PageLayoutControl. m_pToolbarMenu.SetHook PageLayoutControl1 'Load a pre-authored… End Sub 3. 给PageLayoutControl1_OnMouseDown事件添加下列代码: Private Sub PageLayoutControl1_O ByValshift As Long, ByVal x As Long, By AsDouble, ByVal pageY As Double) up the ToolbarMenu 用 ActiveX 建立应用程序 4. 示快捷菜单, 见性 的起始处添加下列代码以触发 TOCControl 的标 签编辑事件。 ate Sub Form_Load() 2. 在TOCControl1_OnEndLableEdit事件中添加下列代码: Private Sub TOCControl1_OnEndLabelEdit(ByVal x As Long, ByVal y As Long, ByVal newLabel As String, pcanEdit As Boolean) 'If the new label is an empty string, then prevent the edit. If newLabel = "" Then pcanEdit = False End Sub 3. 运行应用程序。要编辑 TOCControl 中的地图、图层、标题或图例 类标签,可以先单击该标签一次,然后再次单击该标签以引起标 签编辑。试着用空字符串替代标签。在编辑过程中可以随时用键 盘上的 ESC 键取消编辑。 运行应用程序。右击 PageLayoutControl 的显示区以显 并在页面布局上导航。 控制 TOCControl 中的标签编辑 默认情况下,TOCControl 允许用户在内容表中自 及改变地图和图层名。开发人员要添加代码以防止用户编辑名称并用 空字符串替代该名称。 1. 在 Form_Load 事件 Priv 动切换图层的可 'Set label editing to manual. TOCControl1.LabelEdit = esriTOCControlManual 'Add generic commands… End Sub 第六章·开发情景·249 用 ActiveX 建立应用程序 250·ArcGIS Engine 开发指南 用地图导航工具移动活动地图会改 变 PageLayoutControl 中活动地图 的范围并引起 MapControl 的更新。 用地图导航工具移动页面布局会改 变页面布局的范围(而不是 PageLayoutControl 中活动地图的 范围),因此 MapControl 不会更新。 Control 1. 在窗体的一般声明区添加下列代码: Option Explicit Private m_pToolbarMenu As IToolbarMenu Private m_pEnvelope As IEnvelope 'The envelope drawn on the MapControl Private m_pFillSymbol As ISimpleFillSymbol 'The symbol used to draw the 'envelope on the MapControl Private WithEvents m_pTransformEvents As DisplayTransformation 'The PageLayoutControl's focus map events 2. 创建一个新的名为“CreateOverviewSymbol”的子程序。开发人员 在这个子程序中创建 MapControl 中要使用的符号,以表示 PageLayoutControl 的活动地图中的数据范围。在该子程序中添加 下列代码: Private Sub CreateOverviewSymbol() 'Get the IRGBColor interface. Dim pColor As IRgbColor Set pColor = New RgbColor 'Set the color properties. pColor.RGB = RGB(255, 0, 0) 'Get the ILine symbol interface. Dim pOutline As ILineSymbol Set pOutline = New SimpleLineSymbol 在 MapControl 上绘制几何图形 现在开发人员将用 MapControl 作为概览窗口并绘制 PageLayout 显示的活动地图的当前范围。当用户在 PageLayoutControl 的数据框内 移动时,用户会看到 MapControl 的概览窗口的更新。 用 ActiveX 建立应用程序 'Set the line symbol properties. pOutline.Width = 1.5 pOutline.Color = pColor 'Get the IFillSymbol interface. Set m_pFillSymbol = New SimpleFillSymbol 'Set the fill symbol properties. m_pFillSymbol.Outline = pOutline m_pFillSymbol.Style = esriSFSHollow 3. ontrol 标签编辑代码前调用 子程序。 the MapControl. 4. 该 范围什么时候改变。 ol_OnPageLayout youtReplaced(ByVal outControl. Events of the PageLayoutCntrol's focus map. ransformation ActiveView.Extent 'Load the same pre-authored map document into the MapControl. ntrol1.LoadMxFile PageLayoutControl1.DocumentFilename isibleBoundsUpdated 事件中添加下列代 置地图新 可见范围的封装边界。通过刷新 MapControl,可以强制重绘其 上的几何图形。 End Sub 在 Form_Load 事件中的 TOCC CreateOverviewSymbol Private Sub Form_Load() 'Create symbol used on CreateOverviewSymbol 'Set label editing to manual… End if PageLayoutControl的缺省事件接口是IPageLayoutControlEvents。 接口中的事件不会告诉用户数据框中的地图 要完成这个任务,开发人员将使用PageLayoutControl活动地图的 ITransformEvents接口。在PageLayoutContr Replaced事件中紧挨着装载文档代码的上面添加下列代码: Private Sub PageLayoutControl1_OnPageLa newPageLayoutAs Variant) 'Get the IActiveView of the focus map in the PageLay Dim pActiveView As IActiveView Set pActiveView = PageLayoutControl1.ActiveView.FocusMap 'Trap the ITranform Set m_pTransformEvents = pActiveView.ScreenDisplay.DisplayT 'Get the extent of the focus map. Set m_pEnvelope = p MapCo 'S MapControl1.Extent = MapControl1.FullExtent End Sub 5. 在 m_pTransformEvents_V 码。当地图范围发生变化时就会触发这个事件并用于设 的 显示 et the extent of the MapControl to the full extent of the data. 第六章·开发情景·251 用 ActiveX 建立应用程序 252·ArcGIS Engine 开发指南 isplayTransformation, ByVal sizeChanged As Boolean) 'Set the extent to the new visible extent. Set m_pEnvelope = sender.VisibleBounds 'Refresh the MapControl's foreground phase. MapControl1.Refresh esriViewForeground End Sub 6. 在MapControl_OnAfterDraw事件中添加下列代码,以便用先前创 建的符号在MapControl上显示封装边界。 Private Sub MapControl1_OnAfterDraw(ByVal display As Variant, ByVal viewDrawPhase As Long) If m_pEnvelope Is Nothing Then Exit Sub 'If the foreground phase has drawn Dim pViewDrawPhase As esriViewDrawPhase Phase If pViewDrawPhase = esriViewForeground Then End Sub 7. 运行应用程序。使用前面添加的地图导航工具改变 PageLayoutControl中活动地图的范围。新的范围绘制在MapControl 上。 Private Sub m_pTransformEvents_VisibleBoundsUpdated(ByVal sender AsesriDisplay.ID pViewDrawPhase = viewDraw 'Draw the shape on the MapControl. MapControl1.DrawShape m_pEnvelope, m_pFillSymbol End If 用 ActiveX 建立应用程序 命令类在一个单独的 ActiveX DLL 工程中实现,而不是在 ActiveX exe 工程中实现,因为除非命令处于 ,否则命令不会成为 COM 类。 DLL 内 本情景的源代码位于安装目录下的 \DeveloperKit\Samples\Develope r_Guide_Scenarios\ ArcGIS_Engine\Building_an_ArcG IS_Control_Application\Map_Vie wer 目录中。 创建自定义工具 创建操作 MapControl 和 PageLayoutControl 的自定义命令和工具与先前 在 ESRI ArcMap 应用程序中创建命令非常相似。开发人员将创建一个 自定义命令,在鼠标点击位置上向 PageLayoutControl 添加一个包含当 日日期的文本元素。当然,开发人员将可以创建操作 MapControl、 ToolbarControl 及 PageLayoutControl 的命令。 这个自定义工具的代码可以在本情景后面的源代码中获得。如果用户 想直接使用这个自定义命令而不是亲自创建该命令,请直接跳至步骤 12。 1. 启动 Visual Basic 并在“New project”对话框中创建一个新的 y”、“ESRI RI Display Object Library”、 Object Library”、“ESRI System Object Library” K 按钮。 5. 6. ControlCommands.res Visual Basic 资源 7. 声明区添加下列代码。 8. alize 和 Class_Terminate 方法中添加下列代码。 'Load resources re("Date", vbResBitmap) ookHelper En Pr 'Clear variables En ActiveX DLL 工程。 2. 将工程命名为“EngineScenarioCommands”。 3. 再次单击“Project”菜单并选择“References”。 4. 在“References”对话框中,复选“ESRI Carto Object Librar Control Commands Object Library”、“ES “ESRI Geometry 和“ESRI SystemUI ObjectLibrary”。单击 O 向工程中添加一个类模块并将其命名为“AddDateTool”。 从本示例源代码位置添加 文件。完成这个任务需要 VB6 资源编辑器插件。 在 AddDateTool 类模块的一般 Option Explicit Implements ICommand Implements ITool Private m_pHookHelper As IHookHelper Private m_pBitmap As IPictureDisp 在 Class_Initi Private Sub Class_Initialize() Set m_pBitmap = LoadResPictu 'Create a HookHelper Set m_pHookHelper = New H d Sub ivate Sub Class_Terminate() Set m_pHookHelper = Nothing Set m_pBitmap = Nothing d Sub 第六章·开发情景·253 用 ActiveX 建立应用程序 254·ArcGIS Engine 开发指南 9. 现在 d 接口的所有属性和方法,即使 不使用这些属性和方法。向 ICommand 的属性和方法添加下列代 码。 Pr itmap() As es ap En Private Property and_Caption = "Add Date" ategory() As String "CustomCommands" Pr and_Checked() As Boolean perty Message() As String out" b ICommand_OnClick() Private Sub ICommand_OnCreate(By k 开发人员需要完成 IComman ivate Property Get ICommand_B riSystem.OLE_HANDLE ICommand_Bitmap = m_pBitm d Property Get ICommand_Caption() As String IComm End Property Private Property Get ICommand_C ICommand_Category = End Property ivate Property Get IComm ICommand_Checked = False End Pro Private Property Get ICommand_Enabled() As Boolean If Not m_pHookHelper.ActiveView Is Nothing Then ICommand_Enabled = True Else ICommand_Enabled = False End If End Property Private Property Get ICommand_HelpContextID() As Long 'Not implemented End Property Private Property Get ICommand_HelpFile() As String 'Not implemented End Property Private Property Get ICommand_ ICommand_Message = "Adds a date element to the page lay End Property Private Property Get ICommand_Name() As String ICommand_Name = "CustomCommands_Add Date" End Property Private Su 'Not implemented End Sub Val Hook As Object) Set m_pHookHelper.Hook = Hoo End Sub 用 ActiveX 建立应用程序 ICommand_Create 事件会给命令操 作的应用程序传递一个句柄或钩 子。在本例中可以是 MapControl、 PageLayoutControl 或 ToolbarControl。开发人员不是在 OnCreate 事件中添加代码以确定 传递给命令的钩子的类型,而是使 用 HookHelper 来完成这个任务。命 令或工具需要知道如何处理其传递 的钩子,因此需要执行一个检查以 确定已被传递的 ArcGIS 控件的类 型。HookHelper 用于保留该钩子并 返回活动视图而不管钩子的类型 (在本例中是 MapControl、 PageLayoutControl 或 ToolbarControl)。 10. 属性和方法,即使不使用 : stem.OLE_HANDLE End Property Menu(ByVal x As Long, ByVal y 'N End F Private Sub ITool_OnDblClick() 'N End S Private Sub ITool_OnKey Code As Long, ByVal shift 'N End Sub , ByVal shift ub Private Sub ITool_OnMouseDown(ByVal button As Long, ByVal shift As Long, ByVal x As Long, ByVal y As Long) 'Get the active view. Dim pActiveView As IActiveView Set pActiveView = m_pHookHelper.ActiveView 'Create a new text element. Dim pTextElement As ITextElement Set pTextElement = New TextElement 'Create a text symbol. Dim pTextSymbol As ITextSymbol Set pTextSymbol = New TextSymbol 'Create a font. Dim pFont As stdole.StdFont Set pFont = New stdole.StdFont pFont.Name = "Arial" Private Property Get ICommand_Tooltip() As String ICommand_Tooltip = "Add date" End Property 开发人员现在需要完成 ITool 接口的所有 它们。向 ITool 的属性和方法添加下列代码 Private Property Get ITool_Cursor() As esriSy ‘Not implemented Private Function ITool_Deactivate() As Boolean ITool_Deactivate = True End Function Private Function ITool_OnContext As Long) As Boolean ot implemented unction ot implemented ub Down(ByVal key As Long) ot implemented Private Sub ITool_OnKeyUp(ByVal keyCode As Long As Long) 'Not implemented End S 第六章·开发情景·255 用 ActiveX 建立应用程序 256·ArcGIS Engine 开发指南 pFont.Bold = True 25 ent properties Dim pElement As IElement Dim pPoint As IPoint Set pPoint = 'Set the elements ViewGraphics, Val shift As Long, ByVal x As Long, ByVal y As Long) End Sub Val shift x As Long, ByVal y As Long) 11. ActiveX DLL 。将其命名为 Co 12.注册 13. 开始创建的 Visual Basic 标准可执行工程中,选择 航命令代码后面添加下列代码。 Private Sub Form_Load() … pFont.Size = 'Set the symbol properties. pTextSymbol.Font = pFont 'Set the text elem pTextElement.Symbol = pTextSymbol pTextElement.Text = Date 'QI for IElement Set pElement = pTextElement 'Create a page point. pActiveView.ScreenDisplay.DisplayTransformation.ToMapPoint( x, y) geometry. pElement.Geometry = pPoint 'Add the element to the graphics container. pActiveView.GraphicsContainer.AddElement pTextElement, 0 'Refresh the graphics pActiveView.PartialRefresh esri Nothing, Nothing End Sub Private Sub ITool_OnMouseMove(ByVal button As Long, By 'Not implemented Private Sub ITool_OnMouseUp(By As Long, ByVal Val button As Long, By 'Not implemented End Sub Private Sub ITool_Refresh(ByVal hdc As esriSystem.OLE_HANDLE) 'Not implemented End Sub 现 程编译为在需要将工 ntrolCommonds.dll。 ControlCommonds.dll。 在本情景一 Form_Load 事件并在添加地图导 'Add Map navigation commands 用 ActiveX 建立应用程序 'Add custom date tool. sProgID = "EngineScenarioCommands.AddDateTool" esr ToolbarControl1.AddItem sProgID, , , True, , iCommandStyleIconAndText 14.运行 PageLayoutControl 添加一个 包含当日日期的文本元素。 与在 Fo ontrol 添加控件命令和工具一样, 开发 以通过定制 ToolbarControl 并使用定制对话框来添加命 令和 发人员要使 ToolbarControl 处于定制模式并显示定 制对 1. 在窗体的一般声明区中添加下列代码: lSymbol As IFillSymbol ransformation ize dialog events 'T 2. CreateCustomizeDialog”的新子程序并向该子程序 添 加下 tomizeDialog() _pCustomizeDialog title. 'Create a new ToolbarMenu… End Sub 应用程序并使用 AddDateTool 向 oolbarControl rm_Load 事件中向 ToolbarC 人员也可 工具。这样做开 话框。 Option Explicit Private m_pToolbarMenu As IToolbarMenu Private m_pEnvelope As IEnvelope Private m_pFil Private WithEvents m_pTransformEvents As DisplayT Private WithEvents m_pCustomizeDialogEvents As og CustomizeDial ‘The custom Private m_pCustomizeDialog As ICustomizeDialog he customize dialog box used by the ToolbarControl 添加一个名为“ 中添加下列代码。这就是创建定制对话框的地方。向子程序中 列代码: Private Sub CreateCus Set m_pCustomizeDialog = New CustomizeDialog Set m_pCustomizeDialogEvents = m 'Set the 定制 T 第六章·开发情景·257 用 ActiveX 建立应用程序 258·ArcGIS Engine 开发指南 dd from File' button. 'Set the ToolbarControl that new items will be added to. tion = 3. 在 l 子程序之前调用 C Private Sub Form_Load() log on the MapControl… 4. 向窗体中添加一个复选框并将其命名为“chkCustomize”,标题为 ze”。 5. Private Sub chkCustomize_Click() ' ide the customize dialog box. If chkCustomize.Value = 0 Then m g.StartDialog ToolbarControl1.hWnd 6. 向 m_pCustomizeDialogEvents_OnCloseDialog 和 m_pCustomize ialog() ToolbarControl1.Customize = False nts_OnStartDialog() 7. ToolbarControl 设置为定制模 m_pCustomizeDialog.DialogTitle = "Customize ToolbarControl Items" 'Show the 'A m_pCustomizeDialog.ShowAddFromFile = True Set m_pCustomizeDialog.DoubleClickDestina ToolbarControl1 End Sub Form_Load 事件调用 CreateOverviewSymbo re 。 ateCustomizeDialog 子程序 'Create the customize dialog box for the ToolbarControl. CreateCustomizeDia 'Create symbol used End Sub “Customi 在 chkCustomize_Click 事件中添加下列代码。 Show or h m_pCustomizeDialog.CloseDialog Else _pCustomizeDialo End If End Sub DialogEvents_OnStartDialog 事件添加下列代码。 Private Sub m_pCustomizeDialogEvents_OnCloseD chkCustomize.Value = 0 End Sub Private Sub m_pCustomizeDialogEve ToolbarControl1.Customize = True End Sub 运行应用程序,复选“定制”框以将 式并打开定制对话框。 用 ActiveX 建立应用程序 在定制对话框中,“Add From File…”按钮可以用来浏览和选择 先前编译好的控件命令.dll 文件。 AddDateTool 将被添加到定制对话 框的“CustomCommands”类目中。 8. 在“ 卡上选择“Graphic Element”类目并双击“Select Elem ToolbarControl 上。通过右击 Too 调整命令的外观,如样式和分组等。 Commands”选项 ents”命令以将该命令添加到 lbarControl 上的命令项可以 停止定制应用程序。用选择工具移除包含当日日期的文本元素。9. 第六章·开发情景·259 用 ActiveX 建立应用程序 260·ArcGIS Engine 开发指南 在使用 ESRI ArcObjects 开发独立 的可执行应用程序时,应用程序要 负责检查和配置许可选项。组件对 象类 AoInitialize 及其实现的 IAoInitialize 接口被设计用于支 持许可配置。许可初始化必须在应 用程序启动、访问任何 ESRI ArcObjects 功能之前执行。许可初 始化失败会导致应用程序错误。 部署 为了将本应用程序成功地部署在另一台机器上,应用程序必须配置许 可。首先,必须检查是否有可用的产品许可,其次,必须初始化该许 可。如果许可配置失败,应用程序不能运行。 在窗体的一般声明区添加下列成员变量: Option Explicit Private m_pAoInitialize As IAoInitialize 'The initialization object 2. 在 Form_Load 事件的起始处添加下列代码: Private Sub Form_Load() 'Create a new AoInitialize object. Set m_pAoInitialize = New AoInitialize If m_pAoInitialize Is Nothing Then MsgBox "Unable to initialize. This application cannot run!" Unload Form1 Exit Sub End If 'Determine if the product is available. If m_pAoInitialize.IsProductCodeAvailable(esriLicenseProduct CodeEngine)= esriLicenseAvailable Then If m_pAoInitialize.Initialize(esriLicenseProductCodeEngine) <> esriLicenseCheckedOut Then MsgBox "The initialization failed. This application cannot run!" Unload Form1 Exit Sub End If Else MsgBox "The ArcGIS Engine product is unavailable. This application Unload 事件中添加下列代码: oInitilaize object 4. 1. cannot run!" Unload Form1 Exit Sub End If End Sub 3. 在 Form_ Private Sub Form_Unload(Cancel As Integer) 'Shut down the A m_pAoInitialize.Shutdown End Sub 将应用程序编译城可执行文件。 用 ActiveX 建立应用程序 为成功地将本应用程序部署在用户机器上: 应用程序中包含自定义命令的可执行文件和 DLL 文件需要部署 到用户机器上。 用户机器上需要安装 ArcGIS Engine 运行时和一个标准的 ArcGIS Engine 许可。 其他资源 下列资源可能有助于开发人员理解和应用本情景提出的概念和技术。 ArcGIS Engine 开发工具包中的其他文档,包括 ArcGIS 开发帮助、 组件帮助、对象模型图和示例,以帮助开发人员快速入门。 ArcGIS开发在线网站—提供有关ArcGIS开发方面的最新信息,包 括更新的示例和技术文档。网址为 http://arcgisdeveloperonline.esri.com。 ESRI在线论坛网站—提供来自其他ArcGIS开发人员的无价的帮 助。网址为http://support.esri.com并点击“用户论坛(User Forums)”选项卡。 Microsoft 公司有关 Visual Basic 6 开发环境方面的文档。 第六章·开发情景·261 用 Visual JavaBeans 建立应用程序 用 Visual JavaBeans 建立应用程序 用户可以不按照本情景进行,而可 以从示例安装路径获得完整的应用 程序。示例作为 ArcGIS 开发示例的 组成部分而安装。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 本情景针对那些要用 Visual Java 组件建立和部署应用程序的开发人 员,描述了使用 ArcGIS Engine 开发工具包中的 Visual JavaBeans 建 立和部署应用程序的过程。 可以在 ArcGIS 安装目录下的\DeveloperKit\Samples\Developer_ Guide_Scenarios\ArcGIS_Engine\Building_an_ArcGIS_Control_App lication\Map_Viewer 中找到本示例。 工程描述 本情景展示了创建用于查看用 ArcGIS Desktop 应用程序 ArcMap 预制 作的地图文档的 GIS 应用程序所需的步骤。本情景涵盖下列技术: 设置开发环境 用可视化组件建立 GUI 装载地图文档 向工具条上添加命令 设置 ToolbarControl 和 TOCControl 的伙伴控件 向 ToolbarControl 上添加工具条命令项 用 ToolbarMenu 创建快捷菜单 控制 TOCControl 中的标签编辑 在 MapControl 上绘制“概览矩形” 创建自定义工具 定制 ToolbarControl 使用可执行 JAR 进行部署 概念 ArcGIS Engine 开发工具包提供了对应于每个 ArcGIS 控件的可重用 visual Java 组件。本开发情景将展示如何将这些组件嵌入到 Java GUI 中以建立一个“地图浏览器”应用程序。 ArcGIS Engine 开发工具包提供的可视化组件是符合 JavaBeans 组件架 构的重量级 AWT 组件,允许在 JavaBean 兼容 IDEs 中用作拖放式组件 以设计 Java GUIs。每个组件都有特定的属性和方法,而且可以触发事 件。这些 Java 组件内部使用 JNI(Java 本地接口,Java Native Interface)来驻留 ArcGIS 控件,因此其执行速度几乎与使用控件建 立的任何本地应用程序相同。通过在 Java 应用程序中组装 ArcGIS Engine 可视化组件并将它们之间以及与其他 ArcObjects 组件相结合, 可以快速建立自定义 GIS 应用程序并部署到支持 ArcGIS Engine 的平 台上。 262·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 ArcGIS Engine 开发工具包的典型 安装中不包括 visual JavaBeans。 如果没有安装 visual JavaBeans, 重新运行开发工具包安装向导并选 择 ArcGIS Engine 下的 Java 特性。 此外,为访问 Javadoc 和其他特定 Java 文档,选择软件开发工具包下 的 Java 特性。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 设计 在这个应用程序中,MapControl、PageLayoutControl、TOCControl 和 ToolbarControl 驻留在一个 javax.swing.JFrame 容器中,并在相 互之间以及与其他 ArcGIS Engine 对象之间发生相互作用以提供 GIS 浏览功能。 本情景最先使用 GridLayout 布局管理器放置组件来创建一个 GUI。一 旦各个组件被添加到 JFrame 容器中,就可以用 setBuddy 方法将它们 相互连接起来。在这个阶段,应用程序已经是一个简单的地图浏览器 了。 本情景然后通过建立自定义工具和展示事件处理扩展了简单地图浏览 器的功能。为实现这个目标,本情景进一步探索了 ArcGIS Engine 的 可视化和非可视化组件 API。 虽然这些组件可以在支持可视化 GUI 设计的 Java IDE 中用作“拖放式” JavaBeans,本情景将通过程序编写来放置这些组件;这有助于更好地 理解代码。要了解有关将组件用作“拖放式”JavaBeans 方面的更多信 息,请参考“ArcGIS 开发帮助”。 需求 要成功地按照本情景进行开发,需要下列条件(部署需求将在后面的 “部署”一节中阐述): 安装了 ArcGIS Engine 开发工具包(包括 Java),且具有能用于 开发的授权文件。 安装了Java 2 平台、标准编辑(J2SE)软件开发工具包(SDK), 最好是 1.4.2 或更高版本。如果没有这些软件,可以从Java网站 http://java.sun.com/j2se/downloads.html下载。 选择一种 Java IDE 或喜欢的文本编辑器。 对 Java 编程语言有初步到中等程度的了解。 尽管不需要有其他 ESRI 软件的经验,但具有 ArcObjects 经验并 对地图具有基本的理解是有益的。 可以访问本情景的示例数据和代码。其位置为安装目录下的: \DeveloperKit\Samples\Developer_Guide_Scenarios\ArcGIS_E ngine\Building_an_ArcGIS_Control_Application\Map_Viewer 为建立这个应用程序,会使用 ArcGIS Engine 开发工具包中的下列可 视化组件: map.MapControl pagelayout.PageLayoutControl TOC.TOCControl toolbar.ToolbarControl toolbar.ToolbarMenu 在 Java API 中,这些组件的前缀为“com.esri.arcgis.beans”。 第六章·开发情景·263 用 Visual JavaBeans 建立应用程序 此外,还会用到来自下列类库的对象: Carto ●Geometry Display ●SystemUI 在 Java API 中,这些包名的前缀为“com.esri.arcgis”。 为引用上面提及的包,必须将下列 JAR 文件添加到类路径中: arcobjects.jar,位于安装目录下的\ArcGIS\java\opt arcgis_visualbeans.jar,位于安装目录下的\ArcGIS\java\opt jintegra.jar,位于安装目录下的\ArcGIS\java 实现 要实现本情景,请按下面的步骤进行。虽然本情景使用的是示例附带 的 St Lawrence 海湾地图文档,但用户可以使用自己的地图文档。下 面的实现过程提供了成功完成本情景所需的所有代码。本情景没有提 供在 Java 中开发应用程序的逐步指导,因为我们假设用户已经具有 Java 开发环境的使用经验。 设置开发环境 为了用 ArcGIS Engine 开发工具包编译和运行应用程序,PATH 环境变 量应包括: ArcGIS/bin J2SE SKE/bin J2SE SDK/jre/bin 在 Windows 操作系统上设置 PATH 环境变量的方法为: 1. 右击“我的电脑”图标并选择下拉菜单中的“属性”菜单项。 2. 在“系统属性”窗口中,单击“高级”选项卡。 3. 单击“环境变量”按钮。 4. 在“环境变量”窗口中,双击“系统变量”列表框中的“Path” 变量。 5. 将 ArcGIS/bin 文件夹和 J2SE SDK 及 JRE 的 bin 目录添加到“变 量值”文本框中。例如,如果 ArcGIS 开发工具包安装在 C:\Program Files\ArcGIS 目录中,J2SE SDK 安装在 C:\j2sdk1.4.2 目录中, 则应该将下列文本添加到 PATH 环境变量中: C:\Program Files\ArcGIS\Bin;C:\j2sdk1.4.2\jre\bin;C:\j2sdk1. 4.2\bin 264·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 用自己喜欢的文本编辑器或 IDE 编 写源代码。 用可视化组件建立 GUI 1. 创建一个名为“MapViewerFrame.java”的新 Java 类。这个类将 提供地图浏览器应用程序的 GUI 和功能。将这个类作为 javax.swing.JFrame 的子类来实现。为 WindowClosing 事件添加 一个事件侦听器,当通过单击 X 按钮关闭框架时退出应用程序。 //MapViewerFrame.java import javax.swing.JFrame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; public class MapViewerFrame extends JFrame { //constructor public MapViewerFrame() { setTitle("MapViewer"); } public void buildAndShow() throws IOException { this.show(); addEventListeners(); } public void addEventListeners()throws IOException { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } 2. 创建一个名为“MapViewer.java”的新 Java 类。这个类提供 main() 方法以创建 MapViewerFrame,给该框架一个初始大小并启动该框 架: //MapViewer.java import java.awt.Dimension; import java.awt.Toolkit; import java.io.IOException; import javax.swing.UIManager; import com.esri.arcgis.system.EngineInitializer; public class MapViewer { static { try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.Win dowsLookAndFeel"); } catch (Exception e) { //ignore } } 第六章·开发情景·265 用 Visual JavaBeans 建立应用程序 使用 ArcGIS Engine 开发工具包的 可视化 JavaBean 组件的任何应用 程序应该在其类路径中包括以下三 个 JAR 文件: …\ArcGIS\java\opt\arcobjects. jar …\ ArcGIS\java\opt\arcgis_ visualbeans.jar …\ArcGIS\java\jintegra.jar 这些文件提供了从 Java 访问 ArcObjects 所需的运行时库。 当使用 Java IDE 时,类路径一般通 过在 Java 建立路径中添加上面提 及的作为引用库的 JAR 文件来设 置。 266·ArcGIS Engine 开发指南 要运行 Java 应用程序,单击 IDE 中的“运行”按钮或从命令行给出 下面的命令: java -classpath “C:\Program Files\ArcGIS\java\opt\arcgis_v isualbeans.jar; C:\Program Files\ArcGIS\java\ opt\arcgis_engine.jar;C:\Progr amFiles\ArcGIS\java\jintegra.j ar;.” MapViewer public static void main(String[] args) throws IOException { EngineInitializer.initializeVisualBeans(); MapViewerFrame mapViewerFrame = new MapViewerFrame(); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); int width = d.width; int height = d.height; mapViewerFrame.setBounds(width/6, height/6, width*2/3, height*2/ 3); mapViewerFrame.buildAndShow(); } } 3. 此时可以编译 MapViewerFrame 和 MapViewer Java 文件。要完成 这个任务,需要告诉 Java 编译器到哪里找到被引用的 Java 类。 这要通过指定类路径来完成。 为了用命令行进行编译,进入(cd)包含 MapViewerFrame 和 MapViewer Java 代码的目录并给出类似于下面的命令: javac -classpath “C:\Program Files\ArcGIS\java\opt\arcgis_visualbeans.jar; C:\Program Files\ArcGIS\java\opt\arcgis_engine.jar;C:\Program Files\ArcGIS\java\jintegra.jar;.” *.java 这 会 编 译 Java 程 序 并 产 生 一 个 MapViewerFrame.class 和 MapViewer.class 文件。如果返回一个“NoClassDefFoundError”错误, 则再次检查类路径并确保其包括所需的 JAR 文件—arcobjects.jar、 arcgis_visualbeans.jar 和 jintegra.jar。 4. 启动 MapViewer 类,并确保在进行到下一步之前出现了一个空的 Java 框架。 5. 然后为添加到 MapViewerFrame 类中的组件添加成员变量,并在构 造器中构造这些组件。 //MapViewerFrame.java import javax.swing.JFrame; import java.awt.event.WindowAdapter; 用 Visual JavaBeans 建立应用程序 import java.awt.event.WindowEvent; //add new imports for this step: import javax.swing.JLabel; import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.JPanel; import com.esri.arcgis.beans.TOC.TOCControl; import com.esri.arcgis.beans.map.MapControl; import com.esri.arcgis.beans.pagelayout.PageLayoutControl; import com.esri.arcgis.beans.toolbar.ToolbarControl; public class MapViewerFrame extends JFrame { PageLayoutControl pageLayout; MapControl map; TOCControl toc; ToolbarControl toolbar; JLabel statusLabel; //constructor public MapViewerFrame() { setTitle(“MapViewer”); pageLayout = new PageLayoutControl(); map = new MapControl(); toc = new TOCControl(); toolbar = new ToolbarControl(); statusLabel = new JLabel(" "); 6. 在构造器中设置地图大小和工具条组件大小,而让 BorderLayout 管理其它组件的大小。 //set constraints on the size of map and toolbar //let the layout manager manage the size of all other components map.setSize(new Dimension(200,200)); toolbar.setSize(new Dimension(900, 25)); } 7. 在 MapViewerFrame 接 口 中 , 工 具 条 组 件 将 占 用 BorderLayout.NORTH , pageLayout 组 件 将 占用主面板,如 BorderLayout.CENTER 位置等。 BorderLayout.WEST 位置将由 TOC 和地图组件占用。为实现这个 任务,创建一个新的方法以根据所需的布局建立包含这两个控件 的 JPanel: //new method to build the left panel private JPanel buildMapTOCPanel() { JPanel leftPanel = new JPanel(); leftPanel.setLayout(new BorderLayout()); leftPanel.add(toc, BorderLayout.CENTER); leftPanel.add(map, BorderLayout.SOUTH); return leftPanel; } 第六章·开发情景·267 用 Visual JavaBeans 建立应用程序 8. 在 MapViewerFrame 的 BorderLayout.SOUTH 位置中,添加一个 JLable 当作状态栏。一旦用所有的布局位置进行了更新, buildAndShow()方法应看起来象下面那样: public void buildAndShow()throws IOException { JPanel mapTOCPanel = buildMapTOCPanel(); this.getContentPane().add(toolbar, BorderLayout.NORTH); this.getContentPane().add(pageLayout, BorderLayout.CENTER); this.getContentPane().add(mapTOCPanel, BorderLayout.WEST); this.getContentPane().add(statusLabel, BorderLayout.SOUTH); this.show(); addEventListeners(); } 9. 编译并运行应用程序以确认 GUI 如下图那样放置: 当使用了标准的 Java 布局管理器(BorderLayout)来放置组件时,该 管理器会为用户管理组件的布局和大小。用户可以调整框架的大小并 看到组件会放置到合适位置,而不需要任何明确调整大小的代码。 装载地图文档 1. 现在已经添加了组件,开发人员可以将地图文档装载到控件中 了 。 要 完 成 这 个 任 务 , 向 builAndShow()方法末尾紧接着 addEventListeners()后添加下列代码。 public void buildAndShow() throws IOException { . . . . . . 268·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 作为一个普遍规则,在使用 ArcGIS Engine 开发工具包的 Java API 时, 所有方法都应该在显示可视化组件 后在这些组件上调用。这个规则的 例外就是在调用“setXYZ()”方法 时—例如,设置组件大小。 this.show(); addEventListeners(); //load a pre-authored map document into the PageLayout control String documentPath = new java.io.File("../../../../../data/ ArcGIS_Engine_Developer_Guide/Gulf of St. Lawrence.mxd").getAbsolutePath(); if (pageLayout.checkMxFile(documentPath)) pageLayout.loadMxFile(documentPath,null); 2. 当 地 图 文 档 装 载 到 pageLayout 控 件 中时会产生一个 onPageLayout Replaced 事件。开发人员将添加代码以装载同一 个地图文档到地图组件中以响应这个事件。用 IPageLayoutControlEventsAdapter 在 addEventListeners()方 法中为 onPageLayoutReplaced 事件向 pageLayout 组件添加作为 匿名内部类的事件侦听器。 public void addEventListeners()throws IOException { . . . pageLayout.addIPageLayoutControlEventsListener( new IPageLayoutControlEventsAdapter() { public void onPageLayoutReplaced(IPageLayoutControlEventsOnPageLayoutRe placedEvent evt) throws IOException{ map.loadMxFile(pageLayout.getDocumentFilename(), null, null); map.setExtent(map.getFullExtent()); }); } }); } 设置 ToolbarControl 和 TOCControl 的伙伴控件 尽管组件已经被添加到 JFrame 中,但它们之间相互不“了解”。为了 使控件相互之间同步工作,TOC 和工具条组件应该知道它们与哪个控件 相关联。否则,工具条组件就不知道其“控制”哪个组件,而 TOC 组 件不知道应该显示哪个组件的内容表。 为设置组件之间的这种通信,在 buildAndShow()方法中装载地图文档 代码后面添加下列代码。 public void buildAndShow() throws IOException { . . . //load a pre-authored map document on the pageLayout component String documentPath = new java.io.File("../../data/Gulf of St. Lawrence.mxd").getAbsolutePath(); if (pageLayout.checkMxFile(documentPath)) pageLayout.loadMxFile(documentPath,null); //set buddy controls to wire up the TOC and Toolbar Control //with the PageLayout Control toc.setBuddyControl(pageLayout); toolbar.setBuddyControl(pageLayout); 第六章·开发情景·269 用 Visual JavaBeans 建立应用程序 } 向工具条上添加命令 工具条控件已经被添加到用户界面上,默认情况下,该控件没有任何 工具。开发人员将按以下步骤开始向工具条添加工具。 ArcGIS Engine 开发工具包中含有 120 多个操作 MapControl、 PageLayoutControl 和 ToolbarControl 的命令和工具。这些命令和工 具提供了许多地图导航、图形管理和要素选择的常用 GIS 功能。开发 人员现在将向应用程序添加一些命令和工具。 1. 要添加预先建立的工具条命令,添加下列导入语句: import com.esri.arcgis.controltools.ControlsOpenDocCommand; import com.esri.arcgis.systemUI.esriCommandStyles; 2. 在 buildAndShow()方法中,在调用 this.show()之后和调用 addEventListeners()之前向工具条添加预先建立的命令: this.show(); toolbar.addItem( new ControlsOpenDocCommand(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsPageZoomInTool(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsPageZoomOutTool(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsPagePanTool(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsPageZoomWholePageCommand(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsPageZoomPageToLastExtentBackCommand(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsPageZoomPageToLastExtentForwardCommand(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsMapZoomInTool(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsMapZoomOutTool(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsMapPanTool(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); toolbar.addItem( new ControlsMapFullExtentCommand(), 0, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly ); addEventListeners(); 270·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 //load a pre-authored map document on the pageLayout component 3. 运行应用程序。 地图文档已经被装载到 pageLayout 组件中,而且 TOC 组件列出了 用 ToolbarMenu 创建快捷菜单 组件,可用于向其他组件,如 事件添加 2. rame 构造器中构造一个新的 ToolbarMenu 对象,如 MapViewerFrame extends JFrame{ lbar; u; 3. 为 员变量并用一个 try/catch 代码块包围: 该地图文档中的数据层。使用 TOC 组件来切换图层的可见性。默 认情况下地图文档的活动地图被装载到地图组件中。另外请注意 命令已经被添加到工具条组件中。 ArcGIS Engine 提供了一个 ToolbarMenu MapControl 和 PageLayoutControl 等添加快捷菜单。在本步骤中,开 发人员将给 pageLayout 组件添加一个快捷菜单。该快捷菜单将包含来 自 com.esri.arcgis.beans.toolbaritems 包的预建命令。 为显示该快捷菜单,将在 pageLayout 组件上为 onMouseDown 一个事件处理器。如果单击了右键,该快捷菜单将被显示。 1. 在 MapViewerFrame.java 中添加下列导入语句: import com.esri.arcgis.beans.toolbar.ToolbarMenu; import i.arcgis.beans.pagelayout.IPageLayoutControlEventsAdapter; com.esr import i.arcgis.beans.pagelayout.IPageLayoutControlEventsOnMouscom.esr eDownEvent; 在 MapViewerF 下所示: public class PageLayoutControl pageLayout; MapControl map; TOCControl toc; ToolbarControl too ToolbarMenu popupMen JLabel statusLabel; popuMenu 添加一个成 第六章·开发情景·271 用 Visual JavaBeans 建立应用程序 public MapViewerFrame(){ setTitle( "MapViewer" ); try { pageLayout = new PageLayoutControl(); map = new MapControl(); toc = new TOCControl(); toolbar = new ToolbarControl(); popupMenu = new ToolbarMenu(); statusLabel = new JLabel( " " ); // set constraints on the size of the map and toolbar // let the layout manager map.setSize( new Dimension( 200, 200 ) ); toolbar.setSize( new Dimension( 900, 25 ) ); } catch (Exception e) { e.printStackTrace(); } } 4. 在 buildAndShow()方法中调用 this.show()后给 popupMenu 组件 添加来自 com.esri.arcgis.beans.toolbaritems 包的预建命令: this.show(); // add popup menu items popupMenu.addItem( new ControlsPageZoomInFixedCommand(), 0, -1,false, esriCommandStyles.esriCommandStyleIconAndText ); popupMenu.addItem( new ControlsPageZoomOutFixedCommand(), 0, -1,false, esriCommandStyles.esriCommandStyleIconAndText ); popupMenu.addItem( new ontrolsPageZoomWholePageCommand(), 0, -1,false, esriCommandStyles.esriCommandStyleIconAndText ); popupMenu.addItem( new ControlsPageZoomPageToLastExtent BackCommand(), 0, -1,false, esriCommandStyles.esriCommand StyleIconAndText); popupMenu.addItem( new ControlsPageZoomPageToLastExtent ForwardCommand(),0, -1, false,esriCommandStyles.esri CommandStyleIconAndText ); //add Generic commands to the toolbar . . . 5. 在 buildAndShow()方法中,连同设置伙伴控件的代码一起,将 popupMenu 与 pageLayout 组件相关联: //set buddy controls to wire up the TOC and ToolbarControl //with the pageLayout object toc.setBuddyControl(pageLayout); toolbar.setBuddyControl(pageLayout); popupMenu.setHookByRef(pageLayout); } 272·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 6. Adapter 在 addEventListeners() 方法中,为 onMouseDown 事件向 pageLayout 组件添加作为匿名内 部类的 pageL rolEventsListener( ne tsAdapter(){ pu outControlEventsOnMouse { sed, display the popup menu pa } }); 7. 运行 菜单,并移动页面布局。 控制 TOCContol 中的标签编辑 默认 换出现在内容表中的图层的 可见性和改变地图及图层名称。开发人员将添加代码以防止用户编辑 1. 列导入 OCControlEventsAdapter; ControlEventsOnEnd port com.esri.arcgis.beans.TOC.esriTOCControlEdit; 用 IPageLayoutControlEvents 事件侦听器。 ayout.addIPageLayoutCont w IPageLayoutControlEven blic void onMouseDown(IpageLay DownEventevt) throws IOException //if Right mouse button(2) is pres if(evt.getButton() == 2) popupMenu.popupMenu(evt.getX(), evt.getY(), geLayout.getHWnd()); 应用程序。 在 pageLayout 组件上右击显示快捷 情况下,TOCControl 允许用户自动切 名称并用一个空字符串替代该名称。 为本步骤中要使用的类需向 MapViewerFrame.java 添加下 语句: import com.esri.arcgis.beans.TOC.IT import com.esri.arcgis.beans.TOC.ITOC LabelEditEvent; im 第六章·开发情景·273 用 Visual JavaBeans 建立应用程序 2. 在 buildAndShow()方法中,将 labelEdit 设置为 manual: . . . this.show(); ontrolManual); . . . 3. 用 ITO 为 onE 的事件侦 听器 ,addEventListeners()方法应看起 来象这样: ate void addEventListeners() throws IOException { ITOCControlEventsAdapter(){ public void onEndLabelEdit(ITOCControlEventsOnEndLabelEditEvent labelEditEvt) throws IOException { String newLabel = labelEditEvt.getNewLabel(); //if the new label is an empty string, prevent the //edit if (newLabel.equals("")) labelEditEvt.setCanEdit(false); } }); } 4. 运行应用程序。要编辑 TOCControl 中的地图、图层、标题或图例 类标签,可以先单击该标签一次,然后再次单击该标签以引起标 签编辑。试着用空字符串替代标签。用户可以用任何非空字符串 替代标签。在编辑过程中可以随时用键盘上的 ESC 键取消编辑。 //set label editing to manual iTOCCtoc.setLabelEdit(esriTOCControlEdit.esr //add popup menu items CControlEventsAdapter 在 addEventListeners()方法中, ndLabelEdit 事件向 TOC 组件添加作为匿名内部类 。添加这个事件侦听器后 priv 274·ArcGIS Engine 开发指南 . . . toc.addITOCControlEventsListener(new 用 Visual JavaBeans 建立应用程序 第六章·开发情景·275 用地图导航工具移动活动地图会改 PageLayoutControl 中活动地图 并引起 MapControl 的更新。 用页面布局导航工具移动页面布局 会改变页面布局(而不是 PageLayoutControl 中活动地图) 的范围,因此 MapControl 不会更 新。 现在 把地图组件用作概览窗口并在其显示上绘制 pageLayou 围。当用户移动 pageLayout 组件 数据框中的数据时,会看到地图组件的概览窗口的更新。 1. 步骤中使用的类需向 MapViewerFrame.java 添加下列导入语 sri.arcgis.carto.Map; import com.esri.arcgis.display.DisplayTransformation; im entsAdapter; imp formEventsVisibleBounds Upda impo import c display.SimpleFillSymbol; impo y.SimpleLineSymbol; import com 2. 向 Ma public class s JFrame { SimpleFillS used to draw the envelope elope currentExtent; //The envelope drawn on the MapControl 3. 创建一个名为“createOverviewSymbol()”的新的受保护型方法。 这是开发人员创建用在地图控件中以表示 pageLayout 控件中数 据范围的符号的地方。在子程序中添加以下代码: private void createOverviewSymbol() throws IOException{ RgbColor color = new RgbColor(); color.setRed(255); color.setGreen(0); color.setBlue(0); color.setTransparency((byte)255); SimpleLineSymbol outline = new SimpleLineSymbol(); outline.setWidth(15); outline.setColor(color); fillSymbol = new SimpleFillSymbol(); color.setTransparency((byte) 0); fillSymbol.setColor(color); fillSymbol.setOutline(outline); } 变 的范围 在 MapControl 上绘制“概览矩形” 开发人员将 t 组件中活动地图的当前范 为本 句: import com.esri.arcgis.beans.map.IMapControlEvents2Adapter; import com.esri.arcgis.beans.map.IMapControlEvents2OnAfter DrawEvent; import com.e import com.esri.arcgis.carto.esriViewDrawPhase; port com.esri.arcgis.display.ITransformEv ort com.esri.arcgis.display.Itrans tedEvent; rt com.esri.arcgis.display.RgbColor; om.esri.arcgis. rt com.esri.arcgis.displa .esri.arcgis.geometry.IEnvelope; pViewerFrame 添加下列类成员: MapViewerFrame extend ymbol fillSymbol; //The symbol IEnv Map focusMap; //The PageLayoutControl's focus map PageLayoutControl pageLayout; . . . 用 Visual JavaBeans 建立应用程序 用 buildAndShow()方法 中的 createOverviewSymbol 方法: createOverviewSymbol(); 5. ()方 件侦听器中,获取对 pageLayout 组件的活动地图的 成员变量中。 这个任务, )并在 Listener( tControlEventsOnPageL M 围什么时候改 活动地图的 侦 听 DisplayTransformation 对象的 visibleBoundsUpdated 事件。 每当地图范围改变时都会触发 visibleBoundsUpdated 事件并用于 设置地图新的可视范围的封装边界。通过刷新 MapControl,可以 强迫其重绘显示上的几何图形。 pa lEventsAdapter() { on olEventsOnPageLayoutRe placedEventevt) throws IOException{ m tDocumentFilename(), null, null); map.setExtent(map.getFullExtent()); 4. 在调用设置标签编辑为 manual 代码后调 //set label editing to manual toc.setLabelEdit(esriTOCControlEdit.esriTOCControlManual); //create symbol used to draw overview on the MapControl //add popup menu items . . . 在先前为 IPageLayoutControlEvents 在 addEventListeners 法中添加的事 一个引用。将 focusMap 的范围存储在 currentExtent 这个范围将用于在地图组件上绘制概览窗口。为完成 在装载地图文档后添加几行代码(以粗体显示 onPageLayoutReplaced 事件处理程序中设置其范围: pageLayout.addIPageLayoutControlEvents new IPageLayoutControlEventsAdapter() { public void onPageLayoutReplaced(IPageLayou ayoutReplacedEvent evt) throws IOException{ map.loadMxFile(pageLayout.getDocumentFilename(), null, null); map.setExtent(map.getFullExtent()); focusMap = new ap(pageLayout.getActiveView().getFocusMap()); currentExtent = focusMap.getExtent(); } }); 6. PageLayoutControl 的缺省事件是 IPageLayoutControlEvents。 这些事件不会告诉用户数据框中的地图范 变。要完 成这个任务,开发人员将使用 PageLayoutControl ITr 下 侦 听 器 以ansformEvents 。 添 加 以 geLayout.addIPageLayoutControlEventsListener( new IPageLayoutContro public void PageLayoutReplaced(IPageLayoutContr ap.loadMxFile(pageLayout.ge 276·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 tFocusMap()); .getExtent(); tDisplayT tsListener(new ransformEventsAdapter(){ //set currentExtent to the new visible extent ull,null); } }); 7. 每当 加的) 触发地图刷新时,就向绘制更新范围的地图组件添加一个 ImapC entListeners()方法中 添 IMapControlEvents2Adapter() { ublic void onAfterDraw(IMapControlEvents2OnAfterDrawEvent evt) throws IOException { } catch (Exception e) { } focusMap = new Map(pageLayout.getActiveView().ge currentExtent = focusMap DisplayTransformation dt = new DisplayTransformation(focusMap.getScreenDisplay().ge ransformation()); dt.addITransformEven IT public void visibleBoundsUpdated(ITransformEventsVisibleBoundsUpdatedEve nt evt) throws IOException { currentExtent = evt.getSender().getVisibleBounds(); //refresh the map components foreground phase map.refresh(esriViewDrawPhase.esriViewForeground,n } }); visibleBoundsUpdated 事件处理程序(在前一步中添 ontrolEvents2Listener。在 addEv 加下 map.a 列代码: ddIMapControlEvents2Listener(new p if (evt.getViewDrawPhase() == esriViewDrawPhase.esriViewForeground){ try{ //draw the shape on the MapControl map.drawShape(currentExtent, fillSymbol); System.err.println("Error in drawing shape on MapControl"); //e.printStackTrace(); } } }); 第六章·开发情景·277 用 Visual JavaBeans 建立应用程序 8. 用早先添加的地图导航工具改变 pageLayout 组件 件上,如下 图的 创 开发人员将创建一个能被添加到 ToolbarControl 上的自定义工具。该 工具在鼠标点击位置上向 PageLayoutControl 添加一个包含当日日期 的文本元素。 ToolbarControl 自定 gis.systemUI.ICommand; 1. ommand.java: im imp .ITool; public class ICommand, ITool { 由于 接口,只有在实现了 属于这两 2. 为 I 方法添加实现代码,如下所示。 OnCreat 给它们留下一个“空”的实现。 import j import java.text.SimpleDateFormat; import java.util.Date; im t com.esri.arcgis.carto.IActiveView; ort com.esri.arcgis.carto.TextElement; import com.esri.arcgis.carto.esriViewDrawPhase; import com.esri.arcgis.display.TextSymbol; import com.esri.arcgis.geometry.IPoint; import com.esri.arcgis.support.ms.stdole.StdFont; 运行应用程序。 中活动地图的范围,并观察到新范围绘制在地图组 红色矩形所示。 建自定义工具 该工具将作为一个普通工具建立,这样就可以操作 MapControl、 及 PageLayoutControl。 义工具可以被创建为实现了下面两个接口的 Java 类: import com.esri.arc import com.esri.arcgis.systemUI.ITool; 创建一个新的 Java 类 AddDateC port com.esri.arcgis.systemUI.ICommand; ort com.esri.arcgis.systemUI AddDateCommand implements } 这个 Java 类实现了 ICommand 和 ITool 个接口的所有方法后才能编译该类。 Command 中定义的所有 e 和 onClick 方法的实现将在下一步完成,因此现在可以 ava.io.IOException; por imp 278·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 import com.esri.arcgis.systemUI.ICommand; import com.esri.arcgis.systemUI.ITool; return false; public class AddDateCommand implements ICommand, ITool { /** * @see com.esri.arcgis.systemUI.ICommand#isEnabled() */ public boolean isEnabled() throws IOException, AutomationException { return true; } /** * @see com.esri.arcgis.systemUI.ICommand#isChecked() */ public boolean isChecked() throws IOException, AutomationException { } * @see com.esri.arcgis.systemUI.ICommand#getName() public String getName() throws IOException, “CustomCommands_Add Date”; } mand#getCaption() */ AutomationException { return "Add date"; */ ent to the page layout"; and#getHelpFile() , /** */ AutomationException { return /** * @see com.esri.arcgis.systemUI.ICom public String getCaption() throws IOException, AutomationException { return "Add Date"; } /** * @see com.esri.arcgis.systemUI.ICommand#getTooltip() */ public String getTooltip() throws IOException, } /** * @see com.esri.arcgis.systemUI.ICommand#getMessage() public String getMessage() throws IOException, AutomationException { return "Adds a date elem } /** * @see com.esri.arcgis.systemUI.IComm */ public String getHelpFile() throws IOException AutomationException { return null; 第六章·开发情景·279 用 Visual JavaBeans 建立应用程序 } /** * @see com.esri.arcgis.systemUI.ICommand#getHelpContextID() */ public int getHelpContextID() throws IOException, AutomationException { return 0; } /** * @see com.esri.arcgis.systemUI.ICommand#getBitmap() */ public int getBitmap() throws IOException, AutomationException { return 0;//We rely on being displayed as text } /** * @see com.esri.arcgis.systemUI.ICommand#getCategory() */ public String getCategory() throws IOException, AutomationException { return “CustomCommands”; } /** * @see com.esri.arcgis.systemUI.ICommand#onCreate(java.lang.Object) */ public void onCreate(Object obj) throws IOException, AutomationException { //to be added later } /** * @see com.esri.arcgis.systemUI.ICommand#onClick() */ public void onClick() throws IOException, AutomationException { } } 3. OnCreate 方法会给命令操作的应用程序传递一个引用。在本例中 可以是 MapControl、PageLayoutControl 或 ToolbarControl。开 发人员不是在 onCreate 方法中添加代码以确定传递给命令的钩 子的类型,而是使用 HookHelper 来完成这个任务。命令或工具需 要知道如何处理其传递的钩子,因此需要执行一个检查以确定已 被传递的 ArcGIS 控件的类型。HookHelper 用于保留该钩子并返 回活动视图而不管钩子的类型(在本例中是 MapControl、 PageLayoutControl 或 ToolbarControl)。 给 AddDateCommand 类添加 HookHelper 成员变量: public class AddDateCommand implements ICommand, ITool{ HookHelper hookHelper; 280·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 4. setHookByRef 方法 pu AutomationException { hookHelper = new HookHelper(); ho } 5. 在AddDateCommand类中为ITool接口定义的所有方法添加实现代 码 所有方法都被使用,但需要实现所有方法才能编译该类。 在 当前日 期创建了一个 TextElement 并将它添加到钩子对象的图形容器 中 /** see com.esri.arcgis.systemUI.ITool#getCursor() public int getCursor() throws IOException, AutomationException { rn 0; } /* * @ seDown(int, int, ,int) pu ws IOException, AutomationException { matter; atter = new SimpleDateFormat("EEE d MMM, yyyy"); //create a font fo //create a text symbol //c added to the graphics container Te tElement.setSymbol(textSymbol); lement.setScaleText(false); //add the text element to the graphics container View(); 在 onCreate 方法中,构造 hookhelper 并使用 来传递控件对象。 blic void onCreate(Object obj) throws IOException, okHelper.setHookByRef( obj ); 。不是 下面的代码中,注意 onClick()方法,因为这个方法用 。 * @ */ retu * see com.esri.arcgis.systemUI.ITool#onMou int */ blic void onMouseDown(int button, int shift, int x, int y) thro Date today = new Date(); //format the date in the form "Wed 27 Aug, 2003" SimpleDateFormat for form String dateString = formatter.format(today); StdFont font = new StdFont(); font.setName("Arial"); nt.setBold(true); TextSymbol textSymbol = new TextSymbol(); tSymbol.setFont(font); tex textSymbol.setSize(15); reate a text element to be xtElement textElement = new TextElement(); tex textE textElement.setText(dateString); IActiveView activeView = hookHelper.getActive 第六章·开发情景·281 用 Visual JavaBeans 建立应用程序 282·ArcGIS Engine 开发指南 IPoint pt =activeView.getScreenDisplay().getDisplayTrans ation ().toMapPoint(x,y); iveView.getGraphicsContainer().addElement(textElement, 0); //r activeView.partialRefresh(esriViewDrawPhase.esriViewGraphics, nu } /* * @see com.esri.arcgis.sy in */ public void onMouseMove(int arg0, int arg1, int arg2, int arg3) throws IOException, AutomationException { } /* co int, int) */ public void onMouseUp(int arg0, in throws IOException, AutomationException { } /* * @see com.esri.arcgis.sy */ public void onDblClick() throws IOException, AutomationException { } /* * @ t) */ pu throws IOException, AutomationException { } /* throws IOException, AutomationException { form textElement.setGeometry(pt); act efresh the view ll, null); * stemUI.ITool#onMouseMove(int, int, int, t) * * @see m.esri.arcgis.systemUI.ITool#onMouseUp(int, int, t arg1, int arg2, int arg3) * stemUI.ITool#onDblClick() * see com.esri.arcgis.systemUI.ITool#onKeyDown(int, in blic void onKeyDown(int arg0, int arg1) * * @see com.esri.arcgis.systemUI.ITool#onKeyUp(int, int) */ public void onKeyUp(int arg0, int arg1) throws IOException, AutomationException { } /** * @see com.esri.arcgis.systemUI.ITool#onContextMenu(int, int) */ public boolean onContextMenu(int arg0, int arg1) return false; } /** temUI.ITool#refresh(int) * @see com.esri.arcgis.sys 用 Visual JavaBeans 建立应用程序 第六章·开发情景·283 */ public void refresh(int arg0) throws IOException, IOException, tomationException { 6. 7. 令的代码行后面给工具条组件添加一个 AddDateCommand 例程: to 0, -1, true, 0, esriCommandStyles.esriCommandStyleTextOnly); 8. 上。选中该工具并在页面布局上单击,可以在鼠标单 置上添加当日日期。 除了象 法给 ToolbarControl 添加 ArcGIS Engi 框定制 Toolbar 这 样 做 开 发 人员要使 Toolbar 制对话框。 1. im import java.awt.event.ActionEvent; import java.awt.event.ActionListener; AutomationException { } /** * @see com.esri.arcgis.systemUI.ITool#deactivate() */ public boolean deactivate() throws Au return true; } 现在已经完成了 AddDateCommand 类。编译该类。 在 MapViewerFrame 中,在 buildAndShow()方法中为工具条添加 预建命 类的 olbar.addItem(new AddDateCommand(), 重新编译并启动 MapViewer 类。一个新的“Add date”工具会出 现在工具条 击位 oolbarControl 前面那样用 addItem()方 ne 命令和工具外,开发人员也可以通过使用定制对话 Control 来 添 加 命 令 和 工 具 。 Control 处于定制模式并显示定 添加下列导入语句: port javax.swing.JCheckBox; 定制 T 用 Visual JavaBeans 建立应用程序 注意只有在系统中注册为 COM 组件 加到工具条上。Java 命令和工具 (象在前一步中建立的命令和工 具)不会出现在定制对话框中,因 为它们没有在系统注册表中注册为 COM 组件。 import c import c stomizeDialogEvents; import c stomizeDialogEventsOnClose DialogE impo ans.toolbar.IcustomizeDialogEventsOn StartD 2. 添加类成员。 . lic class MapViewerFrame extends JFrame { . . . heckBox customizeCB; //JCheckbox to control toolbar x . . . 3. 创 话框。给该方法添加下列代码: te void createCustomizeDialog() throws IOException { t the title how the 'Add From File' button stomizeDialog.setShowAddFromFile(true); the toolbar component that the new items will be added to 4. eCustomizeDialog()” 方法以初始化定制对话框。 并 给它添加事件侦听器以启动和关闭定制对话框: JPanel mapTOCPanel = buildMapTOCPanel(); ; customizeCB.addActionListener(new ActionListener(){ ublic void actionPerformed(ActionEvent e) { if (customizeCB.isSelected()) { d()); izeDialog.closeDialog(); } ch (Exception ee) { 的工具和命令才能用定制对话框添 om.esri.arcgis.beans.toolbar.CustomizeDialog; om.esri.arcgis.beans.toolbar.ICu om.esri.arcgis.beans.toolbar.Icu vent; rt com.esri.arcgis.be ialog Event; . . pub . . . JC customization CustomizeDialog customizeDialog; //The customize dialog bo used by//the ToolbarControl constructor public MapViewerFrame() { . . . 建一个新的方 teCustomizeDialog()”以初始化定制对法“crea /** * Method createCustomizeDialog. */ priva customizeDialog = new CustomizeDialog(); //se customizeDialog.setDialogTitle("Customize Toolbar Items"); //s cu //set customizeDialog.setDoubleClickDestination(toolbar); } 在“buildAndShow()”方法中,调用“creat 也要初始化 customizeCBJCheckBox public void buildAndShow() throws IOException { createCustomizeDialog(); customizeCB = new JCheckBox("Customize") p try { customizeDialog.startDialog(toolbar.getHWn } else { custom } cat ee.printStackTrace(); 284·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 } }} 5. 复选框添加到 GUI 中,修改代码以将工具条和 复 只工具条添加 到 位置上。 w BorderLayout()); add(toolbar, BorderLayout.CENTER); topPanel.add(customizeCB, BorderLayout.EAST); ); ... 为将“Customize” 选框添加到一个新的 JPanel 中。将该面板而不是 BorderLayout.NORTH JPanel topPanel = new JPanel(); topPanel.setLayout(ne topPanel. this.getContentPane().add(topPanel, BorderLayout.NORTH); this.getContentPane().add(toolbar, BorderLayout.NORTH);//delete this line this.getContentPane().add(pageLayout, BorderLayout.CENTER); , BorderLayout.WEST); 6. 听 制 Dialog() throws IOException { customizeDialog = new CustomizeDialog(); //set the title customizeDialog.setDialogTitle("Customize Toolbar Items"); //show the ‘Add From File’ button customizeDialog.setShowAddFromFile(true); //set the toolbar component that the new items will be added to customizeDialog.setDoubleClickDestinationByRef(toolbar); customizeDialog.addICustomizeDialogEventsListener(new ICustomizeDialogEvents(){ public void onStartDialog(IcustomizeDialogEventsOn StartDialogEventarg0) throws IOException { toolbar.setCustomize(true); } public void onCloseDialog(ICustomizeDialogEventsOnCloseDialogEvent arg0) n { toolbar.setCustomize(false); 具条设置为“定 话框。 8. Element”类目,并拖 动 工具条中。通过右击工具条 this.getContentPane().add(mapTOCPanel 在 createCutomizeDialog()方法中,给定制对话框添加事件侦 器以将工具条在定制对话框启动时设置为“定制”状态,而当定 对话框关闭时设置为正常状态: private void createCustomize throws IOExceptio customizeCB.setSelected(false); }}); } 7. 运行应用程序并复选“定制”复选框,以便将工 制”模式并打开定制对 在“Commands”选项卡上,选择“Graphic “Select Elements”命令以添加到 第六章·开发情景·285 用 Visual JavaBeans 建立应用程序 样式和分组等。 程序。用“Select”工具移除包含当日日期的文本 元 上的某个命令项,可以调整该命令项的外观,如 9. 停止定制应用 素。 286·ArcGIS Engine 开发指南 用 Visual JavaBeans 建立应用程序 部 为了将本应用程序成功地部署在用户机器上,开发人员要创建一个可 工具包或运行时组成部分而安装的 面的命令: java 要创建可 1. 在 为 “m 2. 给“ M 4. 5. 给 jar .txt mapviewer.jar *.class 6. 这样 在 Arc 行 JAR,用如下命 令运 "C 这个命令 动脚 本。 其他资源 下列资源可能有助于开发人员理解和应用本情景提出的概念和技术。 ne 开发工具包中的其他文档,包括 ArcGIS 开发帮助、 供有关ArcGIS开发方面的最新信息,包 括 术文档。网址为 http://arcgisdeveloperonline.esri.com。 ES 论坛网站—提供来自其他ArcGIS开发人员的无价的帮 。网址为http://support.esri.com并点击“用户论坛(User Forums)”选项卡。 /网站上的Sun Java 署 执行的 JAR 文件。然后用户就可以通过使用作为 ArcGIS Engine 开发 JRE 来启动应用程序,需要给出下 –jar mapviewer.jar 执行的 JAR 需要执行以下步骤: 编译好的 Java 类 文 件 所在的目录中,创建一个名 anifest.txt”的文件。 manifest.txt”文件添加下面一行: ain-Class: com.esri.arcgis.scenario.beans.MapViewer 3. 确保在第一行后面按了回车。 保存该文件,并打开一个命令窗口。进入(cd)包含“manifest.txt” 文件的目录。 出下面的命令以创建可执行的 JAR 文件: cmf manifest 就创建了一个 mapviewer.jar 文件。这是可以用包括 GIS Engine 开发工具包中的 JRE 运行的可执 行: :\Program Files\ArcGIS\java\jre\bin\java" -jar mapviewer.jar 可以捆绑在批处理文件或 shell 脚本文件中,以提供启 ArcGIS Engi 组件帮助、对象模型图和示例,以帮助开发人员快速入门。 ArcGIS开发在线网站—提 更新的示例和技 RI在线 助 http://java.sun.com/docs/books/tutorial 教程。 一般 Java 的帮助网站,如 Javaranch—新手学习 Java 的好地方 (http://www.javaranch.com)。 第六章·开发情景·287 用 Windows 控件建立应用程序 用 Windows 控件建立应用程序 用户可以不按照本情景进行,而可 以从示例安装路径获得完整的应用 程序。示例作为 ArcGIS 开发示例的 组成部分而安装。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 本情景针对那些用.NET 建立和部署应用程序的开发人员,描述了使用 ArcGIS 控件建立和部署应用程序的过程。 可以在 ArcGIS 安装目录下的\DeveloperKit\Samples\Developer_ Guide_Scenarios\ArcGIS_Engine\Building_an_ArcGIS_Control_Applicati on\Map_Viewer 中找到本示例。 工程描述 用Windows控件建立应用程序情景的目标就是向开发人员展示并使其 熟悉在Microsoft Visual Studio .NET API中使用ArcGIS控件开发和部署 GIS应用程序所需的步骤。本情景在Microsoft Visual Studio .NET开发环 境中使用MapControl、PageLayoutControl、TOCControl和ToolbarControl 等Windows控件。使用COM、Java和C++的开发人员可以参考本章中的 下列情景:“用ActiveX建立应用程序”、“用Visual JavaBeans建立应用 程序” 、“建立命令行Java应用程序” 和“建立命令行C++应用程序”。 本情景展示了创建用于浏览用 ArcGIS Desktop 应用程序 ArcMap 预制 作的地图文档的 GIS 应用程序所需的步骤。本情景涵盖下列技术: 在 Microsoft Visual Studio .NET 中装载和嵌入 ArcGIS 控件。 将预制作的地图文档装载到 MapControl 和 PageLayoutControl 中。 设置 ToolbarControl 和 TOCControl 伙伴控件。 处理窗体调整大小事件。 向 ToolbarControl 添加 ArcGIS Engine 命令和工具。 创建快捷菜单。 管理 TOCControl 中的标签编辑。 在 MapControl 上绘制几何形状。 创建自定义工具以操作 MapControl 、 PageLayoutControl 和 ToolbarControl。 定制 ToolbarControl。 将应用程序部署到 Windows 操作系统上。 概念 本情景用 Microsoft Visual Studio .NET 开发环境实现,并使用 ESRI 互 操作套件将.NET Windows 控件中的 ArcGIS 控件驻留在.NET 窗体中。 这些互操作套件是无管理的 COM 代码和有管理的.NET 代码之间的桥 梁。对 COM ArcGIS 控件成员的任何引用都要经过互操作套件并传送 给实际的 COM 对象。同样的,来自 COM 对象的响应也要经过互操作 套件并传送到.NET 应用程序。每个 ArcGIS Engine 控件都有事件、属 288·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 性和方法,一旦控件被嵌入到象.NET 窗体等容器中就可以访问其事件、 属性和方法。这些控件中的对象和功能可以与其它的 ESRI ArcObjects 及自定义控件结合以创建定制的终端用户应用程序。 本情景已经用 C#和 Visual Basic .NET 两种语言编写了代码,但下面的 实现集中使用 C#语言。许多开发人员更喜欢用 Visual Basic .NET,因 为用这种语言编写的代码似乎更为 Visual Basic 6.0 程序员熟悉,而 C# 编程语言的语法更为 Java 和 C++程序员熟悉。不管使用何种开发环境, 使用ArcGIS控件进行应用程序开发的成功与否取决于开发人员所掌握 的编程环境和 ArcObjects 的技能。 本情景使用 MapControl 、 PageLayoutControl 、 TOCControl 和 ToolbarControl 以提供应用程序的用户界面。开发人员同时使用 ArcGIS 控件和其它 ArcObjects 及 ArcGIS Engine 命令来创建 GIS 浏览应用程 序。 设计 本情景的设计目的在于:首先强调 ArcGIS 控件之间如何相互作用,其 次向开发人员展示各个 ArcGIS 控件对象模型的部分内容。 每个.NET ArcGIS Engine 控件都有一系列属性页,一旦控件被嵌入 到.NET 窗体中就可以访问这些属性页。这些属性页提供了控件属性和 方法选择的快捷方式,并可以使开发人员不用写任何代码就建立应用 程序。本情景不使用属性页,而是通过程序编写建立应用程序。要了 解有关属性页方面的更多信息,请参考“ArcGIS 开发帮助”。 需求 要成功地按照本情景进行开发,需要下列条件(部署需求将在后面的 “部署”一节中阐述): 安装了 ArcGIS Engine 开发工具包,且具有能用于开发的许可文 件。 安装了 Microsoft Visual Studio .NET 开发环境和 Microsoft .NET Framework 1.1 并有合适的许可。 熟悉 Microsoft Windows 操作系统并具有使用 Microsoft Visual Studio .NET 和 C#或 Visual Basic .NET 程序语言的经验。虽然本 情景提供了一些有关如何在 Microsoft Visual Studio .NET 中使用 ArcGIS 控件方面的信息,但并不能替代该开发环境的培训。 尽管不需要有其他 ESRI 软件的经验,但具有 ArcObjects 经验并 对诸如 ArcMap 和 ArcCatalog 等 ArcGIS 应用程序具有基本的理 解是有益的。 第六章·开发情景·289 用 Windows 控件建立应用程序 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 AxControlName .NET Framework 组 件表示驻留在.NET 窗体中的控件, 而 esriControlName 套件包含来自 控件类库内部的对象和接口。 可以访问本情景的示例数据和代码,其位置为安装目录下的: \DeveloperKit\Samples\Developer_Guide_Scenarios\ArcGIS_Engine \Building_an_ArcGIS_Control_Application\Map_Viewer 本情景所使用的控件和类库包括: AxMapControl AxTOCControl AxPageLayoutControl AxToolbarControl ESRI.ArcGIS.Carto ESRI.ArcGIS.System ESRI.ArcGIS.Display ESRI.ArcGIS.SystemUI ESRI.ArcGIS.Geometry ESRI.ArcGIS.Utility esriMapControl esriTOCControl esriPageLayoutControl esriToolbarControl 实现 下面的实现过程提供了成功完成本情景所需的所有代码。本情景没有 提供在 Microsoft Visual Studio .NET 中开发应用程序的逐步指导,因为 我们假设用户已经具有该开发环境的使用经验。 装载 ArcGIS 控件 在开始编写应用程序前,应用程序要使用的 ArcGIS 控件和其他 ArcGIS Engine 类库引用应装载到开发环境中。 1. 启动 Microsoft Visual Studio .NET 并从“New project”对话框中创 建一个新的 Visual C#“Windwos 应用程序”工程。 2. 为该工程命名为“Controls”,并浏览到某个位置存储该工程。 3. 在工具箱的“Windows Forms”选项卡中右击并从弹出式菜单中选 择“Add/Remove Items…”菜单项。 290·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 第六章·开发情景·291 通过使用由.NET 框架提供的 COM 互 操作服务,ESRI.NET 套件将被用于 初始化和调用来自 C#工程的 ESRI 对象库中的对象。 4. 在“Cutomize Toolbox ”对话框中,选择“.NET Framework Components ”选项卡并复选“AxMapControl ”、 “AxPageLayoutControl”、“AxTOCControl”和“AxToolbarControl”。 单击 OK 按钮。现在这些控件会出现在工具箱的“Windows Forms” 选项卡中。 5. 点击“Project”菜单并选择“Add Reference…”。 6. 在“Add Reference”对话框中,双击“ESRI.ArcGIS.Carto”、 “ ESRI.ArcGIS.Display ”、“ ESRI.ArcGIS.Geometry ”、 “ ESRI.ArcGIS.System ”、“ ESRI.ArcGIS.SystemUI ”和 “ESRI.ArcGIS.Utility”。单击 OK 按钮。 用 Windows 控件建立应用程序 在.NET 中,用名字空间完全限定变 量。名字空间是.NET 中的一个概 念,允许对象以等级形式组织,而 不管这些对象在哪个套件中定义。 为了使代码更简单和更具可读性, 在引用名字空间中指定的数据项时 指令可以作为快捷方式使用。 292·ArcGIS Engine 开发指南 记住 C#是区分大小写的。如果输入 “ESRI.”,则 IntelliSense 的自动 完成特性将允许通过按 Tab 键来完 成代码的下一部分。 将 ArcGIS 控件嵌入到容器中 在访问控件的属性、方法和事件之前,要将各个控件嵌入到.NET 容器 中。一旦控件被嵌入到窗体中,就可以塑造应用程序的用户界面。 1. 以设计模式打开.NET 窗体。 2. 双击工具箱中“Windows Forms”选项卡中 AxMapControl 以将 MapControl 添加到窗体上。 3. 重复步骤 2 将 AxPageLayoutControl 、 AxTOCControl 和 AxToolbarControl 添加到窗体中。 4. 如下图所示,调整窗体上各个控件的大小和位置。 5. 双击窗体,显示窗体的代码窗口。在代码窗口的顶部添加下列 “using”指令: using System; using System.Windows.Forms; using ESRI.ArcGIS.SystemUI; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Display; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.esriSystem; using esriToolbarControl; using esriTOCControl; 用 Windows 控件建立应用程序 将地图文档装载到 PageLayoutControl 和 MapControl 中 单独的数据层或用 ArcGIS Desktop 应用程序 ArcMap 预制作的地图文 档可以装载到 MapControl 和 PageLayoutControl 中。可以装载 ESRI 提 供的示例地图文档或用户自己的地图文档。以后用户可以添加一个对 话框以浏览选择地图文档。 1. 选择“Form_Load”事件并输入下列代码(如果使用用户自己的地 图文档,则用该地图文档的正确文件名代替)。 private void Form1_Load(object sender, System.EventArgs e) { //Load a pre-authored map document into the PageLayoutControl using relative paths. string fileName = @"..\..\..\..\..\..\..\..\Data\\ArcGIS_Engine_Developer_Guide\Gulf of St. Lawrence.mxd"; if (axPageLayoutControl1.CheckMxFile(fileName)) { axPageLayoutControl1.LoadMxFile(fileName,""); } } 2. 以设计模式显示窗体,在“Properties ”窗口中选择 axPageLayoutContrl1 窗体,并显示 axPageLayoutContrl1 的事件。 双击 OnPageLayoutReplaced 事件以便在代码窗口中添加一个事件 处理器。 3. 在 axPageLayoutControl_OnPageLayoutReplaced 事件中输入下列代 码,将同一个地图文档装载到 MapControl 中。每当将一个文档装 载到 PageLayoutControl 中时都会触发 OnPageLayoutReplaced 事 件。 第六章·开发情景·293 用 Windows 控件建立应用程序 private void axPageLayoutControl1_OnPageLayoutReplaced(object sender, ESRI.ArcGIS.PageLayoutControl.IPageLayoutControlEvents_OnPag eLayoutReplacedEvent e) { //Load the same pre-authored map document into the MapControl. axMapControl1.LoadMxFile(axPageLayoutControl1.DocumentFil ename,null, null); //Set the extent of the MapControl to the full extent of the data. axMapControl1.Extent = axMapControl1.FullExtent; } 设置 TOCControl 和 ToolbarControl 的伙伴控件 由于这个应用程序的缘故,TOCControl 和 ToolbarControl 将与 Page LayoutControl 而不是 MapControl 协同工作。为此必须将 PageLayout Control 设置为伙伴控件。TOCControl 使用伙伴控件的活动视图 (ActiveView)以存放其地图、图层和符号,而 ToolbarControl 上的所 有命令、工具或菜单项均会与伙伴控件的显示交互。 1. 在“Form_Load”事件中装载地图文档代码后输入下列代码: private void Form1_Load(object sender, System.EventArgs e) { //Load a pre-authored map document into the PageLayoutControl using relative paths. string fileName = @"..\..\..\..\..\..\..\..\Data\\ArcGIS_Engine_Developer_Guide\Gulf of St. Lawrence.mxd"; if (axPageLayoutControl1.CheckMxFile(fileName)) { axPageLayoutControl1.LoadMxFile(fileName,""); } //Set buddy controls. axTOCControl1.SetBuddyControl(axPageLayoutControl1); axToolbarControl1.SetBuddyControl(axPageLayoutControl1); } 2. 建立并运行应用程序。地图文档已被装载到 PageLayoutControl 中, 而 TOCControl 列出了该地图文档中的数据层。通过使用选择和取 消选择 TOCControl 的可见性复选框来打开和关闭图层的可见性。 默认情况下,地图文档中的活动地图被装载到 MapControl 中。此 时 ToolbarControl 是空的,因为还没有向其添加命令。试着改变窗 体的大小,会注意到控件的大小并没有改变。 294·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 第六章·开发情景·295 处理窗体调整大小事件 在运行时调整窗体大小并不会使 PageLayoutControl 和 MapControl 自动 调整大小。为了调整控件大小以便它们总是填充满整个窗体,开发人 员必须将控件泊靠在窗体上。如果 PageLayoutControl 或 MapControl 包含大量数据,在 Form_Resize 过程中重绘这些数据的代价很高。为提 高性能,开发人员可以禁止数据重绘,直到完成大小调整。在调整大 小过程中,将绘制一个被拉伸的位图。 1. 以设计模式显示窗体,在“Properties ”窗口中选择 axPageLayoutControl1。单击“锚”属性并将 axPageLayoutControl1 泊靠在窗体的顶部、左侧、底部和右侧。 2. 将 axPageLayoutControl1 泊靠在窗体的顶部、左侧、底部和右侧。 用 Windows 控件建立应用程序 这种在调整窗体大小时禁止重绘的 方法要检查发送给窗体的 windows 消息。当一个窗体开始调整大小时, windows 会发送 WM_ENTER SIZEMOVE Windwors(消息)。此时 禁止绘制 MapControl 和 PageLayoutControl 并用一个“拉 伸位图”绘制。当 windows 发送 WM_EXITSIZEMOVE 时,窗体停止调 整大小,此时恢复对新范围的完全 重绘。 3. 在“Form_Load”事件的起始处添加下列代码: private void Form1_Load(object sender, System.EventArgs e) { //Suppress drawing while resizing this.SetStyle(ControlStyles.EnableNotifyMessage,true); } 4. 添加下列常量到类中: public class Form1 : System.Windows.Forms.Form { … private const int WM_ENTERSIZEMOVE = 0x231; private const int WM_EXITSIZEMOVE = 0x232; … 5. 添加下列代码以重载 OnNotifyMessage 方法: protected override void OnNotifyMessage(System.Windows.Forms.Message m) { base.OnNotifyMessage (m); if (m.Msg == WM_ENTERSIZEMOVE) { axMapControl1.SuppressResizeDrawing(true, 0); axPageLayoutControl1.SuppressResizeDrawing(true, 0); } else if (m.Msg == WM_EXITSIZEMOVE) { axMapControl1.SuppressResizeDrawing(false, 0); axPageLayoutControl1.SuppressResizeDrawing(false, 0); } } 6. 建立并运行应用程序。试着调整窗体的大小。 向 ToolbarControl 添加命令 ArcGIS Engine 带有 120 多个直接操作 MapControl、PageLayoutControl 和 ToolbarControl 的命令和工具。这些命令和工具为用户提供了许多地 图导航、图形管理和要素选择等经常使用的 GIS 功能。下面开发人员 将向应用程序添加部分这样的命令和工具。 1. 在“Form_Load”事件中装载文档代码前输入下列代码。 private void Form1_Load(object sender, System.EventArgs e) { string progID; //Add generic commands progID = "esriControlToolsGeneric.ControlsOpenDocCommand"; 296·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 axToolbarControl1.AddItem(progID, -1 , -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); //Add PageLayout navigation commands progID = "esriControlToolsPageLayout.ControlsPageZoomInTool"; axToolbarControl1.AddItem(progID, -1, -1, true, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsPageLayout.ControlsPageZoomOutTool"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsPageLayout.ControlsPagePanTool"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsPageLayout.ControlsPageZoomWholePageCom mand"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsPageLayout.ControlsPageZoomPageToLastExte ntBackCommand"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsPageLayout.ControlsPageZoomPageToLastExtentF orwardCommand"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); //Add Map naviagtion commands progID = "esriControlToolsMapNavigation.ControlsMapZoomInTool"; axToolbarControl1.AddItem(progID, -1, -1, true, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsMapNavigation.ControlsMapZoomOutTool"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsMapNavigation.ControlsMapPanTool"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); progID = "esriControlToolsMapNavigation.ControlsMapFullExtentComma nd"; axToolbarControl1.AddItem(progID, -1, -1, false, 0, esriCommandStyles.esriCommandStyleIconOnly); //Load a pre-authored… } 第六章·开发情景·297 用 Windows 控件建立应用程序 2. 建立并运行应用程序。ToolbarControl 现在包含 ArcGIS Engine 命 令和工具,用户可以用这些命令和工具导移动载到 PageLayoutControl 中的地图文档。用“页面布局”命令移动实际 的页面布局,用“地图”命令移动数据框中的数据。用“open document”命令浏览和装载其他地图文档。 为 PageLayoutControl 创建快捷菜单 与前一个步骤中向 ToolbarControl 添加 ArcGIS Engine 命令以便操作伙 伴控件一样,开发人员也可以从 ArcGIS Engine 命令创建快捷菜单。下 面将给应用程序添加一个操作 PageLayoutControl 的快捷菜单。每当在 PageLayoutControl 显示区域上单击鼠标右键时都会显示该快捷菜单。 1. 将下列成员变量添加到类中: public class Form1 : System.Windows.Forms.Form { private ESRI.ArcGIS.ToolbarControl.AxToolbarControl axToolbarControl1; private ESRI.ArcGIS.TOCControl.AxTOCControl axTOCControl1; private ESRI.ArcGIS.MapControl.AxMapControl axMapControl1; private ESRI.ArcGIS.PageLayoutControl.AxPageLayoutControl axPageLayoutControl1; private IToolbarMenu m_ToolbarMenu = new ToolbarMenuClass(); //The popup menu … 2. 在 Form_Load 事件中向 ToolbarControl 添加命令代码段之后和装 载文档代码之前添加下列代码: private void Form1_Load(object sender, System.EventArgs e) { 298·ArcGIS Engine 开发指南 //Add Map naviagtion commands… 用 Windows 控件建立应用程序 //Share the ToolbarControl’s command pool m_ToolbarMenu.CommandPool = axToolbarControl1.CommandPool; //Add commands to the ToolbarMenu progID = "esriControlToolsPageLayout.ControlsPageZoom InFixedCommand"; m_ToolbarMenu.AddItem(progID, -1, -1, false, esriCommandStyles.esriCommandStyleIconAndText); progID = "esriControlToolsPageLayout.ControlsPage ZoomOutFixedCommand"; m_ToolbarMenu.AddItem(progID, -1, -1, false, esriCommandStyles.esriCommandStyleIconAndText); progID = "esriControlToolsPageLayout.ControlsPageZoom WholePageCommand"; m_ToolbarMenu.AddItem(progID, -1, -1, false, esriCommandStyles.esriCommandStyleIconAndText); progID = "esriControlToolsPageLayout.ControlsPageZoomPageToLastExtent BackCommand"; m_ToolbarMenu.AddItem(progID, -1, -1, true, esriCommandStyles.esriCommandStyleIconAndText); progID = "esriControlToolsPageLayout.ControlsPageZoomPageToLastExte ntForwardCommand"; m_ToolbarMenu.AddItem(progID, -1, -1, false, esriCommandStyles.esriCommandStyleIconAndText); //Set the hook to the PageLayoutControl m_ToolbarMenu.SetHook(axPageLayoutControl1); //Load a pre-authored… } 3. 以设计模式显示窗体,从“Properties”窗口中选择 axPageLayoutControl1窗体并显示axPageLayoutControl1的事件。双 击OnMouseDown事件,以便在代码窗口中添加事件处理器。 4. 给axPageLayoutControl1_OnMouseDown事件添加下列代码: private void axPageLayoutControl1_OnMouseDown(object sender, ESRI.ArcGIS.PageLayoutControl.IPageLayoutControlEvents_OnMo useDownEvente) { //Popup the ToolbarMenu if (e.button == 2) { m_ToolbarMenu.PopupMenu(e.x,e.y,axPageLayoutControl1.hWn d); } } 第六章·开发情景·299 用 Windows 控件建立应用程序 5. 建立 的显示区以显示弹 出式 的标签编辑 默认 层的可见性 及改变地 用空字符 1. 在F pr s e) { it = esriTOCControlEdit.esriTOCControlManual; 2. 件。 双 3. OnEndLableEdit事件中添加下列代码: vate void axTOCControl1_OnEndLabelEdit(object sender, ESRI.ArcGIS.TOCControl.ITOCControlEvents_OnEndLabelEditEve nt { ewLabel.Trim() == "") e.canEdit = false; } } 并运行应用程序。右击 PageLayo 菜单,然后移动页面布局。 utControl TOCControl 中控制 情况下,TOCControl 允许用户在内容表中自动切换图 图和图层名。开发人员要添加代码以防止用户编辑名称时, 串替代该名称。 orm_Load事件的起始处添加下列代码: ivate void Form1_Load(object sender, System.EventArg //Set label editing to manual axTOCControl1.LabelEd //Add generic commands } 以设计模式显示窗体,选择“Properties”窗口中的 AxTOCControlControl1窗体并显示AxTOCControlControl1的事 击OnEndLabelEdit事件,以便在代码窗口中添加事件处理器。 在axTOCControl1_ pri e) //If the new label is an empty string then prevent the edit string newLabel = e.newLabel; if (n { 300·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 用地图导航工具移动活动地图会改 变 PageLayoutControl 中活动地图 的范围并引起 MapControl 的更新。 用地图导航工具移动页面布局会改 变页面布局的范围(而不是 PageLayoutControl 中活动地图的 范围),因此 MapControl 不会更新。 声明为 visBoundsUpdateE 的变量 是一个代表(delegate)。代表是能 保留对特定方法的引用并将其链接 到特定事件的一个类。事件和方法 的链接过程在.NET 中有时称为“布 线(wiring)”。 一次,然后再次单击该标签以 引起标签编辑。试着用空字符串替代标签。在编辑过程中可以随 时用键盘上的 ESC 键取消编辑。 outControl 据框 内移动时,用户会看到 MapControl 的概览窗口的更新。 1. 将下 pu { barControl.AxToolbarControl axToolbarControl1; ntrol olbarMenuClass(); the l; //The symbol used to draw the velope on the M vate ITransformEvents_VisibleBoundsUpdatedEventHandler visBoundsUpdatedE; //The PageLayoutControl's focus map events 4. 建立并运行应用程序。要编辑 TOCControl 中的地图、图层、标题 或图例类标签,可以先单击该标签 在 MapControl 上绘制几何图形 现在开发人员将用 内的活动地图的当前范围。当用户在 PageLayoutControl 的数 列成员变量添加给类: blic class Form1 : System.Windows.Forms.Form private ESRI.ArcGIS.Tool MapControl 作为概览窗口并绘制 PageLay 显示 private ESRI.ArcGIS.TOCControl.AxTOCControl axTOCControl1; private ESRI.ArcGIS.MapControl.AxMapControl axMapControl1; private ESRI.ArcGIS.PageLayoutControl.AxPageLayoutCo axPageLayoutControl1; private IToolbarMenu m_ToolbarMenu = new To private IEnvelope m_Envelope; //The envelope drawn on MapControl private Object m_FillSymbo en apControl pri 第六章·开发情景·301 用 Windows 控件建立应用程序 2. 创建一个名为“CreateOverviewSymbol” 新函数。开发人员在这 个函数中创建 MapControl 中要使用的符号,以表达 PageLayoutControl 的活动地图中的数据范围。在该函数中添加下 列代码: private void CreateOverviewSymbol() { //Get the IRGBColor interface IRgbColor color = new RgbColor(); //Set the color properties color.RGB = 255; //Get the ILine symbol interface ILineSymbol outline = new SimpleLineSymbol(); //Set the line symbol properties outline.Width = 1.5; outline.Color = color; //Get the IFillSymbol interface ISimpleFillSymbol simpleFillSymbol = new SimpleFillSymbolClass(); //Set the fill symbol properties simpleFillSymbol.Outline = outline; le.esriSFSHollow; m_FillSymbol = simpleFillSymbol; private void Form1_Load(object sender, System.EventArgs e) CreateOverviewSymbol(); } 4. 添加 图范 的封装边界。 通过 l,可以强迫重绘其显示上的几何图形。 ) { l1.ActiveView.PartialRefresh(esriViewDrawPhase. } 5. l的缺省事件接口是IPageLayoutControlEvents。该 接口中的事件不会告诉用户数据框中的地图范围什么时候改变。 要完 simpleFillSymbol.Style = esriSimpleFillSty } 3. 在 Form_Load 事件中 TOCControl 标签编辑代码前调用 CreateOverviewSymbol 函数。 { //Create symbol used on the MapControl //Set label editing to manual… 下面的OnVisibleBoundsUpdated函数。这个函数会被链接到地 围改变的事件上,并用于设置地图新的可视范围 刷新MapContro pr sender, bool sizeChanged ivate void OnVisibleBoundsUpdated(IDisplayTransformation //Set the extent to the new visible extent m_Envelope = sender.VisibleBounds; //Refresh the MapControl's foreground phase axMapContro esriViewForeground, null, null); PageLayoutContro 成这个任务,开发人员将使用PageLayoutControl活动地图的 302·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 ITransformEvents接口。在PageLayoutControl_OnPageLayout Replaced事件中紧挨着装载文档代码的上面添加下列代码: private void axPageLayoutControl1_OnPageLayoutReplaced(object sender, outControlEvents_OnPag outControl iew) ax eView.FocusMap; Events of the PageLayoutCntrol's focus map vi IT er(OnVisibleB ou vent)activeView.ScreenDisplay. Di isibleBoundsUpdated += visBoundsUpdatedE; ap document into the MapControl ayoutControl1.DocumentFil the full extent of the data axMapControl1.Extent = axMapControl1.FullExtent; 6. roperties”窗口中的axMapControl1 代码 7. 在a 添加下列代码,以便 用先前创建的符号在 上绘制封装边界。 der,ESRI .ArcGIS.MapControl.IMapControlEvents2_OnAfter (esriViewDrawPhase) IGeometry geometry = m_Envelope; ESRI.ArcGIS.PageLayoutControl.IPageLay eLayoutReplacedEvente) { //Get the IActiveView of the focus map in the PageLay IActiveView activeView = (IActiveV PageLayoutControl1.Activ //Trap the ITranform sBoundsUpdatedE = new ransformEvents_VisibleBoundsUpdatedEventHandl ndsUpdated); ((ITransformEvents_E splayTransformation).V //Get the extent of the focus map m_Envelope = activeView.Extent; //Load the same pre-authored m axMapControl1.LoadMxFile(axPageL ename,null,null); //Set the extent of the MapControl to } 以设计模式显示窗体,选择“P 窗体,并显示该窗体的事件。双击OnAfterDraw事件,以便在 窗口中添加事件处理器。 xMapControl_OnAfterDraw事件处理器中 MapControl private void axMapControl1_OnAfterDraw(object sen DrawEvente) { if (m_Envelope == null) { return; } //If the foreground phase has drawn esriViewDrawPhase viewDrawPhase = e.viewDrawPhase; if (viewDrawPhase == esriViewDrawPhase.esriViewForeground) { axMapControl1.DrawShape(geometry, ref m_FillSymbol); } } 第六章·开发情景·303 用 Windows 控件建立应用程序 本情景的源代码位于安装目录下的 \DeveloperKit\Samples\Develope r_Guide_Scenarios\ ArcGIS_Engine\Building_an_ArcG IS_Control_Application\Map_Vie wer 目录中。 8. trol 在 ESRI 中创建命令非常相似。开发人员将创建一个 自定义命 当 日日期的文本元素。当然,开发人员也可以创建操作 MapControl、 1. 命令而不是亲自创建该命令,请直接 2. Class Library” 工 3. Commands”并浏览选择一个目录保存该工程。 4. 点击 “Add References…”。 5. 在“ ences”对话框中,复选“ESRI.ArcGIS.Carto”、 “ rcGIS.Display ”、“ ESRI.ArcGIS.Geometry ”、 “ ESRI.ArcGIS.System ”、 “ ESRI.ArcGIS.SystemUI ” 和 “E 6. 向工程中添加一个类,并将其命名为“AddDateTool”。 7. 点击“ 源代码 工程中。在“Solution Explo 其 属性 将“Build Action”属性更改为“Embedded Resource”。这个 位图将用在命令按钮上面。 建立并运行应用程序。使用前面添加的地图导航工具改变Page LayoutControl中活动地图的范围。新的范围将绘制在MapCon 上。 创建自定义工具 创建操作 MapControl 和 PageLayoutControl 的自定义命令和工具与先前 ArcMap 应用程序 令,在鼠标点击位置上向 PageLayoutControl 添加一个包含 ToolbarControl 及 PageLayoutControl 的命令。 这个自定义工具的代码可以在本情景后面的源代码中获得。如果 用户想直接使用这个自定义 跳至步骤 24。 在“ New project”对话框中创建一个新的 Visual C#“ 程。 将工程命名为“ “Project”菜单并选择 Add Refer ESRI.A SRI.ArcGIS.ControlCommands”。 Project”菜单并选择“Add Existing Item”菜单项。在示例 目录下找到 date.bmp 并将其添加到 rer”窗口中点击 date.bmp,以在“Properties”窗口中显示 。 304·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 Visual Basic .NET 中改变名 ,在“Solution Explorer” 工程并选择“Properties”。 的属性页中选择“General” 并改变“Root Namespace”。 按 OK 按钮。 象类中继承。与直接实现 iSystemUI的ICommand和ITool ESRI BaseTool 抽 象类允许开发人员更快速和更简单 地创建命令和工具。 封闭的类修改器规定不能从中继承 器方法的名称与类名 用户不用分别实现 Bitmap, Caption,Category,Name,Message 和 Tooltip 方法,而可以设置从这 些方法返回的值,并依靠 BaseTool 类提供这些方法的实现。其他成员 将被设置返回由 BaseTool 类实现 的默认值。 要在 Visual Basic .NET 中重载属 性和方法,从代码窗口顶部的 “Class Name”组合框中选择要重 载的类并从“Method Name”组合框 中选择属性或方法的成名。 。 …. 9. 在 AddDateTool 类代码窗口顶部添加下列 using 指令。 using System; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Display; using ESRI.ArcGIS.Geometry; using ESRI.ArcGIS.SystemUI; using ESRI.ArcGIS.esriSystem; using ESRI.ArcGIS.ControlCommands; using ESRI.ArcGIS.Utility.BaseClasses; using System.Runtime.InteropServices; 10.指定 AddDateTool 类继承自 ESRI BaseTool 抽象类。同 添加 “sealed”类修饰语。 public sealed class AddDateTool : BaseTool 类构造器中添加下列代码: .GetManifestResourceS tream(res[0])); age = "Adds a date element to the page layout"; base.m_name = "CustomCommands_Add Date"; public sealed class AddDateTool : BaseTool 13. ommand OnCreate 方法并右 中添加 属性。 要在 字空间 中右击 在工程 选项卡 抽象类就是不能初始化的类,而且 往往只包含部分实现代码或根本没 有实现。它们与接口有很密切的关 联,但与接口存在差异,类可以实 现任意多个接口,但只能从一个抽 esr 接口相比,继承 类。由于这个类的设计不是目的, 添加这个修改器以防止其它类从这 个类中继承是明智的。 类构造器是在创建类时调用的一种 方法,可以用来设置类成员。构造 相同;与其他 方法不同,该方法没有返回类型。 8. 将 AddDateTool 的名字空间更改为“CSharpDotNETCommands” namespace CSharpDotNETCommands { 时 11.在 AddDateTool public AddDateTool() { //Get an array resources in the assmebly string[] res = GetType().Assembly.GetManifestResourceNames(); //Set the tool properties base.m_bitmap = new System.Drawing.Bitmap(GetType().Assembly base.m_caption = “Add Date”; base.m_category = "CustomCommands"; base.m_mess base.m_toolTip = "Add date"; } 12.在 AddDateTool 类中添加下列成员变量: { //The HookHelper object that deals with the hook passed to the OnCreateevent private IHookHelper m_HookHelper = new HookHelperClass(); … 在“Class View”窗口中定位到 BaseC 击以显示弹出式菜单。选择 Add,然后重载以在代码窗口 14.添加下列代码重载 OnCreate 方法。 public override void OnCreate(object hook) { m_HookHelper.Hook = hook; } 第六章·开发情景·305 用 Windows 控件建立应用程序 ICommand_Create 事件 作的应用程序传递一个句柄或钩 子。在本例中可以是 MapControl、 PageLayoutControl 或 ToolbarControl。开发人员不是在 确定已被 Pa 击 载以在代码窗口中添加属 性。 .添加下列代码,以重载由BaseTool类实现的 “Enabled”值。 iew != null) 17. 定位到BaseTool的OnMouseDown方法并右 击以显示弹出式菜单。 性。 18.添加 ool类实现的默认的OnMouseDown功 能。 Y) { ookHelper.ActiveView; //Create a new text element entClass(); ITextSymbol textSymbol = new TextSymbolClass(); ent element = (IElement) textElement; //Create a page point Transformation.ToMap 会给命令操 OnCreate 事件中添加代码以确定 传递给命令的钩子的类型,而是使 用 HookHelper 来完成这个任务。命 令或工具需要知道如何处理其传递 的钩子,因此需要执行一个检查以 传递的 ArcGIS 控件的类 型。HookHelper 用于保留该钩子并 返回活动视图而不管钩子的类型 (在本例中是 MapControl、 geLayoutControl 或 ToolbarControl)。 15.在“Class View”窗口中定位到BaseCommand的Enabled属性并右 以显示弹出式菜单。选择Add,然后重 16 默认的 public override bool Enabled { get { //Set the enabled property if (m_HookHelper.ActiveV { return true; } else { return false; } } } 在“Class View”窗口中 选择Add,然后重载以在代码窗口中添加属 下列代码重载由BaseT public override void OnMouseDown(int Button, int Shift, int X, int base.OnMouseDown (Button, Shift, X, Y); //Get the active view IActiveView activeView = m_H ITextElement textElement = new TextElem //Create a text symbol textSymbol.Size = 25; //Set the text element properties Element.Symbol = textSymbol; text textElement.Text = DateTime.Now.ToShortDateString(); //QI for Ielement IElem IPoint point = new PointClass(); point = activeView.ScreenDisplay.Display Point(X,Y); 306·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 COM Interop” (Regasm.exe)。这样会 中 如果“Regis iveView.GraphicsContainer.AddElement(element, 0); sriViewDrawPhase.esriViewGraphics, ; } 19.ArcGI 是 COM 类;因此,必须通过为类 创建一 ET 类也是 COM 类。 在“S ”窗口中,右击“Commands”工程并选择弹 出式菜单中的“Properties”。 20.在工程的属性页对话框中选择“Configuration Properties”,然后单 击“B l 面板上将“Register for COM Interop”属性 更改为“True”。单击 OK 按钮。 21. dDateTool 类的代码窗口中,添加下列代码到 AddDateTool 类声明的起始处,以指定 COM ("D880184E-AC81-47 363-781F4DC4528F")] 数和解除注册函数,这些函数用类目工具将 AddDateTool 类 gister in the 'ESRI Controls Commands' component category gion Component Category Registration [C [ComVisible(false)] static void RegisterFunction(String sKey) { ve(0, 18) + @"\Implemented gistry.ClassesRoot.OpenSubKey(fullKey, { 284D891-22EE-4F12-A0A9- } [ComUnregisterFunction()] [C static void UnregisterFunction(String sKey) { n32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(fullKey, true); 将“Register for 属性设置为“True”会调用“套件 注册工具” 将有关该类的信息添加到注册表 ,COM 客户预期可以找到该注册 表。 ter for COM Interop” 属性无效,则请检查工程是否为 C# 类库类型。 用 Visual Studio .NET 中的 GuidGen.exe 工具或选择“Tools” 菜单中的“Create GUID”可以产生 新的 GUID。GUID 应该用右边所示的 格式说明。 //Set the elements geometry element.Geometry = point; //Add the element to the graphics container act //Refresh the graphics activeView.PartialRefresh(e null, null) S Engine 预期自定义命令 个可调用的外壳来指定自己创建的.N olution Explorer ui d”。在右侧的 在 Ad 所需的属性。 [ClassInterface(ClassInterfaceType.None)] [Guid E5-B 22.在 AddDateTool 类中成员变量后面添加下列代码。这段代码定义 注册函 注册到 ESRI 控件命令组件类目中或解除这种注册。 //Re #re omRegisterFunction()] string fullKey = sKey.Remo Categories"; Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Re true); if (regKey != null) regKey.CreateSubKey("{B B1DDED9197F4}"); } omVisible(false)] string fullKey = sKey.Remove(0, 18) + @"\Implemented Categories"; Microsoft.Wi 第六章·开发情景·307 用 Windows 控件建立应用程序 if (regKey != null) { regKey.DeleteSubKey("{B284D891-22EE-4F12-A0A9-B 9197F4}"); 1DDED } #endregion 23.建立工程。 //Add Map naviagtion commands gID, -1, -1, true, 0, esriCommandStyles.esriCommandStyleIconAndText); 25. PageLayoutControl 添 加 } 24.在本情景一开始创建的 Visual Studio .NET“Windows Application” 工程中,在添加地图导航工具的代码后添加下列代码: private void Form1_Load(object sender, System.EventArgs e) { … //Add custom date tool progID = "CSharpDotNETCommands.AddDateTool"; axToolbarControl1.AddItem(pro //Add commands to the ToolbarMenu } 建立并运行应用程序,使用 AddDateTool 向 一个包含当日日期的文本元素。 308·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 Visual Studio .NET 可以指定函数 来执行 COM 互操作套件在系统上注 册和解除注册。这允许用户将自己 的类注册到定制对话框中的某个组 件类目中。 ComVisible 属性被设置为 false 以 确保 COM 客户不能直接调用此方 法。这样不会影响 COM 套件注册时 调用的方法。 定制 T lbarControl 与在 Fo ToolbarControl ArcGIS Engine 具一样, 发人员也可以通过定制 ToolbarControl 并使用定制对话框来 添加命令和工具。这样做开发人员要使 ToolbarControl 处于定制模式并 显示定制对话框。 ler visBoundsUpdatedE; izeDialog = new lass(); //The CustomizeDialog used by the andler andler g close event 2.创建一个名为“CreateCustomizeDialog”的新函数。在该函数添加 { //Set the customize dialog box events startDialogE = new ICustomizeDialogEvents_OnStartDialogEventHandler(OnStartDia log); ((ICustomizeDialogEvents_Event)m_CustomizeDialog).OnStartDi alog += startDialogE; closeDialogE = new ICustomizeDialogEvents_OnCloseDialogEventHandler(OnClose Dialog); ((ICustomizeDialogEvents_Event)m_CustomizeDialog).OnClose Dialog += closeDialogE; //Set the title m_CustomizeDialog.DialogTitle = “Customize ToolbarControl Items”; //Show the ‘Add from File’ button m_CustomizeDialog.ShowAddFromFile = true; //Set the ToolbarControl that new items will be added to m_CustomizeDialog.SetDoubleClickDestination(axToolbarContro l1); } 3. 在 Form_Load 事件调用 CreateOverviewSymbol 子程序之前调用 CreateCustomizeDialog 函数。 private void Form1_Load(object sender, System.EventArgs e) { //Create the customize dialog box for the ToolbarControl CreateCustomizeDialog(); //Create symbol used on the MapControl… } oo rm_Load 事件中向 添加 命令和工 开 1.将下列成员变量添加到类中: public class Form1 : System.Windows.Forms.Form { … private ITransformEvents_VisibleBoundsUpdatedEventHand private ICustomizeDialog m_Custom CustomizeDialogC ToolbarControl tHprivate ICustomizeDialogEvents_OnStartDialogEven startDialogE; //The CustomizeDialog start event private ICustomizeDialogEvents_OnCloseDialogEventH closeDialogE; //The CustomizeDialo 下列代码来创建定制对话框: private void CreateCustomizeDialog() 第六章·开发情景·309 用 Windows 控件建立应用程序 4. 向窗体中添加一个复选框并将其命名为“chkCustomize”,标题为 “Customize”。 事件中添加下列代码。 System.EventArgse) { //Sho if (chk { m_CustomizeDialog.CloseDialog(); ax } else { ze = true; } } 7. 添加 ialog 事件处理器。在打开 或关 private v { axToolbarControl1.Customize = true; } private v { axToolbarControl1.Customize = false; chkC ked = false; } 8. 建立 ToolbarControl 设置为 定制 5. 以设计模式显示窗体,选择“Propeties”窗口中的“chkCustomize” 并显示其事件。双击“CheckedChanged”事件以在代码窗口中添 加一个事件处理器。 6. 在 chkCustomize_ CheckedChanged private void chkCustomize_CheckedChanged(object sender, w or hide the customize dialog C alse) ustomize.Checked == f ToolbarControl1.Customize = false; m_CustomizeDialog.StartDialog(axToolbarControl1.hWnd); axToolbarControl1.Customi 下面的 OnStartDialog 和 OnCloseD 闭定制对话框时会触发这两个函数。 oid OnStartDialog() oid OnCloseDialog() ustomize.Chec 并运行应用程序,复选“定制”框以将 模式并打开定制对话框。 310·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 将该命令添加到 ToolbarControl 上。通过右击 10 9. 在“Commands”选项卡上选择“Graphic Element”类目并双击“Select Elements”命令以 ToolbarControl 上的命令项可以调整命令的外观,如样式和分组等。 .停止定制应用程序。用选择工具移除包含当日日期的文本元素。 第六章·开发情景·311 用 Windows 控件建立应用程序 在使用 ESRI ArcObjects 开发独立 的可执行应用程序时,应用程序要 负责检查和配置许可选项。组件对 象类 AoInitialize 及其实现的 IAoInitialize 接口被设计用于支 持许可配置。许可初始化必须在应 用程序启动、访问任何 ESRI ArcObjects 功能之前执行。许可初 始化失败会导致应用程序错误。 为了将本应用程序成功地部署在另一台机器上,应用程序必须配置 可。首先,必须检查是否有可用的产品许可,其次,必须初始化该许 可。如果许可配置失败,应用程序不能运行。 1. 将下列成员变量添加给类: public class Form1 : System.Windows.Forms.Form { … private IAoInitialize m_AoInitialize = new AoInitializeClass(); //The initialization object private IToolbarMenu m_ToolbarMenu = new ToolbarMenuClass(); //The popup menu … } 2. 在 Form_Load 事件的起始处添加下列代码: private void Form1_Load(object sender, System.EventArgs e) { //Create a new AoInitialize object { System.Windows.Forms.MessageBox.Show("Unable to initialize. This application cannot run!"); this.Close(); } //Determine if the product is available esriLicenseStatus licenseStatus = (esriLicenseStatus) m_AoInitialize.IsProductCodeAvailable( esriLicenseProductCode.esriLicenseProductCodeEngine); if (licenseStatus == esriLicenseStatus.esriLicenseAvailable) { licenseStatus = (esriLicenseStatus) m_AoInitialize.Initialize(esriLicenseProductCode. esriLicenseProductCodeEngine); if (licenseStatus != esriLicenseStatus.esriLicenseCheckedOut) { System.Windows.Forms.MessageBox.Show("The initialization failed. This application cannot run!"); this.Close(); } } else { System.Windows.Forms.MessageBox.Show("The ArcGIS Engine 部署 许 if (m_AoInitialize == null) 312·ArcGIS Engine 开发指南 用 Windows 控件建立应用程序 product is unavailable. This application cannot run!"); this.Close(); } Properties”窗口中的 Form1 并显示 其 一个事件处理 器。 4. 在 private void Form1_Closing(object sender, Sy { s and shut down the AoInitilaize object .COMSupport.AOUninitialize.Shutdown(); _AoInitialize.Shutdown(); . 成功地将本应用程序部署到用户机器上需要以下条件: 包 执行和 DLL 文件要部署到用户机 器上。 该自 用户机器上需要安装 ArcGIS Engine 运行时和一个标准的 ArcGIS En 用 其他资 下列资源可能有助于开发人员理解和应用本情景提出的概念和技术。 ArcG 开发帮助、 以帮助开发人员快速入门。 ArcGIS开发在线网站—提供有关ArcGIS开发方面的最新信息,包 括更新 http://ar ESRI在线论坛网站—提供来自其他ArcGIS开发人员的无价的帮 助。网 rt.esri.com并点击“用户论坛(User Forums)” 选项卡。 Microsoft 公司有关 Visual Studio .NET 开发环境方面的文档。 //Create the customize dialog box for the ToolbarControl … } 3. 以设计模式显示窗体,选择“ 事件。双击“Closing”事件以在代码窗口中添加 Form_Closing 事件中添加下列代码: stem.ComponentModel.CancelEventArgs e) ect//Release COM obj ESRI.ArcGIS.Utility m } 5 构建工程并以发行模式构建解决方案。 含自定义命令的应用程序可 必须用套件注册工具(RegAsm.exe)向注册表中添加有关 定义类的信息。 gine 许可。 户机器上需要安装 Microsoft .NET Framework 1.1。 源 IS Engine 开发工具包中的其他文档,包括 ArcGIS 组件帮助、对象模型图和示例, 的示例和技术文档。网址为 cgisdeveloperonline.esri.com。 址为http://suppo 第六章·开发情景·313 建立命令行 Java 应用程序 建立命令行 Java 应用程序 用户可以不按照本情景进行,而可 以从示例安装路径获得完整的应用 程序。示例作为 ArcGIS 开发示例的 组成部分而安装。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 Java API 不包含在 ArcGIS Engine 开发工具包的“典型”安装中。如 果没有安装 Java API,可以重新运 行开发工具包安装向导,选择“自 定义”或“修改”并选择 ArcGIS Engine 下的 Java 特性。另外,为 访问 Javadoc 和其他 Java 文档,选 择软件开发工具包下的 Java 特性。 本情景的目标读者是希望了解更多有关 ArcGIS Engine 中 Java API 使 用方法的程序员。掌握本情景的大部分内容需要用户理解基本的 Java 编程概念,如类、继承和“using”包等。对 ArcObejcts 有些了解是 有益的,但不要求必须了解。虽然本情景的确要求用户具有 Java 语言 的概念性知识,但不要求用户具有丰富的编程经验。本示例中使用的 代码为用户提供了一个在小而简单的尺度上学习 ArcGIS Engine 中 Java API 编程的简单切入点。 用户可以在 ArcGIS 安装目录下的\DeveloperKit\Samples\Developer_ Guide_Scenarios\ ArcGIS_Engine\Building_a_Command_Line_ Appli- cation\Converting_A_Tin_To_Point_Shapefile 文件夹中找到本示例。 工程描述 本情景涵盖了 ArcGIS Engine Java API 的若干个方面。本练习的目标 就是用 ArcGIS Engine Java API 创建一个独立的命令行应用程序。该 应用程序的输入是某个表面的一个 TIN 表示,通过对 TIN 节点的插值 创建一个三维的 shapefile 表示。一旦完成了本情景,用户就会明白 使用 ArcGIS Engine Java API 所需的技术。 概念 地形数据大多数采集成一系列离散的(X,Y,Z)数据点。数字地面模 型(DTM)的组织方式一般为点格网或三角形节点阵列,称为不规则三 角网或 TIN。这些节点将被转换为点并用于创建一个新的要素类。本练 习将使用 TIN 对象及其实现的 ITinAdvanced 接口。ITinAdvanced 接口 提供访问 TIN 基本属性的方法,也是理解底层数据结构的入口点。此 外,本情景利用 GeometryDef 和 FieldsEdit 对象来存放新建的要素类。 设计 应用程序将完全用 Java 语言编写。这样允许用户在任意一种平台上编 写代码并将应用程序部署到任何支持 ArcGIS Engine 的平台上。本情 景以 Microsoft Windows XP 作为开发平台,但可以轻松地在任何基于 UNIX 的开发平台上完成本情景。 我们使用 Ant—一个基于 Java 的跨平台构建工具—来构建和部署本情 景。Ant 执行一些已经实现为 Java 类的任务,这允许它继承独立于 Java 的平台。ArcGIS Engine 开发工具包包括 Ant 的一个扩展版本,称为 “arcgisant”。本情景将使用 arcgisant,但用户可以自由使用 Ant 1.5.x 或更高版本。 要求 要成功地按照本情景进行开发,需要下列条件(部署需求将在后面的 “部署”一节中阐述): 安装了 ArcGIS Engine 开发工具包(包括 Java),且具有能用于 开发的许可文件。 314·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 安装了Java 2 平台、标准编辑(J2SE)软件开发工具包(SDK), 最好是 1.4.2 或更高版本。如果没有这些软件,可以从Java网站 http://java.sun.com/j2se/downloads.html下载。 选择一种 Java IDE 或喜欢的文本编辑器。 理解基本的 Java 编程概念,如类、继承和“using”包等。 尽管不需要有其他 ESRI 软件的经验,但具有 ArcObjects 经验并 对地图具有基本的理解是有益的。 一个 TIN 数据集。 可以访问本情景的示例数据和代码。其位置为 ArcGIS 安装目录下 的:\DeveloperKit\Samples\Developer_Guide_Scenarios\ArcGIS_ Engine\Building_a_Command_Line_Application\Converting_A_Tin _To_Point_Shapefile 本情景将使用下列类库中的对象: DataSourcesFile ● Geometry GeoDatabase ● System 在 Java API 中,这些包名的前缀为“com.esri.arcgis”。 实现 要实现本情景,请按下面的步骤进行。这个实现过程提供了成功完成 本情景所需的所有代码。本情景没有提供在 Java 中开发应用程序的逐 步指导,因为我们假设用户已经具有 Java 开发环境的使用经验。 设置环境变量 Windows 用户可以添加、修改或删除用户环境变量。设置这些环境变量 可以最有效地使用本情景。下面将向全局 PATH 变量添加三个环境变量 及它们各自的可执行(bin)文件夹。 1. 右击“我的电脑”,然后单击“属性”。 2. 单击“高级”选项卡。 3. 单击“环境变量”按钮。 4. 单击“系统变量”下的“新建”按钮。 在变量名文本框中输入“ARCENGINEHOME”。 在变量值文本框中输入 ArcGIS Engine 安装的根目录(如, C:\ArcGIS)。 5. 单击 OK 按钮。 6. 在“系统变量”下单击“Path”变量并单击“编辑”按钮。 7. 在变量值文本框的最前面添加下面路径: %ARCENGINEHOME%\bin; 8. 单击 OK 按钮关闭所有“系统属性”对话框。 第六章·开发情景·315 建立命令行 Java 应用程序 为使用 ArcGIS Engine 开发工具包 编译和运行应用程序,环境变量 PATH 应包括到 ArcGIS\bin 和 J2SE SDE\bin 的路径。此外,ArcGIS Engine 开发工具包带有一个 Ant 的 扩展版本,称为 arcgisant。环境 变量 PATH 也应该包含到该工具的 路径。 316·ArcGIS Engine 开发指南 为使构建脚本正确工作所需的文件 夹和文件结构。 重复上述步骤添加下列环境变量: JAVA_HOME=J2SE SDK 安装目录 ANT_HOME=%ARCENGINEHOME%\DeveloperKet\tools\arcgisant 在继续建立应用程序前,用户必须准备好构建脚本。如左边所示那样 设置好构建结构至关重要,以便脚本能正确的工作。 TintoPoint 文件夹—工程的根目录 Src 文件夹—包含应用程序所有源代码的子目录 build.xml—Ant 构建脚本文件。 properties.xml—外部的 Ant 属性文件,扩展了构建环境。 sample.properties—具有命令行参数的外部 Java 属性文件 build.xml、properties.xml 和 sample.properties 这三个文件必须在 建立和部署本情景前创建。 创建 sample.properties 文件 我们首先创建 sample.properties 文件。该文件提供带有必要命令行 参量(argument)的构建脚本,以成功执行本练习创建的应用程序。 该文件将使用变量=参量(argument)的模式。 1. 创建一个名为 sample.properties 的文本文件,并向该文件添加 下面几行代码。修正变量 input.tin.path 和 output.shape.path 的参量以匹配用户存放 TIN 数据集和输出的 shape 文件数据集的 路径。 # TinToPoint unit.name=TintoPoint main.class=engine.scenario.analyst3d.TintoPoint #TinToPoint command line args input.tin.path=\ output.shape.path=\ 2. 保存并关闭该文件。 创建 properties.xml 文件 properties.xml 文件设置了构建环境的 Ant 属性。Ant 属性可以明确 地设置或从文件中加载。为了简单起见,这里将从 properties.xml 文 件中添加依赖属性: 1. 创建一个名为 properties.xml 的 XML 文件并添加以下 Ant 属性: 建立命令行 Java 应用程序 要了解更多有关Ant脚本中使用的 私有和公有目标方面的信息,请参 阅Apache Ant网站 http://ant.apache.org/上的Ant 文档。 2. 保存并关闭该文件。 创建 build.xml 文件 Ant 构建脚本用 XML 编写,并包含一个工程和至少一个任务。每个工程 定义一个或多个组合执行任务的目标(target)。在本情景中,工程名 为“ArcGIS Engine Developer Scenario”,其构建脚本要包含四个私 有和三个公有的目标,分别列在下面: inti—创建构建目录结构。 validate-engine—确保ArcGIS Engine开发环境进行了合适的设 置。 compile—编译本情景。 execute—在一个单独的 JVM 例程中运行本情景。 all—默认目标,构建整个情景。 clean—清除所有构建产品。 run-scenario—构建并运行本情景的应用程序。 为了理解 Ant 目标的结构,我们逐行看一下示例中的 compile 目标。 如上所示,在第一行中,该目标有一个引用名和对另一个目标的依赖。 为确保构建成功,“depends”属性用于确认正确地设置了环境。 第六章·开发情景·317 建立命令行 Java 应用程序 这里不详细讨论整个构建脚本;要 了解更多有关建立Ant脚本方面的 信息,请参阅Apache Ant网站 http://ant.apache.org/上的Ant 文档。 在这个示例目标的最后部分,Ant 核心任务之一“javac”被创建,以 编译源目录中的文件并将它们放在类目录中。javac 任务支持一个类路 径任务以确保类路径的设置。 完整的示例目标,包括 XML 标签的结束符,如下所示: 现在用户已经基本清楚了目标及其在 Ant 中的使用,下面我们继续创 建本情景所需的 build.xml 文件。 1.创建一个名为 build.xml 的 XML 文件并添加下列代码: ]> &properties; 318·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 第六章·开发情景·319 建立命令行 Java 应用程序 用喜欢的文本编辑器或 IDE 编写源 代码。 2. 保存并关闭该文件。 测试构建环境 现在已经设置好构建应用程序所需的所有文件,在继续之前先进行测 试。 1. 打开一个命令提示符,并进入工程的根目录。例如, cd C:\TintoPoint 2. 用户应该把所有构建脚本文件放在这个根目录下。 3. 在命令提示符中输入“arcgisant”。 4. 用户应该接收到类似于下面的输出: Buildfile: build.xml init: [mkdir] Created dir: Q:\dop\dev\ant\projects\engine.scenario\build [mkdir] Created dir: Q:\dop\dev\ant\projects\ engine.scenario\build\classes validate-engine: compile: BUILD FAILED file:Q:/dop/dev/ant/projects/engine.scenario/build.xml:49: srcdir "Q:\dop\dev\ant\projects\engine.scenario\src" does not exist! Total time: 1 second 由于还没有任何源代码用来构建,因此构建应该会失败。如果磁盘上 的检查工程目录,可以发现创建了一个 build 文件夹。所有的构建产 品将在给目录中生成。 用下面的命令清空构建: arcgisant clean 这应该产生与下面相似的输出: Buildfile: build.xml clean: [delete] Deleting directory Q:\dop\dev\ant\projects\engine.scenario\build [echo] build directory gone! BUILD SUCCESSFUL Total time: 1 second 现在用户的构建环境已经准备好,可以为示例程序继续编写 Java 源代 码了。 创建“TintoPoint”主类 1.首先在源代码的开始处添加本练习中主类的署名: package engine.scenario.analyst3d; public class TintoPoint{} 这个主类最终会有三个私有的静态方法和一个公有的主入口点。为了 简单起见,用私有静态方法来完成应用程序的工作。 320·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 datasourcesfile、geodatabase、 geometry 和 system 等 Java 包不等 于 ArcGIS Engine 的 DataSourcesFile、GeoDatabase、 Geometry 和 System 类库。 虽然有的 Java 开发人员认为当某个方法可以是静态的或是一个例程时 应该使用例程方法,但本情景使用最简单的途径—静态方法。这些方 法将在下面的部分中涉及。 其次,我们看看该类需要哪些导入。 如前所述,本情景使用 datasourcesfile、geodatabase、geometry 和 system 等 Java 包。 datasourcesfile—为geodatabase API支持的矢量数据格式提供 工作空间工厂和工作空间。本练习中将用到 ShapefileWorkspaceFactory 类,以创建生成的 3D Shape 文件。 geodatabase—提供与数据访问相关的所有定义,包括 Tins 和 FeatureClasses。本情景将用到 GeometryDef 类,以便为用户生 成的要素类定义空间质量。 geometry—包含核心几何图形对象及空间参考信息。本情景中将 用到 Point 类作为用户输入 TIN 数据集中所有 TIN 节点的表示。 system—包含为 ArcGIS Engine 内其他类库提供服务的对象。本 情景中将用到 AoInitializer 对象以初始化应用程序和解除初始 化。 2.在上述包声明后面,如下所示导入类。使用完整描述的导入语句, 以便可以明确哪些类来自用户正使用的 ArcGIS Engine Java API。 import java.io.File; import java.io.IOException; import com.esri.arcgis.datasourcesfile.ShapefileWorkspaceFactory; import com.esri.arcgis.geodatabase.Field; import com.esri.arcgis.geodatabase.Fields; import com.esri.arcgis.geodatabase.GeometryDef; import com.esri.arcgis.geodatabase.IEnumTinNode; import com.esri.arcgis.geodatabase.IFeatureBuffer; import com.esri.arcgis.geodatabase.IFeatureClass; import com.esri.arcgis.geodatabase.IFeatureClassProxy; import com.esri.arcgis.geodatabase.IFeatureCursor; import com.esri.arcgis.geodatabase.IFeatureWorkspace; import com.esri.arcgis.geodatabase.IFeatureWorkspaceProxy; import com.esri.arcgis.geodatabase.IFieldEdit; import com.esri.arcgis.geodatabase.IFields; import com.esri.arcgis.geodatabase.IFieldsEdit; import com.esri.arcgis.geodatabase.ITinAdvanced; import com.esri.arcgis.geodatabase.ITinNode; import com.esri.arcgis.geodatabase.IWorkspaceFactory; import com.esri.arcgis.geodatabase.Tin; import com.esri.arcgis.geodatabase.esriFeatureType; import com.esri.arcgis.geodatabase.esriFieldType; import com.esri.arcgis.geodatabase.esriTinQualification; 第六章·开发情景·321 建立命令行 Java 应用程序 ITinAdvanced 接口在部署时需要 一个具有 3D 选项的 ArcGIS Engine 运行时许可或一个 ArcGIS 3D 分析 扩展模块。在本练习的后面将添加 代码检查这个许可并将其检出。 注意此处的 try/catch 代码段。 TODO import com.esri.arcgis.geometry.IPoint; import com.esri.arcgis.geometry.ISpatialReference; import com.esri.arcgis.geometry.Point; import com.esri.arcgis.geometry.esriGeometryType; import com.esri.arcgis.system.AoInitialize; import com.esri.arcgis.system.EngineInitializer; import com.esri.arcgis.system.esriLicenseExtensionCode; import com.esri.arcgis.system.esriLicenseProductCode; 现在已经完成了类的导入,可以开始向类中添加方法了。 添加 tinToPoint 方法 这个方法执行了应用程序的大部分任务。该方法有四个参数,分别为 TIN 文件的路径、TIN 文件名、输出的 shapefile 路径和输出的 shapefile 文件名。 4. 为 tinToPoint 方法创建署名如下: /** * @param tinPath – path to input tin data source * @param tinName – name of input tin data source * @param shapePath – path to output shapefile data source * @param shapeFile – name of output shapefile data source */ private static void tinToPoint(String tinPath, String tinName, String shapePath, String shapeFile){ 首先,通过实例化一个新的 Tin 类,需要打开 TIN 文件目录下的 TIN 数据集,该数据集作为此方法的一个参数被传递。该 Tin 类实现了许 多接口;这里要用到 ITinAdvanced 和 IGeoDataset 接口提供的方法。 通过调用 ITinAdvanced 接口提供的 init()方法可以打开特定的 TIN。 5. 通过在方法署名后面添加下列代码来实例化新的 Tin 类。 try{ //Get tin from tin file ITinAdvanced tinAdv = new Tin(); String path = tinPath + File.separator + tinName; System.out.println(" - Path to Tin: " + path); tinAdv.init(path); System.out.println(" - Calculating ... "); Tin tin = new Tin(tinAdv); 6. 下一步,获得 Tin 的空间参考并将其作为参数传递给 createBasicFields()方法,该方法将在下一个部分定义。此外, 由于结果要素类为点要素类,因此要传递一个点类型的几何图形。 ISpatialReference tinSpatialRef = tin.getSpatialReference(); IFields fields = createBasicFields(esriGeometryType.esriGeometryPoint, false, true, tinSpatialRef); 322·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 4. 现在已经生成了基本字段,下一步就要创建一个输出的 shapefile。用 WorkspaceFactory 作为工作空间的分配器,以创 建 一 个 ShapefileWorkspaceFactory 类的例程。用 IfeatureWorkspace 接口来访问和管理数据集。通过使用其对象构 造器创建 IfeatureWorkspaceProxy 类的一个例程,用户可以得到 实 现 了 Iworkspace 接 口 的被返回对象。最后,用 createFeatureClass()方法创建一个独立的要素类,就可以生成 输出的 shapefile。 // create output shapefile IWorkspaceFactory wkspFactory = new ShapefileWorkspaceFactory(); IFeatureWorkspace featureWksp = new IFeatureWorkspaceProxy( wkspFactory.openFromFile(shapePath, 0)); IFeatureClass outFC = new IFeatureClassProxy( featureWksp.createFeatureClass( shapefile, fields, null, null, esriFeatureType.esriFTSimple, "Shape", "”)); 5. 创建本方法的最后一步就是将输入 TIN 中的节点的值存放在新创 建的要素类中。makeNodeEnumerator()方法基于某个范围和准则返 回节点枚举。所用的范围就是输入的 TIN 数据集,TIN 中的所有数 据都用作准则。一旦用 TIN 节点填充了该枚举对象,就可以用一个 数据访问对象 FeatureCursor 迭代访问新创建的要素类的记录集, 用一个 FeatureBuffer 对象保留记录的状态。然后通过用上面的枚 举返回的对象实例化 ITinNode 接口,创建一个 Point 类的例程并 用 TIN 节点填充该例程。当节点不为 NULL 时,循环访问枚举并填 充要素类。 // get tin node enum IEnumTinNode enum = tin.makeNodeEnumerator(tin.getExtent(), esriTinQualification.esriTinInsideDataArea, null); IFeatureCursor outCursor = outFC.IFeatureClass_insert(true); IFeatureBuffer outBuffer = outFC.createFeatureBuffer(); IPoint point = new Point(); ITinNode node = enum.IEnumTinNode_next(); while(node != null){ node.queryAsPoint(point); outBuffer.setShapeByRef(point); outCursor.insertFeature(outBuffer); node = enum.IEnumTinNode_next(); } 第六章·开发情景·323 建立命令行 Java 应用程序 6. 最后捕获任何例外以结束 tinToPoint 方法的代码编写工作。 System.out.println(" - Path to Generated Shapefile: " + shapePath + File.separator + shapeFile); }catch (Exception ex) { ex.printStackTrace(); } } 现在已经完成了产生用于表示磁盘上输入 TIN 文件节点数据的新 shapefile。 7. 回顾整个方法的代码,以便确保用户的代码与之相匹配。 /** * @param tinPath – path to input tin data source * @param tinName – name of input tin data source * @param shapePath – path to output shapefile data source * @param shapeFile – name of output shapefile data source */ private static void tinToPoint(String tinPath, String tinName, String shapePath, String shapeFile){ try{ //Get tin from tin file ITinAdvanced tinAdv = new Tin(); String path = tinPath + File.separator + tinName; System.out.println(" - Path to Tin: " + path); tinAdv.init(path); System.out.println(" - Calculating ... "); Tin tin = new Tin(tinAdv); ISpatialReference tinSpatialRef = tin.getSpatialReference(); IFields fields = createBasicFields(esriGeometryType.esriGeometryPoint, false, true, tinSpatialRef); // create output shapefile IWorkspaceFactory wkspFactory = new ShapefileWorkspaceFactory(); IFeatureWorkspace featureWksp = new IFeatureWorkspaceProxy( wkspFactory.openFromFile(shapePath, 0)); IFeatureClass outFC = new IFeatureClassProxy( featureWksp.createFeatureClass( shapefile, fields, null, null, esriFeatureType.esriFTSimple, "Shape", "”)); // get tin node enum IEnumTinNode enum = tin.makeNodeEnumerator(tin.getExtent(), esriTinQualification.esriTinInsideDataArea,null); 324·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 // store node to shapefile IFeatureCursor outCursor = outFC.IFeatureClass_insert(true); IFeatureBuffer outBuffer = outFC.createFeatureBuffer(); IPoint point = new Point(); ITinNode node = enum.IEnumTinNode_next(); while(node != null){ node.queryAsPoint(point); outBuffer.setShapeByRef(point); outCursor.insertFeature(outBuffer); node = enum.IEnumTinNode_next(); } System.out.println(„ - Path to Generated Shapefile: " + shapePath + File.separator + shapeFile); }catch (Exception ex) { ex.printStackTrace(); } } 为三维 shapefile 创建基本字段 现在已经创建好了用于执行从 TIN 到 shapefile 转换的方法,下一步 就要为三维 shapefile 定义程式结构。上面定义的 tinToPoint()方法 使用这里创建的 createBasicFields 方法来生成一个 fields 对象并将 其作为参数传递给 createFeatureClass()方法。 1. 为本方法创建署名如下: /** * @param shapeType - a geometry object type * @param hasM - m-value precision defined * @param hasZ - z-value precision defined * @param spatialRef - Spatial Reference * * @return IFields - a collection of columns in a table */ private static IFields createBasicFields(int shapeType, boolean hasM, boolean hasZ, ISpatialReference spatialRef){ 2. 用一个 GeometryDef 对象来定义要素类的空间质量。本方法用作 参数的最基本空间质量就是几何图形的类型。用 IFieldsEdit 接 口创建新的 fields 并返回该对象。 try { Fields fields = new Fields(); IFieldsEdit fieldsEdt = fields; Field field = new Field(); IFieldEdit fieldEdt = field; GeometryDef geometryDef = new GeometryDef(); 第六章·开发情景·325 建立命令行 Java 应用程序 double dGridSize; if (spatialRef.hasXYPrecision()) { double[] xmin = {0}; double[] ymin = {0}; double[] xmax = {0}; double[] ymax = {0}; spatialRef.getDomain(xmin, xmax, ymin, ymax); double dArea = (xmax[0] - xmin[0]) * (ymax[0] - ymin[0]); dGridSize = Math.sqrt(dArea / 100); }else { dGridSize = 1000; } geometryDef.setGeometryType(shapeType); geometryDef.setHasM(hasM); geometryDef.setHasZ(hasZ); geometryDef.setSpatialReferenceByRef(spatialRef); geometryDef.setGridCount(1); geometryDef.setGridSize(0,dGridSize); // add oid field - must come before geometry field fieldEdt = new Field(); fieldEdt.setName("OBJECTID"); fieldEdt.setAliasName("OBJECTID"); fieldEdt.setType(esriFieldType.esriFieldTypeOID); fieldsEdt.addField(fieldEdt); //add Geometry field fieldEdt = new Field(); fieldEdt.setName("SHAPE"); fieldEdt.setIsNullable(true); fieldEdt.setType(esriFieldType.esriFieldTypeGeometry); fieldEdt.setGeometryDefByRef(geometryDef); fieldEdt.setRequired(true); fieldsEdt.addField(fieldEdt); return fieldsEdt; }catch (IOException ex) { ex.printStackTrace(); return null; } } 初始化 ArcObjects 用 ArcGIS Engine 开发工具包建立的每个应用程序都必须在某个产品 层次上初始化 ArcObjects,包括任何合适的扩展许可。AoInitialize 对象用于完成这个任务。这个类初始化 ArcObjects 运行时环境,必须 是第一个创建的 ArcObjects 组件。将从这个对象调用两个方法: initialize(int product code)—此方法的参数为一个表示产 品代码的整数值。Java API 提供了一个名为 esriLicenseProductCode 的接口,该接口中包含一些静态的整数 字段,代表着不同的 ESRI 产品级别。要获得完整的产品列表,请 326·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 参阅 javadoc 描述。 checkOutExtension(int extensioncode)—此方法的参数为一 个表示扩展许可代码的整数值。Java API 提供了一个名为 esriLicenseExtensionCode 的接口,该接口中包含一些静态的整 数字段,代表着不同的 ESRI 扩展产品。 本应用程序需要一个具有 3D 分析扩展许可的 ArcGIS Engine 运行时许 可。 1. 创建此方法的署名如下: private static void licenseCheckOut(){} 2. 实现这个私有方法。 /** * Initialize ArcObjects product usage and check out * available 3D Analyst extension license */ private static void licenseCheckOut(){ try{ AoInitialze aoInit = new AoInitialize(); aoInit.initialze(esriLicenseProductCode.esriLicenseProductCodeE ngine); aoInit.checkOutExtension(esriLicenseExtensionCode. esriLicenseExtensionCode3DAnalyst); }catch(IOException e){ System.out.println(„Program Exit: Unable to initialize ArcObjects „); System.exit(0); } } 整合应用程序 现在已经构造好了所有的私有方法,接下来需要为应用程序创建的一 个入口点。这个主方法将获得命令行参量并将它们传递给前面创建好 的 tinToPoint 方法。 1. 首先,以一个 if/else 代码块的形式插入特定的逻辑,以确保获 得了正确数目的参量。 /* * Description: * Main Method - Application Entry Point */ public static void main(String[] args) { if(args.length != 2){ System.out.println( "Tin to Point: ArcGIS Engine Developer Scenario"); System.out.println("Usage: TintoPoint [Path-to-tin] [Path-to-outputshapefile]"); 第六章·开发情景·327 建立命令行 Java 应用程序 本情景并没有实现用于确定被传递 的参量是否为有效的字符串或数据 路径这样的任何逻辑。这种校验过 程在开发产品应用程序时很有用。 System.exit(0); }else{ System.out.println( "Tin to Point: ArcGIS Engine Developer Sample"); String inDataset = args[0]; String outDataset = args[1]; 本应用程序有两个参量:一个是 TIN 数据集的完整路径,另一个是生 成的输出 shapefile 的完整路径。一旦应用程序确定其获得了正确数 目的参量,就可以将它们拆分成前面生成的 tinToPoint()方法所需的 路径和文件名格式。 2. 在刚刚创建的 if/else 代码块后面添加下列代码。 String inDataPath = inDataset.substring( 0, inDataset.lastIndexOf(File.separator)); String inDataName = inDataset.substring( inDataset.lastIndexOf(File.separator) + 1)); String outDataPath = outDataset.substring( 0, outDataset.lastIndexOf(File.separator)); String outDataName = outDataset.substring( outDataset.lastIndexOf(File.separator) + 1)); 创建主方法的下一步就是初始化编程环境。在 ArcGIS Java API 中, 通过调用静态类 EngineInitializer 来完成这个任务。该类是 Java API 提供的一个外表(facade)类,以确保 Java 最优利用本地 ArcObjects。 接下来必须调用前面描述过的静态 licenseCheckOut()方法。 3. 在主方法中添加下列代码。 EngineInitializer.initilizeEngine(); licenseCheckOut(); 4. 最后一步就是调用静态工作方法 tinToPoint()并给其传递字符串 参数。 tinToPoint(inDataPath, inDataName, outDataPath, outDataName); System.out.println("Tin to Point - Done"); } } 这样就完成了命令行应用程序的实现代码。 部署 部署 Java 应用程序有许多选择,用户可以根据自己的喜好选择任何一 种部署方法。本情景利用前面创建好的 Ant 构建脚本来部署 Java 应用 程序。 重做前面进行测试时的构建步骤。 1. 打开一个命令提示符,并进入工程的根目录。例如, cd C:\TintoPoint 2. 用户应该把所有构建脚本文件放在这个根目录下。 3. 在命令提示符中输入“arcgisant”。 4. 用户应该接收到类似于下面的输出: Buildfile: build.xml init validate-engine: 328·ArcGIS Engine 开发指南 建立命令行 Java 应用程序 compile: execute: [java] Tin to Point: ArcGIS Engine Developer Sample [java] - Path to Tin: Q:\dop\data\imagery\tin\bachtin [java] - Calculating ... [java] - Path to Generated Shapefile: Q:\dop\data\workspace\newshp.shp [java] Tin to Point - Done run-scenario: BUILD SUCCESSFUL Total time: 9 seconds 如果由于某种原因导致构建失败,则应确保正确地设置好了环境并在 sample.properties 文件中正确地设置好了参数。如果用户的构建没有 编译,则应确保源代码是否正确。 故障解决 如果用户的构建返回下列语句: Buildfile: build.xml init: validate-engine: compile: execute: [java] Tin to Point: ArcGIS Engine Developer Scenario [java] Usage: TintoPoint [Path-to-tin] [Path-to-output-shapefile] run-scenario: BUILD SUCCESSFUL 则已经成功地编译了源代码,但没有在 sample.properties 文件中提 供路径变量。 如果用户的构建返回下列语句: Buildfile: build.xml init: validate-engine: compile: execute: [java] Tin to Point: ArcGIS Engine Developer Sample [java] java.lang.StringIndexOutOfBoundsException: String index out of range: -1 [java] at java.lang.String.substring(String.java:1444) [java] at engine.scenario.analyst3d.TintoPoint.main(Unknown Source) [java] Exception in thread "main" BUILD FAILED file:Q:/dop/dev/ant/projects/engine.scenario/build.xml:53: Java returned: 1 第六章·开发情景·329 Total time: 1 second 建立命令行 Java 应用程序 则已经编译好了源代码,但没有提供表示数据路径的有效变量字符串。 确保提供了下列格式的数据路径: C:\\data\imagery\\tin\\bachtin C:\\data\\workspace\\newshp.shp 其他资源 下列资源可能有助于开发人员理解和应用本情景提出的概念和技术。 ArcGIS Engine 开发工具包中的其他文档,包括 ArcGIS 开发帮助、 组件帮助、对象模型图和示例,以帮助开发人员快速入门。 ArcGIS开发在线网站—提供有关ArcGIS开发方面的最新信息,包 括更新的示例和技术文档。网址为 http://arcgisdeveloperonline.esri.com。 ESRI在线论坛网站—提供来自其他ArcGIS开发人员的无价的帮 助。网址为http://support.esri.com并点击“用户论坛(User Forums)”选项卡。 大多数 Java 开发人员的工具包中都保留有下面的 URLs 列表。这些链 接在本书出版时是正确的,但可能会改变。 Sun 公司的 Java 和 JDK FAQ 网站 (http://java.sun.com/products/jdk/faq.html)—提供有关 Java 的高级介绍性常问问题(FAQs)。 Sun 公司的 Java 网站(http://java.sun.com/)—Java 技术的源。 Sun 公司的 Java 教程网站 (http://java.sun.com/docs/books/tutorials)—来自 Addison-Wesley 书籍的在线版本,提供所有有关 Java 的学习教 程。 Java 思想(Thinking in Java)网站 (http://www.mindview.net/Books/TIJ)—来自 Prentice Hall 书籍的在线版本,很适于学习 Java 中的面向对象编程概念。 Apache Ant 计划是一个非常工程的开放源代码计划,而且拥有许多资 源商标。 Apache Ant 计划(http://ant.apache.org)—介绍 Apache Ant 计划的网站。 用 Ant 进行 Java 开发网站(http://www.manning.com/antbook) —介绍 Ant 1.5 的好书。 Ant in Anger 网站 (http://ant.apache.org/ant_in_anger.html)—这个文档描述 了如何使用 Ant 的策略和一些基本示例。 jGuru网站(http://www.jguru.com/forums/home.jsp?topic =Ant)—jGuru主持的一个交互式Ant论坛。 330·ArcGIS Engine 开发指南 建立命令行 C++应用程序 建立命令行 C++应用程序 用户可以不按照本情景进行,而可 以从示例安装路径获得完整的应用 程序。示例作为 ArcGIS 开发示例的 组成部分而安装。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 虽然本情景指导用户用 C++进行开 发,但也可以获得用其他编程语言 编写的解决方案代码,包括 C#, Java,Visual Basic 6.0,Visual Basic .NET 和 Visual C++。 要了解更深层次的坡度解释,请参 阅本情景末尾“其他资源”一节中 列出的 Burrough和 McDonnell的参 考文献。 本情景的目的在于介绍使用 ArcGIS Engine C++ API 开发跨平台应用 程序的方法。掌握本情景的大部分内容需要用户理解基本的 C/C++编程 概念,如预处理器、函数和内存管理等。对 ArcObejcts 有些了解是有 益的,但不要求必须了解。虽然本情景的确要求用户具有 C++语言的概 念性知识,但不要求用户具有丰富的编程经验。本示例中使用的代码 为用户提供了一个在小而简单的尺度上学习 ArcGIS Engine 中 C++ API 编程的简单切入点。 本情景的目的不是讲授设置 C++环境或在各种支持的操作系统上进行 编译的方法。本情景自始至终假设用户已经有一个正常运作的 C++环 境,并且知道在该环境中编译 C++程序的方法。本情景提供的就是创建 一个能根据给定的数字高程模型计算坡度的命令行应用程序需要采取 的步骤及编写的代码。 可以在 ArcGIS 安装目录下的\DeveloperKit\Samples\Developer_Guide_ Scenarios\ArcGIS_Engine\Building_a_Command_Line_Application\Comp uting_the_slope_of_a_raster_dataset文件夹中找到本示例。 工程描述 本情景涵盖了 ArcGIS Engine C++ API 的若干个方面。RasterSlope 情景的目标就是用 ArcGIS Engine C++ API 创建一个独立的命令行应 用程序。该应用程序的输入是一个数字高程模型(DEM),并可以建立 和保存坡度地图的栅格数据集。一旦完成了本情景,用户就会明白使 用 ArcGIS Engine C++ API 所需的技术,包括使用空间分析扩展模块。 本情景涵盖下列技术: 在一个标准的文本编辑器中用 ArcGIS Engine C++开发工具包编 写程序。 解释命令行参量。 使扩展模块有效:特别是使空间分析扩展模块有效。 在栅格数据集上执行坡度计算。 保存结果栅格数据集。 在 ArcGIS Engine C++ API 支持的所有平台上部署应用程序。 概念 坡度数据集经常用作物理过程模型的输入。坡度常用度或百分比表示。 在本情景的坡度计算中,可以指定一个参数(z 因子),以规定1个z 单位中地面 x,y 单位的数量。这使得用户可以用不同的 z 单位在输入 表面上创建坡度数据集。建立坡度数据集将用到 RasterSurfaceOp 类 及该类实现的 ISurfaceOp 接口。此外也会用到 Raster 类及该类实现 的 IRasterBandCollection 接口,以保存结果栅格数据集。 第六章·开发情景·331 建立命令行 C++应用程序 要了解更详细的信息,请参阅第四 章“开发环境”中的“C++应用程序 接口”一节。 C++ API 不包含在 ArcGIS Engine 开发工具包的“典型”安装中。如 果没有安装 C++ API,可以重新运 行开发工具包安装向导,选择“自 定义”或“修改”并选择软件开发 工具包下的 Native C++特性。 ArcGIS 开发示例不包含在 ArcGIS Engine 开发工具包的“典型”安装 中。如果没有安装这些开发示例, 可以重新运行开发工具包安装向 导,选择“自定义”或“修改”并 选择软件开发工具包下的示例特 性。 本软件的作用就是计算某个给定的栅格数据集的坡度。用户的任务就 是提供一个控制台及一个数字高程模型来运行本情景。由于这是一个 跨平台的应用程序,控制台可以是任何被支持的平台。 设计 应用程序将完全用 C++语言编写。这样允许用户在任意一种平台上编写 代码并将应用程序部署到任何支持 ArcGIS Engine 的平台上。本情景 以 Microsoft Windows XP 作为开发平台,从命令行用.NET 2003 编译 器 nmake 编译和运行应用程序。然而,可以地在任何被支持的平台上 使用任何被支持的构建策略轻松地完成本情景。Visual Studio 6.0 和 Visual Studio .NET 2003 工程都包含在开发工具包中的解决方案 代码中。 在设计中,采取了一些安全措施以确保应用程序可以跨平台。这些安 全措施包括避免调用 ArcGIS Engine 和 C++ API 之外定义的函数和数 据类型,以及平台无关的路径处理。例如,用户在应用程序的参量使 用“/”还是“\”作为路径隔离符应该是无关紧要的,只要应用程序 执行于其上的操作系统使用这种路径隔离符就可以。此外,也要遵循 C++标准以避免对编译器的依赖性。 要求 要成功地按照本情景进行开发,需要下列条件: 安装了 ArcGIS Engine 开发工具包(包括 C++),且具有能用于开 发的许可文件。 诸如 NotePad 或 Microsoft Visual C++等文本编辑器。 被支持的 C/ C++编译器。本情景使用 Microsoft Visual C++编译 器.NET 2003(7.1)。要了解安装细节,请参阅第四章“开发环境” 中的“C++ API”一节。 配置好的 ArcObjects 环境。要了解安装细节,请参阅第四章“开 发环境”中的“C++ API”一节。 ArcSDK.h:ArcGIS Engine C++ API 头文件。 熟悉用户自己选用的操作系统并对 C++编程有一定的基础。 尽管不需要有其他 ESRI 软件的经验,但具有 ArcObjects 经验并 对地图具有基本的理解是有益的。 可以访问本情景附带的示例数据、方案代码和 Makefiles。其位 置 为 ArcGIS 安 装 目 录 下 的 : \DeveloperKit\Samples\ Developer_Guide_Scenarios\ArcGIS_Engine\Building_a_Command _Line_Application\Computing_the_slope_of_a_raster_dataset 需要一个 ArcGIS 空间分析扩展模块或具有空间分析选项许可的 ArcGIS Engine 运行时,以便部署应用程序后可以运行。 运行应用程序需要一个栅格数据集。 332·ArcGIS Engine 开发指南 建立命令行 C++应用程序 第四章“开发环境”的“C++ API” 一节详细讨论了错误检查问题。鼓 励用户阅读该部分以掌握更多信 息。 如果用户不熟悉 nmake 工具,请参 阅 Microsoft Developer Network (MSDN)以获得进一步的信息。 与本情景的解决方案代码中提供的 Makefile.Windows 拷贝一样, ArcGIS Engine 开发工具包中包括 一个 Makefile.Windows 的模板拷 贝,可以作为 Makefile.Windows 从开发环境-> C++ -> Makefiles 下的帮助系统中访问。 将在右边列出的步骤 3 和步骤 5 至 7 中用 RasterSlope 替换用户 Makefile.Windows 文件中的所有 basic_sample。 实现 下面的实现过程提供了成功完成本情景所需的所有代码。本情景没有 提供编译 C++应用程序的逐步指导,因为我们假设用户已经具有其所选 择的开发环境的使用经验。省去了错误检查以提高代码的可读性。 本情景的示例应用程序展示了空间分析功能;完整的源代码可以从 ArcGIS Engine 开发工具包的示例中获得。下面是本情景中讨论到的文 件: RasterSlope.cpp—主 C++源文件 RasterSlope.h—主 C++头文件 Makefile.Windows.template—nmake 工具文件模板。在本练习中 用户将从解决方案代码中复制该文件并重命名为 Makefile.Windows。然后用户就可以根据本情景更新该文件。 Makefile.Windows—nmake 工具文件,指定了编译器设置和规则、 文件依赖关系、输入参量和应用程序执行规则。在解决方案代码 中,此文件是本情景完成了的 Makefile。当用户完成本情景时, 文件名将引用用户建立的 Makefile。 PathUtilities.cpp—平台无关的路径处理帮助功能的实现文件。 PathUtilities. H—平台无关的路径处理帮助功能的头文件。 创建用户构建环境 本情景用 nmake 来构建和部署应用程序。为了利用 nmake,用户必须编 写一个 Makefile 以执行 nmake。本情景的目的不是讲授用 nmake 工具 管理工程的基本知识。但是本情景将指导用户完成 Makefile 的一部分, 该部分必须针对用户构建的应用程序进行定制。 首先,从本情景的解决方案代码中复制 Makefile.Windows.template 到用户代码目录。一旦将该文件复制到用户代码目录,就可以按照下 面的步骤准备使用该文件: 1. 将复制的 Makefile.Windows.template 重命名为 Makefile.Windows。 2. 打开重命名后的新 Makefile.Windows 文件。 3. 将“PROGRAM”更新为“RasterSlope.exe”。 4. 将“INCLUDEDIRS”宏更新为 ArcGIS Engine 安装目录,该宏包含 要传递给编译器的包括目录。 5. 将“CPPSOURCES”更新为“RasterSlope.cpp”。 6. 将“CPPOBJECTS”更新为“RasterSlope.obj”。 将依赖关系行从 basic_sample.obj 更新为 RasterSlope.obj 及依赖于 RasterSlope.cpp 和 RasterSlope.h。 第六章·开发情景·333 建立命令行 C++应用程序 Visual Studio 的命令行构建工具 (如 nmake,cl 和 link 等)默认情 况下不可用。但是,Microsoft 提 供的一个批处理文件使它们在 Windows 中可用。这个批处理文件 名为 vcvars32.bat,用户每次打开 一个新的命令提示符时都必须运 行。用户可以通过创建一个运行 Visual Studio 6.0 版本的 vcvars32.bat 并打开一个用于开 发的命令提示符的批处理文件,或 者通过使用能为用户运行 vcvars32.bat 的 Visual Studio .NET 2003 命令提示符来使 这个过程自动化。要了解这方面的 详细信息,请参阅第四章“开发环 境”中的“C++ API”一节。 现在可以用 nmake 进行编译了。当本情景指导用户完成这个任务时, 需要使用“f”标记来指定被使用的 Makefile 的文件名。在命令行中 输入下面的指令: nmake /f Makefile.Windows. 设置执行参数 为了使用 Makefile 以便运行本情景,需要将参数存储在该文件中,而 且必须设置一个目标来运行应用程序。为了设置用户的 Makefile 以完 成这个任务,需要更新其中的参数以匹配用户希望处理的数据以及输 出文件的名称。 1. 在 Makefile.Windows 的开始处找到以下几行: # # Command line parameters: Edit these parameters so that you can # easily run the sample by typing “nmake /f Makefile.Windows run”. # # You will need to: # (1) Describe parameters here. ex: IN_SHAPEFILE is the input shapefile # (2) Define parameters below this comment box. # ex: IN_SHAPEFILE = “c:\data\shapefile.shp” # (3) Add the parameters to the run target at the end of this file # ex: $(PROGRAM) $(IN_SHAPEFILE) # 2. 在这后面添加一些运行本示例的参数。例如,如果用户的输入栅 格是“C:\MyComputer\Rasters\RasterDataset”,且输出数据集 将命名为“tempslope”,则用户要添加以下几行: IN_RASTER = “C:\MyComputer\Rasters\RasterDataset” OUT_RASTER = “tempslope” 3. 在 Makefile.Windows 的末尾有一个运行目标,该目标当前只执行 这个程序。更新运行目标使其也传入输入参数。如果用户使用如 上面例子所示的变量名 IN_RASTER 和 OUT_RASTER,运行目标现在 应该看起来象下面那样: # # Run target: “nmake /f Makefile.Windows run” to execute the application # run: $(PROGRAM) $(IN_RASTER) $(OUT_RASTER) 现在可以用 nmake 运行应用程序了。当本情景指导用户完成这个任务 时,需要使用“f”标记来指定被使用的 Makefile 的文件名。在 Windows 系统中输入下面的指令: nmake /f Makefile.Windows run 这样做与在命令行中输入“RasterSlope.exe C:\MyComputer\Rasters\ RasterDataset tempslope”的效果相同。 334·ArcGIS Engine 开发指南 建立命令行 C++应用程序 确定成员在名字空间中的范围有三 种方式。下面用名字空间 std 的一 个成员 cerr 为例说明各种方式: 用名字空间 std; 用 std::cerr; std::cerr<<“Prepend namespace”; 本情景中始终用第三中方式。 处理参数 用户在运行时为应用程序提供输入和输出文件信息(通过 Makefile 或 用命令行)。为了获得并在程序中使用这些信息,必须完成某种参数处 理工作。 1. 在文本编辑器中创建一个新文件 RasterSlope.h。将文件的内容放 在#ifndef 和#define 部分。包含头文件以便信息(如使用消息) 可以显示给用户。 #ifndef __RASTERSLOPE_ESRISCENARIO_h__ #define __RASTERSLOPE_ESRISCENARIO_h__ #include #endif // __RASTERSLOPE_ESRISCENARIO_h__ 2. 在另一个新文件 RasterSlope.cpp 中,开始实现坡度计算应用程 序。首先将上一步创建的头文件包含进来。然后开始编写函数。 现在仅在该函数中处理参数。确保输入了正确数目的参数,否则 打印出一条使用消息并退出程序。由于第一个参数是程序名,可 以忽略。第二个参数是输入数据,第三个参数是结果坡度文件。 用户将在后面应用程序执行中检验传入的参数是否有效。 #include “RasterSlope.h” int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << “Usage: RasterSlope [sourceFile] [outputFile]” << std::endl; return 0; } char* source = argv[1]; char* result = argv[2]; return 0; } 3. 但用户需要的是分开的输入路径和输入文件名。为获得这些信息, 用户将创建一个新的文件,在该文件中放置路径解释工具函数。 开启一个新文件 PathUtilities.h 并声明一个帮助函数,以获得 父目录和文件名。 #ifndef __PATHUTILITIES_ESRISCENARIO_H__ #define __PATHUTILITIES_ESRISCENARIO_H__ #include #include 第六章·开发情景·335 建立命令行 C++应用程序 // Extract the shape file name from the full path of the file HRESULT GetFileFromFullPath(const char* inFullPath, BSTR* outFileName); // Remove the file name from the full path and return the directory HRESULT GetParentDirFromFullPath(const char* inFullPath, BSTR* outFilePath); #endif // __PATHUTILITIES_ESRISCENARIO_H__ 4. 在新文件 PathUtilities.cpp 中实现路径工具函数。 #include “PathUtilities.h” // Function to remove the file name from the full path and return the // path to the directory. Caller is responsible for freeing the memory // in outFilePath (or pass in a CComBSTR which has been cast to a BSTR // to let the CComBSTR handle memory management). HRESULT GetParentDirFromFullPath(const char* inFullPath, BSTR* outFilePath) { if (!inFullPath || !outFilePath) return E_POINTER; // Initialize output *outFilePath = 0; const char *pathEnd = strrchr(inFullPath, ‘/’); // UNIX if (pathEnd == 0) pathEnd = strrchr(inFullPath, ‘\\’); // Windows if (pathEnd == 0) return E_FAIL; int size = strlen(inFullPath) - strlen(pathEnd); char *tmp = new char[size+1]; strncpy(tmp, inFullPath, size); *(tmp+size) = ‘\0’; CComBSTR bsTmp (tmp); delete[] tmp; if (!bsTmp) return E_OUTOFMEMORY; *outFilePath = bsTmp.Detach(); return S_OK; } // Function to extract the file (or directory) name from the full path // of the file. Caller is responsible for freeing the memory in // outFileName (or pass in a CComBSTR which has been cast to a BSTR // to let the CComBSTR handle memory management). HRESULT GetFileFromFullPath(const char* inFullPath, BSTR *outFileName) { 336·ArcGIS Engine 开发指南 建立命令行 C++应用程序 if (!inFullPath || !outFileName) return E_POINTER; *outFileName = 0; const char* name = strrchr(inFullPath, ‘/’); // UNIX if (name == 0) name = strrchr(inFullPath, ‘\\’); // Windows if (name == 0) return E_FAIL; name++; char* tmp = new char[strlen(name)+1]; strcpy(tmp, name); CComBSTR bsTmp (tmp); delete[] tmp; if (!bsTmp) return E_OUTOFMEMORY; *outFileName = bsTmp.Detach(); return S_OK; } 5. 用 PathUtilities.h 中的函数来解释输入。 a. 更新 RasterSlope.h 以包括 PathUtilities.h,以便可以使 用上面编写的函数。找到代码行 #include 在该行代码下面添加 #include “PathUtilities.h” b. 在 RasterSlope.cpp 的主程序中,解释输入以获得路径和文 件名。找到代码行 char* result = argv[2]; return 0; 在这两行代码之间插入 // Parse path CComBSTR sourceFilePath; CComBSTR sourceFileName; HRESULT hr = GetParentDirFromFullPath(source, &sourceFilePath); if (FAILED(hr) || sourceFilePath.Length() <= 0) { std::cerr << “Couldn’t parse source file path.” << std::endl; return 0; } hr = GetFileFromFullPath(source, &sourceFileName); if (FAILED(hr) || sourceFileName.Length() <= 0) { 第六章·开发情景·337 建立命令行 C++应用程序 用灰色显示的代码已经在前面的步 骤中输入到程序中。这里给出这些 代码的目的是为了说明本步骤中添 加的代码的正确位置。 std::cerr << “Couldn’t parse source file name.” << std::endl; return 0; } 6. 更 新 Makefile 以反映新的 PathUtilities.cpp 和 PathUtilities.h 文件,包括 RasterSlope 对其的依赖关系。 7. 编译并运行应用程序。应用程序应只是退出,似乎没有做任何事 情,尽管对参数进行了解释。 访问 ArcGIS Engine 为了使用 ArcGIS Engine,必须对其进行初始化并将合适的文件包括进 来。使用完 ArcGIS Engine 后必须解除其初始化。 1. 在 RasteSlope.h 文件的顶部引用 iostream 和 PathUtilities.h 代码行后添加代码以引用 ArcSKD.h 文件。现在看起来应象下面那 样: #include #include “PathUtilities.h” #include 2. 在退出应用程序前必须调用 AoExit()方法。这样做可以使需要 AoExit()来正确清空各种 ArcGIS Engine 和 COM 元素的操作系统 更加轻便。用这个函数代替 return 以更新 RasterSlope.cpp 的 main()函数。 int main(int argc, char* argv[]) { if (argc != 3) { std::cerr << “Usage: RasterSlope [sourceFile] [outputFile]” << std::endl; AoExit(0); return 0; } char* source = argv[1]; char* result = argv[2]; ... { std::cerr << “Couldn’t parse source file path.” << std::endl; AoExit(0); return 0; } hr = GetFileFromFullPath(source, &sourceFileName); if (FAILED(hr) || sourceFileName.Length() <= 0) { std::cerr << “Couldn’t parse source file name.” << std::endl; AoExit(0); return 0; } 338·ArcGIS Engine 开发指南 建立命令行 C++应用程序 看起来好像在 ShutdownApp()中创 建了一个新的 AoInitialize 例程。 但实际上 AoInitialize 是一个独 立对象,因此返回一个指向先前创 建的 AoInitialize 对象的指针。 AoExit(0); return 0; } 3. 编写初始化和关闭 engine 的帮助函数。这些普通的函数可以用在 任何命令行应用程序中。 a. 在 RasterSlope.h 文件中添加下列代码: #include bool InitializeWithExtension(esriLicenseProductCode product, esriLicenseExtensionCode extension); void ShutdownApp(esriLicenseExtensionCode license); b. 在 RasterSlope.cpp 文件的底部添加下列代码: bool InitializeWithExtension(esriLicenseProductCode product, esriLicenseExtensionCode extension) { ::AoInitialize(0); IAoInitializePtr ipInit(CLSID_AoInitialize); esriLicenseStatus licenseStatus = esriLicenseFailure; ipInit->IsExtensionCodeAvailable(product, extension, &licenseStatus); if (licenseStatus == esriLicenseAvailable) { ipInit->Initialize(product, &licenseStatus); if (licenseStatus == esriLicenseCheckedOut) ipInit->CheckOutExtension(extension, &licenseStatus); } return (licenseStatus == esriLicenseCheckedOut); } void ShutdownApp(esriLicenseExtensionCode license) { // Scope ipInit so released before AoUninitialize call { IAoInitializePtr ipInit(CLSID_AoInitialize); esriLicenseStatus status; ipInit->CheckInExtension(license, &status); ipInit->Shutdown(); } ::AoUninitialize(); } 命令行应用程序可以在任何 ArcGIS Engine 安装—运行时和开发工具 包—上和任何 ArcGIS Desktop 产品(ArcView,ArcEditor 和 ArcInfo) 上运行。但是,本情景的这个特定应用程序除了核心许可外还需要空 间分析许可。 第六章·开发情景·339 建立命令行 C++应用程序 其他任何扩展功能必须使用与现在 使用的核心许可相匹配的扩展许 可。如果应用程序使用 Engine 运行 时许可而且需要 3D 或空间许可,则 必须用 ArcGIS Engine 的 3D 或空间 选项。如果应用程序使用 ArcGIS Desktop 许可(ArcView,ArcEditor 或 ArcInfo)而且需要 3D 或空间许 可,则必须用 3D 分析或空间分析扩 展模块许可。 这是一个占位符注释,用于指示本 练习后面将在此处添加其他代码。 它取决于所使用的核心产品许可,Engine 和 Desktop,必须有一个 ArcGIS Engine 运行时空间选项和一个 ArcGIS 空间分析扩展模块。应 用程序必须确认所需的许可的可用性并将其检出。 4. 在 RasterSlope.cpp 的 main()函数中初始化 ArcGIS Engine 并设 置其产品许可。然后在关闭时解除 ArcGIS Engine 的初始化。这 些代码行放在处理命令行参数的代码后面;如果这些参数无效, 应用程序就不能访问 ArcGIS Engine,因此就不用启动 ArcGIS Engine。 if (FAILED(hr) || sourceFileName.Length() <= 0) { std::cerr << “Couldn’t parse source file name.” << std::endl; AoExit(0); } if (!InitializeWithExtension(esriLicenseProductCodeEngine, esriLicenseExtensionCodeSpatialAnalyst)) if (!InitializeWithExtension(esriLicenseProductCodeArcView, esriLicenseExtensionCodeSpatialAnalyst)) if (!InitializeWithExtension(esriLicenseProductCodeArcEditor, esriLicenseExtensionCodeSpatialAnalyst)) if (!InitializeWithExtension(esriLicenseProductCodeArcInfo, esriLicenseExtensionCodeSpatialAnalyst)) { std::cerr << “Exiting Application: Engine Initialization failed” << std::endl; ShutdownApp(esriLicenseExtensionCodeSpatialAnalyst); AoExit(0); } // Insert code here ShutdownApp(esriLicenseExtensionCodeSpatialAnalyst); AoExit(0); 5. 与前面一样,用 nmake 工具编译应用程序。 6. 允许应用程序。现在仍然似乎什么事情也没做,而实际上执行了 许可检查。 计算坡度 此时用户已经确定了在哪个数据集上计算坡度,且已经访问了 ArcGIS Engine。现在可以开始计算坡度了。坡度计算在一个单独的函数 CalculateSlope()中进行,在 main()函数中要调用该函数。 1. 在 RasterSlope.h 文件中声明 CalculateSlope()函数。该函数的 返回类型为 HRESULT,以便可以用来进行错误检查。 void ShutdownApp(esriLicenseExtensionCode license); HRESULT CalculateSlope(BSTR inPath, BSTR inName, BSTR outFile); 340·ArcGIS Engine 开发指南 建立命令行 C++应用程序 2. 在 RasterSlope.cpp 文 件 中 ShutdownApp()函数后,添加 CalculateSlope()函数的实现代码。这一步只是将函数放在这里。 后面几个步骤将继续编写 CalculateSlope()函数的代码。 HRESULT CalculateSlope(BSTR inPath, BSTR inName, BSTR outFile) { } 3. 打开输入栅格的工作空间。 HRESULT CalculateSlope(BSTR inPath, BSTR inName, BSTR outFile) { // Open the workspace IWorkspaceFactoryPtr ipWorkspaceFactory (CLSID_RasterWorkspaceFactory); IWorkspacePtr ipWorkspace; HRESULT hr = ipWorkspaceFactory->OpenFromFile(inPath, 0, &ipWorkspace); if (FAILED(hr) || ipWorkspace == 0) { std::cerr << “Could not open the workspace factory.” << std::endl; return E_FAIL; } 4. 查询接口以获取对栅格工作空间功能的存取权限并打开输入栅格 数据集。 HRESULT hr = ipWorkspaceFactory->OpenFromFile(inPath, 0, &ipWorkspace); if (FAILED(hr) || ipWorkspace == 0) { std::cerr << “Could not open the workspace factory.” << std::endl; return E_FAIL; } // Open the raster dataset IRasterWorkspacePtr ipRastWork(ipWorkspace); IRasterDatasetPtr ipRastDataset; hr = ipRastWork->OpenRasterDataset(inName, &ipRastDataset); if (FAILED(hr) || ipRastDataset == 0) { std::cerr << “Could not open the raster dataset.” << std::endl; return E_FAIL; } 5. 为执行坡度计算,要用到 ISurfaceOp 接口的 Slope()方法。为此 要访问工作空间上的 ISurfaceOp 接 口 。 查询接口 IRasterAnalysisEnvironment 以设置工作空间。 hr = ipRastWork->OpenRasterDataset(inName, &ipRastDataset); if (FAILED(hr) || ipRastDataset == 0) { std::cerr << “Could not open the raster dataset.” << std::endl; return E_FAIL; } 第六章·开发情景·341 建立命令行 C++应用程序 // Set up the ISurfaceOp interface to calculate slope IRasterAnalysisEnvironmentPtr ipRastAnalEnv(CLSID_RasterSurfaceOp); ipRastAnalEnv->putref_OutWorkspace(ipWorkspace); ISurfaceOpPtr ipSurfOp(ipRastAnalEnv); 6. 现在可以执行坡度计算并完成 CalculateSlope()函数了。该函数 返回 HRESULT 类型以表明计算是否成功。 ISurfaceOpPtr ipSurfOp(ipRastAnalEnv); IGeoDatasetPtr ipGeoDataIn(ipRastDataset); IGeoDatasetPtr ipGeoDataOut; HRESULT slopeHR = ipSurfOp->Slope(ipGeoDataIn, esriGeoAnalysisSlopeDegrees, 0, &ipGeoDataOut); if (FAILED(slopeHR) || ipGeoDataOut == 0) { std::cerr << “slopeHR = “ << slopeHR << std::endl; return slopeHR; } return slopeHR; 7. 现在已经完全实现了 CalculateSlope()函数,可以开始使用了。 在主函数中初始化 ArcGIS Engine 后调用 CalculateSlope()函数。 删除占位符注释。 // Insert code here hr = CalculateSlope(sourceFilePath, sourceFileName, CComBSTR(result)); if (FAILED(hr)) std::cerr << “The slope calculation failed.” << std::endl; else std::wcerr << L“The slope of “ << (BSTR) sourceFileName << L“ has been calculated.” << std::endl; ShutdownApp(esriLicenseExtensionCodeSpatialAnalyst); 8. 编译并运行应用程序。注意到根本没有用到输出数据路径信息, 并且坡度计算结果也没有存在的任何地方。 保存结果 在计算坡度时结果只在内存中创建。用户必须通过编程将计算结果保 存到磁盘上。 1. 由于不能新建一个已经存在的栅格数据集,因此应确保输出坡度 文件目前还不存在。可以通过在 CalculateSlope()函数中尝试打 开一个具有特定名称的工作空间来完成这个任务。如果成功打开, 则表明已经存在一个具有该名称的数据集。 342·ArcGIS Engine 开发指南 建立命令行 C++应用程序 在 CalculateSlope()函数中打开输入数据集后添加下列代码来执 行这种检查。 hr = ipRastWork->OpenRasterDataset(inName, &ipRastDataset); if (FAILED(hr) || ipRastDataset == 0) { std::cerr << “Could not open the raster dataset.” << std::endl; return E_FAIL; } // Check for existence of a dataset with the desired output name. IRasterDatasetPtr ipExistsCheck; hr = ipRastWork->OpenRasterDataset(outFile, &ipExistsCheck); if (SUCCEEDED(hr)) { std::cerr << “A dataset with the output name already exists!” << std::endl; return E_FAIL; } // Set up the ISurfaceOp interface to calculate slope 2. 一旦确定不存在该名称的数据集,就可以用该文件保存坡度计算 创建的结果数据集。在计算坡度后通过 IRasterBandCollection 接口来完成保存工作。 HRESULT CalculateSlope(BSTR inPath, BSTR inName, BSTR outFile) { … HRESULT slopeHR = ipSurfOp->Slope(ipGeoDataIn, esriGeoAnalysisSlopeDegrees, 0, &ipGeoDataOut); if (FAILED(slopeHR) || ipGeoDataOut == 0) { std::cerr << “slopeHR = “ << slopeHR << std::endl; return slopeHR; } // Persist the result IRasterBandCollectionPtr ipRastBandColl(ipGeoDataOut); IDatasetPtr ipOutDataset; ipRastBandColl->SaveAs(outFile, ipWorkspace, CComBSTR(L” GRID”),&ipOutDataset); return slopeHR; } 3. 编译并运行应用程序。浏览坡度数据存放的位置,可以看到生成 了一个具有用户给定输出文件名的新 ESRI grid 文件。 第六章·开发情景·343 建立命令行 C++应用程序 输出坡度文件保存在与输入栅格文 件相同的目录中。 部署 开发过程的最后部分就是成功地将应用程序部署到终端用户的机器 上。部署应用程序需要下列条件: 要在用户机器上安装具有空间选项的 ArcGIS Engine 运行时。 要将应用程序在编译时创建的可执行文件复制到终端用户机器 上。 一旦满足了上述条件,终端用户就可以通过在命令行中输入下列指令 为任何数据集创建坡度文件: RasterSlope inputRaster outputRaster 其中 inputRaster 参数是栅格数据文件的完整路径(包括文件名), outputRaster 参数是要创建的输出坡度文件的文件名。 另外,终端用户也可以用 nmake 工具允许本示例。为此,必须制作一 个具有正确参数的合适的 Makefile 并在命令行中输入下列指令: nmake /f Makefile.Windows run 其他资源 下列资源可能有助于开发人员理解和应用本情景提出的概念和技术。 ArcGIS Engine 开发工具包中的其他文档,包括 ArcGIS 开发帮助、 组件帮助、对象模型图和示例,以帮助开发人员快速入门。 ArcGIS开发在线网站—提供有关ArcGIS开发方面的最新信息,包 括更新的示例和技术文档。网址为 http://arcgisdeveloperonline.esri.com。 ESRI在线论坛网站—提供来自其他ArcGIS开发人员的无价的帮 助。网址为http://support.esri.com并点击“用户论坛(User Forums)”选项卡。 Peter A. Burrough 和 Rachel A. McDonnell 著,《地理信息系统 原理》,牛津大学出版社,1998。 344·ArcGIS Engine 开发指南 理解对象模型图 A ArcObjects 对象模型图是对用户在对象浏览器中获得的信息的 一种重要补充。本章将描述本书中使用的对象模型图及 ArcGIS 开发帮助中对象模型图的标记。 解释对象模型图 解释对象模型图 对象模型图关键要素显示了 ArcObjects 的类型及类之间的关 系。 本书使用的模型图和 ArcObjects 对象模型图中的标记基于统一建模语 言(UML)—面向对象分析和设计模型图制作的工业标准—标记方法 之上,并专门针对 COM 进行了一些修改。 对象模型图是对用户在对象浏览器中获得的信息的一种重要补充。 Visual Basic 或其他开发环境都列出了所有的类和成员,但并没有展示 这些类的结构或它们之间的关系。这些模型图可以完善用户对 ArcObjects 组件的理解。 类的类型 抽象类不能用于创建新对象,对子类例程进行了 规定(通过类型继承)。 组件对象类可以直接通过声明新对象来创建对 象。 类不能直接创建对象,但可以作为另一个类的属 性或通过另一个类的实例化来创建类对象。 关系的类型 关联表示类之间的关系。关联的两端都定义了多 样性。 类型继承定义共享父类属性和方法,同时又具有 自己独特的属性和方法的特定对象类。注意父类 中的接口并不是直接复制到子类中。 实例化表示某个类的对象具有创建另一个类的对 象的方法。 组成是一种“whole”类对象控制着“part”类对 象生命期的关系。 N 元关联表示两个以上的类相互关联。在各个关 联分支的交叉点上放置一个菱形。 多样性是对能与另一个对象关联的对象数目的一 种限制。关联和组成关系的两端都定义了多样性。 下面列出了可能的多样性标记: 1—多样性为 1 且仅为 1(如果没有显示多样性, 则暗示该多样性为“1”) 0…1—多样性为 0 或 1。 M…N—多样性为 M 到 N(正整数)。 *或 0…*—多样性为从 0 到任意大的正整数。 1…*—多样性为从 1 到任意大的正整数。 对象模型关键元素 特定接口 (可选)表示可以被某些子类但不是全部子类继承的 接口。子类会列出其实现了的可选接口。 (例程)表示只在类的特定例程上实现的接口。 (<类名>)表明在 Visual Basic 中支持此事件接口所 需的帮助类的名称。 346·ArcGIS Engine 开发指南 解释对象模型图 类与对象 UML 模型图中的类有三种类型:抽象类、组件对象类和类。 Multiplicity abstract class class Type inheritance Instantiation Association Composition 1..* 附件 A·理解对象模型图·347 组件对象类的对象可以直接在开发环境中用对象声明语法创建。在 Visual Basic 中,其语法为:Dim pFoo As New FooObject。 类不能直接创建新对象,但可以作为另一个类的属性或者用另一个类 的方法来创建类的对象。 抽象类不能用来创建新对象,是对子类的一种规定。一个例子就是 “line”可以是“primary line”和“secondary line”的抽象类。抽象类 对于那些要创建自己的子类的开发人员而言十分重要,因为抽象类说 明了为实现某种类型的类哪些接口是需要的,哪些接口是可选的。需 要的接口在抽象类的任何子类上必须实现,以确保新类在 ArcObjects 系统中正确地运作。 关系 抽象类、组件对象类和类之间存在多种可能的关系。 coclass 1..* 1..*Owner Land parcel 在这个模型图中,owner 可以拥有一个或多个 land parcels,而一个 land parcel 也可以由一个或多个 owners 占有。 关联表示类之间的关系,在其两端都定义了多样性。 多样性是对能与另一个对象关联的对象数目的一种限制。下面列出了 可能的多样性标记: 1—表示只有一个对象与另一个对象发生关系。这种多样性的显示是可 选的;如果没有显示多样性,则暗示该多样性为“1”。 0…1—表示多样性为 0 或 1。 M…N—表示多样性为 M 到 N(正整数)。 解释对象模型图 *或 0…*—表示多样性为从 0 到任意大的正整数。 1…*—表示多样性为从 1 到任意大的正整数。 类型继承 类型继承用来定义共享父类的属性和方法,同时又具有自己独特的属 性和方法的特定类。 这个模型图表示 primary line(可创建类)和 secondary line(可创建类) 都是 line(抽象类)的子类。 实例化 实例化表示某个类的对象具有创建另一个类的对象的方法。 Line Primary line Secondary line TransformerPole 一个 pole 对象可能具有创建 transformer 对象的方法。 组成 组成是一种更强的关联,在这种关联中,“whole”类对象控制着“part” 类对象的生命周期。 CrossarmPole 1..* 一个 pole 包含一个或多个 crossarms。在这个设计中,当 pole 被删除时 crossarm 也就不存在了。Pole 对象控制着 crossarm 对象的生命周期。 348·ArcGIS Engine 开发指南 B ArcGIS 开发资源 ArcGIS 开发资源 ESRI 为 ArcGIS 开发人员建立了两个基本资源:ArcGIS 软件开 发工具包和 ArcGIS 开发在线 (http://ArcGISDeveloperOnline.esri.com)。 ArcGIS 软件开发工具包 350·ArcGIS Engine 开发指南 一个 SDK 的典型安装 ArcGIS 软件开发工具包 ArcGIS 软件开发工具包(SDK)是模型图、工具、插件、示例和文档 的集合,用于帮助开发人员实现自定义 ArcGIS 功能。 ArcGIS 开发帮助系统 ArcGIS 开发帮助系统是通往 SDK 所有文档,包括插件、开发工具和示 例帮助文档的关口;此外,该系统还提供所有对象库的完整语法参考。 所有被支持的 API 都有一个与其相对应的帮助系统版本。不管用户选 用哪种 API,都可以看到合适的类库参考语法,拥有一套集成在开发环 境中的帮助系统。例如,使用 Visual Basic 6 的开发人员可以使用 ArcGISDevHelp.chm,该文档使用 VB6 语法并集成在 VB6 的 IDE 中, 因此支持在代码窗口中通过按 F1 键获得帮助。 ArcGIS 开发帮助系统存放在 DeveloperKit\Help 文件夹中,但一般从“开 始”菜单或在 Visual Basic 6 和 Visual Studio .NET 2003 中按 F1 键启动帮助系 统。下面的图形显示了用于打开该帮助系 统的“开始”菜单选项。 示例 ArcGIS 开发帮助系统包含 600 多个示例,其中许多示例都采用几种语 言编写。帮助系统对这些示例进行了详细描述,而这些示例的源代码 和工程文件安装在 DeveloperKit\samples 文件夹中。帮助系统示例部分 的内容表反映了这些示例的目录结构。 帮助系统根据功能组织这些示例。例如,所有的 Geodatabase 示例都归 在 Samples\Geodatabase 组下。多数一级分组还有下一级的分组。由于 帮助系统按字母顺序列出了所有的示例,用户也可以用帮助系统中的 “Query the Samples”主题在 SDK 中查 找示例;此外,用户还可以根据语言对 示例列表排序。例如,用户可以选择仅 列出可用的 Java 示例。 在安装过程中,示例源代码和工程文件 的安装是可选的。示例安装在 ArcGIS\DeveloperKit\samples 文件夹下。 如果用户的计算机上没有该文件夹,可 以重新运行安装程序并复选“Developer Kit”下的“Samples”。 ARCGIS 9 软件开发工具包 附加 B·ArcGIS 开发资源·351 用于选择 Visual Basic 插件的安装 对话框。 开发工具 ArcGIS 开发工具是 ESRI 提供的、方便用户进行 ArcObjects 开发的一 些可执行文件。用户会发现有些工具非常必要。例如,Visual Basic 6 桌面开发人员很可能会用 Categories.exe 工具把组件注册到各个组件类 目中。 所有的开发工具都安装在 DeveloperKit\tools 文件夹中,组件类目管理 器除外,此工具安装在 ArcGIS\bin 文件夹中。要了解这些工具及其使 用方法的详细讨论,请参考“ArcGIS 开发帮助”。 各种 ArcGIS 开发工具包都有的工具 组件类目管理器(Component Categories Manager)—该工具可以 将组件注册到指定的组件类目中。 注册表修正工具(Fix Registry Utility)—该工具可以修正注册表 组件类目部分的错误。 GUID 工具—该工具可以生成以注册表格式表示的 GUIDs,生成 GUIDs 的可以用在源代码中。 类库定位器(Library Locator)—该工具可以识别包含指定接口、 组件对象类、枚举或结构的对象库。 Desktop 开发工具包中的其他工具 ESRI 对象浏览器—高级的对象浏览器。 VBA 析取工具(Extract VBA)—该工具可以从损坏的地图文档 (mxd)中析取 VBA 代码。 插件(ADD-INS) 在进行 ArcObjects 开发时,ESRI 插件可以自动执行某些原来由软件工 程师执行的任务,同时插件还提供一些简化代码调试工作的工具。 ESRI 为 Visual Basic 6 IDE 和 Visual Studio .NET IDE 提供了插件。 Visual Basic 6 插件 如果在安装过程中用户选择安装 Visual Basic 6 插件,则下列 Visual Basic 6 插件可用: ESRI 控件与列表索引对齐插件(Align Controls with Tab Index) —该插件确保控件创建顺序与列表索引相匹配。 ArcGIS 软件开发工具包 ESRI 自动引用插件(Automatic Reference)—该插件可以自动添 加 ArcGIS 类库引用。 ESRI 代码转换器(Code Converter)—该插件可以将 ArcGIS 8.X 工程转换为 ArcGIS 9.X 工程。 ESRI 命令创建向导(Command Creation Wizard)—该插件可以方 便命令和工具的创建。 ESRI 编译和注册插件(Compile and Register)—该插件可以辅助 组件编译以及把这些组件注册到期望的组件类目中。 ESRI 错误处理程序生成器(ErrorHandler Generator)—该插件可 以自动生成错误处理代码。 ESRI 错误处理程序删除器(ErrorHandler Remover)—该插件可 以从源代码文件中删除错误处理程序。 ESRI 接口实现器(Interface Implementer)—该插件自动完成接口 的实现。 ESRI 行号生成器(Line Number Generator)—该插件可以在源代 码文件中为合适的代码行添加行号。 ESRI 行号删除器(Line Number Remover)—该插件可以删除源 代码文件中的行号。 Visual Studio .NET 插件 在安装过程中,如果检测到 Visual Studio .NET 2003 版本,则自动安装 下列.NET 插件: ESRI 组件类目注册器(Component Category Registrar)—该插件 可以完成注册功能,以便进行组件类目本身的注册。 ESRI Guid 生成器(Guid Generator)—该插件可以插入一个 GUID 属性。 352·ArcGIS Engine 开发指南 ARCGIS 9 软件开发工具包 附加 B·ArcGIS 开发资源·353 ArcGIS 开发在线 ArcGIS开发在线网站提供了最新的ArcGIS 9开发信息,包括示例代码、 技术文档、对象模型图和完整的对象库参考。 ArcGIS 开发在线网站是 ArcGIS 开发帮助系统的一个映像,但该网站 是在线的,因此更具实时性。该网站具有其他一些特性,包括一个先 进的搜索工具,使用户可以控制搜索的范围。例如,用户可以创建一 个只扫描帮助系统的类库参考部分的搜索。 通过下面的网址访问 ArcGIS 开发在线网站: http://arcgisdeveloperonline.esri.com 术语表 C 下面是本书使用的常用术语表。尽管没有列出本书中使用的全 部术语,但可以提供对 ArcGIS Engine 特定术语的快速参考。 术语表 术语表 抽象类(abstract class) 活动服务器页面(Active Server Pages) 活动模板库(Active Template Library) 插件(add-in) ADF(ADF) ADF 运行时(ADF runtime) 单元(apartment) API(API) 应用程序接口(application programming interface) 应用 Web 服务(application Web service) ArcGIS Server Web 服务(ArcGIS Server Web service) ArcGIS ANT 工具(arcgisant) ArcObjects(ArcObjects) ASCII 码(ASCII) 356·ArcGIS Engine 开发指南 ASP(ASP) 对子类的一种规定,在对象模型图中可以经常见到,是对象模型图中 三种类型的“类”之一。抽象类在类库中没有定义,也不能实例化。 用于建立和运行动态、交互式 Web 服务器应用程序的一种 Microsoft 服 务端脚本编写环境,一般用 JavaScript 或 VBScript 编写代码。与标准 Web 文档类似,ASP 文件不仅包含文本和 HTML 标签,而且还包含用 脚本语言编写的命令,这些命令可以在服务器上运行。 C++模板类的一个集合,具有小型、快速和可扩展的特点。 一种可以执行自定义任务的开发环境扩展。ESRI 提供各种开发插件作 为 ArcGIS 开发工具包的组成部分。 “应用开发框架”的缩写。用于建立与 GIS 服务器进行通信的 Web 应用 程序的自定义 Web 控件和模板集。ArcGIS Server 包括.NET 和 Java 两种 ADF。 用 ADF 建立的应用程序运行时所需的组件。另见“ADF”。 某个过程中在相同的运行环境中工作的一组线程。另见“MTA”,“STA”, “线程(thread)”,“TNA”。 参见“应用程序接口(Application Programming Interface)”。 应用程序开发人员用于建立或定制程序的一系列程序、协议和工具的 集合。APIs 提供了一套预先创建好的接口而不是直接对设备或软件进 行编程,从而使程序开发更加容易。APIs 还确保了所有使用公共 API 的程序具有相似的接口。诸如 C、COM 和 Java 等编程语言都可以建立 APIs。 解决特定问题的 Web 服务,例如,查找某个地址一定范围内的所有医 院的 Web 服务。应用 Web 服务可以使用 Web 服务器的本地 Web 服务 框架来实现,如 ASP.NET 的 Web 服务(WebMethod)或 Java 的 Web 服务(Axis)。 由 ArcGIS Server 处理和执行的 Web 服务。每个 Web 服务都是一个不同 的 HTTP 端点(URL)。管理员可以将 MapServer 和 GeocodeServer 对象 用作通过 Internet 可以访问的 ArcGIS Server Web 服务。另见“Web 服务 目录(Web service catalog)”。 由 Java ADF 提供、启动用于建立和部署 Web 应用程序的 Apache ANT 工具命令。另见“ADF”。 构成 ArcGIS 基础的软件组件库。ArcGIS Desktop、ArcGIS Engine 和 ArcGIS Server 都是建立在 ArcObjects 类库基础之上的。 “美国信息交换标准码”的缩写。是计算机和 Internet 上文本文件格式的 实际标准。每个字母、数字或特定字符都用一个 7 位的二进制数值(7 个 1 和 0 组成的字符串)表示。定义了 128 个可能的字符。 参见“活动服务器页面(Active Server Pages)”。 术语表 ASP.NET(ASP.NET) 软件套件(assembly) ALT(ALT) 认证(authentication) .bat文件(.bat file) 大端格式(big endian) 二进制(binary) 传值(by value) C++(C++) 层次样式单(Cascading Style Sheets) CASE(CASE) 类(class) 建立在公用语言运行时(Common Language Runtime,CLR)基础上的 编程框架,可用于服务器端以便用.NET 支持的任何编程语言建立 Web 应用程序。另见“活动服务器页面(Active Server Pages)”。 软件包及其相关资源。一般地,一个 ArcGIS Win32 软件套件包括可执 行文件和 DLLS、对象库、注册表文件及每个软件单元的帮助文件。一 个.NET 软件套件就是一个用.NET 语言建立、使用.NET 框架和 CLR 来执行的软件单元。 参见“活动模板库(Active Template Library)”。 获得用户名和密码等身份证明并依据某种权威确认这些证明的过程。 如果身份证明有效,提交身份证明的实体就被认为是一个认证的身份。 认证可用于确定某个实体是否有权访问给定的资源。 有时指批处理文件,包含可以在一个命令窗口中运行的命令。批处理 文件用于执行一些重复性的任务和按时刻表运行的命令。 一种计算机硬件结构,在这种结构中,多字节的数字表示中最重要的 字节具有最低的地址,重要性越低,其编码地址越高。另见“小端格 式(little endian)”。 编码为位(1 和 0)序列但不包含可打印字符序列(ASCII 格式)的任 何数字数据文件格式。常用于可执行的机器码,如包含计算机可以直 接装载或执行的信息的 DLL 或 EXE 文件。 给函数传递参数的一种方式,在这种方式中,建立了参数值的一个临 时拷贝。函数对这个临时拷贝作出更改,函数存在后就抛弃该临时拷 贝。如果参数是对某个基本对象的引用,对该基本对象所作的任何更 改将在函数存在后得到保留。 一种常用的面向对象编程语言,针对不同的平台设计了许多不同的实 现。 定义 HTML 或 XML 文档布局或表达的一个标准。样式信息包括字体 大小、背景颜色、文字对齐方式和页边距等。多个样式单可以应用于 “层次(cascade)”,以向先前的样式设置添加新样式或覆盖它们。万 维网协会(W3C)负责维护 CSS 标准。另见“万维网协会(World Wide Web Consortium)”。 “计算机辅助软件工程”的缩写。为编程人员提供开发环境的一类软 件。CASE 系统提供各种工具对开发过程进行自动化、管理和简化。经 常需要多行代码的复杂任务可以用 CASE 用户接口和代码生成器进行 简化。 面向对象编程语言中一类对象的模板。类可以认为是共享公用结构和 行为的对象的集合。 附件 C·术语表·357 术语表 类识别符(class identifier) 客户(client) 克隆(cloning) CLR(CLR) CLSID(CLSID) 组件对象类(coclass) COM(COM) COM协议(COM contract) COM接口(COM interface) COM生成语言(COM-compliant language) 命令(command) 命令条(command bar) 命令行(command line) 组件(component) 组件类目(component category) 组件类目管理器(Component Category Manager) 组件对象模型(Component Object Model) 计算机辅助软件工程(computer -aided software engineering) 一个 COM 术语,指系统注册表和 COM 框架用于识别某个特定组件对 象类的全局唯一标识符。另见“GUID”。 在客户/服务器模式中,向服务器作出请求的应用程序、计算机或设备。 建立某个类的一个新例程的过程,建立的新例程与现有的一个例程具 有相同的状态。 “公用语言运行时”的缩写。是.NET 框架应用程序的执行引擎,提供 诸如代码装载和执行及内存管理等服务。 参见“类识别符(class identifier)”。 可以在内存中实例化的对象的模板。 参见“组件对象模型(Component Object Model)”。 对 COM 的一种要求,即接口一旦发布就不能更改。 一组逻辑相关的虚拟函数,由服务器对象实现,允许客户通过它们与 服务器对象进行交互。接口构成了 COM 对象之间通信以及 COM 协议 的基础。 用于创建 COM 组件的一种语言。 ArcGIS 系统中实现了 ICommand 接口并因此可以添加到 ArcGIS 应用程 序菜单或工具条上的任何类。 ArcGIS 应用程序中的工具条、菜单条、菜单或弹出式菜单。 一种让用户在提示符后输入命令的屏幕界面。在地理处理中,添加到 ArcToolbox 窗口中的任何工具都可以从命令行中运行。 可用于创建 COM 对象的一种二进制代码单元。 可用于对类进行功能分类的一种注册表项。组件类目广泛用于 ArcGIS 中以允许对系统进行扩展。 ArcGIS 的一个实用程序(Categories.exe),可用于查看和处理组件类目 信息。 使软件组件可以在网络环境中进行互操作而不管这些组件用何种语言 开发的一种二进制标准。COM 技术由微软公司开发,提供界面规划、 生命周期管理(决定何时从系统中删除某一对象)、许可及事件服务(当 某一对象的事件发生后,将在服务中引入另一对象)等底层服务。 ArcGIS 系统就是用 COM 对象创建的。 参见“CASE”。 358·ArcGIS Engine 开发指南 术语表 容器帐户(container account) 容器过程(container process) 数字地理空间元数据内容标准 (Content Standard for Digital Geospatial Metadata) 控件(control) 控制点(control points) 创建时(creation time) CSDGM(CSDGM) CSS(CSS) 自定义软件(custom) 数据类型(data type) 数据库管理系统(database management system) 数据库支持(database support) DBMS DCOM 调试(debug) 深状态应用程序(deeply stateful application) 默认接口(default interface) 服务器对象容器过程运行时的操作系统帐户,由 GIS 服务器后台安装 工具指定。运行在服务器容器过程中的对象对系统资源的访问权限与 容器帐户相同。 一个或多个服务器对象运行于其中的一种过程。容器过程运行于 SOC 机器上并由 SOM 启动和关闭。 由联邦地理数据委员会(FGDC)推出的标准,规定了数字地理空间数 据元数据的信息内容。该标准的目的是为元数据相关的概念提供一套 公共的术语和定义。美国所有接受联邦资助以创建元数据的政府机构 (联邦、州和地方政府)都必须遵照此标准。 具有用户界面的组件。在 ArcGIS 中,此术语常指 MapControl、 PageLayoutControl、TOCControl、ToolbarControl 和 ArcReadControl,这 些都是 ArcGIS Engine 的组成部分。 参见“控件(Control)”。 当服务器对象由于服务器启动或响应客户对服务器对象请求的结果在 GIS 服务器中创建的时候,服务器对象初始化其例程的时间。 参见“数字地理空间元数据内容标准(Content Standard for Digital Geospatial Metadata)”。 参见“层次样式单(Cascading Style Sheets)”。 由源软件开发者之外的另一方提供或建立的软件功能。 变量、表格中的字段或列的属性,决定了它们可以存储的数据种类。 常用的数据类型包括字符型、整型、实型、单精度、双精度和字符串 等。 根据某种概念模式组织数据库中信息的一系列计算机程序,提供了输 入、验证、存储、修改和检索数据的工具。 程序或组件支持的版权数据库平台。 参见“数据库管理系统(Database Management System)”。 “分布式组件对象模型”的缩写。是 COM 的扩展,支持网络中不同计 算机上各个对象之间的通信。 测试程序或组件以确定发生错误的原因。 通过改变服务器对象或其相关对象的状态以便用 GIS 服务器维护其状 态的一种应用程序。深状态应用程序需要非池服务器对象。 在创建 COM 对象时,在没有指定其他接口的情况下返回的接口。大多 数 ArcObjects 类指定 Iuknown 作为其默认接口。 附件 C·术语表·359 术语表 部署(deployment) 开发示例(developer sample) 开发环境(development environment) 设备环境(device context) 显示(display) DLL 泊靠窗口(dockable window) 动态链接库(dynamic link library) EMF 预先绑定(early binding) EJB 企业级JavaBeans (Enterprise JavaBeans) EOBrowser(EOBrowser) 事件处理(event handling) 可执行文件(executable file) 扩展模块(extension) 联邦地理数据委员会(Federal Geographic Data Committee) 安装组件或应用程序到目标机器的过程。 ArcGIS 开发帮助系统中包含的示例。 用于编写、编译和调试组件或应用程序的一种软件产品。 表示可以在其上绘制的表面,如屏幕、位图或打印机等。在 ArcGIS 中, Display 抽象类用于抽象表示设备环境。 常用于指代 Display 抽象类的子类。例如,“当绘制到显示时”意味着 当绘制到任何显示组件对象类时;“显示管道”指的是当发生绘制时所 做的调用序列。 参见“动态链接库(Dynamic Link Library)”。 可处于浮动状态或固定在应用程序主窗口上的一种窗口。 包含一系列从过程中调用的例行程序的代码模块。一个 DLL 通过其调 用模块(EXE 或 DLL)在运行时被装载并链接到应用程序。 “增强型元文件”的缩写。Windows 操作系统在打印时使用的一种文 件格式。 应用程序用于访问对象的一种技术。在预先绑定中,一个对象的属性 和方法从一个类中定义,而不象后期绑定那样在运行时检测。这个差 别常常为预先绑定带来比后期绑定更好的性能。另见“后期绑定(Late Binding)”。 参见“企业级 JavaBeans(Enterprise JavaBeans)”。 J2EE 服务端组件结构。EJB 可以开发分布式、事务型、安全和便携式 Java 应用程序。 用于查看对象库内容的一种 ArcGIS 实用程序。 对由另一个类引起的事件接口进行的处理。 包含可执行或运行程序的一种二进制文件。可执行文件的后缀名 为.exe。 在 ArcGIS 中,为 ArcGIS Desktop 添加特殊工具和功能的一种可选软件 模块。ArcGIS Network Analyst、StreetMap 和 ArcGIS Business Analyst 等 都是 ArcGIS 的扩展模块。 360·ArcGIS Engine 开发指南 由美国联邦管理和预算办公室(United States Federal Office of Management and Budget)创建的一个组织,负责协调测量、制图和相关 空间数据的开发、使用、共享和分发。该委员会由来自联邦和州政府 机构、学术机构和私人企业的代表组成。FGDC 在其颁布的数字地理 空间元数据内容标准(Content Standard for Digital Geospatial Metadata) 中制定了美国空间数据元数据标准并管理国家空间数据基础设施 (NSDI)的开发工作。 术语表 FGDC 框架(framework) GDB GDI 地理编码服务器(GeocodeServer) 地理数据库(geodatabase) 几何图形(geometry) 地理处理工具(geoprocessing tool) GIS服务器(GIS server) GUID 十六进制(hexadecimal) HKCR HRESULT IDE IDispatch IDL 参见“联邦地理数据委员会(Federal Geographic Data Committee)”。 组成 ArcGIS 系统的现有 ArcObjects 组件。 参见“地理数据库(Geodatabase)”。 “图形设备接口”的缩写。表达图形对象并将它们传送到输出设备, 如监视器等上的一种标准。GDI 一般指 Windows GDI API。 提供对地址定位器进行编程访问和执行单个地址匹配及其批处理的一 种 ArcGIS 软件组件。其设计目的在于使用 ArcGIS Server 建立 Web 服 务和 Web 应用程序。 一种由ESRI提出的将地理要素和属性表示为对象和对象之间关系的面 向对象数据模型,但是它由关系数据库管理系统来实现。地理数据库 能存储多种对象,例如要素类、要素数据集、非空间表和关系类。 点、线和面的度量和属性。在 GIS 中,几何图形用于表达地理要素的 空间组成。一个 ArcGIS 几何图形类源自 Geometry 抽象类,用于表示 要素形状,如多边形或点。 可以创建或修改空间数据,包括分析功能(叠加、缓冲区、坡度),数 据管理功能(添加字段、复制,重命名)或数据转换功能的一种 ArcGIS 工具。 驻留和运行服务器对象的 ArcGIS Server 组件。一个 GIS 服务器包含一 个服务器对象管理器和一个或多个服务器对象容器。 “全局唯一标识符”的缩写。用于唯一识别接口、类、类库或组件目 录的一个字符串。另见“类识别符(Class Identifier)”。 以 16 为基数的一种数值系统。 HKEY_CLASSES_ROOT 注册表项。指向 HKEY_LOCAL_MACHINE\Software\Classes 注册表键的一个 Windows 注册表根键。显示有关支持拖放操作、Windows 快捷键和 Windows 用 户界面核心方面的 OLE 和关联映射方面的信息。 COM 接口的任何成员返回的、指示成功或失败的一个 32 位整数,通 常为十六进制。一个 HRESULT 还可以给出在调用 COM 接口的成员时 发生的错误方面的信息。Visual Basic 将 HRESULT 转化为错误;而 Visual C++开发人员可以直接操作 HRESULT 的值。 参见“集成开发环境(Integrated Development Environment)”。 一个普通的 COM 接口,具有允许客户询问支持哪些成员的方法。实现 了 IDispatch 的类可以用于后期绑定和 ID 绑定。 参见“接口定义语言(Interface Definition Language)”。 附件 C·术语表·361 术语表 IID 冒充(impersonation) 实现(implement) 入接口(inbound interface) 继承(inheritance) 入进程(in-process) 集成开发环境(integrated development environment) 接口定义语言(Interface Definition Language) IUnknown接口 Java服务器外观(JavaServer Faces) Java服务器页面(JavaServer Pages) Java服务器页面标准标签库 (JavaServer Pages Standard Tag Library) JSF JSP JSTL 后期绑定(late binding) “接口识别符”的缩写。为接口提供唯一名称的一个字符串。IID 是一 种全局唯一识别符。另见“GUID”。 Web 应用程序冒充某个特殊用户的身份从而获得该用户所拥有的全部 权限的过程。 与接口有关,指的是为某个接口的所有成员提供代码的过程(接口是 单独定义的)。 由类实现的一种接口,用户可以调用其成员。另见“出接口(outbound interface)”。 在面向对象程序语言中,从现有类或接口生成新类或接口的方法。新 类和接口包含另一个类或接口的所有方法和属性,此外还有其自身的 方法和属性。继承是面向对象系统的定义特征之一。 在客户应用程序的过程空间内,包含在 DLL 中的类就是入-过程,因为 对象被载入到客户 EXE 的过程空间中。包含在一个独立 EXE 中的组 件就是一个出-进程。 用于建立诸如桌面和 Web 应用程序的一种软件开发工具。IDEs 将用户 界面设计和布局工具与编码和调试工具组合在一起,使开发人员易于 将功能链接到用户界面组件。 用于定义 COM 接口的一种语言。微软公司的 IDL 实现指的是 MIDL 或 Microsoft IDL。 所有的 COM 接口都继承自 Iunknown 接口,该接口控制对象的生命期 并提供运行时类型支持。 为 Java Web 应用程序建立用户界面的一种框架。JSF 的设计初衷在于减 轻运行在 Java 应用服务器上应用程序的写入和维护负担,并将它们的 用户界面归还给目标客户。 使快速开发独立于平台的、以 Web 为基础的应用程序成为可能的一种 Java 技术。JSP 将用户接口与内容生成相分离,使设计者可以改变整个 页面布局而不用更改底层的动态内容。 将许多Web 应用程序常用的核心功能封装为简单标签的一种Java 技术。 JSTL 包括结构化任务迭代和条件、XML 文档处理、国际化和局部敏感 的格式以及 SQL 等的标签。 参见“Java 服务器外观(JavaServer Faces)”。 参见“Java 服务器页面(JavaServer Pages)”。 参见“Java 服务器页面标准标签库(JavaServer Pages Standard Tag Library)”。 应用程序在运行时而不是代码编译时使用 IDispatch 接口用于确定数据 类型的一种技术。脚本语言一般使用后期绑定。另见“预先绑定(Early Binding)”。 362·ArcGIS Engine 开发指南 术语表 LIBID 类库(library) 许可(license) 小端格式(little endian) 宏(macro) 地图文档(map document) MapServer(MapServer) 调度(Marshaling) 成员(members) 内存泄漏(memory leak) MTA 网络(network) 对象(object) 对象定义语言(Object Definition Language) “类库识别符”的缩写。由分配给某个类库的一个唯一字符串组成的 一类 GUID。另见“GUID”。 在面向对象程序语言中,普通的、独立于平台的术语,指类的一个逻 辑组。ArcGIS 就是由约 50 个类库组成的。虽然“类库”这个术语指 ArcGIS 类型的一个概念组,但它确实在磁盘上有多种表示:每个开发 环境都有一个。在 COM 中,OLBs 包含所有的类型信息;在.NET 中, 软件套件包含类型信息;而在 Java 中,JAR 文件包含类型信息。 给某一方授予使用软件包或组件的权限。 一种计算机硬件结构,在这种结构中,多字节的数字表示中最不重要 的字节具有最低的地址,重要性越高,其编码地址越高。另见“大端 (big endian)”。 一种计算机程序,通常是一个文本文件,包含一系列单个执行的命令。 宏用于执行通常使用的命令序列或复杂操作。 在 ArcMap 中,基于磁盘的地图表示,包含用于某个特定应用或多个相 关应用的所有地图、表格、图表、布局和报告。地图文档可以被打印 或嵌入到其他文档中。地图文档文件的扩展名为.mxd。 对磁盘上的地图文档内容可以提供编程访问并建立基于用户请求的地 图内容图像的一种 ArcGIS Server 软件组件。其设计用于使用 ArcGIS Server 建立基于地图的 Web 服务和 Web 应用程序。 通过规定函数调用和参数传递方法,使相同过程中的不同单元、不同 过程中的不同单元或者不同机器上不同过程的不同单元的客户对象和 服务器对象之间进行通信的一个过程。 接口或类的属性、方法或函数的合称。 当应用程序或组件分配了一部分内存而且在使用完后没有释放内容时 就称为有内存泄漏,这样这部分内存就不能为其他应用程序所使用。 “多线程单元”的缩写。即同时有多个线程在运行的一种单元。一个 过程只能有一个 MTA。另见“单元(Apartment)”,“STA”,“线程 (Thread)”和“TNA”。 1.一组边、接合点、转动元素,以及它们之间的连接性;也称为逻辑 网络。也就是说,表示两个位置之间可能路径的内部连通线的集合。 城市道路层就是网络的一个实例。2.在计算领域,指的是一组共享软 件、数据和外部设备的计算机,如局域网(LAN)或广域网(WAN)。 面向对象程序语言中类的例程。 类似于接口定义语言,但主要用来定义包含在对象库中的对象。另见 “接口定义语言(Interface Definition Language)”和“对象库 (Object Library)”。 附件 C·术语表·363 术语表 对象库(object library) 对象模型图(object model diagram) 对象池(object pooling) 面向对象编程(object-oriented programming) OCX ODL OGIS OLB OLE OLE客户控件(OLE Custom Control) OleView 出进程(out-of-process) 出接口(outbound interface) PDF 性能(performance) 以某种格式存储 COM 对象及其属性和方法的一个逻辑集合的一种二 进制文件,其他应用程序在运行时可以访问这些对象及其属性和方法。 应用程序或浏览器可以用类库确定对象支持哪些接口并调用对象的接 口方法。 类库中类及类之间关系的一种图形表示。 预先创建类的例程集合的过程,以便这些例程可以在多个应用程序的 请求会话之间进行共享。池对象允许将潜在成本很高的初始化和资源 获取与对象要完成的实际工作相分离。池对象的使用方式为无态方式。 一种编程模式,在这种模式中,开发人员定义具有某种数据结构的数 据类型并定义应用到该数据结构的函数或操作类型。开发人员还可以 建立对象之间的关系。例如,对象可以从其他对象集成特征。 参见“OLE 客户控件(OLE Custom Control)”。 参见“对象定义语言(Object Definition Language)”。 “开放地理数据互操作规程”的缩写。由开放 GIS 协会制定的一个规 程,用于支持异质计算环境中 GIS 系统之间的互操作。 参见“对象库(Object Library)”。 “对象链接和嵌入”的缩写。来自微软公司的一个分布式对象系统和 协议,允许应用程序交换信息。使用 OLE 的应用程序可以建立链接到 另一个应用程序的复合文档。数据可以从该文档中直接编辑而不用在 应用程序间来回切换。OLE 基于组件对象模型技术,允许开发可重用、 在多个应用程序间可互操作的对象。 也称为 ActiveX 控件,包含在一个扩展名为.ocx 的文件中。ArcGIS 控件 都是 ActiveX 控件。 Microsoft Visual Studio 中的一个实用工具,可用于查看存储在类库或对 象库或 DLL 文件中的类型信息。 在客户应用程序的进程空间中,包含在 EXE 中的组件就是出进程。初 始化类装载到定义它们的 EXE 的进程空间中而不是装载到客户进程空 间中。 类实现的一种接口,在这种接口上,对象可以调用其客户端,类似于 回调机制。另见“入接口(Inbound Interface)”。 “便携式文档格式”的缩写。来自 Adobe 公司的一种版权文件格式, 可以创建分发到各种操作系统中的轻便的、文本格式化文件。 计算机运转速度的一种度量。影响性能的因子包括可用性、输入输出 总量和响应时间。 364·ArcGIS Engine 开发指南 术语表 保存(Persistence) 像元类型(pixel type) 平台(platform) 插入式数据源(plug-in data source) PMF ProgID 属性页(property page) 代理对象(proxy object) 发布式地图文件(Published Map File) 查询接口(query interface) 栅格(raster) 回收(recycling) 引用(reference) Regedit 注册(register) 表示对象当前状态的信息被写入诸如磁盘文件等存储介质中的过程。 在 ArcGIS 中,保存通过标准的 COM 接口 IPersist 和 IPersistStream 或 ArcObjects 接口 IPersistVariant 实现。 参见“数据类型(Data Type)”。 常指机器上的操作系统的一般术语。也可以指编程语言或开发环境, 如 COM,.NET 或 Java 等。 由 ESRI 或第三方开发商提供的一种附加的只读数据源,可以是构成核 心 ArcObjects 或扩展模块的数据源。 参见“发布地图文件(Published Map File)”。 存储在系统注册表中的一个字符串值,可以通过库名和类名识别,如 esriCarto.FeatureLayer。ProgID 注册表键也包含类的可读名称,类的当 前版本号以及一个唯一的类识别符。ProgIDs 用在 VB 对象的实例化过 程中。另见“类识别符(Class Identifier)”和“IID”。 提供修改对象属性途径的一种用户界面组件。 远程对象的一个当地表示,支持与远程对象相同的接口。当地过程与 远程对象的所有交互都被迫通过该代理对象。当地对象可以象直接操 作远程对象一样调用代理对象的成员。 Publisher 扩展模块输出的一种文件,ArcReader 可以读取这种文件。发 布地图文件的扩展名为.pmf。 通过调用 IUnknown 接口的 QueryInterface 方法,客户端可以请求某个 对象上另一个接口的引用。 将空间定义为按行列排列且大小相等的像元组成的阵列的一种空间数 据模型。每个像元包含一个属性值及其位置的坐标。与矢量结构显式 存储坐标不同,栅格坐标包含在矩阵的顺序中。用相同属性值的像元 组表示地理要素。另见“矢量(vector)”。 对象池中的对象被这些对象的新例程代替的过程。回收可以清除已经 不可用的对象,并用新的服务器对象代替,这样可以收回这些旧服务 器对象占用的资源。 指向内存中分配的对象、接口或其他数据项的一个指针。COM 对象通 过 IUnknown 接口的 AddRef 和 Release 方法为其本身保持一个引用运行 总量。 Windows 操作系统中的一个实用工具,允许用户查看和编辑系统注册 表。 将组件的有关信息添加到系统注册表的过程,一般用 RegSvr32 来执行 注册。 附件 C·术语表·365 术语表 注册表(registry) 注册表文件(registry file) RegSvr32 再初始化(rehydrate) 着色(render) 运行时环境(runtime environment) 可伸缩系统(scalable) SCM 脚本(script) 序列化(serialization) 服务器(server) 服务器帐户(server account) 服务器环境(server context) 服务器目录(server directory) 存储了某台 Windows 机器系统配置方面的信息。COM 经常使用注册表, 用于存储 COM 组件的细节信息,包括 ProgIDs、ClassIDs、二进制代码 文件位置、调度信息及该组件参与的类目等。 包含 Windows 注册表格式信息的一个文件。双击 Windows 中的一个.reg 文件可以将该文件中的信息加入到系统注册表中。经常用于把组件注 册到组件类目中。 一个 Windows 实用工具,可以将某个组件有关的信息加入到系统注册 表中。组件在使用前必须注册。 从持久存储中重新初始化一个对象及其状态的过程。 绘制一个显示。将对象的几何图形、颜色、纹理、明暗和其他特征转 换到显示图像中的过程。 提供服务编译代码以执行该代码的宿主。服务控制管理器就是 COM 的 高效运行时环境。Visual Basic 虚拟机(VBVM)是运行 Visual Basic 代 码的运行时环境。 当其大小或复杂性增加时不会出现负面效应的一种系统。 “服务控制管理器”的缩写。用于建立和修改系统服务的一个管理工 具,是 COM 的高效运行时环境。 以纯文本表示的指令集合,通常存储在一个文件中,在运行时进行解 释或编译。在地理处理中,脚本可用于自动化任务,如数据转换或生 成地理数据库,而且可以从脚本应用程序中运行或添加到工具箱中。 地理处理脚本可以在任何 COM-生成脚本语言中编写,如 Python,Jscript 和 VBScript 等。 保存的一种形式,在序列化中,对象顺次被写到某个目标中,通常是 某个流。另见“保存(Persistence)”。 1.为网络上其它计算机提供服务(如访问文件或电子邮件路由等)的 一种网络计算机。服务器也可以用来驻留 Web 站点或应用,以便对它 们进行远程访问。2.为客户端提供功能的一个软件项,如用户应用程 序或数据库客户程序使用的 COM 组件或对象。 服务器对象管理器服务运行所采用的操作系统帐户。服务器帐户由 GIS 服务器后台安装实用工具指定的。 GIS 服务器上的一个空间,服务器对象及其相关对象在其中运行。服务 器环境在服务器容器过程中运行。开发人员通过服务器对象的服务器 环境得到该服务器对象的一个引用,而且可以在服务器对象环境中创 建其他对象。 文件系统中 GIS 服务器用于存储临时文件的一个位置,临时文件由 GIS 服务器清空。 366·ArcGIS Engine 开发指南 术语表 服务器对象(server object) 服务器对象孤立度(server object isolation) 服务器对象类型(server object type) 会话状态(session state) 浅状态应用程序(shallowly stateful application) 单例程类(singleton) 智能指针(smart pointer) SOAP SOC SOM SQL STA 独立应用程序(standalone application) 状态(state) 管理和提供GIS资源,如地图或定位器等的粗粒度ArcObjects组件。服 务器对象是一种高级对象,简化了执行某些操作的程序模型,隐藏了 真正执行工作任务的细粒度ArcObjects。服务器对象还具有SOAP接口, 使其可以作为Web服务供Internet上的客户使用。 描述服务器对象是否与其他服务器对象共享过程的参数。高孤立度的 服务器对象运行专用的过程,而低孤立度的服务器对象与其他相同类 型的服务器对象共享过程。 定义了服务器对象的初始化参数及开发人员可用的方法和属性。 ArcGIS 9.0 中有两种服务器对象类型:MapServer 和 GeocodeServer。 同一个客户端向同一个 Web 应用程序发出的请求序列过程中 Web 应用 程序维护信息的过程。 使用 Web 服务器的会话状态管理功能维护应用程序状态并无态使用 GIS 服务器中的服务器对象的一个应用程序。浅状态应用程序可以使用 池服务器对象。 在任何过程中只能有一个例程的类。 封装了一个接口指针的一种 Visual C++类实现,可以提供一些操作符和 函数,使开发人员在操作底层类型时更容易且更少出错。 “简单对象访问协议”的缩写。由 Microsoft/Lotus/IBM 联合开发、用 于在分散的、分布式环境中伙伴程序之间交换信息的一种基于 XML 的 协议。SOAP 允许不同计算机上、不同操作系统或平台中的程序通过万 维网协会的 HTTP 和 XML 作为信息交换的基础进行通信。 “服务器对象容器”的缩写。一个或多个服务器对象运行的一种过程。 SOC 过程由 SOM 启动和关闭。SOC 过程在 GIS 服务器容器计算机上 运行。每个容器计算机可以驻留多个 SOC 过程。另见“SOM”。 “服务器对象管理器”的缩写。管理分布于一个或多个服务器对象容 器计算机上的服务器对象集合的一种 Windows 服务。当某个应用程序 连接到 LAN 上的一个 ArcGIS Server 时,该应用程序就连接到了 SOM。 另见“SOC”。 参见“结构化查询语言(Structured Query Language)”。 “单线程单元”的缩写。即只有一个线程的单元。用户接口代码一般 放在 STA 中。另见“单元(Apartment)”,“MTA”,“线程(Thread)” 和“TNA”。 一个独立运行的应用程序,而不是在 ArcGIS 应用程序中运行。 某个对象当前包含的数据。 附件 C·术语表·367 术语表 状态操作(stateful operation) 无态(stateless) 无态操作(stateless operation) 流模式(stream) 结构化查询语言(Structured Query Language) SXD 同步(synchronization) 目标计算机(target computer) 线程(thread) TNA 工具(tool) 类型继承(type inheritance) 类库(type library) UI UML URL 使用时(usage time) 对某个对象或与其相关的一个对象进行更改的一种操作,例如,从一 个地图中删除一个图层。另见“无态操作(Stateless Operation)”。 在成员调用之间没有存储状态数据的一种对象。 不对某个对象进行更改的一种操作,例如,绘制某个地图。另见“状 态操作(Stateful Operation)”。 数据传输的一种模式,在这种模式中,对象可以提供数据存储。流对 象可以包含任何类型、具有任何内部结构的数据。另见“保存 (Persistence)”。 定义和处理关系数据库中数据的一种语法。由 IBM 于 1970s 开发,SQL 已经成为大多数关系数据库管理系统查询语言的一个工业标准。 Scene 文档。由 ArcScene 保存的一种文档,扩展名为.sxd。 自动更新元数据文件中某些元素的过程。 应用程序部署在其上的一台计算机。 贯穿某个应用程序的一个过程。一个应用程序可以有多个线程。另见 “(Apartment)”,“MTA”,“STA”和“TNA”。 “线程中立单元”的缩写。即没有线程与之持续相关的一种单元。线 程根据需要进入和离开该单元。另见“单元(Apartment)”,“MTA”, “STA”和“线程(Thread)”。 执行某个动作前需要与用户界面交互的命令。例如,使用“放大(Zoom In)”命令,在以更大比例尺重绘前,用户必须在地理数据或地图上单 击或拉一个框。工具可以被添加到任何工具条上。 一种继承方法,在这种继承中,一个接口可以从一个父接口中继承而 来。一个客户程序可以象调用父接口一样调用子接口,因为子接口支 持所有的相同成员。 有关类、接口、枚举等信息的集合,这些提供给编译器以包含在组件 中。类库也被用于使诸如 IntelliSense 等要素正常起作用。类库通常具 有扩展名.tlb。 “用户接口”的缩写。使人机交互易于实现的计算机硬件和软件部分。 UI 包括可被显示在屏幕上并通过键盘、鼠标、视频、打印机和数据获 取工具等进行交互的数据项。 “统一建模语言”的缩写。对象建模的一种图形语言。另见“CASE”。 “统一资源定位器”的缩写。网站地址的一种标准格式。一个URL看 起来象这样:www.esri.com。地址的第一部分指示使用什么协议,而第 二部分规定网站的IP地址或域名。 客户程序获得一个服务器对象的引用到其释放该对象之间的时间总 量。 368·ArcGIS Engine 开发指南 术语表 公用COM对象(utility COM object) variant VB VBA VBVM 矢量(vector) 虚拟目录(virtual directory) Visual C++ W3C 等候时(wait time) Web 应用程序(Web application) Web 应用程序模板(Web application template) Web 控件(Web control) Web 窗体(Web form) 封装了大量细粒度 ArcObjects 方法调用并向外展现了一个单个粗粒度 方法调用的 COM 对象。公用 COM 对象安装在 GIS Server 上,由服务 器应用程序调用,以最小化客户应用程序和 GIS Server 之间的往返过 程。另见“组件对象模型(COM)”。 可以包含任何数据种类的一种数据类型。 “Visual Basic”的缩写。微软公司开发的一种编程语言,是 BASIC 语 言的面向对象形式,目的在于开发应用程序。Visual Basic 在 Microsoft Windows 平台上运行。 “Visual Basic for Application”的缩写。为自动化、定制和扩展 ESRI 应 用程序,如 ArcMap 和 ArcCatalog 等提供的内嵌式编程环境。对已有的 应用程序文件,它能提供与 Visual Basic 相同的编程工具。VBA 程序对 应用程序对象进行操作,能创建定制符号、工作空间扩展、命令、工 具、浮动窗口、以及其它能插入到 ArcGIS 框架中的对象。 “Visual Basic 虚拟机”的缩写。Visual Basic 代码运行时使用的运行时环 境。 1.一种基于坐标的数据模型,将地理要素表示为点、线和多边形。每 个点要素被表示成一个单个的坐标对,而线和多边形要素则被表示为 许多顶点的有序列表。与栅格数据模型将属性关联到格网单元相对应, 每个要素都与一些属性相关联。2.具有大小和方向的一种量。另见“栅 格(Raster)”。 用作 URL 的一个目录名,对应于 Web 服务器上的一个物理目录。 C++语言的 Microsoft 实现,在 Microsoft 应用程序 Visual Studio 中使用, 可以生产运行在 Windows 机器上的软件。 参见“万维网协会(W3C)”。 客户请求到接收服务器对象之间的时间。 专门建立和设计以运行在 Internet 上的一种应用程序。 包含用作建立新的定制 Web 应用程序入口点的用户接口和所有代码及 必要文件的一个文件。ArcGIS Server 包含许多 Web 应用程序模板。 服务器上执行其自身动作的 Web 窗体的可视化组件。Web 控件专门针 对 Web 窗体操作而设计,外观类似于 HTML 元素。 基于 ASP.NET 的技术,Web 窗体允许在一个 Web 应用程序中建立动态 网页。Web 窗体在 Web 浏览器或其他设备中给客户展示其用户接口, 但一般在服务器上执行其动作。 附件 C·术语表·369 术语表 Web 服务器(Web server) Web 服务(Web service) Web 服务目录(Web service catalog) 万维网协会(World Wide Web Consortium) WSDL XMI XML XML 元数据交换(XML Metadata Interchange) XSL XSLT 管理 Web 文档、Web 应用程序和 Web 服务并使它们可以为外界访问的 计算机。 万维网上其他应用程序可以访问使用的一种软件组件。Web 服务建立 在诸如 XML 和 SOAP 之类的工业标准基础之上,因此不依赖于任何具 体的操作系统或编程语言,允许各种应用程序访问。 ArcGIS Server Web 服务的集合。一个 Web 服务目录其本身就是一个带 有明显端点(URL)的 Web 服务,可以查询以获得该目录中的 Web 服 务及其 URLs 列表。另见“ArcGIS Server Web 服务”。 旨在发展万维网(World Wide Web)标准和促进各种 Web 技术,如浏览 器等之间的互操作的组织。成员遍布全世界,制定了 XML、XSL、HTML 标准和许多基于 Web 的协议。 “Web 服务描述语言”的缩写。描述 Web 服务的方法和类型的标准格 式,用 XML 表达。 参见“XML 元数据交换(XML Metadata Interchange)”。 “扩展标记语言”的缩写。由万维网协会(W3C)开发,设计用于促 进计算机应用程序间数据交换的文本格式。XML 是使用自定义标签建 立标准信息格式和跨应用程序共享格式和数据的一套规则。 由对象管理工作组(Object Management Group)开发的一个标准,规定 了如何将 UML 模型存储在一个 XML 文件中。ArcGIS 可以阅读 XML 文件中的模型。 “扩展样式语言”的缩写。定义 XML 文档表示和转换的一套标准集。 一个 XSL 样式单可能包含如何显示 XML 文档中的标签内容,如字体大 小、背景颜色和文字对齐方式等方面的信息。一个 XSL 样式单还可能 包含 XSLT 代码,描述如何将 XML 文档中的标签内容转换为其它格式 的输出文档。XSL 标准由万维网协会(W3C)维护。另见“XML”和 “万维网协会(W3C)”。 “扩展样式语言转换”的缩写。将 XML 文档中的标记内容转换成其它 格式输出文档的一种语言。一个 XSL 样式单包含定义每个转换应用方 式的XSLT代码。转换一个文档需要一个原始XML文档、一个包含XSLT 代码的 XSL 文档和一个执行转换的 XSLT 解释器(parser)。XSLT 标准 由万维网协会(W3C)维护。另见“XML”、“扩展样式语言(eXtensible Style Language)”和“万维网协会(W3C)”。 370·ArcGIS Engine 开发指南
还剩373页未读

继续阅读

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

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

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

下载pdf

pdf贡献者

nufront

贡献于2018-04-18

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