Converting Flex 3 Microphone application to Flex 4 (part 2)

Posted on April 29, 2010 | Comments Off

This is the second part of a series of posts detailing the migration of a Flex 3 AIR 2.0 application to Flex 4. In this post we’ll start converting old MX components to the new Spark components architecture.

Converting Flex 3 Microphone application to Flex 4

High Level – The Before and After of this Post in Code

Before just talking through all the changes I am about to make I’ll post the original code of the main application for reference. The code snippet directly after the original application code snippet shows all changes made through out this post.

Original MicrophoneExamples.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!--
////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2009 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
-->
<mx:WindowedApplication
    xmlns:mx="http://www.adobe.com/2006/mxml"
    showEffect="Fade" hideEffect="Fade"
    width="460" height="210" showFlexChrome="false"
    creationComplete="showMain();"
    horizontalScrollPolicy="off" verticalScrollPolicy="off"
    layout="absolute" xmlns:view="view.*" viewSourceURL="srcview/index.html">
   
    <mx:Style source="embed_assets/stylesheet_common.css" />
    <mx:Script source="MicrophoneExamplesSource.as" />
   
    <mx:VBox  id="bgBox" width="460" height="210" styleName="mainBox" />
   
    <mx:HBox
        horizontalAlign="left" verticalAlign="middle"
        width="100%" styleName="titleBox">
        <mx:Canvas y="5" mouseDown="startMove(event)" mouseUp="movingReferenceEvent = null">
            <mx:Image source="embed_assets/mic16.png" y="3" />
            <mx:Label text="MICROPHONE" styleName="titleText" x="16" />
            <mx:Label text="{viewName}" x="98" styleName="titleTextGrey"    />
        </mx:Canvas>
        <mx:HBox mouseDown="startMove(event)" mouseUp="movingReferenceEvent = null"
            verticalAlign="bottom" width="100%" horizontalAlign="right" horizontalGap="10">
            <mx:Button styleName="leftArrowButton" click="changeView('left')" />
            <mx:Button styleName="rightArrowButton" click="changeView('right')" />
            <mx:Button id="btnInfo" styleName="helpButton" click="changeView('info')"
                tabEnabled="false" toolTip="Information" toggle="true" />
            <mx:Button styleName="appCloseButton" click="stage.nativeWindow.close()" tabEnabled="false" toolTip="Close" />
        </mx:HBox>
    </mx:HBox> 
    <mx:ViewStack id="vsMain" left="0" top="40" right="0" bottom="0"
        hideEffect="Fade" showEffect="Fade">
        <view:SampleMicPanel id="pnlMic" width="100%" height="100%" micSelector="{micSelector}" />
        <view: PitchDetection id="pnlTuner" width="100%" height="100%" styleName="mainPaddedBox"
                             micSelector="{micSelector}" />
        <view:InformationPanel id="pnlInfo" width="100%" height="100%" styleName="mainPaddedBox" />
    </mx:ViewStack>
   
    <view:InputDeviceSelector id="micSelector" left="40" right="40" top="66" bottom="24" visible="false" />
   
</mx:WindowedApplication>

After changes made in this Post:

<?xml version="1.0" encoding="utf-8"?>
<!--
////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2009 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
-->
<s:WindowedApplication
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/mx"
    xmlns:view="view.*"
    showEffect="Fade" hideEffect="Fade"
    width="460" height="210"
    creationComplete="showMain();"
    showStatusBar="false"
    backgroundColor="0x666666"
     viewSourceURL="srcview/index.html">
   
    <fx:Style source="embed_assets/stylesheet_common.css" />
    <fx:Script source="MicrophoneExamplesSource.as" />
   
    <!-- Layout for top Bar -->
    <s:SkinnableContainer
        skinClass="controls.skins.TopBarSkin" width="100%">
        <s:Group y="8" mouseDown="startMove(event)" mouseUp="movingReferenceEvent = null">
            <s:BitmapImage source="@Embed('embed_assets/mic16.png')" />
            <s:Label text="MICROPHONE" styleName="titleText" x="18" y="3" backgroundAlpha="0" />
            <s:Label text="{viewName}" x="100" y="3" styleName="titleTextGrey" backgroundAlpha="0" />
        </s:Group>
        <s:HGroup mouseDown="startMove(event)" mouseUp="movingReferenceEvent = null"
            verticalAlign="bottom" width="100%" horizontalAlign="right" gap="10">
            <s:Button styleName="leftArrowButton" click="changeView('left')" />
            <s:Button styleName="rightArrowButton" click="changeView('right')" />
            <s:ToggleButton id="btnInfo" styleName="helpButton" click="changeView('info')"
                tabEnabled="false" toolTip="Information" />
            <s:Button styleName="appCloseButton" click="stage.nativeWindow.close()" tabEnabled="false" toolTip="Close" />
        </s:HGroup>
    </s:SkinnableContainer>
    <mx:ViewStack id="vsMain" left="0" top="40" right="0" bottom="0"
        hideEffect="Fade" showEffect="Fade">
        <view:SampleMicPanel id="pnlMic" width="100%" height="100%" micSelector="{micSelector}" />
        <view: PitchDetection id="pnlTuner" width="100%" height="100%" styleName="mainPaddedBox"
                             micSelector="{micSelector}" />
        <view:InformationPanel id="pnlInfo" width="100%" height="100%" styleName="mainPaddedBox"
                applicationVersion="{applicationVersion}" />
    </mx:ViewStack>
   
    <view:InputDeviceSelector id="micSelector" left="40" right="40" top="66" bottom="24" visible="false" />

