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

Posted on May 4, 2010 | Comments Off

This is the third 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 the custom components that make up the actually Microphone example code. This code comes across more of the same migration issues as well as some new ones. There will be migrating of css styles to skins which in turn then get reused across components. Also I noticed a difference in mx:Label and s:Label causes some tweaking on placement/layout on the converted components.

Converting Flex 3 Microphone application to Flex 4

Approaching the mx:ViewStack in Flex 4

Flex 4 overhauled how states work in mxml. This new approach is a lot easier then the old method of all state logic being inside the state mxml blocks. The new states mechanism is a decent approach to replace most of the simple cases of ViewStack functionality. There is no Spark ViewStack direct component replacement but if you want the old selecetedChild property and similiar syntax you can find people that have created a ViewStack Flex 4 Spark component on the web. I choose to use the new state method, in the main applications mxml I added three states for the three views declared in the ViewStack. The three states are called: sampleMic, pitchDetection, and info.

NOTE: The <s:states> property has to be before any content components or you will get a compiler error shown below.

Child elements of ‘WindowedApplication’ serving as the default property value for ‘mxmlContentFactory’ must be contiguous. MicrophoneExamples.mxml /microphone/src line 57 Flex Problem

Next I removed the mx:ViewStack and added the includedIn property with the respective state name for each custom component that was in the mx:ViewStack. This will compile with some errors in MicrophoneExamplesSource.as file relating to the vsMain.selectedChild = X (X is the id/instance of the custom component), which is then changed to currentState = Y (Y is the string name of the new states). I went ahead and implemented a Fade state transition in the new Flex 4 transitions approach. Here is what the code looks like, MicrophoneExamples.mxml:

<?xml version="1.0" encoding="utf-8"?>
<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">
   
    <s:states>
        <s:State name="sampleMic" />
        <s:State name="pitchDetection" />
        <s:State name="info" />
    </s:states>
   
   
    <s:transitions>
        <s:Transition toState="pitchDetection">    
            <s:Fade alphaFrom="0.0" alphaTo="1.0" duration="600"
                        targets="{[pnlTuner,txtExample]}"/>                
        </s:Transition>    
        <s:Transition toState="sampleMic">    
            <s:Fade alphaFrom="0.0" alphaTo="1.0" duration="600"
                    targets="{[pnlMic,txtExample]}"/>                
        </s:Transition>    
       
        <s:Transition toState="info">    
            <s:Fade alphaFrom="0.0" alphaTo="1.0" duration="600"
                    targets="{[pnlInfo,txtExample]}"/>                
        </s:Transition>
    </s:transitions>
   
    <!-- A bunch of code from previous Part 1 and Part 2 migration -->
   
    <view:SampleMicPanel id="pnlMic" left="0" top="40" right="0" bottom="0"
                         includeIn="sampleMic"
                         micSelector="{micSelector}"/>
    <view: PitchDetection id="pnlTuner" left="0" top="40" right="0" bottom="0"
                         includeIn="pitchDetection"
                         micSelector="{micSelector}" />
    <view:InformationPanel id="pnlInfo" left="0" top="40" right="0" bottom="0"
                           paddingLeft="10" paddingTop="8" paddingRight="10"
                           includeIn="info"
            applicationVersion="{applicationVersion}" />
   
    <view:InputDeviceSelector id="micSelector" left="40" right="40" top="66" bottom="24"
                              visible="false" />

</s:WindowedApplication>

Converting Custom View SampleMicPanel

I started by adding the three new namespaces: fx, mx, and s. Then changed the main component from mx:Canvas to s:Group. The mx:Script block was changed to fx:Script because of the new namespace change. The first child mx:Canvas component has styleName="controlsBox"which I removed from the css and provided a custom skin to take care of the controlsBox‘s background and border style values. I removed the styleName property and then change mx:Canvas to mx:SkinnableContainer with skinClass="controls.skins.ControlsBoxSkin". The ControlsBoxSkin will be reused later in the other custom components here is what it 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:mx="library://ns.adobe.com/flex/mx">
    <!-- host component -->
    <fx:Metadata>
        [HostComponent("spark.components.supportClasses.SkinnableComponent")]
    </fx:Metadata>
    <s:states>
        <s:State name="normal" />
        <s:State name="disabled" />
    </s:states>
   
    <s:Rect width="100%" height="100%">
        <s:stroke>
            <s:SolidColorStroke color="0x3A3A3A" />
        </s:stroke>
        <s:fill>
            <s:SolidColor color="0x565656" />
        </s:fill>
    </s:Rect>

    <s:Group id="contentGroup" left="5" right="5" top="4" bottom="4" />
   
</s:Skin>

The second mx:Canvas with id="spectrum" is a place holder for the wave form to be dynamically drawn on. I changed mx:Canvas to s:Group which extends Sprite which can be drawn on programmatically.

