Flash9, ActionScript3, Flex3; Flex3

Besonderheit / Zielsetzung / Zielgruppe

Flex wurde entwickelt um Entwicklern von Rich Internet Applications die Vorteile von ActionScript3 näher zu bringen. Es setzt als Klassenbibliothek auf die Technik des Flashplayers auf und ermöglicht die Erstellung von Anwendungen per MXML und ActionScript3. Flex Builder 3 ist als Ecplise Plug-In realisiert und bietet Entwicklern eine gewohnte Umgebung.

IDE Überblick

Wie Flash hat Flex Builder 3 auch zwei verschiedene Ansichten. In der Quelltext Ansicht (Abb. 3.1) kann man MXML und ActionScript Texte verfassen. Anders als bei Flash kennt Flex Builder auch eigene ActionScript Klassen und MXML Komponenten und schlägt Vervollständigungen vor. Ausserdem wird in einem Panel immer angezeigt in welchem Element man sich grade befindet. Fehler im Quelltext werden sofort hervorgehoben und nicht erst beim Kompilieren angezeigt.
Die Designansicht (Abb. 3.2) bietet die Möglichkeit per Drag und Drop die GUI zusammenzustellen und jede Komponenteneigenschaft zu ändern. Flex Anwendungen können pixelgenau erstellt werden, oder fliessend. Bei fliessenden Layouts, nehmen die Komponenten soviel Platz wie sie brauchen und positionieren sich bei Änderungen der Größe automatisch neu.

Abb 3.1 Source
Abb. 3.1: Quelltext Ansicht im Flex Builder 3
Abb 3.2 Design
Abb. 3.2: Design Ansicht im Flex Builder 3

MXML & ActionScript

Merkmale

MXML ist eine deklarative XML Sprache mit der man das Layout sowie nichtoptische Elemente und deren Datenbindung beschreiben kann.
In MXML ist neben XML auch ActionScript Code möglich. Im <mx:Script> Bereich können Methoden, Datenmodelle und Eventhandler erstellt werden. Im folgenden eine Beispiel Anwendung, die Celsius Temperaturen in Fahrenheit und umgekehrt rechnet.

Datei CtoF.mxml // Ein Umrechnungstool von Celsius zu Fahrenheit und umgekehrt

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
  layout="vertical"
  horizontalAlign="center" verticalAlign="middle"
  currentState="FtoC">

  <mx:Script>
    <![CDATA[
      private const C_INPUT_TEXT:String = "Geben Sie eine Temperatur in Celsius ein:";
      private const F_INPUT_TEXT:String = "Geben Sie eine Temperatur in Fahrenheit ein:";
      private const CF_STATE:String = "CtoF";
      private const FC_STATE:String = "FtoC";
      
      private function convertTemp():void
      {
        var inputTemp:Number = parseFloat(inputTempField.text);
        var outputTemp:Number;
        
        switch (currentState) {
          case CF_STATE:
            outputTemp = convertCtoF(inputTemp);
            outputTempField.text = outputTempFormatter.format(outputTemp);
            outputTempField.text += "°F";
            break;
          case FC_STATE:
            outputTemp = convertFtoC(inputTemp);
            outputTempField.text = outputTempFormatter.format(outputTemp);
            outputTempField.text += "°C";
            break;
          default:
            throw new Error("Unerwarteter Zustand");
        }
      }
      
      private function clearTemp():void
      {
        inputTempField.text= "";
        outputTempField.text = "";
      }
      
      private function convertCtoF(celsius:Number):Number
      {
        return ((celsius * 9) / 5 + 32);
      }
      
      private function convertFtoC(fahrenheit:Number):Number
      {
        return 5/9 * (fahrenheit - 32);
      }
    ]]>
  </mx:Script>
  
  <mx:Style>
    .outputTemp { fontsize: 18; fontweight: bold; color: red}
  </mx:Style>
  
  <-- Anwendungs Status -->
  <mx:states>
    <mx:State name="{FC_STATE}" enterState="convertTemp()">
      <mx:SetProperty target="{inputLabel}" name="text" value="{F_INPUT_TEXT}"/>
    </mx:State>
    <mx:State name="{CF_STATE}" enterState="convertTemp()">
      <mx:SetProperty target="{inputLabel}" name="text" value="{C_INPUT_TEXT}"/>
    </mx:State>
  </mx:states>
  
  <mx:NumberValidator source="{inputTempField}" property="text" triggerEvent="change" valid="convertTemp()" invalid="clearTemp()"/>
  <mx:NumberFormatter id="outputTempFormatter" precision="2"/>
  
  <!-- Layout -->
  <mx:Panel title="Temperaturumrechnung" layout="vertical" horizontalAlign="middle">
    <mx:HBox horizontalAlign="center" verticalAlign="middle">
      <mx:Label id="inputLabel"/>
      <mx:TextInput id="inputTempField" maxChars="5"/>
      <mx:Spacer width="10"/>
      <mx:Text id="outputTempField" styleName="outputTemp" width="100"/>
    </mx:HBox>
    <mx:HBox horizontalAlign="center" creationComplete="conversionMode.selectedValue=currentState;">
      <mx:RadioButtonGroup id="conversionMode" change="currentState = conversionMode.selectedValue as String"/>
      <mx:RadioButton group="{conversionMode}" label="Fahrenheit zu Celsius" value="FtoC"/>
      <mx:RadioButton group="{conversionMode}" label="Celsius zu Fahrenheit" value="CtoF"/>
    </mx:HBox>
  </mx:Panel>
