14. [SimuPaie] 应用程序——第 10 版——一个用于 ASP.NET Web 服务的 Flex 客户端
现向大家展示一个用于 ASP.NET Web 服务的 Flex 客户端(第 5 版)。所使用的 IDE 是 Flex Builder 3。该产品的演示版可从以下网址下载:[https://www.adobe.com/cfusion/tdrc/index.cfm?loc=fr_fr&product=flex]。 Flex Builder 3 是一款基于 Eclipse 的集成开发环境(IDE)。此外,为运行该 Flex 客户端,我们使用了 Wamp 工具 [http://www.wampserver.com/] 提供的 Apache Web 服务器。任何 Apache 服务器均可满足需求。显示 Flex 客户端的浏览器必须安装 Flash Player 9 或更高版本。
Flex 应用程序的独特之处在于它们在浏览器的 Flash Player 插件中运行。从这一角度来看,它们与 Ajax 应用程序相似,后者将 JavaScript 脚本嵌入发送到浏览器的页面中,然后在浏览器内执行。 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 服务器构建并发送给它的 HTML 页面。
14.1. 客户端/服务器应用程序架构
此处实现的客户端/服务器架构与第 6 版和第 8 版类似:
![]() |
在[1]中,ASP.NET Web层被用MXML和ActionScript编写的Flex Web层所取代。客户端[C]将由Flex Builder IDE生成。需要指出的是,该架构包含两个未显示的Web服务器:
- 一个运行 Web 服务 [S] 的 ASP.NET Web 服务器
- 一个运行 Web 客户端 [1] 的 Apache Web 服务器
14.2. Flex 3 客户端项目
我们使用 Flex Builder 3 IDE 构建 Flex 客户端:
![]() |
- 在 Flex Builder 3 中,于 [1] 处创建一个新项目
- 在 [2] 中为其命名,并在 [3] 中指定生成项目的文件夹
![]() |
- 在 [4] 中,为主要应用程序(即将被执行的程序)命名
- 在 [5] 中,生成后的项目
- 在 [6] 中,应用程序的主 MXML 文件
- MXML 文件包含一个视图及其事件处理代码。[源代码] 选项卡 [7] 提供了对 MXML 文件的访问。在那里,您将看到描述视图的 <mx> 标签以及 ActionScript 代码。
- 可以通过 [设计] 选项卡 [8] 以图形化方式构建视图。随后,描述该视图的 MXML 标签会自动生成在 [源代码] 选项卡中。反之亦然:直接在 [源代码] 选项卡中添加的 MXML 标签也会在 [设计] 选项卡中以图形形式显示。
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[
...
// données
[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" 属性表示组件将上下排列。
- 视图实例化时(即所有组件均已实例化)需执行的方法。属性 creationComplete="init();" 表示必须执行第 38 行中的 init 方法。creationComplete 是 Application 类可触发的事件之一。
- 第 4–25 行定义了视图的组件
- 第 4–25 行:一个垂直容器:组件将上下排列
- 第 5 行:定义一个文本组件
- 第 6–23 行:一个水平容器:组件将水平排列在其内部
- 第 7–10 行:一个垂直容器,将包含文本和下拉列表
- 第 8 行:文本
- 第 9 行:用于放置员工列表的下拉列表。dataProvider="{employees}" 标签指定了用于填充该列表的数据源。在此处,该列表将填充第 36 行定义的 employees 对象。为了能够写入 dataProvider="{employees}",employees 字段必须具有 [Bindable] 属性(第 35 行)。 该属性允许在 <mx:Script> 标签外部引用 ActionScript 变量。employees 字段的类型为 ArrayCollection,这是一种 ActionScript 类型,允许存储对象列表,在本例中即存储 Employee 类型的对象列表。
- 第 11–14 行:一个用于容纳文本和输入字段的垂直容器
- 第 12 行:文本
- 第 13 行:工作小时数的输入字段。
- 第 15-18 行:一个将包含文本和计数器的垂直容器
- 第 16 行:文本
- 第 17 行:用于输入工作天数的计数器
- 第 19–22 行:一个垂直容器,用于放置文本和一个按钮,该按钮将触发对下拉菜单中选定人员的薪资计算。
- 第 20 行:文本
- 第 21 行:按钮。
- 第 23 行:第 6 行开始的水平容器的结束
- 第 24 行:TextArea 组件中的文本区域。将用于显示错误消息。
- 第 25 行:第 4 行开始的垂直容器的结束
第 4–25 行在 [设计] 选项卡中生成以下视图:
![]() |
- [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 行:调用此方法成功完成时(即 Web 服务成功返回员工列表时)要执行的方法
- 第 8 行:调用此方法时若出现错误,则执行该方法。
- 第 9 行:GetAllEmployeeIDs 操作的参数。我们知道该方法不期望接收任何参数,因此将 <mx:request> 标签留空。
现在让我们查看与 Web 服务关联的 ActionScript 代码:
<mx:Script>
<![CDATA[
import mx.rpc.events.FaultEvent;
import mx.collections.ArrayCollection;
import mx.rpc.events.ResultEvent;
// données
[Bindable]
private var employes : ArrayCollection;
private function init():void{
// on note les coordonnées de la zone de message
msgHeight=msg.height;
msgWidth=msg.width;
// on cache la zone de message
hideMsg();
// requête au service web distant pour avoir la liste simplifiée des employés
pam.GetAllIdentitesEmployes.send();
}
private function wsFault(event:Event):void{
// on signale l'erreur
msg.text="Service distant indisponible";
showMsg();
}
private function loadEmployesCompleted(event:ResultEvent):void{
// remplissage combo des employés
employes=event.result as ArrayCollection;
}
private function displayEmploye(employe:Object):String{
// identité d'un employé
return employe.Prenom + " " + employe.Nom;
}
private function loadEmployesFault(event:FaultEvent):void{
// affichage msg d'erreur
msg.text=event.fault.message;
// formulaire
showMsg();
}
// gestion des blocs
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 行:调用 pam Web 服务(Web 服务第 1 行)的 GetAllIdentitiesEmployees 方法(Web 服务第 6 行)。该调用为异步调用。Web 服务第 7 行指出,如果此异步调用成功完成,将执行 loadEmployeesCompleted 方法。 Web 服务第 8 行指出,如果此异步调用失败,将执行 loadEmployesFault 方法。
- 第 27 行:如果第 18 行的 Web 服务调用成功完成,则执行 loadEmployesCompleted 方法。
- 第 29 行:我们知道 Web 服务会返回一个 XML 响应。回顾这一点有助于理解 ActionScript 代码:
![]() |
- 在 [1] 中,Web 服务页面 [Service.asmx]
- 在 [2] 中,[GetAllIdentitesEmployes] 方法的测试页面链接
- 在 [3] 中,执行测试。不期望有参数。
- 在 [4] 中:XML 响应包含一个员工数组。对于每位员工,有五项信息被封装在 <Id>、<Version>、<SS>、<LastName> 和 <FirstName> 标签中。如果将 XML 响应存储在类型为 `ArrayCollection` 的 `employees` 数组中:
- employees.getItemAt(i):是数组的第 i 个元素
- employees.getItemAt(i).SS:是该员工的社会保障号码。
- employees.getItemAt(i).LastName:是该员工的姓
- ...
让我们回到 ActionScript 代码:
- 第 29 行:event.result 代表来自 Web 服务的 XML 响应。GetAllIdentitiesEmployees 方法返回一个员工数组。event.result 代表这个员工数组。它被存储在一个类型为 ArrayCollection 的变量中,该类型通常表示一组对象。这个名为 employees 的变量在第 9 行声明。请记住,该变量是员工下拉列表框的数据源:
<mx:ComboBox id="cmbEmployes" dataProvider="{employes}" labelFunction="displayEmploye"/>
对于数据源中的每位员工,下拉列表框都会调用 displayEmploye 方法(labelFunction 属性)来显示该员工。第 32–34 行显示,该方法会显示员工的姓名和姓氏。
- 第 37 行:loadEmployesFault 方法,当第 18 行对 Web 服务的调用失败时,该方法将被执行。event.fault.message 是 Web 服务返回的错误消息。
- 第 39 行:将此错误消息显示在消息框中
- 第 41 行:显示消息框。
应用程序构建完成后,其可执行代码位于 Flex 项目的 [bin-debug] 文件夹中:
![]() |
上文所述,
- [main.html] 文件代表浏览器将向 Web 服务器请求以获取 Flex 客户端的 HTML 文件
- [main.swf] 文件是 Flex 客户端二进制文件,它将被嵌入发送到浏览器的 HTML 页面中,随后由浏览器的 Flash Player 插件执行。
现在我们可以运行 Flex 客户端了。首先,我们需要配置其所需的运行时环境。让我们回到之前测试过的客户端/服务器架构:
![]() |
服务器端:
- 启动 ASP.NET Web 服务 [S]
客户端:
- 启动将托管 Flex 应用程序的 Apache 服务器。
这里我们使用 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 或更高版本插件的浏览器中访问此 URL:
![]() |
- [1] 中的 Flex 应用程序 URL
- [2] 处,当一切正常时显示的员工下拉列表
- [3] 中,当 Web 服务停止时显示的结果
您可能想查看收到的 HTML 页面的源代码:
- 页面主体从第 25 行开始。它不包含标准 HTML,而是一个类型为 "application/x-shockwave-flash"(第 41 行)的对象(第 28 行)。 这是位于 Flex 项目 [bin-debug] 文件夹中的 [main.swf] 文件(第 31 行)。这是一个大文件:仅此简单示例就约有 600 KB。
14.4. 视图 #2
我们将向当前视图添加一个新的 VBox 容器:
![]() |
![]() |
容器 [1] 将显示在组合框 [2] 中选定的员工信息。我们将 [main.xml] 复制为 [main2.xml] [3] 以构建新的视图。接下来我们将使用 [main2.xml] 进行操作。
![]() |
对前一个项目所做的修改是在第26行添加了容器,其中包含视图[1]容器的MXML代码。我们为其赋予标识符employe,以便通过代码进行操作。该容器必须能够采用与消息区域相同的技术进行隐藏或显示。
让我们回到视图的视觉布局:
![]() |
让我们来识别用于显示新信息的不同容器:
- V1:用于容纳所有组件的垂直容器:"员工 [1]" 标签以及水平容器 [H1] 和 [H2]
- H1:用于“姓氏”、“名字”和“地址”信息的水平容器
- V2:用于“姓氏”标签及员工姓氏显示的垂直容器
- H2:用于“城市”、“邮政编码”和“索引”信息的水平容器
“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 行:"Name" 标签
- 第 6–8 行:一个用于显示员工姓名(第 7 行)的垂直容器。我们希望为显示员工信息的字段设置不同的背景色。Text 组件不提供此选项(或者是我没找仔细)。你可以设置容器的背景色,这就是为什么这里使用它。
- 第 7 行:用于显示员工姓名的 Text 组件。我们为其设置了最小高度和宽度。
我们将使用“employee”容器来显示用户从员工下拉菜单中选择的员工信息,该容器独立于[Salary]按钮——该按钮将在所有必要信息输入完毕后用于计算薪资。
为了处理“employees”下拉框中选项的变化,其 MXML 代码如下所示:
<mx:ComboBox id="cmbEmployes" dataProvider="{employes}" labelFunction="displayEmploye" change="displayInfosEmploye();"/>
当用户更改选择时,下拉列表会触发 change 事件。该事件的处理程序将是 displayInfosEmploye 方法。
让我们回顾一下远程 Web 服务公开的方法:
// liste de toutes les identités des employés
public Employe[] GetAllIdentitesEmployes();
// ------- le calcul du salaire
public FeuilleSalaire GetSalaire(string ss, double heuresTravaillees, int joursTravailles);
在此,我们希望显示下拉列表中选定员工的个人信息(姓氏、名字等)。Web 服务并未提供用于检索此类信息的方法。不过,我们可以调用 GetSalary 方法,并传入所选员工的社保号(SSN),同时将工作小时数和工作天数设为 0。虽然会进行一次冗余的薪资计算,但 GetSalary 方法将返回一个包含所需信息的 PayStub 对象。
当前的 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 服务中 GetSalary 方法的定义
- 第 12 行:定义了调用 GetSalaire 方法成功时要执行的方法
- 第 13 行:定义调用 GetSalaire 方法失败时要执行的方法
- 第 14-18 行:GetSalaire 方法需要三个参数。这些参数在 <mx:request> 标签内以 <param1>value1</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 应用程序启动时,所有员工的数组被存储在一个名为 employees 的 ArrayCollection 类型的变量中。
- employees.getItemAt(i):表示数组中的第 i 个员工
- employees.getItemAt(i).SS:即该员工的社会保障号码
- cmbEmployees.selectedIndex:表示 employees 下拉列表框中选中项的索引。
在上述代码中,我们如何知道 SS 是员工的社保号?要回答这个问题,我们需要回溯 GetAllIdentitiesEmployees 方法发送的响应:
![]() |
- 在 [1] 中,[Service.asmx] Web 服务页面
- 在 [2] 中,[GetAllIdentitesEmployes] 方法的测试页面链接
- 在 [3] 中,执行了测试。不期望有参数。
- 在 [4] 中:XML 响应包含一个员工数组。该数组存储在 `employees` 变量中。如 [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 行:工作小时数将由变量 hoursWorked 提供
- 第 6 行:工作日数将由变量 daysWorked 提供
这些变量必须在 <mx:Script> 标签内声明,并添加 [Bindable] 属性,这样 MXML 组件才能引用它们(见下文第 7–10 行)。
<mx:Script>
<![CDATA[
...
// données
[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;
// données
[Bindable]
private var employes : ArrayCollection;
[Bindable]
private var heuresTravaillees:Number;
[Bindable]
private var joursDeTravail:int;
private function init():void{
// on note les hauteur / largeur de # blocs
employeHeight=employe.height;
employeWidth=employe.width;
// on cache certains éléments
hideEmploye();
...
}
private function displayInfosEmploye():void{
// formulaire
hideEmploye();
// on calcule un salaire fictif
heuresTravaillees=0;
joursDeTravail=0;
pam.GetSalaire.send();
}
private function getSalaireCompleted(event:ResultEvent):void{
...
}
private function getSalaireFault(event:FaultEvent):void{
...
}
// vues partielles -------------------------------------------------
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 行:init 方法在 Flex 应用程序启动时执行,用于存储垂直员工容器的高度和宽度,以便在隐藏(第 45–48 行)后能够恢复(第 50–53 行)。
- 第 24 行:当用户在员工下拉列表中更改选择时,将执行 displayInfosEmploye 方法。
- 第 26 行:如果员工容器之前处于可见状态,则将其隐藏
- 第 30 行:异步调用 Web 服务的 GetSalary 方法。我们知道该方法需要三个参数:
<ss>{employes.getItemAt(cmbEmployes.selectedIndex).SS}</ss>
<heuresTravaillees>{heuresTravaillees}</heuresTravaillees>
<joursTravailles>{joursDeTravail}</joursTravailles>
- 第 1 行:ss 参数将是员工下拉列表中选定员工的社保号
- 第 2 行:displayInfosEmploye 方法将值 0 赋给 hoursWorked 变量(第 28 行)
- 第 3 行:displayInfosEmploye 方法将值 0 赋给 daysWorked 变量(第 29 行)
如果 Web 服务的 GetSalaire 方法执行成功,则会执行 GetSalaireCompleted 方法:
private function getSalaireCompleted(event:ResultEvent):void{
// hide error msg
hideMsg();
// you receive a payslip
var feuilleSalaire:Object=event.result;
// display
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 行:我们获取 GetSalary 方法返回的工资单
要确切了解 GetSalary 方法返回的内容,我们需要返回 Web 服务页面:
![]() |
- [1] 是 Web 服务页面 [Service.asmx]
- 在 [2] 中,是通往 [GetSalaire] 方法测试页面的链接
- 在 [3] 中,我们提供参数
- 在 [4] 中,是生成的 XML。
让我们回到 getSalaireCompleted 方法:
private function getSalaireCompleted(event:ResultEvent):void{
// hide error msg
hideMsg();
// you receive a payslip
var feuilleSalaire:Object=event.result;
// display
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 行:PayrollSheet = event.result 表示 GetSalary 方法返回的 XML 数据流 [4]。从该数据流中,我们可以看出:
- payrollSheet.Employee 是某位员工的 XML 数据流
- sheetSalary.Employee.Name 是该员工的姓名
- ...
- 第 7–12 行:使用 Payroll.Employee XML 数据流填充员工容器的各个字段。
- 第 13 行:显示员工容器。
如果 Web 服务的 GetSalaire 方法失败,则执行 getSalaireFault 方法:
private function getSalaireFault(event:FaultEvent):void{
// error msg display
msg.text=event.fault.message;
// form
showMsg();
}
- 第 3 行:将错误消息 event.fault.message 放入消息框中
- 第 5 行:显示消息框
至此,新版本所需的修改已完成。保存后,如果语法正确,可执行版本将生成在项目的 [bin-debug] 文件夹中:
![]() |
上文中的 [main2.html] 是嵌入 Flex 应用程序二进制文件 [main2.swf] 的 HTML 页面,该文件将由 Flash Player 执行。
我们可以测试这个新版本:
- ASP.NET Web 服务必须正在运行
- Apache 服务器必须为 Flex 客户端运行
假设上一版本中使用的别名 [pam-v10-flex-client-webservice] 仍然存在,我们可以在浏览器中向 Apache 服务器请求 URL [http://localhost/pam-v10-flex-client-webservice/main2.html]:
![]() |
![]() |
- 在 [1] 中,请求的 URL
- 在 [2] 中,员工下拉菜单
- 在 [3] 中,更改下拉菜单中的选项以触发更改事件
- 在 [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.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
{
// validate hours worked
var evt:ValidationResultEvent = heuresTravailleesValidator.validate();
// successful validation?
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]。我们在浏览器中输入网址 [http://localhost/pam-v10-flex-client-webservice/main3.html],并检查各种错误情况:
![]() |
![]() |
- 错误的字段带有红色边框 [1, 2, 3],正确的字段带有蓝色边框 [4]。
- 在 [4] 中,请注意 [薪资] 按钮处于可用状态,因为工作小时数是正确的。
14.6. 视图 #4
视图 4 完成了薪资计算表单。为此,我们将 [main3.xml] 复制为 [main4.xml],并开始使用 main4,将其设置为默认应用程序(参见第 14.4 节)。
![]() |
在 [main4.xml] [1] 中所做的更改如下:
- 在视图中添加了一个新的垂直容器 [2],用于显示员工的薪资构成
- 添加了一个用于格式化货币值的组件 [3]
- 薪资组成部分的显示由与“btnSalaire”按钮“click”事件关联的处理程序负责。
视图演变如下:
![]() |
新容器遵循与前一个容器相同的原理。它是一个垂直的 VBox 容器 [V1],其中包含四个水平的 HBox 容器 [Hi]。水平容器 H1 至 H3 由垂直容器组成,每个垂直容器内包含两个标签,其中第二个标签本身位于一个垂直容器内,用于提供背景色。
问题 1:编写薪资容器。下文将称其为 complements。
问题 2:编写用于隐藏/显示 complements 容器的方法。可参考之前为 employee 容器所做的实现。
我们将一个处理程序与“btnSalaire”按钮的“click”事件关联起来:
<mx:Button id="btnSalaire" label="Salaire" click="calculerSalaire()"/>
*calculerSalaire* 方法如下:
private function calculerSalaire():void{
// form preparation
affichageSalaire=true;
msg.text="";
// salary calculation parameters
heuresTravaillees=Number(txtHeuresTravaillees.text);
joursDeTravail=int(joursTravailles.value);
// the salary is requested from the web service
pam.GetSalaire.send();
}
- 第 3 行:布尔变量 displaySalary 用于控制是否显示显示薪资详细信息的补充容器。getSalaryCompleted 方法在以下两种情况下执行:
- 当员工下拉列表中的员工发生变更时,将显示不含薪资的员工信息。此时,salaryDisplay 被设置为 false。
- 薪资计算
- 第 6 行:将 txtHeuresTravaillees 输入字段中的文本转换为实数。
- 第 7 行:将 daysWorked 计数器的值转换为整数。
- 第 9 行:调用远程 GetSalary 方法。请注意,该方法需要三个参数,包括第 6 行和第 7 行初始化的 hoursWorked 和 daysWorked 参数。另请注意,如果对 GetSalary 方法的异步调用:
- 成功,则会调用 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 方法,使其使用货币格式化器。





























