Skip to content

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] 中,该页面被发送出去。它随附一个二进制文件 SWFShockWave 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 页面的源代码:

<!--  url=(0014)about:internet 保存 -->
<html lang="en">

<!-- 
Smart developers always View Source. 

This application was built using Adobe Flex, an open source framework
for building rich Internet applications that get delivered via the
Flash Player or to desktops via Adobe AIR. 

Learn more about Flex at http://flex.org 
// -->

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<!--   BEGIN 浏览器历史记录必填部分 -->
<link rel="stylesheet" type="text/css" href="history/history.css" />
<!--   END 需要浏览器历史记录的部分 -->

<title></title>
....
</head>

<body scroll="no">
...
<noscript>
        <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
                        id="main" width="100%" height="100%"
                        codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
                        <param name="movie" value="main.swf" />
                        <param name="quality" value="high" />
                        <param name="bgcolor" value="#869ca7" />
                        <param name="allowScriptAccess" value="sameDomain" />
                        <embed src="main.swf" quality="high" bgcolor="#869ca7"
                                width="100%" height="100%" name="main" align="middle"
                                play="true"
                                loop="false"
                                quality="high"
                                allowScriptAccess="sameDomain"
                                type="application/x-shockwave-flash"
                                pluginspage="http://www.adobe.com/go/getflashplayer">
                        </embed>
        </object>
</noscript>
</body>
</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:用于显示 NomPrénomAdresse 信息的水平容器
  • 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 指定货币符号相对于数字的位置。

该格式化器在脚本代码中的使用方式如下:

                    lblSH.text=eurosFormatter.format(feuilleSalaire.Indemnites.BaseHeure);
  • eurosFormatter 是待使用的格式化器的 ID
  • format 是用于格式化数字的调用方法。它返回一个字符串。
  • feuilleSalaire.Indemnites.BaseHeure 代表待格式化的数字。
  • lblSH 是一个 Text 类型组件的名称。

问题 4:修改方法 getSalaireCompleted,使其使用货币格式化器。