</mx:Application>

Weiter kann auch auf alle Klassen zugegriffen werden die importiert wurden, oder im Verzeichnis der Anwendung liegen. Die Trennung von Ausgabe, Datenmodellen und der Logik ist ein Ziel vom Flex Framework. Im folgenden das Beispiel mit getrennter Ausgabe und Logik (da sich in der MXML Datei nur der Script Teil geändert hat, ist nur dieser angegeben)

Datei CtoF.as // als statische Logikklasse

package {
  public class CtoF {
  	
    public static function convertCtoF(celsius:Number):Number
    {
      return ((celsius * 9) / 5 + 32);
    }
    
    private function convertFtoC(fahrenheit:Number):Number
    {
        return 5/9 * (fahrenheit - 32);
    } 
  }
}

Änderungen in der Datei CtoF.mxml

	…
      private function convertTemp():void
      {
        var inputTemp:Number = parseFloat(inputTempField.text);
        var outputTemp:Number;
        
        switch (currentState) {
          case CF_STATE:
            outputTemp = CtoF.convertCtoF(inputTemp);
            outputTempField.text = outputTempFormatter.format(outputTemp);
            outputTempField.text += "°F";
            break;
          case FC_STATE:
            outputTemp = CtoF.convertFtoC(inputTemp);
            outputTempField.text = outputTempFormatter.format(outputTemp);
            outputTempField.text += "°C";
            break;
          default:
            throw new Error("Unerwarteter Zustand");
        }
      }
	…