For the TOP CONTROLS section of code I changed mx:HBox to s:HGroup, mx:Button to s:Button and s:ToggleButton (removing the toggle property), and mx:Label to s:Label. The s:Label padding is not the same as mx:Label and I had to tweak the font-size of footerPTText to 12. I didn’t change mx:Spacer as it made more since then just putting a s:Group with height="100%" in there.Here is the code:

<!-- TOP CONTROLS -->
    <s:HGroup id="topControls" left="10" right="10" top="0">
        <s:ToggleButton id="btnRecord" click="recordSound()"
            styleName="recordButton" />
        <s:Label styleName="footerPTText" text="{micSelector.micName}"
                 click="micSelector.visible = !micSelector.visible" />
        <mx:Spacer width="100%" />
        <s:Button id="btnSave" click="savePrompt()" enabled="false"
            toolTip="Save and Open in Default OS WAV Player"
            styleName="saveOpenButton" />
    </s:HGroup>

NOTE: There is no mx:Spacer Spark equivalent, you could create a s:Group width=”100%” but they both inherit UIComponent at some point in their class structure, mx:Spacer has less hierarchy so is smaller in size.

For the TIMING UI section of code I changed mx:HBox to s:HGroup and the property horizontalGap to gap. I moved down the s:HGroup down four pixels because of padding differences. Here is the code:

<!-- TIMING UI -->
    <s:HGroup id="timings" left="10" right="10" top="31" gap="0">
        <s:Label styleName="titleTextBlack" text="Last Data Event:" />
        <s:Label styleName="footerText" text="{micStats}" />
        <mx:Spacer width="100%" />
        <s:Label styleName="titleTextBlack" text="Recording Time:" />
        <s:Label styleName="footerText" text="{micTimer}" textAlign="right" />
    </s:HGroup>

For the PLAY CONTROLS section of code I changed mx:HBox to s:HGroup, mx:Button to s:ToggleButton (removing the toggle property), all mx:Canvas to s:Group, mx:HSlider to s:HSlider. The playHeadCanvas and playHead components had background colors set onto the mx:Canvas, these css styles where removed and drawn into the s:Group‘s directly with s:Rect. The s:HSlider handles the DataTip styling through skins and not styles now, so I had to create a custom skin and added it to the component as skinClass="controls.skins.MyHSliderSkin". I created a copy of the Spark HSlider skin and just tweaked the DataTip section to be a rounded dark rectangle with light grey text, code is found in MyHSliderSkin.mxml. There was no equivalent in the css file but the mx:HSlider would use global styles in setting the DataTip style so this had to be changed manually in the skin for the s:HSlider. Here is the code:

<!-- PLAY CONTROLS -->
    <s:HGroup id="playControls" left="10" right="10" bottom="30" verticalAlign="middle"
        gap="2">
        <s:ToggleButton styleName="playButton"
            id="btnPlay" click="isPlayingFlag = true;playRecordedData()" />
        <s:Group id="playHeadCanvas" height="16" y="5" width="100%">
            <s:Rect width="100%" height="100%">
                <s:stroke>
                    <s:SolidColorStroke color="0x3A3A3A" />
                </s:stroke>
                <s:fill>
                    <s:SolidColor color="0xAAAAAA" />
                </s:fill>
            </s:Rect>
            <s:Group id="playHead" width="8" height="15" y="0" x="0">
                <s:Rect width="100%" height="100%">
                    <s:fill>
                        <s:SolidColor color="0x333333" />
                    </s:fill>
                </s:Rect>
            </s:Group>
        </s:Group>
        <s:Group height="100%">
            <s:HSlider width="64" dataTipFormatFunction="volumeDataTipFunction" toolTip="Volume"
                       y="7" skinClass="controls.skins.MyHSliderSkin"
                       id="vsVol" minimum="0" maximum="100" value="60" />  
        </s:Group>
    </s:HGroup>

NOTE: In Part 1 I removed HSlider, RadioButton and ComboBox from the css file. They used custom images for icon style values. As we saw with the s:Button in the previous Parts of this series, I had to do this through skins not styles (or a custom skin that reads styles). The default skin’s for the above Spark components is the look and feel I am looking for, although slightly different, so I didn’t create custom skins for those components. The approach is like that of the controls.skins.IconButtonSkin but is a little more complex because of nested skins. I am leaving this from the series of posts for another time.

For the rest of the code the same type of changes where applied for the various components. The one thing to point out is that the mx:ComboBox to s:ComboBox the dataProvider doesn’t take a value of Array but has to be an IList. I wrapped the Array into an ArrayList that implements IList. Here is the code:

    <s:Group width="100%" left="10" right="10" top="148">
        <s:Label styleName="titleTextBlack" text="Playback Speed:" y="1" />
        <s:HSlider id="hsSpeed" left="94" right="0" dataTipFormatFunction="dataTipFunction"
            toolTip="Speed" value="50" snapInterval="1" minimum="10" maximum="90" liveDragging="true" />
    </s:Group>
    <s:HGroup id="bottomControls" left="10" right="10" bottom="10">
        <s:ComboBox id="nmQuality" visible="false" includeInLayout="false"
            dataProvider="{new ArrayList([2048,(1024*3),(1024*4),(1024*5),(1024*6),(1024*7),8192])}" />
    </s:HGroup>