</s:WindowedApplication>

Migrating the Components

First one up is the main WindowedApplication class, changing mx:WindowedApplication to s:WindowedApplication. Doing so there are a few properties that need to be changed. Four properties where removed: layout, showFlexChrome, horizontalScrollPolicy, and verticalScrollPolicy. The showStatusBar="false" property was added to remove the Flex status bar chrome.

Changing the Background Custom Chrome

In the original code a custom background was created with a VBox and css style of mainBox. Since all mainBox style provided was a grey background with the width and height of the application I went ahead and removed the VBox component from the main application and removed the mainBox style from the css file. Then added the backgroundColor="0x666666" property on to the main application class for the same affect.

HBox with a styles is both a Group and SkinnableContainer

So the next conversion step was changing the HBox to some equivalent in the spark world, but this is where it gets a little tricky. The HBox doesn’t have a direct one to one component match, especially since the titleBox defines padding, border, and background values in the stylesheet. In Spark the Group classes are meant to be lightweight layout and container classes but do not provide any skinning. For skinning or any display components it should be a class that extends SkinnableComponent. Now you can use SkinnableContainer to both display visual content and layout elements within the container. This leads to a lot of options and a bit of confusion. You might be thinking when should I use what classes, well that is for another discussion.

For this migration since I do have visual content and layout that the HBox was providing I’ll go ahead and use the SkinnableContainer class. First I replaced mx:HBox with s:SkinnableContainer, then I created a skinClass called controls.skins.TopBarSkin. In this skin I create the background and bottom border by drawing a Rect and Line with the values from the css stylesheet for titleBox. The SkinnableContainer looks for a skin part called contentGroup, which I turn into a HGroup with the padding values and layout values I used on the HBox and titleBox css. Here is what the new controls.skins.TopBarSkin looks like:

<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:fb="http://ns.adobe.com/flashbuilder/2009" alpha.disabled="0.5">

    <fx:Metadata>
    <![CDATA[
       [HostComponent("spark.components.SkinnableContainer")]
   ]]>
    </fx:Metadata>
   
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>

    <s:Rect id="background" width="100%" height="35">
        <s:fill>
            <s:SolidColor color="0x212121" />
        </s:fill>
    </s:Rect>
    <s:Line width="100%" y="34">
        <s:stroke>
            <s:SolidColorStroke  color="0x121212" />
        </s:stroke>
    </s:Line>
   
    <s:HGroup id="contentGroup"
        paddingLeft="10" paddingTop="9" paddingRight="10" paddingBottom="6"
        horizontalAlign="left" verticalAlign="middle"
        width="100%" />

</s:Skin>

TopBar Content Component Changes

In side the newly changed HBox the first container is a Canvas component that contains the app mic icon and some labels. Its purpose is just to layout components with some specific y values. This calls for mx:Canvas becoming s:Group.

The mx:Image is not loading dynamic image so it can be changed to s:BitmapImage, but with this change you have to use the @Embed code in the source property for s:BitmapImage to work. The mx:Image class allows for both methods, but using s:BitmapImage will be smaller class size and faster.

The mx:Label‘s change to s:Label, but there is some crazy stuff on the embedding of fonts in the css to be changed. Flex 4 spark controls use the new Flash Player 10 Text Layout Framework (TLF) to display text, its newer and provides a bunch of features like bidirectional text. But it works with embedded fonts differently. In the css file where @font-face is embedding the different fonts a property of embedAsCFF: true; has to be added, then in the specific style classes that you want to force to use the embedded font you add fontLookup: embeddedCFF;. Here is an example of what this looks like:

