1、 Flex Viewer 开发教程 作者 ropp 邮箱 文档版本 1.0 目 录 1 Flex Viewer 配置文件 . 1 2 Widget 配置文件 4 3 Widget 与 WidgetTemplate 7 4 Widget 与 Map 交互 9 4.1 交互方式 1: map 实例 . 10 4.2 交互方式 2: BaseWidget 封装的方法 . 12 5 Widget 与 Widget 交互 17 6 Widget 与共享数据 19 7 Widget 与服务器交互 25 8 后记 . 26 1 Flex Viewer 的设计原则是 SIMPLICITY(简单)。 因为简单
2、原则, Flex Viewer 易于部署、配置和 扩展。 为了达到“简单”这个 目的, Flex Viewer 在设计和实现上未引入第三方 框架。但是从其框架结构上,我们能捕捉到 一些 Flex 框架中 最佳实践的影子 ,比如 事件机制就与 PureMVC 中的 Notification 机制类似。 其实, Flex Viewer 本身就可以被认为是一个框架。在这个框架基础上,可以 通过扩展 快速实现业务系统原型。 本文档将详细介绍如何在“简单”原则下实现自定义 Widget,以及 Widget 如何与其它模块 交互。 说明 本文档使用的代码位于 widgets.FlexViewerInAct
3、ion 目录下。 1 Flex Viewer 配置文件 Flex Viewer 通过 配置文件配置系统 数据和功能,配置文件内容如下: ArcGIS Viewer for Flex a configurable web mapping application assets/images/logo.png 0xFFFFFF,0x333333,0x101010,0x000000,0xFFD700 0.8 title: Flex Viewer 自带 Banner 实现的标题; subtitle: Flex Viewer 自带 Banner 实现的副标题; logo: Flex Viewer 自带 B
4、anner 实现的 Logo 图标; style: 用来设置全局的组件样式 ,具体详见 UIManager 代码; UI Elements: 指 Control Widget,比如 HeaderControllerWidget、NavigationWidget 等这些提供系统级别功能的 Widget; map: Flex Viewer 用来设置地图属性,包括底图、业务图层; widgetcontainer: Business Widget 的容器,用来管理 Business Widget。 Business Widget 指提供业务功能的 Widget。 上述配置文件并未使用到所有可用的属性,
5、比如在 style 中设置 font 属性, map中设置 fullextent 等 ,此处不一一列举,详见 ConfigManager 代码。 4 需要说明的是,基于 Flex Viewer 开发业务系统,一般通过实现自定义的 Business Widget 来实现具体的业务功能,通过修改或者自定义 Control Widget 实现符合需求的系统级 别 功能组件。所以,配置文件中的前三项并不是必须的,通常我们的业务系统需要更具特色的 Banner 实现。 Flex Viewer 各部分与配置文件的对应关系见下图: 2 Widget 配置文件 在 Flex Viewer 的设计中,每个 Wi
6、dget 都可以有一个配置文件,来配置 Widget所需要的各种资源,提倡的做法是配置文件名与 Widget 名称 保持一致 ,并且位于同一目录下 。 当然, Widget 的配置文件不是必须的,当不需要配置信 息时,配置文件可省略。 在 Flex Viewer 解析中,我们以 HelloWorldWidget 为例,说明如何在 Flash Builder 中实现自定义 Widget。 下面我们 同样 以 HelloWorldWidget为例,来说明 Widget 如何从其配置文件获取资源信息。 先来看一下 HelloWorldWidget.xml,也就是配置文件的内容: 5 Hello, F
7、lex Viewer! 再来看一下 HelloWorldWidget 的实现代码,如下: widgetConfigLoaded 事件 widgetConfigLoaded 事件是 BaseWidget 中设计的一个事件,用来说明 Widget对应的配置文件已经成功读取。通常在这个事件的响应方法中解析 XML 数据,获取所需的资源信息。 如果自定义 Widget 需要配置文件中的信息,如 所示 ,设置 widgetConfigLoaded 事件的响应方法即可。 响应方法 6 widgetConfigLoaded 事件的响应方法,在该方法中对 XML 配置信息进行解析。 configXML 对象
8、Widget 配置文件是一个 XML 文档, configXML 是在 BaseWidget 中定义的 XML对象,用来表示配置文件的 XML 数据。通常, widgetConfigLoaded 事件的响应方法中,直接访问 configXML 即可。 解析配置信息 从 configXML 中获取所需资源信息,此处是 ActionScript 中对于 XML 数据的操作 ,可参考相关教程。 使用 配置信息 配置信息可以有多种使用方式,此处只是简单的把字符串信息显示出来。除此之外,配置信息可以是各种资源的 url、对 Timer 设置的时间间隔等等。 在具体系统的开发过程中,应 尽可能多的将 Wi
9、dget 用到的资源 放 到配置文件 中 ,这样的话,即便在 系统交付 之后 ,用户可以通过 修改 配置文件达到特定需求,而不需要更改 源代码 。 下图是 HelloWorldWidget 打开时的画面: 7 3 Widget 与 WidgetTemplate HelloWorldWidget 中短短几行代码,却 能 实现如此 cool 的一个 Widget,这 要归功于 WidgetTemplate。 WidgetTemplate 是 IWidgetTemplate 接口的默认实现 ,提供 组成 Widget 的 各个部分,包括标题栏、内容面板、控制按钮、工具按钮、 Widget图标等等。
10、Widget 将 WidgetTemplate 作为 UI 容器 ,比如在 HelloWorldWidget 中,我们将显示信息的 Label 放在 WidgetTemplate 中。 当然,也可以实现自定义WidgetTemplate,只要实现 IWidgetTemplate 接口即可。 在 Flex Viewer2.0 以后,WidgetTemplate 的外观通过皮肤( Skin)来定义,详见 WidgetTemplateSkin 代码。要想实现其它 风格的 WidgetTemplate,自定义一个 WidgetTemplateSkin 即可。 下图是一个自定义 WidgetTempla
11、teSkin 的效果 。 WidgetTemplate 定义了三个事件 :打开( open)、最小化( minimized)、关闭( closed)。这三个事件分别在 Widget 打开、最小化和关闭的时候发生。这三个事件在某些特殊业务需求下能发挥很大的作用,比如某 个 Widget 对应的一个 GraphicsLayer8 (见 ArcGIS API for Flex) ,当 Widget 打开时需要显示,当 Widget 最小化或者关闭时需要隐藏。 此需求 可以分别在三个事件的响应方法中实现。 在HelloWidgetTemplateWidget 中我们分别对三个事件进行响应,每个响应中显
12、示一个弹出框来说明当前所发生的事件类型。 窗口 打开 时的情形如下图 所示 : HelloWidgetTemplateWidget 代码如下: 设置 open 事件的响应方法; 设置 minimized 事件的响应方法; 设置 closed 事件的响应方法; 设置 WidgetTemplate 的皮肤; 实现 open 事件的响应方法; 实现 minimized 事件的响应方法; 实现 closed 事件的响应方法。 4 Widget 与 Map 交互 Widget 与 Map 之间的交互是最常见的一种交互 , BaseWidget 不仅定义了 Map实例对象,而且封装了与 Map 进行交互的
13、方法。 BaseWidget 良好封装使 Widget与 Map 交互非常简单。 10 4.1 交互方式 1: map 实例 在 BaseWidget 中,有如下代码: /* * Current active map that the container shows. * The WidgetManager will set its value when a widget is initialized. */ private var _map:Map; Bindable /* * Set a map object reference. Used by WidgetManager to pass
14、 in the current * map. * param value the map reference object. */ public function get map():Map return _map; public function set map(value:Map):void _map = value; 通过注释可知, Widget 在初始化的时候, WidgetManager 会将当前的 map 实例注入 Widget。所以, 一旦 Widget 初始化完成,就有一个 map 实例可供使用。 下面我们实现一个 HelloMapWidget, 来说明在 Widget 如何使
15、用 map 实例 ,代码如下: HelloMapWidget 实现的功能是,单击 “ Say Hi to Map”按钮 ,在地图当前视图范围的中心点显示 infoWindow, infoWindow 显示的内容是配置文件中的 字符 。 运行结果如下图所示: 有了 map 实例,在 Widget 就可以做任何与 Map 相关的事情, 比如控制 Map 图层、获得 Map 的各种信息等等,具体可参考 Flex Viewer 中的 NavigationWidget、12 MapSwitcherWidget 等。 4.2 交互方式 2: BaseWidget 封装的 方法 除了 map 实例, Wid
16、get 可以通过 BaseWidget 中封装的方法与 Map 进行交互(实际上是与 MapManager 的交互),见如下代码: /* * Show information window (infoWindow) based on infoData from widget. */ public function showInfoWindow(infoData:Object):void ViewerContainer.dispatchEvent(new AppEvent(AppEvent.SHOW_INFOWINDOW, infoData); /* * Set map action from
17、widget. */ public function setMapAction(action:String, status:String, symbol:Symbol, callback:Function):void var data:Object = tool: action, status: status, symbol: symbol, handler: callback; ViewerContainer.dispatchEvent(new AppEvent(AppEvent.SET_MAP_ACTION, data); /* * Set map navigation mode, suc
18、h a pan, zoomin, etc. * The navigation methods supported are: * * pan (Navigation.PAN) * zoomin (Navigation.ZOOM_IN) * zoomout (Navigation.ZOOM_OUT) * zoomfull (ViewerContainer.NAVIGATION_ZOOM_FULL) * zoomprevious (ViewerContainer.NAVIGATION_ZOOM_PREVIOUS) * zoomnext (ViewerContainer.NAVIGATION_ZOOM
19、_NEXT) * */ public function setMapNavigation(navMethod:String, status:String):void var data:Object = tool: navMethod, status: status; ViewerContainer.dispatchEvent(new AppEvent(AppEvent.SET_MAP_NAVIGATION, data); 13 showInfoWindow() 弹出窗口并显示信息。 setMapAction() 设置画图操作。 setMapNavigation() 设置导航操作。 由于是与 M
20、apManager 交互,上述三个方法中只是派发了相应的事件,这些事件由 MapManager 监听、捕捉和响应,在 MapManager 中有如下代码说明对上述三个方法派发的事件进行了监听: ViewerContainer.addEventListener(AppEvent.SET_MAP_NAVIGATION, changeNavigationbyMenu); ViewerContainer.addEventListener(AppEvent.SET_MAP_ACTION, enableMapAction); ViewerContainer.addEventListener(AppEven
21、t.SHOW_INFOWINDOW, widgetShowInfo); 下面实现 HelloMapManagerWidget,演示如何使用上述三个方法,代码如下: map 添加 GraphicsLayer 实例 ,用于显示画图结果 ; 构造 infoData 对象, 调用 showInfoWindow()方法 , 单击“ Say Hi to MapManager”按钮,将显示 下图所示 信息框 ( InfoPupup.mxml 和 defaults.css 已作出相应调整,见源代码) : 调用 setMapNavigation()方法,设置当前地图 导航 操作, Zoom In 的 效果如下图
22、所示: 16 调用 setMapAction()方法,设置地图画图操作,如下图所示: 响应画图事件的方法,在此方法中将画图事件中的 graphic 添加到graphicsLayer 中 ; 设置地图导航操作的按钮, click 事件中调用 activateMapNavigation()方法 ; 设置画图操作的按钮, click 事件中调用 draw()方法。 17 5 Widget 与 Widget 交互 有些情况下一个特定功能需要多个 Widget 相互协作共同完成 ,但是这种协作不能打破 Widget 彼此之间的独立性。本着简单原则, Widget 之间通过事件进行交互 。 本小节设计了两
23、个 Widget 来说明 Widget 之间通过事件进行交互,名为HelloWidgetWidgetA 和 HelloWidgetWidgetB, HelloWidgetWidgetB 可以打开、最小化、关闭 HelloWidgetWidgetA,如下图所示: HelloWidgetWidgetA 代码如下所示: 对 creationComplete 事件进行监听 ,一般在此事件的响应方法中做一些初始化的工作 ; 在 creationComplete 事件的响应方法中通过 ViewerContainer 添加对SEND_MESSAGE_TO_ANOTHER_WIDGET 事件的监听; 对 SE
24、ND_MESSAGE_TO_ANOTHER_WIDGET 事件的响应方法; 考虑一下,此处为什么不用 setState()方法呢? HelloWidgetWidgetB 代码如下所示: 实现按钮的单击事件响应,派发 SEND_MESSAGE_TO_ANOTHER_WIDGET 事件。 AppEvent 中 新添加的事件如下所示: / add /* * event used to test communication between widgets */ public static const SEND_MESSAGE_TO_ANOTHER_WIDGET:String = “sendMessag
25、eToAnotherWidget“; / end 需要注意的是, HelloWidgetWidgetA 只有在已经打开(通过菜单中的图标)的情况下,才能响应 HelloWidgetWidgetB 派发的事件,与 HelloWidgetWidgetB 进行交互。因为 Flex Viewer 中的 Widget 采用的是 Lazy-Load 方式,即只有在第一次打开时才加载。 那么,在 Widget 第一次打开的时候,如何知道在这之前发生的事情呢?找 DataManager 帮忙 ! 下一节我们看一下 Flex Viewer 中的数据共享机制。 6 Widget 与共享数据 Flex Viewer
26、 通过 DataManager 提供数据共享服务,各个模块可通过 事件进行数据共享和数据获取。 DataManager 负责将共享数据以 key-value 的形式存储于内存,并随时准备接收和派发共享数据。先来分析一下 DataManager 的代码: 20 public class DataManager extends EventDispatcher private var dataTable:Hashtable; public function DataManager() super(); dataTable=new Hashtable(); ViewerContainer.addEve
27、ntListener(AppEvent.CONFIG_LOADED, config); /this is a example to setup the listner to get the type of data the Data /Manager is interested in. ViewerContainer.addEventListener(AppEvent.DATA_FETCH_ALL, fetchAllData); ViewerContainer.addEventListener(AppEvent.DATA_PUBLISH, addData); ViewerContainer.a
28、ddEventListener(AppEvent.DATA_FETCH, fetchData); private function config(event:AppEvent):void private function fetchAllData(event:AppEvent):void ViewerContainer.dispatchEvent(new AppEvent(AppEvent.DATA_SENT, dataTable); private function fetchData(event:AppEvent):void var key:String=event.data.key as
29、 String; var data:Object=key: key, collection: dataTable.find(key); ViewerContainer.dispatchEvent(new AppEvent(AppEvent.DATA_SENT, data); private function addData(event:AppEvent):void var key:String=event.data.key; if (key) var dataCollection:Object=event.data.collection; if (dataTable.containsKey(k
30、ey) dataTable.remove(key); dataTable.add(key, dataCollection); /var data:Object=key: key, data: dataTable; / change by ropp to just send new published data var data:Object=key: key, data: dataCollection; ViewerContainer.dispatchEvent(new 21 AppEvent(AppEvent.DATA_NEW_PUBLISHED, data); 哈希表实例,用来以 key-
31、value 的形式存储共享数据; 监听 CONFIG_LOADED 事件; 监听 DATA_FETCH_ALL(获取所有共享数据) 事件; 监听 DATA_PUBLISH(发布共享数据) 事件; 监听 DATA_FETCH(根据 key 获取共享数据)事件; DATA_FETCH_ALL 事件响应方法; DATA_FETCH 事件响应方法,根据 key 返回对应的共享数据; DATA_PUBLISH 事件响应方法,将发布的共享数据保存,并将最新的共享数据分发出去 ( DATA_NEW_PUBLISHED 事件) 。此处源代码 有一点小问题,修改见注释。 再来看一下 BaseWidget 对共享
32、数据的支持, BaseWidget 有如下代码: /* * Add information from a widget to the DataManager so that it can be shared between widgets. * param key the widget name * param arrayCollection the list of object in infoData structure. */ public function addSharedData(key:String, arrayCollection:ArrayCollection):void va
33、r data:Object = key: key, collection: arrayCollection ; ViewerContainer.dispatchEvent(new AppEvent(AppEvent.DATA_PUBLISH, data); /* * Fetch shared data from DataManager. */ public function fetchSharedData():void ViewerContainer.dispatchEvent(new 22 AppEvent(AppEvent.DATA_FETCH_ALL); /* * Fetch share
34、 data from DataManager by key * param key */ public function fetchShareDataByKey(key:String):void 新添加 方法 ViewerContainer.dispatchEvent(new AppEvent(AppEvent.DATA_FETCH, key:key); addShareData()方法用来向 DataManager 发布共享数据; fetchShareData()方法用来向 DataManager 请求所有的共享数据; fetchShareDataByKey()方法在 BaseWidget
35、中未提供,我们不妨把这个方法加上去,用来向 DataManager 请求以 key 存储的共享数据。 下面实现一个 HelloDataManagerWidget 来说明如何发布和获取共享数据,代码如下: 24 在 Widget 的 creationComplete 事件的响应方法中,通过 ViewerContainer 对DATA_SENT 和 DATA_NEW_PUBLISHED 事件进行监听。 DATA_SENT 事件的响应方法, DataManager 在两种情况下派发 DATA_SENT 事件,一是获取所有的共享数据,二 是通过 key 获取共享数据。所以在此实现方法中要对这两种数据区
36、别对待,如果返回的数据是 Hashtable 类型,则返回的是所有的共享数据,否则返回的是根据 key 获取的共享数据。 DATA_NEW_PUBLISHED 事件的响应方法,获取的是最新被共享的数据。DataManager 会把最新 被共享的数据分发出来(我们已经对 DataManager 的addData()方法进行了修改,以期达到这个目的)。 调用 addShareData()方法共享数据。 调用 fetchShareDataByKey()方法通过 key 获取共享数据。 调用 fetchShareData()方法获取所有的共享数据。 HelloDataManagerWidget 运行时
37、如下图所示 : 25 点击 Share 按钮 :共享 key-value 数据,同时最新的共享数据会在 New Share Data 中显示出来; 点击 Fetch by Key 按钮 :根据 key 获取 value; 点击 Fetch All 按钮 :获取所有的共享数据。 数据共享机制为业务系统开发提供了很大的方便,比如当一个 Widget 第一次打开时,需要之前的一些数据进行运算,此时即可通过共享机制实现 。 7 Widget 与服务器交互 本小节将不 涉及具体的代码细节,因为 Widget 与服务器的交互不会因为 Flex Viewer 的架构而有所不同,了解了 Flex 程序如何与服
38、务器端进行交互,直接应用到 Widget 中即可。 下表是 Flex 与服务器端进行交互可用的技术,具体细节可参考 Flex 4 in Action中的第 15 章。 Communication Server Support Application Benefits HTTP Simple Widget-based Easy implementation via 26 (includes REST and RPC hybrids) -All applications; speed and real-time UI updates arent required. the HTTPService
39、object; RPC hybrid protocols can be invoked using RemoteObject. SOAP/WSDL -All Data aggregation from external web services. Easy implementation; pull data from multiple outside resources regardless of platform. AML -BlazeDS speed is important; data is usually pulled from server by polling. Binary da
40、ta compression makes communications 12 times faster; strong data typing; multiplatform support. RTMP -LiveCycle Data Services(LCDS), -Flash Media Server(FMS) Enterprise level, messaging, instantaneous UI updates; data can be pushed to the client; streaming media content; data intensive RIAs. Integra
41、tes into existing J2EE infrastructure; document management, rapid data transfer, clustering, data tracking, syncing, paging, and conflict resolution. Flash Remoting -Native to ColdFusion Robust, enterprise platform for client/server Flex communications; native. Seamless integration with the Flash pl
42、atform; removes the need for an intermediate code library to do data type mapping and date serialization. JSON -All (JavaScript data objects are serialized and transferred in binary form.) AIR applications that use AJAX or Flex applications that use the ExternalInterface API. Easy implementation with the HTTPService object; part of the AS3CorLib library. 8 后记 Flex Viewer 解析 主要阐述了 Flex Viewer 设计方面的内容 ,本文档 则 更多 关注如何在 Flex Viewer 简单原则下开发 Widget, 所以 实例代码在设计和实现上尽量保持 简单 , 尽量少的涉及 ArcGIS API for Flex,也 没有针对具体 的业务功能进行开发。 27 大家若 发现文档中的错误和不妥之处, 请发送邮件到 告知,文档会及时修正和改进, 同时 对于 文档中 未涉及 的 内容 , 欢迎大家来信交流。