Converting Custom View PitchDetection

This is like the other custom component by adding namespaces and changing all the mx components to their Spark equivalents. The main mx:Canvas was changed to s:Group and the mx:Canvas with styleName="controlsBox" was changed to s:SkinnableContainer with a value of skinClass="controls.skins.ControlsBoxSkin", like in the previous SampleMicPanel component migration. Chalk one up for reuse of skins. Here is code of just the content section of the PitchDetection:

    <s:ToggleButton id="btnRecord" click="recordSound()" left="10"
        styleName="recordButton" />
    <s:Label styleName="titleTextBlack" text="Start Sampling to Determine the Pitch of the Audio"
              bottom="6" horizontalCenter="0" />
    <s:Label styleName="footerPTText" text="{micSelector.micName}" click="micSelector.visible = !micSelector.visible"
              y="0" left="50" />
       
    <s:Label id="note_tx" text="Note"
        verticalCenter="-48" horizontalCenter="0" styleName="titleTextBlack" fontSize="32"/>
   
    <s:SkinnableComponent left="15" right="15" bottom="31" height="80"
                          skinClass="controls.skins.ControlsBoxSkin" />
   
    <s:BitmapImage source="@Embed('embed_assets/pitch/musicstaff.png')" left="15" bottom="31" />
    <s:BitmapImage source="@Embed('embed_assets/pitch/notedown.png')"
              verticalCenter="31" horizontalCenter="0" id="notedown" visible="false" />
    <s:BitmapImage source="@Embed('embed_assets/pitch/noteup.png')"
              verticalCenter="16" horizontalCenter="0" id="noteup" visible="false" />
    <s:BitmapImage source="@Embed('embed_assets/pitch/noteupwbar.png')"
              verticalCenter="16" horizontalCenter="-1" id="noteupwbar" visible="false" />
    <s:BitmapImage source="@Embed('embed_assets/pitch/sharp.png')"
              verticalCenter="32" horizontalCenter="-15" id="sharp" visible="false" />

I removed the styleName="mainPaddedBox" from the PitchDetection component in the MicrophoneExamples.mxml file as it was not needed for this component.

Converting Custom View InformationPanel

This component inherits a mx:VBox which I changed to s:VGroup, as well as changed verticalGap to gap. I removed the styleName="mainPaddedBox" from the InformationPanel component in the MicrophoneExamples.mxml file and from the main css file. This is because the Spark component does not do padding on styles but as properties on the s:VGroup component. The old padding style values where applied to the as properties on the InformationPanel declaration in the MicrophoneExamples.mxml file.

Changing the mx:Label to s:Label again messed with padding and size of the text, I had to change css infoText to font-size of 12. The mx:HRule is basically a line so I removed this component and added s:Line with weight of 2 for the thickness. Here is the code changes:

    <s:Label styleName="titleText" text="CREDITS {applicationVersion}" />

    <s:Line width="100%">
        <s:stroke>
            <s:SolidColorStroke color="0xffffff" weight="2" />
        </s:stroke>
    </s:Line>
    <mx:Spacer height="0" />
    <s:Label styleName="titleTextBlack" text="Application Author: "/>
    <s:Label styleName="infoText" text="Renaun Erickson" />
    <s:Label styleName="titleTextBlack" text="Pitch Detection Code: "/>
    <s:Label styleName="infoText" text="John Montgomery (psychicorigami.com) AS3 port Benjamin Dobler" />
    <s:Label styleName="infoText" text="Modified by Renaun Erickson" />

Converting Custom View InputDeviceSelector

This custom component makes use of the controlsBox style, which means I reused the controls.skins.ControlsBoxSkin in connection with s:SkinnableContainer to replace the mx:Canvas. I made namespace, fx:Script, s:Label, and s:VGroup changes like before. The mx:ButtonRadioGroup change to s:ButtonRadioGroup requires that it is placed into a new fx:Declarations code block to keep non-visual declarations separate. Then when I compiled it comes back with an error about using addElement instead of addChild, with Spark containers components have to implement the IVisualElement interface and use the new addElement methods. Changing vbButtons.addChild to vbButtons.addElement fixes the compiler error and its all ready. Here is the content section of this custom component:

    <fx: Declarations>
        <s: RadioButtonGroup id="grpRadio" itemClick="changeMic(event)" /> 
    </fx: Declarations>
    <s:Label styleName="infoText" text="Select An Input Device:" left="6" top="6" />
    <s:VGroup id="vbButtons" left="6" right="6" top="22" bottom="6" gap="5" />

The End

This is the end of these series of posts on a real world example of converting a Flex 3 application to Flex 4 and the Spark architecture. Flash Builder 4 can still be used for Flex 3 applications both with the Flex 3 sdk and Flex 4 sdk in mx compability mode, for more information check out the ADC article.

Also a great post on learning more about Flex 3 and Flex 4 differences can be found on ADC here.