Die Aufteilung einer Anwendung in mehrere MXML Dateien ist auch möglich. Zu beachten ist das <mx:Application> nur einmal in der gesamten Anwendung vorkommen darf. Weitere Komponenten erstellt man ohne den Application Container. Die Namespace Deklaration muss dann in der umschliessenden Komponente (zb <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" …>) erfolgen. Der Zugriff auf die Komponente kann unterschiedlich erfolgen. Im folgenden Beispiel wird die Komponenten "FlickrImg" als ItemRenderer in einer TileList genutzt. Hier erfolgt der Zugriff unter der Angabe des Namens der MXML Datei.

Datei FlickrFun.mxml // Ein Anwendung um die FlickrDatenbank nach Bildern zu einem Stichwort zu durchsuchen

	<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    backgroundGradientColors="[0xFFFFFF, 0xAAAAAA]"
    horizontalAlign="left"
    verticalGap="15"
    horizontalGap="15">

    <mx:Script>
        
            import mx.collections.ArrayCollection;
            import mx.rpc.events.ResultEvent;

            [Bindable]
            private var photoFeed:ArrayCollection;

            private function requestPhotos(event:Event):void {
                photoService.cancel();
                var params:Object = new Object();
                params.format = 'rss_200_enc';
                params.tags = searchTerms.text;
                photoService.send(params);
            }

            private function photoHandler(event:ResultEvent):void {
                 photoFeed = event.result.rss.channel.item as ArrayCollection;
            }
         
    </mx:Script>

    <mx:HTTPService id="photoService"
        url="http://api.flickr.com/services/feeds/photos_public.gne"
        result="photoHandler(event)" />

  <mx:HBox>
    <mx:Label text="Flickr tags or search terms:" />
    <mx:TextInput id="searchTerms"/>
    <mx:Button label="Search"
      click="requestPhotos(null)" />
  </mx:HBox>

  <mx:TileList width="100%" height="100%"
    dataProvider="{photoFeed}"
    itemRenderer="FlickrImg">
  </mx:TileList>

</mx:Application>

Datei FlickrImg.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
  width="75" height="75"
  paddingBottom="5" paddingLeft="5" paddingRight="5" paddingTop="5"
  mouseOver="currentState='active'"
  mouseOut="currentState='inactive'">

  <mx:Image id="img"
    width="75" height="75" 
    source="{data.thumbnail.url}"/>
    <mx:Label id="caption" 
      width="75" text="{data.credit}"  textAlign="center"
      />
  
  <!-- EyeCandy -->
  <mx:states>
    <mx:State name="active">
      <mx:SetProperty target="{caption}" name="alpha" value="0.0"/>
    </mx:State>
    <mx:State name="inactive">
      <mx:RemoveChild target="{caption}"/>
    </mx:State>
  </mx:states>
  
  <mx:transitions>
    <mx:Transition fromState="active" toState="inactive">
      <mx:Parallel target="{img}">
        <mx:Blur blurXTo="5" blurYTo="5"/>
      </mx:Parallel>
    </mx:Transition>
    <mx:Transition fromState="inactive" toState="active">
      <mx:Parallel target="{img}">
        <mx:Blur blurXTo="0" blurYTo="0"/>
      </mx:Parallel>
    </mx:Transition>
  </mx:transitions>
</mx:Canvas>

Wenn man auf selbst erstellte Komponente zugreifen will, importiert man sie durch Definition eines eigenen NameSpace mit dem Pfad der Komponente. In der Anwendung spricht man die Komponenten dann statt mit <mx:…> mit <myNs:DateiName…> an.

Datenbindungen

In MXML ist es sehr einfach Datenmodelle mit anderen Komponenten zu verbinden. Im FlickrFun Beispiel haben wir unser Datenmodell photoFeed einfach als dataProvider unserer TileList angegeben und Flex macht für uns den Rest. Sobald sich das Datenmodell ändert wird auch die Ausgabe angepasst.
Eine weitere Datenbindung erstellen wir in diesem Beispiel mit dem itemRenderer, der dataProvider stellt dem itemRenderer-Objekt die Daten in der Variable data zur Verfügung, die dann im flickrImg genutzt werden.

States und Transitions

Als letztes gehen wir noch auf States und Transitions ein, beides ist uns in den obigen Beispielen begegnet. Mit States definiert man den Unterschied des Erscheinungsbild der Anwendung im Vergleich zum Ausgangsstatus, also der Situation die man per MXML geschrieben hat. Im Celsius zu Fahrenheit Beispiel wird über die RadioButtons der aktuelle Status gesetzt "currentState = conversionMode.selectedValue as String" und die Ausgabetexte werden angepasst, sowie die richtige Umrechnungsformel ausgesucht.
Im FlickrFun Beispiel wird per States und Transitions ein wenig EyeCandy realisiert. Die States nutzen hier nicht der Programmlogik, sondern steuern nur die Ausgabe der angezeigten Photos. Wenn der Mauszeiger über einem Bild ist, wird dieses klar angezeigt, ansonsten ist es geblurt. Hier werden auch Transitions eingesetzt, also Übergänge von einem Status zu einem anderen Status. In diesem Beispiel werden die Bilder beim Wechsel von einem zum anderen Status ge- bzw entblurt.
Mit Transitions kann man auch Komponenten ein- und ausfahren lassen und viele weitere Effekte, die dem Anwender helfen können, Veränderungen an der Benutzeroberfläche zu sehen.

Fazit

Der FlexBuilder bietet dem Entwickler eine Menge Unterstützung die Flash nicht bietet. In einer gewohnten Umgebung wie Ecplise findet man sich schnell zurecht, der Editor unterstützt den Entwickler mit Highlighting der Syntax und der Fehler, Codevervollständigung und Consolen- und Kompilerausgabe. Mit dem Designmodus kann man immer kontrollieren wie die Anwendung aussehen wird und hat alle Einstellungen direkt sichtbar. Mit MXML entwickelt man schnell ansprechende Oberflächen und Anwendungsverhalten die in Flash wesentlich aufwendiger umzusetzten sind.

«ActionScript | Seitenanfang | »weitere Entwicklungsmöglichkeiten