css changes:

@font-face
{
    src:url("/embed_assets/fonts/MyriadPro-Black.otf");
    font-family: "Myriad Pro Black";
    embedAsCFF: true;

}

.titleText, .titleTextGrey, .titleTextBlack
{
    font-size: 12;
    color: #FFFFFF;
    font-family: "Myriad Pro Black";
    fontLookup: embeddedCFF;

}

The s:Label puts a background color by default so I made it invisible by setting backgroundAlpha="0" on the component in the main application mxml file. I also had to tweak some y and x values to make things line up again because of the new TLF usage in the s:Labels.

The second group of components in the TopBar are the buttons that resides on the right. These buttons include the navigation left/right between examples, help button and close button. The container class mx:HBox does not have any visual content so it is changed to s:HGroup to preserve the horizontal layout. The horizontalGap property needs to change to just gap but the rest does not need changing.

There are some changes to Spark buttons that are different in the MX controls. You’ll see the btnInfo button is set to toggle=”true”, well by default the Spark button does not do toggle. There is a Spark ToggleButton that handles the button’s with selection states. The next big issue is by default Spark button’s don’t support up, down, over, and disabled skins through styles. But this is not hard to fix. So we basically want to use images for button states and throw away all the drawing of a normal button skin, which is what Flex 4 skinning is all about.

First I changed the mx:Button to s:Button and s:ToggleButton for btnInfo. Then I created a new skin called controls.skins.IconButtonSkin. In the new Skin file I listen for state changes and then using the state value set the icon based on style lookup, the styles are those of the old mx:Button so I don’t have to go change the css code. Note it also checks to see if the style is present to make sure it is not trying to set a invalid image source. The IconButtonSkin doesn’t contain any drawing code or label but just a s:BitmapImage. The BitmapImage‘s source property then gets set with values set in the css styles. Here is the skin file:

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009"
             xmlns:s="library://ns.adobe.com/flex/spark"
            xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
             currentStateChanging="onCurrentStateChanging(event)">
    <fx:Metadata>[HostComponent("spark.components.supportClasses.ButtonBase")]</fx:Metadata>
   
    <!-- host component -->
    <fx:Script fb:purpose="styling">
        <![CDATA[        
           
            import mx.events.StateChangeEvent;
           
            private function onCurrentStateChanging(event:StateChangeEvent):void
            {
                switch (event.newState)
                {
                    case "up":
                        setIcon("upSkin");
                        break;
                    case "over":
                        setIcon("overSkin");
                        break;
                    case "down":
                        setIcon("downSkin");
                        break;
                    case "disabled":
                        setIcon("disabledSkin");
                        break;
                    case "upAndSelected":
                        setIcon("selectedUpSkin");
                        break;
                    case "overAndSelected":
                        setIcon("selectedOverSkin");
                        break;
                    case "downAndSelected":
                        setIcon("selectedDownSkin");
                        break;
                    case "disabledAndSelected":
                        setIcon("selectedDisabledSkin");
                        break;
                }
            }
            private function setIcon(type:String):void
            {
                if (hostComponent.getStyle(type) != null)
                {
                    icon.source = hostComponent.getStyle(type);
                }
            }

       ]]>        
    </fx:Script>
   
    <!-- states -->
    <s:states>
        <s:State name="up" />
        <s:State name="over" />
        <s:State name="down" />
        <s:State name="disabled" />
        <s:State name="upAndSelected" />
    <s:State name="overAndSelected" />
    <s:State name="downAndSelected" />
    <s:State name="disabledAndSelected" />
    </s:states>
   
    <s:BitmapImage id="icon"
                   source="{hostComponent.getStyle('upSkin')}"
                   left="0" right="0" top="0" bottom="0" />
</s:SparkSkin>

Since this application has no normal buttons I can go ahead and in the css file set the default skin for s|Button and s|ToggleButton to the newly created IconButtonSkin. Here is the css addition:

s|Button, s|ToggleButton
{
    skin-class: ClassReference("controls.skins.IconButtonSkin");
}

Now all my other button css values for up-skin, over-skin, etc… will work with the new s|Button and s|ToggleButton skin file.

End of Part 2

This is the end of the Part 2 in the migration of the Microphone example application from Flex 3 to Flex 4 series. In the next part I’ll finish up converting the ViewStack and the custom components SampleMicPanel, PitchDetection, InformationPanel, and InputDeviceSelector.