Skip to content

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 页面的源代码:

<!-- saved from 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 Browser History required section -->
<link rel="stylesheet" type="text/css" href="history/history.css" />
<!--  END Browser History required section -->

<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 行)。 这是位于 Flex 项目 [bin-debug] 文件夹中的 [main.swf] 文件(第 31 行)。这是一个大文件:仅此简单示例就约有 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:用于容纳所有组件的垂直容器:"员工 [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 指定货币符号相对于数字的位置。

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

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

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