14. 应用程序 [SimuPaie] – 版本 10 – Web 服务 ASP.NET 的 Flex 客户端
现在我们介绍一个基于第 5 版 Web 服务 ASP.NET 的 Flex 客户端。所使用的 IDE 是 Flex Builder 3。 该产品的演示版本可从 URL [https://www.adobe.com/cfusion/tdrc/index.cfm?loc=fr_fr&product=flex] 处下载。Flex Builder 3 是一款基于 Eclipse 的 IDE。 此外,为运行 Flex 客户端,我们使用了 Wamp 工具中的 Apache 网页服务器。任何 Apache 服务器均可满足需求。显示 Flex 客户端的浏览器必须安装至少 Flash Player 9 版本的插件。
Flex应用程序的特点是在浏览器的Flash Player插件中运行。在这方面,它们与Ajax应用程序相似,后者在发送至浏览器的页面中嵌入脚本,这些脚本随后在浏览器中执行。 Flex 应用程序并非通常意义上的 Web 应用程序:它是一个由 Web 服务器提供服务的客户端应用程序。从这一点来看,它类似于作为这些服务客户端的桌面应用程序。但有一点不同:它最初是从 Web 服务器下载到安装了能够运行它的 Flash Player 插件的浏览器中的。
与桌面应用程序一样,Flex 应用程序主要由两个部分组成:
- 呈现部分:即在浏览器中显示的视图。这些视图具备桌面应用程序窗口的丰富功能。视图通过一种名为MXML的标记语言进行描述。
- 代码部分:主要处理用户在视图上操作所触发的事件。该代码既可用 MXML 编写,也可使用一种名为 ActionScript 的面向对象语言编写。需区分两种类型的事件:
- 需要与Web服务器进行交互的事件:例如通过Web应用程序提供的数据填充列表,将表单数据发送至服务器等。Flex提供了一系列方法,使开发者能够以透明的方式与服务器进行通信。这些方法默认是异步的:在向服务器发送请求期间,用户可以继续与视图进行交互。
- 不与服务器交换数据但会修改显示视图的事件,例如从树中拖拽一个元素并将其放入列表中。此类事件完全在浏览器内部本地处理。
Flex 应用程序通常按以下方式运行:
![]() |
- 在 [1] 中,请求了页面 HTML
- 在 [2] 中,该页面被发送出去。它随附一个二进制文件 SWF(ShockWave Flash),其中包含完整的 Flex 应用程序:所有视图及其事件处理代码。 该文件将由浏览器的 Flash Player 插件执行。
![]() |
- Flex客户端通常在浏览器本地运行,除非需要外部数据。此时,它会向服务器[3]请求数据,并以[4]格式接收数据,数据格式可能为XML或二进制格式。 被调用的Web服务器端应用程序可以使用任何编程语言编写,关键在于响应的格式。
我们描述了 Flex 应用程序的运行架构,以便读者理解其与传统 Web 应用程序的区别——在传统 Web 应用程序中,页面不包含由浏览器执行的代码(如 JavaScript、Flex、Silverlight 等)。 在传统Web应用中,浏览器处于被动状态:它仅显示由Web服务器构建并发送给它的页面。
14.1. 客户端/服务器应用程序架构
所采用的客户端/服务器架构与第 6 版和第 8 版类似:
![]() |
在 [1] 中,Web 层 ASP.NET 被用 MXML 和 ActionScript 编写的 Flex Web 层所取代。 客户端 [C] 将由 IDE Flex Builder 生成。需要在此提醒的是,该架构包含两个未显示的 Web 服务器:
- 一个运行 Web 服务 [S] 的 Web 服务器 ASP.NET
- Web 服务器 APACHE,用于运行 Web 客户端 [1]
14.2. 客户端的 Flex 3 项目
我们使用 Flex Builder 3 构建 Flex 客户端:
![]() |
- 在 Flex Builder 3 中,创建一个新项目 [1]
- 为其命名 [2],并在 [3] 中指定生成文件夹
![]() |
- 在 [4] 中,为主要应用程序(即将被执行的应用程序)命名
- 在 [5] 中,生成后的项目
- 在 [6] 中,应用程序的主文件 MXML
- 文件 MXML 包含一个视图及其事件处理代码。标签页 [Source] [7] 可访问文件 MXML。 其中包含描述视图的 <mx> 标签以及 ActionScript 代码。
- 可以通过使用 [Design] 和 [8] 选项卡以图形方式构建视图。描述该视图的 MXML 标签随后会自动生成在 [Source] 选项卡中。 反之亦然:直接在 [Source] 选项卡中添加的 MXML 标签,将在 [Design] 选项卡中以图形形式体现。
14.3. 视图 1
我们将逐步构建一个与第 1 版(参见第 4 节)类似的 Web 界面。首先,我们构建以下界面:
![]() |
- 在 [1] 中,显示成功连接到 Web 服务时的视图。此时员工下拉列表已填充完毕。
- 在 [2] 中,显示无法连接到 Web 服务时的界面。此时将显示一条错误消息。
客户端的主文件 [main.xml] 如下:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
creationComplete="init()">
<mx:VBox width="100%">
<mx:Label text="Feuille de salaire" fontSize="30"/>
<mx:HBox>
<mx:VBox>
<mx:Label text="Employés"/>
<mx:ComboBox id="cmbEmployes" dataProvider="{employes}" labelFunction="displayEmploye"/>
</mx:VBox>
<mx:VBox>
<mx:Label text="Heures travaillées"/>
<mx:TextInput id="txtHeuresTravaillees"/>
</mx:VBox>
<mx:VBox>
<mx:Label text="Jours travaillés"/>
<mx:NumericStepper id="joursTravailles" minimum="0" maximum="31" stepSize="1"/>
</mx:VBox>
<mx:VBox>
<mx:Label text=""/>
<mx:Button id="btnSalaire" label="Salaire"/>
</mx:VBox>
</mx:HBox>
<mx:TextArea id="msg" minWidth="400" minHeight="100" editable="false" visible="true" enabled="true" horizontalScrollPolicy="auto" verticalScrollPolicy="auto" x="0" y="0" maxHeight="100" maxWidth="400"/>
</mx:VBox>
<mx:WebService ...>
...
</mx:WebService>
<mx:Script>
<![CDATA[
...
// 数据
[Bindable]
private var employes : ArrayCollection;
private function init():void{
...
}
]]>
</mx:Script>
</mx:Application>
在此代码中,需区分以下内容:
- 应用程序定义(第2-3行)
- 应用程序视图的描述(第4-25行)
- 位于 <mx:Script> 标签内的 ActionScript 语言事件处理程序(第 31-42 行)。
- 远程 Web 服务的定义(第 27-29 行)
首先,让我们来分析应用程序本身的定义及其视图的描述:
- 第 2-3 行:定义:
- 视图容器中组件的布局模式。layout="vertical" 属性表示组件将上下排列。
- 视图实例化后需执行的方法:c.a.d。即所有组件均已实例化之时。属性 creationComplete="init();" 表示应执行第38行中的方法init。creationComplete是类Application可能触发的事件之一。
- 第4-25行定义了视图的组件
- 第 4-25 行:一个垂直容器:组件将在此容器中上下排列
- 第 5 行:定义文本
- 第 6-23 行:一个水平容器:组件将水平排列在其中
- 第 7-10 行:一个垂直容器,将包含一段文本和一个下拉列表
- 第 8 行:文本
- 第 9 行:下拉列表,其中将放入员工列表。标签 dataProvider="{employes}" 指明了用于填充该列表的数据源。在此,该列表将通过第 36 行定义的对象 employes 进行填充。 要能够写入 dataProvider="{employes}",employes 字段必须具有 [Bindable] 属性(第 35 行)。 该属性允许在 <mx:Script> 标签外部引用变量 ActionScript。 字段 employes 的类型为 ArrayCollection,这是一种 ActionScript 类型,用于存储对象列表,此处为 Employe 类型的对象列表。
- 第 11-14 行:一个垂直容器,其中将包含一段文本和一个输入框
- 第12行:文本
- 第13行:工作小时数输入框。
- 第 15-18 行:一个垂直容器,其中包含一段文本和一个递增器
- 第 16 行:文本
- 第 17 行:用于输入工作天数的递增控件
- 第19-22行:一个垂直容器,内含文本和一个按钮,点击该按钮将触发对下拉列表中选定人员的工资计算。
- 第20行:文本
- 第 21 行:按钮。
- 第23行:结束第6行开始的水平容器
- 第24行:一个类型为TextArea的组件中的文本框。它将显示错误信息。
- 第 25 行:结束第 4 行开始的垂直容器
第 4-25 行在 [Design] 选项卡中生成以下视图:
![]() |
- [1]:由第 5 行中的组件 Label 生成
- [2]:由第 9 行的组件 ComboBox 生成
- [3]:由第 13 行中的组件 TextInput 生成
- [4]:由第17行的组件 NumericStepper 生成
- [5]:由第21行的组件Button生成
- [6]:由第 24 行中的组件 TextArea 生成
现在让我们查看远程 Web 服务的声明:
<mx:WebService id="pam"
wsdl="http://localhost:1077/Service1.asmx?WSDL"
fault="wsFault(event);"
showBusyCursor="true">
<mx:operation
name="GetAllIdentitesEmployes"
result="loadEmployesCompleted(event)"
fault="loadEmployesFault(event);">
<mx:request/>
</mx:operation>
</mx:WebService>
- 第1行:该Web服务是标识符pam(id属性)的一个组件
- 第2行:Web服务文件WSDL中的URI(参见第9.2节)
- 第3行:与Web服务交互时发生错误时需执行的方法:wsFault方法。
- 第 4 行:要求显示一个指示器,以告知用户正在与 Web 服务进行交互。
- 第 5-10 行:远程 Web 服务提供的操作之一。此处为方法 GetAllIdentitesEmployes。
- 第 7 行:当该方法调用正常结束时需执行的方法,即 c.a.d。即当 Web 服务成功返回员工列表时
- 第 8 行:当该方法调用因错误终止时需执行的方法。
- 第 9 行:操作 GetAllIdentitesEmployes 的参数。已知该方法不接受参数,因此将 <mx:request> 标签留空。
现在让我们查看与该 Web 服务相关的代码 ActionScript:
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
// 数据
[Bindable]
private var employes : ArrayCollection;
private function init():void{
// 记录消息区域的坐标
msgHeight=msg.height;
msgWidth=msg.width;
// 隐藏消息区域
hideMsg();
// 向远程 Web 服务发送请求以获取员工简化列表
pam.GetAllIdentitesEmployes.send();
}
private function wsFault(event:Event):void{
// 报告错误
msg.text="Service distant indisponible";
showMsg();
}
private function loadEmployesCompleted(event:ResultEvent):void{
// 填充员工下拉列表
employes=event.result as ArrayCollection;
}
private function displayEmploye(employe:Object):String{
// 员工身份
return employe.Prenom + " " + employe.Nom;
}
private function loadEmployesFault(event:FaultEvent):void{
// 显示错误信息
msg.text=event.fault.message;
// 表单
showMsg();
}
// 块管理
private var msgWidth:int;
private var msgHeight:int;
private function hideMsg():void{
msg.height=0;
msg.width=0;
}
private function showMsg():void{
msg.height=msgHeight;
msg.width=msgWidth;
}
]]>
</mx:Script>
- 第 11 行:由于编写了以下代码,因此 init 方法会在应用程序启动时执行:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
creationComplete="init()">
- 第 13-14 行:记录消息区域的高度和宽度。 使用两个方法 hideMsg(第 48-51 行)和 showMsg(第 53-56 行),分别根据是否发生错误来隐藏/显示消息区域。 方法 hideMsg 通过将消息区域的高度/宽度设为 0 来隐藏该区域。方法 showMsg 通过恢复在方法 init 中存储的高度/宽度来显示消息区域。
- 第 16 行:隐藏消息区域。起初,没有错误。
- 第 18 行:调用 Web 服务 pam(Web 服务第 1 行)中的方法 GetAllIdentitesEmploye(Web 服务第 6 行)。该调用为异步调用。 Web 服务的第 7 行指出,如果该异步调用成功完成,则将执行方法 loadEmployesCompleted。Web 服务的第 8 行指出,如果该异步调用失败,则将执行方法 loadEmployesFault。
- 第 27 行:方法 loadEmployesCompleted,当第 18 行的 Web 服务调用成功时执行。
- 第29行:已知Web服务返回响应XML。为理解代码ActionScript,建议回顾该响应:
![]() |
- 转换为 [1],即 Web 服务页面 [Service.asmx]
- 转换为 [2],即方法 [GetAllIdentitesEmployes] 的测试页面链接
- 转换为 [3],测试已完成。不期望任何参数。
- 在 [4] 中:响应 XML 包含一个员工数组。 对于每位员工,有五项信息封装在 <Id>、<Version>、<SS>、<Nom>、<Prenom> 标签中。 如果将响应 XML 放入类型为 ArrayCollection 的数组 employes 中:
- employes.getItemAt(i):是数组中的第 i 个元素
- employes.getItemAt(i).SS:是该员工的社保号。
- employes.getItemAt(i).姓名:是该员工的姓名
- ...
回到代码 ActionScript:
- 第 29 行:event.result 代表 Web 服务的响应 XML。方法 GetAllIdentitesEmployes 返回一个员工数组。event.result 代表该员工数组。 该数组被赋值给类型为 ArrayCollection 的变量,该类型通常表示对象集合。该变量名为 employes,在第 9 行声明。请注意,该变量是员工下拉列表的数据源:
<mx:ComboBox id="cmbEmployes" dataProvider="{employes}" labelFunction="displayEmploye"/>
对于数据源中的每位员工,下拉列表将调用方法 displayEmploye(属性 labelFunction)来显示该员工。从第 32-34 行可以看到,该方法会显示员工的姓和名。
- 第37行:如果第18行的Web服务调用失败,则会执行方法loadEmployesFault。event.fault.message是Web服务返回的错误消息。
- 第 39 行:该错误消息被放入消息区域
- 第 41 行:显示消息区域。
应用程序构建完成后,其可执行代码位于 Flex 项目的 [bin-debug] 文件夹中:
![]() |
上文中的
- 文件 [main.html] 代表文件 HTML,浏览器将向 Web 服务器请求该文件以获取 Flex 客户端
- 文件 [main.swf] 是 Flex 客户端的二进制文件,它将被封装在发送给浏览器的页面 HTML 中,随后由浏览器的 Flash Player 插件执行。
现在我们可以运行 Flex 客户端了。但在运行之前,我们需要先搭建其所需的运行环境。让我们回到之前测试过的客户端/服务器架构:
![]() |
服务器端:
- 启动 Web 服务 ASP.NET [S]
客户端:
- 启动 Apache 服务器,Flex 应用程序将向其发起请求。
此处我们使用工具 Wamp。借助该工具,我们可以为 Flex 项目的 [bin-debug] 文件夹设置别名。
![]() |
- Wamp图标位于[1]屏幕底部
- 左键单击图标 Wamp,选择选项 Apache [2] / 别名目录 [3, 4]
- 选择选项 [5]:添加别名
![]() |
- 在 [6] 中为即将执行的 Web 应用程序指定一个别名(任意名称)
- 在 [7] 中指定将使用该别名的 Web 应用程序的根目录:即我们刚刚构建的 Flex 项目的 [bin-debug] 文件夹。
回顾一下 Flex 项目中 [bin-debug] 文件夹的结构:
![]() |
文件 [main.html] 即为 Flex 应用程序中的 HTML 文件。 借助我们刚刚在文件夹 [bin-debug] 上创建的别名,该文件将通过 URL 和 [http://localhost/pam-v10-flex-client-webservice/main.html] 生成。我们使用安装了 Flash Player 9 及以上版本插件的浏览器访问该文件:
![]() |
- 在 [1] 中,Flex 应用程序生成的 URL
- 在 [2] 中,一切正常时的员工下拉列表
- [3],当Web服务停止时获得的结果
您可能想查看收到的 HTML 页面的源代码:
- 页面主体从第25行开始。其中不包含经典的HTML,而是一个类型为"application/x-shockwave-flash"(第41行)的对象(第28行)。 该文件为 [main.swf](第 31 行),可在 Flex 项目的 [bin-debug] 文件夹中找到。这是一个体积较大的文件:仅此简单示例就约有 600 KB。
14.4. 视图 2
我们将向当前视图添加一个新的 VBox 类型容器:
![]() |
![]() |
- 在[4,5]中,我们将[main2.mxml] 设为新的默认应用程序。今后将编译该应用程序。
- 在 [6] 中,默认应用程序由一个蓝色圆点标识。
容器 [1] 将显示下拉列表 [2] 中所选员工的相关信息。 我们将 [main.xml] 复制为 [main2.xml] 和 [3] 以构建新视图。接下来我们将使用 [main2.xml] 进行操作。
![]() |
对前一个项目所做的修改是添加了上文第26行中的容器,该容器包含视图中[1]容器的代码MXML。 我们为其赋予标识符 employe,以便通过代码进行操作。实际上,该容器应能采用与之前消息区域相同的技术进行隐藏/显示。
让我们回到视图的界面:
![]() |
让我们定位新显示信息的各个容器:
- V1:包含所有组件的垂直容器: 标签为 Employé、[1] 的容器,以及水平容器 [H1] 和 [H2]
- H1:用于显示 Nom、Prénom、Adresse 信息的水平容器
- V2:用于Nom标签及员工姓名显示的垂直容器。
- H2:用于显示 Ville(邮政编码)和 Indice 信息的水平容器
“employe”容器的完整代码如下:
<mx:VBox id="employe" width="100%">
<mx:Label text="Employé" fontSize="20" color="#09F3EB"/>
<mx:HBox>
<mx:VBox >
<mx:Label text="Nom"/>
<mx:VBox backgroundColor="#EECA05">
<mx:Text id="lblNom" minWidth="100" minHeight="20" fontFamily="Verdana" textAlign="center"/>
</mx:VBox>
</mx:VBox>
<mx:VBox >
<mx:Label text="Prénom"/>
<mx:VBox backgroundColor="#EECA05">
<mx:Text id="lblPreNom" minWidth="100" minHeight="20" fontFamily="Verdana" textAlign="center"/>
</mx:VBox>
</mx:VBox>
<mx:VBox >
<mx:Label text="Adresse"/>
<mx:VBox backgroundColor="#EECA05">
<mx:Text id="lblAdresse" minWidth="250" minHeight="20" fontFamily="Verdana" textAlign="center"/>
</mx:VBox>
</mx:VBox>
</mx:HBox>
<mx:HBox>
<mx:VBox >
<mx:Label text="Ville"/>
<mx:VBox backgroundColor="#EECA05">
<mx:Text id="lblVille" minWidth="100" minHeight="20" fontFamily="Verdana" textAlign="center"/>
</mx:VBox>
</mx:VBox>
<mx:VBox >
<mx:Label text="Code Postal"/>
<mx:VBox backgroundColor="#EECA05">
<mx:Text id="lblCodePostal" minWidth="70" minHeight="20" fontFamily="Verdana" textAlign="center"/>
</mx:VBox>
</mx:VBox>
<mx:VBox >
<mx:Label text="Indice"/>
<mx:VBox backgroundColor="#EECA05">
<mx:Text id="lblIndice" minWidth="20" minHeight="20" fontFamily="Verdana" textAlign="center"/>
</mx:VBox>
</mx:VBox>
</mx:HBox>
</mx:VBox>
该代码不言自明。我们仅以显示员工姓名的垂直容器为例进行说明:
- 第 4-9 行:垂直容器
- 第 5 行:标签 Nom
- 第6-8行:一个垂直容器,用于显示员工姓名(第7行)。 我们希望为显示员工信息的字段设置不同的背景色。组件 Text 不支持此功能(或者是我没找对)。我们可以设置容器的背景色,因此此处使用了该组件。
- 第7行:Text组件,用于显示员工姓名。为其设置了最小高度和宽度。
我们将使用容器“employe”来显示用户在员工下拉列表中选定的员工信息,这与按钮[Salaire]无关——该按钮的作用是在所有必要信息输入完毕后计算工资。
为处理下拉列表“employes”中的选项变更,其代码MXML的修改如下:
<mx:ComboBox id="cmbEmployes" dataProvider="{employes}" labelFunction="displayEmploye" change="displayInfosEmploye();"/>
当用户更改选择时,下拉列表会触发“change”事件。该事件的处理程序为方法 displayInfosEmploye。
回顾远程 Web 服务公开的方法:
// 所有员工身份信息列表
public Employe[] GetAllIdentitesEmployes();
// ------- 工资计算
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles);
此处我们希望显示下拉列表中选定员工的个人信息(姓名、名字等)。 Web 服务并未提供获取这些信息的专用方法。不过,我们可以调用 GetSalaire 方法,并传入所选员工的编号 SS,同时将工作时长和天数设为 0。 虽然会进行一次无用的薪资计算,但 GetSalaire 方法将返回一个 FeuilleSalaire 类型的对象,其中包含我们所需的信息。
当前 Web 服务的声明已修改,以包含方法 GetSalaire 的定义:
<mx:WebService id="pam"
wsdl="http://localhost:1077/Service1.asmx?WSDL"
fault="wsFault(event);"
showBusyCursor="true">
<mx:operation
name="GetAllIdentitesEmployes"
result="loadEmployesCompleted(event)"
fault="loadEmployesFault(event);">
<mx:request/>
</mx:operation>
<mx:operation name="GetSalaire"
result="getSalaireCompleted(event)"
fault="getSalaireFault(event);">
<mx:request>
<ss>{employes.getItemAt(cmbEmployes.selectedIndex).SS}</ss>
<heuresTravaillees>{heuresTravaillees}</heuresTravaillees>
<joursTravailles>{joursDeTravail}</joursTravailles>
</mx:request>
</mx:operation>
</mx:WebService>
- 第 11-19 行:Web 服务的 GetSalaire 方法定义
- 第 12 行:定义了在调用 GetSalaire 方法成功时要执行的方法
- 第 13 行:定义了调用 GetSalaire 方法失败时要执行的方法
- 第 14-18 行:方法 GetSalaire 需要三个参数。这些参数在 <mx:request> 标签内以 <param1>值1</param1> 的形式定义。标识符 param1 不能是任意名称。 必须使用 Web 服务所要求的名称:
![]() |
- 在 [1] 中,Web 服务页面 [http://localhost:1077/Service1.asmx]
- 在 [2] 中,方法 [GetSalaire] 的测试页面链接
- 在 [3] 中,方法所需的参数。这些名称应作为 <mx:request> 标签的子标签使用。
让我们回到Web服务的声明:
<mx:operation name="GetSalaire"
result="getSalaireCompleted(event)"
fault="getSalaireFault(event);">
<mx:request>
<ss>{employes.getItemAt(cmbEmployes.selectedIndex).SS}</ss>
<heuresTravaillees>{heuresTravaillees}</heuresTravaillees>
<joursTravailles>{joursDeTravail}</joursTravailles>
</mx:request>
</mx:operation>
- 第 5 行:参数 ss。请注意,在 Flex 应用程序启动时,所有员工的数组已被存储在类型为 ArrayCollection 的变量 employes 中。
- employes.getItemAt(i):表示数组中的第 i 位员工
- employes.getItemAt(i).SS:是该员工的社保号。
- cmbEmployes.selectedIndex:是员工下拉列表中选定条目的编号,对应 cmbemployes。
那么,如何确定 SS 是某位员工的社保号呢?为此,我们需要回到由方法 GetAllIdentitesEmployes 返回的响应中:
![]() |
- 在 [1] 中,即 Web 服务 [Service.asmx] 的页面
- 转换为 [2],即方法 [GetAllIdentitesEmployes] 的测试页面链接
- 为 [3],测试已完成。不期望任何参数。
- 在 [4] 中:响应 XML 包含一个员工数组。正是该数组被存储在变量 employes 中。 从 [5] 可以看出,SS 确实是用于存储社会保险号的标签。
让我们结束对该 Web 服务的分析:
<mx:operation name="GetSalaire"
result="getSalaireCompleted(event)"
fault="getSalaireFault(event);">
<mx:request>
<ss>{employes.getItemAt(cmbEmployes.selectedIndex).SS}</ss>
<heuresTravaillees>{heuresTravaillees}</heuresTravaillees>
<joursTravailles>{joursDeTravail}</joursTravailles>
</mx:request>
</mx:operation>
- 第 6 行:工作小时数将由变量 heuresTravaillees 提供
- 第6行:工作天数将由变量 joursDeTravail 提供
这些变量必须在 <mx:Script> 标签中声明,并使用 [Bindable] 属性,以便 MXML 组件(下文第 7-10 行)能够引用它们。
<mx:Script>
<![CDATA[
...
// 数据
[Bindable]
private var employes : ArrayCollection;
[Bindable]
private var heuresTravaillees:Number;
[Bindable]
private var joursDeTravail:int;
...
</mx:Script>
视图的事件管理代码如下所示:
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
// 数据
[Bindable]
private var employes : ArrayCollection;
[Bindable]
private var heuresTravaillees:Number;
[Bindable]
private var joursDeTravail:int;
private function init():void{
// 记录 # 个区块的高度和宽度
employeHeight=employe.height;
employeWidth=employe.width;
// 隐藏某些元素
hideEmploye();
...
}
private function displayInfosEmploye():void{
// 表单
hideEmploye();
// 计算一个虚拟工资
heuresTravaillees=0;
joursDeTravail=0;
pam.GetSalaire.send();
}
private function getSalaireCompleted(event:ResultEvent):void{
...
}
private function getSalaireFault(event:FaultEvent):void{
...
}
// 局部视图 -------------------------------------------------
private var employeHeight:int;
private var employeWidth:int;
private function hideEmploye():void{
employe.height=0;
employe.width=0;
}
private function showEmploye():void{
employe.height=employeHeight;
employe.width=employeWidth;
}
]]>
</mx:Script>
- 第 15 行:在 Flex 应用程序启动时执行的 init 方法会保存垂直容器 employe 的高度和宽度,以便在将其隐藏(第 45-48 行)后能够将其恢复(第 50-53 行)。
- 第 24 行:当用户在员工下拉列表中更改选择时,将执行方法 displayInfosEmploye。
- 第 26 行:如果容器 employe 处于可见状态,则将其隐藏
- 第 30 行:异步调用 Web 服务的 GetSalaire 方法。已知该方法需要三个参数:
<ss>{employes.getItemAt(cmbEmployes.selectedIndex).SS}</ss>
<heuresTravaillees>{heuresTravaillees}</heuresTravaillees>
<joursTravailles>{joursDeTravail}</joursTravailles>
- 第 1 行:参数 ss 将是员工下拉列表中选定员工的编号 SS
- 第 2 行:方法 displayInfosEmploye 将值 0 赋给变量 heuresTravaillees(第 28 行)
- 第 3 行:方法 displayInfosEmploye 将值 0 赋给变量 joursDeTravail(第 29 行)
如果 Web 服务的 GetSalaire 方法执行成功,则执行 GetSalaireCompleted 方法:
private function getSalaireCompleted(event:ResultEvent):void{
// 隐藏错误消息
hideMsg();
// 获取工资单
var feuilleSalaire:Object=event.result;
// 显示
lblNom.text=feuilleSalaire.Employe.Nom;
lblPreNom.text=feuilleSalaire.Employe.Prenom;
lblAdresse.text=feuilleSalaire.Employe.Adresse;
lblVille.text=feuilleSalaire.Employe.Ville;
lblCodePostal.text=feuilleSalaire.Employe.CodePostal;
lblIndice.text=feuilleSalaire.Employe.Indice;
showEmploye();
}
- 第 3 行:隐藏消息区域,以防其被显示。
- 第 5 行:获取由方法 GetSalaire 返回的工资单
要了解方法 GetSalaire 确切返回的内容,请返回 Web 服务页面:
![]() |
- 在 [1] 中,Web 服务页面 [Service.asmx]
- 变为 [2],指向方法测试页面的链接 [GetSalaire]
- 在 [3] 中,提供参数
- 在 [4] 中,显示所得结果 XML。
回到方法 getSalaireCompleted:
private function getSalaireCompleted(event:ResultEvent):void{
// 隐藏错误信息
hideMsg();
// 收到工资单
var feuilleSalaire:Object=event.result;
// 显示
lblNom.text=feuilleSalaire.Employe.Nom;
lblPreNom.text=feuilleSalaire.Employe.Prenom;
lblAdresse.text=feuilleSalaire.Employe.Adresse;
lblVille.text=feuilleSalaire.Employe.Ville;
lblCodePostal.text=feuilleSalaire.Employe.CodePostal;
lblIndice.text=feuilleSalaire.Employe.Indemnites.Indice;
showEmploye();
}
- 第 5 行:feuilleSalaire=event.result 表示由方法 GetSalaire 返回的流程 XML [4]。 根据该数据流,我们可以看到:
- feuilleSalaire.Employe 是某位员工的流程 XML
- feuilleSalaire.Employe.Nom 是该员工的姓名
- ...
- 第 7-12 行:利用数据流 XML feuilleSalaire 来填充“employe”容器的各个字段。
- 第 13 行:显示“employe”容器。
如果 Web 服务的 GetSalaire 方法执行失败,则执行 getSalaireFault 方法:
private function getSalaireFault(event:FaultEvent):void{
// 显示错误信息
msg.text=event.fault.message;
// 表单
showMsg();
}
- 第 3 行:将错误消息 event.fault.message 写入消息区域
- 第 5 行:显示消息区域
至此,针对此新版本所需的修改已完成。保存文件后,若语法正确,可执行版本将生成在项目中的 [bin-debug] 文件夹内:
![]() |
上文中的 [main2.html] 是页面 HTML,该页面嵌入了 Flex 应用程序二进制文件 [main2.swf],该文件将由 Flash Player 执行。
我们可以测试这个新版本:
- 必须启动 ASP.NET Web 服务
- 需为 Flex 客户端启动 Apache 服务器
假设上一版本中使用的别名 [pam-v10-flex-client-webservice] 仍然存在,则在浏览器中向 Apache 服务器请求 URL 和 [http://localhost/pam-v10-flex-client-webservice/main2.html]:
![]() |
![]() |
- 在 [1] 中,请求的 URL
- 变为 [2],员工下拉列表
- 在 [3] 中,更改下拉列表中的选择以触发事件 change
- 在 [4] 中,得到的结果:Justine Laverti 的档案。
14.5. 视图 3
视图 3 负责表单的有效性检查。在此,仅对输入字段“txtHeuresTravaillees”进行验证。只要表单填写不正确,“btnSalaire”按钮将保持禁用状态。
为实现此功能,我们将 [main2.mxml] 复制为 [main3.mxml]:
![]() |
接下来我们将使用 [main3.mxml],并将其设为默认应用程序(参见第 14.4 节的相关概念)。首先,我们在“txtHeuresTravaillees”组件中添加一个属性:
<mx:TextInput id="txtHeuresTravaillees" change="validateForm(event)"/>
每当输入字段“txtHeuresTravaillees”的内容发生变化时,都会调用方法 validateForm。这是一个由开发人员编写的本地方法。 在该方法中,我们可以验证输入字段“txtHeuresTravaillees”的内容是否为正整数。我们将采用另一种方法,即使用验证组件:
<mx:NumberValidator id="heuresTravailleesValidator" source="{txtHeuresTravaillees}" property="text"
precision="2" allowNegative="false"
invalidCharError="Caractères invalides"
precisionError="Deux chiffres au plus après la virgule"
negativeError="Le nombre d'heures doit être positif ou nul"
invalidFormatCharsError="Format invalide"
required="true"
requiredFieldError="Donnée requise"/>
- 第 1 行:<mx:NumberValidator> 组件用于验证另一个组件是否包含整数或实数。
- 第 1 行:id 属性为该组件分配了一个标识符。
- 第 1 行:source 是被组件 NumberValidator 验证的组件的 ID。此处,被验证的是输入字段“txtHeuresTravaillees”。
- 第 1 行:property 是组件 source 的属性名称,该属性包含待验证的值。最终被验证的是 source.property 的值,此处为 txtHeuresTravaillees.text。
- 第2行:precision 设定允许的小数位数上限。precision=0 相当于验证输入的数字是否为整数。
- 第2行:allowNegative 用于指定是否允许输入负数
- 第 7 行:required 指定该字段是否为必填项。
当某项验证条件未通过时,会在出错控件旁的气泡中显示一条错误信息。默认情况下,这些信息为英文。用户可以自行定义这些信息:
- (续)
- invalidCharError:当文本包含数字中不应出现的字符时显示的错误信息
- precisionError:当小数位数与属性precision不符时的错误信息
- negativeError:当数字为负数,且 allowNegative="false" 属性存在时的错误信息
- requiredFieldError:当未输入数据,且属性 requiredField="true" 时出现的错误信息
- invalidFormatCharsError:当文本包含无效字符或格式时显示的错误信息?
让我们回到“txtHeuresTravaillees”组件:
<mx:TextInput id="txtHeuresTravaillees" change="validateForm(event)"/>
在 <mx:Script> 标签中,validateForm 方法可能如下所示:
private function validateForm(event:Event):void
{
// 确认工作时长
var evt:ValidationResultEvent = heuresTravailleesValidator.validate();
// 验证成功?
btnSalaire.enabled=evt.type==ValidationResultEvent.VALID;
}
- 第4行:执行了验证器“heuresTravailleesValidator”。它返回类型为ValidationResultEvent的结果。
- 第 6 行:evt.type 的类型为 String,表示事件的类型。 对于类型 ValidationResultEvent,evt.type 有两个可能的值:“invalid”或“valid”,分别由常量 ValidationResultEvent.INVALID 和 ValidationResultEvent.VALID 表示。 如果第 4 行验证成功,evt.type 的值应为 ValidationResultEvent.VALID。在此情况下,按钮 btnSalaire 处于激活状态,否则处于禁用状态。
仅此即可测试工时的有效性。
![]() |
如上所述,项目编译生成了文件 [main3.html] 和 [main3.swf]。我们在浏览器中请求 URL 和 [http://localhost/pam-v10-flex-client-webservice/main3.html],并验证各种错误情况:
![]() |
![]() |
- 错误字段带有红色边框 [1, 2, 3],正确字段带有蓝色边框 [4]。
- 在 [4] 中,请注意按钮 [Salaire] 处于活动状态,因为工作小时数是正确的。
14.6. 视图 4
视图 4 完成了工资计算表单。为此,我们将 [main3.xml] 复制到 [main4.xml],并开始使用 main4,将其设为默认应用(参见第 14.4 节)。
![]() |
在 [main4.xml] 和 [1] 中进行的更改如下:
- 在视图 [2] 中添加了一个新的垂直容器,用于显示员工的工资项目
- 在 [3] 中添加了一个用于格式化货币值的组件
- 工资项目的显示由与“btnSalaire”按钮“点击”事件关联的处理程序负责。
视图的变化如下:
![]() |
新容器遵循与之前相同的原理。这是一个垂直容器 VBox [V1],其中包含四个水平容器 HBox [Hi]。 水平容器 H1 至 H3 由垂直容器组成,每个垂直容器包含两个标签,其中第二个标签本身位于一个垂直容器中,以便设置背景色。
问题 1:编写工资容器。该容器后续将命名为 complements.
问题 2:编写用于隐藏/显示容器 complements 的方法。可参考之前为容器 employe 所做的处理。
为按钮“btnSalaire”的“click”事件关联一个处理程序:
<mx:Button id="btnSalaire" label="Salaire" click="calculerSalaire()"/>
方法 calculerSalaire 如下:
private function calculerSalaire():void{
// 准备表单
affichageSalaire=true;
msg.text="";
// 工资计算参数
heuresTravaillees=Number(txtHeuresTravaillees.text);
joursDeTravail=int(joursTravailles.value);
// 向 Web 服务请求工资
pam.GetSalaire.send();
}
- 第3行:布尔值affichageSalaire用于指示是否显示包含薪资明细的容器complements。方法getSalaireCompleted在两个事件中执行:
- 员工下拉列表中切换员工时,用于显示该员工的个人信息(不含薪资)。此时需将 affichageSalaire 设为 false。
- 计算工资
- 第 6 行:将输入字段 txtHeuresTravaillees 的文本转换为实数。
- 第 7 行:增量器 joursTravailles 的值被转换为整数。
- 第 9 行:调用远程方法 GetSalaire。 需要提醒的是,该方法需要三个参数,其中参数 heuresTravaillees 和 joursDeTravail 分别在第 6 行和第 7 行初始化。此外,如果方法 GetSalaire 的异步调用:
- 成功,则将调用方法 getSalaireCompleted
- 失败,则将调用方法 getSalaireFault
问题3:完善当前方法getSalaireCompleted,使其在点击按钮btnSalaire时显示员工的工资。
目前,工资项目的显示不包含欧元符号。可以在代码中直接添加该符号,或者使用格式化器。现建议采用后者。格式化器如下:
<mx:CurrencyFormatter id="eurosFormatter" precision="2"
currencySymbol="€" useNegativeSign="true"
alignSymbol="right"/>
- 第 1 行:id 是格式器的标识符,precision 表示保留的小数位数。
- 第2行:currencySymbol是使用的货币符号。useNegativeSign表示是否对负数使用“-”符号。
- 第 3 行:alignSymbol 指定货币符号相对于数字的位置。
该格式化器在脚本代码中的使用方式如下:
- eurosFormatter 是待使用的格式化器的 ID
- format 是用于格式化数字的调用方法。它返回一个字符串。
- feuilleSalaire.Indemnites.BaseHeure 代表待格式化的数字。
- lblSH 是一个 Text 类型组件的名称。
问题 4:修改方法 getSalaireCompleted,使其使用货币格式化器。





























