Renaun Erickson
Created
28 September 2010
Prerequisites
Familiarity with Adobe Flex is required.
 
User level
Intermediate
Required products
Flash Builder (Download trial)
Sample files
In this tutorial I walk you through transitioning a real-world application from Flex 3 to Flex 4. The example provided covers all the major areas of Flex application development including CSS, Spark components, custom skins, embedding fonts, and more.
 
Before you do it yourself, it can seem like a big job to migrate a Flex 3 application to Flex 4. After completing this tutorial, you'll see how easy it can be to start benefiting from the new features offered by Flex 4, including its component architecture, CSS improvements, new state mechanisms, and a new graphic markup language called FXG. Backward compatibility with MX components and improvements in Flash Builder 4 make it even easier to migrate to Flex 4.
 
The sample files for this tutorial include the source code for the original Flex 3 application as well as the converted Flex 4 application. While completing the steps in this tutorial you may want to use the completed Flex 4 application source code as a reference.
 
This article is divided into the following sections:
 

Opening the project

The first thing you'll need to do is to open the Flex 3 project and change the settings to point to the Flex 4.1 SDK. I'll walk through the changes needed to get it compiling again and take a look at the visual differences when not using the MX compatibility mode in Flex 4.
 
  1. Unzip the MicrophoneExamplesFlex3.zip sample file inside flex3_to_flex4_transitioning.zip to a folder of your choosing.
  2. In Flash Builder 4 choose File -> Import -> Flash Builder Project.
  3. Select Project Folder.
  4. Click Browse, navigate to the MicrophoneExamplesFlex3 folder that you unzipped above, and click OK.
  5. Click Finish.
The original project used the Flex 3.2 SDK with AIR 2.0 beta overlaid on top of it. If you try to build the project, you should see an error message in the Problems panel that says "Unknown Flex SDK: …". To resolve this issue, you'll need to change the SDK version used for this project.
 
  1. Download and install the Flex 4.1 SDK, which includes AIR 2.0. (Refer to the release notes for installation instructions.)
  2. Right-click the project and select properties to open the Properties dialog box.
  3. Select the Flex Compiler category on the left.
  4. In the Flex SDK Version section select the Flex 4.1 SDK.
  5. Click OK.
Note: If you are using a different Flex 4 and AIR 2.0 SDK or Flex 4.1 version use the Configure Flex SDKs and select the appropriate SDK.
 
Because the original application used a beta version of AIR 2.0, you'll need to update the namespace in the application descriptor file.
 
  1. Open the MicrophoneExamples-app.xml file in the src folder and locate the following line of code:
<application xmlns="http://ns.adobe.com/air/application/2.0beta2">
  1. Delete the "beta2", so the line reads as follows:
<application xmlns="http://ns.adobe.com/air/application/2.0">
  1. Save your changes and build the project.
The application should compile without errors (although there may be some warnings).
 
  1. Choose Run > Run > MicrophoneExamples to run it.
The original Flex 3 version of the application (see Figure 1) and the new Flex 4 version (see Figure 2) do not look the same.
 
Figure 1. The original application built with the Flex 3 SDK.
Figure 1. The original application built with the Flex 3 SDK.
Figure 2. The application built with the Flex 4 SDK, before any code changes.
Figure 2. The application built with the Flex 4 SDK, before any code changes.
The differences are due to the new Flex 4 default CSS and theme values. If you want the Flex 4 compiler to have strict compatibility with the old MX components, you can select the "Use Flex 3 compatibility mode" option in the Flex Compiler dialog box of the Flex project.
 
It is important to also note that Flex 4.1 SDK requires Flash Player 10 as the minimum required player.
 

Namespace changes

The next step in the migration process is making namespace changes. The new namespaces separate the MXML 2006 namespace into three areas: fx, s, and mx. The new areas are MXML 2009 language and constructs (fx), Spark components (s), and MX components (mx).
 
Follow these steps to make the namespace changes:
 
  1. Open the main MicrophoneExamples.mxml application file.
  2. Remove the old namespace declaration:
xmlns:mx="http://www.adobe.com/2006/mxml"
  1. Insert the new namespace declarations:
xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"
  1. Save your changes and build the project.
Changing the namespace yields two errors.
 
  1. Locate the errors (near lines 24 and 25 of of the file) and change the mx:Style and mx:Script to fx:Style and fx:Script respectively.
Now that there are namespaces covering different sets of components, it is possible that two components will have the same name, for example s:Button and mx:Button. The purpose of namespaces is to clearly define different component packages; this is important not only for MXML but for CSS files and styling as well. Flex 4 includes multiple namespace support for CSS.
 
  1. Open the embed_assets/stylesheet_common.css file and add the Spark and MX namespaces to the top of the file as shown below:
@namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx";
You'll need to add the MX namespace to MX components.
 
  1. Add mx| in front of WindowedApplication, Application, Window, ComboBox, HSlider, RadioButton, and ToolTip.
The rest of the styles are specific style names.
 
At this point you can also take the opportunity to fix the theme-color compiler warning.
 
  1. Change theme-color to chrome-color.
It is important to note that chrome-color is not a true replacement of theme-color but works for the application in this article. The new Spark components default set of styles is different from the Halo theme.
 
The first lines of the file should look like this:
 
/** * Common CSS definitions for the Adobe Directory */ @namespace s "library://ns.adobe.com/flex/spark"; @namespace mx "library://ns.adobe.com/flex/mx"; mx|WindowedApplication, mx|Application, mx|Window { /* make app window transparent */ font-family: "Myriad Web"; font-size: 12; font-anti-alias-type:advanced; disabled-overlay-alpha: 0; chrome-color: #444444; color: #AAAAAA; text-roll-over-color: #AAAAAA; text-selected-color: #AAAAAA; }

 
Warning cleanup

At this point there is still one compiler warning to address before moving on. You'll see a warning that notes that Application.application has been deprecated and suggests using FlexGlobals.topLevelApplication instead. You can't bind FlexGlobals, but you can take a different approach in which you create a public bindable property in InformationPanel.mxml that you reference in MicrophoneExamples.mxml.
 
  1. In InformationPanel.mxml locate the following code:
<mx:Script> <![CDATA[ import mx.core.Application; ]]> </mx:Script> <mx:Label styleName="titleText" text="CREDITS {Application.application.applicationVersion}" />
  1. Change it to the new code:
<mx:Script> <![CDATA[ [Bindable] public var applicationVersion:String = ""; ]]> </mx:Script> <mx:Label styleName="titleText" text="CREDITS {applicationVersion}" />
  1. In MicrophoneExamples.mxml locate the following code:
<view:InformationPanel id="pnlInfo" width="100%" height="100%" styleName="mainPaddedBox" />
  1. Add the applicationVersion property:
<view:InformationPanel id="pnlInfo" width="100%" height="100%" styleName="mainPaddedBox" applicationVersion="{applicationVersion}" />

 
Updating the application and its background

The main application file includes several components that make up the application. In this section and the next, you'll change the old MX components to their Spark equivalents and explore the new approach to laying out and styling your application.
 
The first change is the main WindowedApplication class, for which you'll update mx:WindowedApplication to s:WindowedApplication.
 
You'll also need to change a few properties. Four properties need to be removed: layout, showFlexChrome, horizontalScrollPolicy, and verticalScrollPolicy. The layout property is type LayoutBase in Spark (not type String), and it's typically defined in MXML. A Spark WindowedApplication component's default layout is equivalent to the old absolute layout property.
 
In place of showFlexChrome, you'll want to set showStatusBar="false" to remove the Flex status bar chrome, which is turned on by default in the Spark WindowedApplication component.
 
Scrolling in Spark is usually handled in the Skin, and that's the case for this component. Also by default the Spark WindowedApplication does not draw any chrome for the application; it's up to the developer to draw chrome if needed.
 
In the original code, a custom background was created using a VBox and the mainBox CSS style. The mainBox style simply provided a grey background across the width and height of the application. For the new application, it makes sense to remove the VBox component from the main application and remove the mainBox style from the CSS file. You can then add backgroundColor="0x666666" to the main application class to achieve the same effect.
 
Follow the steps below to implement these changes:
 
  1. Locate the following code in MicrophoneExamples.mxml:
<mx: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" 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">
  1. Update the WindowedApplication component as follows:
<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">
  1. Remove the VBox by deleting the following line:
<mx:VBox id="bgBox" width="460" height="210" styleName="mainBox" />
  1. Change the end-tag from </mx:WindowedApplication> to </s:WindowedApplication>.
  2. Remove the mainBox definition from stylesheet_common.css.

 
Layout and styling

In Flex 3 both the layout and the ability to style (padding, background colors, and so on) are mixed together in one component. As you transition the microphone application—or any application—from Flex 3 to Flex 4, you'll notice areas where you can handle layout and styling independently.
 
Examine the first HBox in MicrophoneExamples.mxml, for example. The HBox doesn't have a direct one to one component match in Spark, especially since the titleBox style class defines padding, border, and background values in the CSS file. In Spark, the Group classes are designed to be lightweight layout and container classes, but they do not provide any skinning. For skinning or any display components, you'll need to use a class that extends SkinnableComponent. You can use SkinnableContainer to both display visual content and layout elements within the container. This opens up many options and can lead to a bit of confusion.
 
Since the HBox was providing visual content and layout it's a good idea to use the SkinnableContainer class.
 
Here is the SkinnableContainer after all the changes have been made; the individual steps are explained below.
<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 id="txtExample" 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>
  1. Begin by replacing the mx:HBox with s:SkinnableContainer in MicrophoneExamples.mxml.
  2. Create a new package in the project named controls.skins.
  3. In that package, create a new MXML skin named TopBarSkin.mxml with a host component of spark.components.SkinnableContainer.
  4. Copy the code below into TopBarSkin.mxml, overwriting its default contents.
<?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="6" paddingRight="10" paddingBottom="6" horizontalAlign="left" verticalAlign="middle" width="100%" /> </s:Skin>
This skin creates the background and bottom border by drawing a Rect and Line with the values from the CSS style sheet for titleBox. The SkinnableContainer looks for a skin part called contentGroup, which in this skin is an HGroup with the padding values and layout values used on the original HBox via the titleBox style.
 
Inside the original HBox the first container is a Canvas component that contains the app microphone icon and some labels. Its purpose is just to layout components with some specific y values.
 
  1. Change the mx:Canvas to s:Group.
Inside the Canvas, there was an Image component:
 
<mx:Image source="embed_assets/mic16.png" y="3" />
The mx:Image was not loading a dynamic image so it can be changed to s:BitmapImage. 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 dynamic and static loading of images, but using s:BitmapImage will be faster and use a smaller class size.
 
  1. Change the mx:Image line to:
<s:BitmapImage source="@Embed('embed_assets/mic16.png')" />
  1. Change the mx:Label components to s:Label components.
This change will also require some changes to the CSS file. Flex 4 Spark controls use the new Flash Player 10 Text Layout Framework (TLF) to display text; it's newer and provides several new features, including bidirectional text. It also works with embedded fonts differently. In the CSS file where @font-face is used to embed the different fonts, you'll need to add embedAsCFF: true;. You also have to add fontLookup: embeddedCFF; for the specific style classes that you want to use the embedded font.
 
  1. Make the following changes in the stylesheet_common.css file:
@font-face { src:url("/embed_assets/fonts/MYRIAD.TTF"); font-family: "Myriad Web"; advanced-anti-aliasing: true; embedAsCFF: true; } @font-face { src:url("/embed_assets/fonts/MYRIADB_0.TTF"); font-family: "Myriad Web"; font-weight: bold; advanced-anti-aliasing: true; embedAsCFF: true; } @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 has a background color by default so you'll want to make it invisible by adding a s|Label CSS style with background-alpha: 0; in the CSS file.
 
  1. Add the following code to stylesheet_common.css:
s|Label { background-alpha: 0; font-size: 12; }
  1. Also, on the Label components, add y="3" and add two pixels to the x values of the label components to make the text line up again because of the TLF's default positioning:
<s:Label text="MICROPHONE" styleName="titleText" x="18" y="3" backgroundAlpha="0" /> <s:Label id="txtExample" text="{viewName}" x="100" y="3" styleName="titleTextGrey" backgroundAlpha="0" />
The second group of components in the original HBox comprises the four buttons on the right: left navigation, right navigation, help, and close.
 
  1. The container class mx:HBox does not have any visual content, so change it s:HGroup, which preserves the horizontal layout. Also, change the horizontalGap property to just gap.
There are some changes to Spark buttons that you need to keep in mind. You'll see the original btnInfo button has toggle="true". By default the Spark button does not implement the toggle functionality. There is a Spark ToggleButton for buttons with a selection state. The next big issue is that, by default, Spark buttons do not support up, down, over, and disabled skins through styles. This is fortunately not hard to fix. You 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.
 
  1. Change the mx:Button components to s:Button components—for btnInfo use s:ToggleButton instead of s:Button.
  2. Create a new MXML Skin named IconButtonSkin in the controls.skins package.
  3. Copy the code below into IconButtonSkin.mxml:
<?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>
The code in this Skin file listens for state changes and then, using the state value, sets the icon based on style lookup. The styles are those of the old mx:Button so you don't have to change the CSS code. Note the code also checks to see if the style is present to make sure it is not trying to set an invalid image source. The IconButtonSkin doesn't contain any drawing code or labels, just a single s:BitmapImage. This component's source property is set to values set in the CSS styles.
 
Since this application has no other buttons you can set the default skin for s|Button and s|ToggleButton in the CSS file to the newly created IconButtonSkin.
 
  1. Add the following code to stylesheet_common.css:
s|Button, s|ToggleButton { skin-class: ClassReference("controls.skins.IconButtonSkin"); }
Now all the specific button CSS class styles for up, over, and other states will work with the new s|Button and s|ToggleButton skin file.
 

 
From ViewStack to Flex 4 states

Flex 4 overhauled how states work in MXML, making it much easier than the old method in which all state logic was inside the state MXML blocks. You can use the Flex 4 states mechanism to replace most of the simple cases of mx|ViewStack functionality.
 
Note: There is no Spark ViewStack direct component replacement but if you want the old selectedChild property and similar syntax you can find a third-party ViewStack Flex 4 Spark component on the web.
 
  1. To use the new state method, open MicrophoneExamples.mxml and add three states, one for each of the three views declared in the ViewStack:
<s:states> <s:State name="sampleMic" /> <s:State name="pitchDetection" /> <s:State name="info" /> </s:states>
Note: The <s:states> block has to be positioned before any content components or you will get the following compiler error: "Child elements of 'WindowedApplication' serving as the default property value for 'mxmlContentFactory' must be contiguous. MicrophoneExamples.mxml /microphone/src line 57 Flex Problem"
 
  1. Next, remove the mx:ViewStack and add the includedIn property with the respective state name for each custom component that was in the mx:ViewStack:
<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}" />
  1. Build the project.
In the Problems panel, you'll see three errors in MicrophoneExamplesSource.as, one on each of the following lines:
 
vsMain.selectedChild = pnlInfo; vsMain.selectedChild = pnlMic; vsMain.selectedChild = pnlTuner;
Instead of changing the selectedChild, you want to change the currentState on each of these lines.
 
  1. In MicrophoneExamplesSource.as edit each of the lines above and change them to:
currentState = "info"; currentState = "sampleMic"; currentState = "pitchDetection";
When the value of the currentState property changes an event is fired off that can be used for state transitions. Here are the code changes for MicrophoneExamplesSource.as:
 
if (view != "info") { btnInfo.selected = false; } else { viewName = "(Application Info)"; currentState = "info"; } if (view == "mic") { viewName = "(Record & Playback)"; currentState = "sampleMic"; } if (view == "tuner") { viewName = "(Pitch Detection)"; currentState = "pitchDetection"; }

 
Adding a fade transition

Transitions are supported within the Flex 4 approach to states. They allow you to easily apply effects to state transitions. For the microphone example, you can add a fade state transition for each change in state.
 
  1. Add the following code to MicrophoneExamples.mxml below the <s:states> block:
<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>
The txtExample in each custom view refers to the label in the top bar that changes with each view. In the Flex 3 version of the application, the label didn't have an id, so you'll need to add one.
 
  1. Add id="txtExample" to the Label in the application with text="{viewName}":
<s:Label id="txtExample" text="{viewName}" x="100" y="3" styleName="titleTextGrey" backgroundAlpha="0" />

 
Converting the SampleMicPanel custom view

The last pieces of the application to migrate to Flex 4 are the custom component views for the two microphone examples and information panel. Each component migration uses the same basic approach and techniques, and while it may seem like a repeat of steps covered previously, this will demonstrate some reuse of code and skins.
 
Start with the sample mic view.
 
  1. Open SampleMicPanel.mxml and change the main component from mx:Canvas to s:Group since there is no styling on the mx:Canvas.
  2. Add the three new namespaces:
<s:Group 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.*" creationComplete="init();" hide="stop()">
  1. Change the mx:Script block to fx:Script because of the new namespace change.
The first child mx:Canvas component has styleName="controlsBox".
 
  1. Remove the controlsBox style from the CSS file.
  2. Change the Canvas component to a SkinnableComponent:
<s:SkinnableComponent left="9" right="9" top="50" height="60" skinClass="controls.skins.ControlsBoxSkin" />
  1. Create a new MXML Skin named ControlsBoxSkin.mxml in the controls.skins package and copy the following code into it:
<?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>
This skin will be reused later in the other custom components.
 
The second mx:Canvas with id="spectrum" is a placeholder for the wave form to be dynamically drawn on.
 
  1. Change mx:Canvas to s:Group. The Group class extends Sprite, which can be drawn on programmatically.
  2. Change the TOP CONTROLS section of code as follows:
<!-- 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" /> <s:Group width="100%" /> <s:Button id="btnSave" click="savePrompt()" enabled="false" toolTip="Save and Open in Default OS WAV Player" styleName="saveOpenButton" /> </s:HGroup>
In the code above mx:HBox was changed to s:HGroup, mx:Button to s:Button and s:ToggleButton (removing the toggle property), and mx:Label to s:Label. The mx:Spacer was changed to s:Group with width="100%".
 
  1. The s:Label padding is not the same as mx:Label so edit the CSS file and change padding-top: 2px to padding-top: 6px for the footerPTText style.
  2. Change the TIMING UI section of code as follows:
<!— 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}" /> <s:Group width="100%" /> <s:Label styleName="titleTextBlack" text="Recording Time:" /> <s:Label styleName="footerText" text="{micTimer}" textAlign="right" /> </s:HGroup>
In the code above, mx:HBox was changed to s:HGroup and the property horizontalGap to gap. Also, mx:Spacer was changed to s:Group with width="100%". The s:HGroup was moved down five pixels because of padding differences.
 
  1. Change the PLAY CONTROLS section of code as follows:
<!-- 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>
In the PLAY CONTROLS section of code, mx:HBox was changed to s:HGroup, mx:Button to s:ToggleButton (removing the toggle property), all mx:Canvas to s:Group, and mx:HSlider to s:HSlider. The playHeadCanvas and playHead components had background colors set onto the mx:Canvas; these were removed and drawn into the Group components directly with Rect components.
 
Note: Drawing directly into the Spark Group component is a departure from the normal skinning practice. I included it here to show another method of doing the same thing. This approach is less robust but not necessarily less valid.
 
The s:HSlider handles the DataTip styling through skins rather than styles as it did before. So you'll have to create a custom skin.
 
  1. Create a new MXML Skin named MyHSliderSkin.mxml in the controls.skins package and copy the code from the complete Flex 4 samples files into it.
To make this skin, I created a copy of the Spark HSlider skin and simply tweaked the DataTip section to be a rounded dark rectangle with light grey text. 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 for the DataTip section of the MyHSliderSkin.mxml skin:
 
<fx:Declarations> <!--- The tooltip used in the mx.controls.Slider control. To customize the DataTip's appearance, create a custom HSliderSkin class.--> <fx:Component id="dataTip"> <s:DataRenderer minHeight="24" minWidth="40" y="-34"> <s:Rect top="0" left="0" right="0" bottom="0" radiusX="2" radiusY="2"> <s:fill> <s:SolidColor color="0x353535" alpha=".9"/> </s:fill> <s:filters> <s:DropShadowFilter angle="90" color="0x000000" distance="2"/> </s:filters> </s:Rect> <s:Label id="labelDisplay" text="{data}" horizontalCenter="0" verticalCenter="1" left="5" right="5" top="5" bottom="5" color="0xdddddd" textAlign="center" verticalAlign="middle" fontWeight="normal" fontSize="11"> </s:Label> </s:DataRenderer> </fx:Component> </fx:Declarations>
Note: The HSlider, RadioButton, and ComboBox CSS styling values use custom images for icon style values. As with the s:Button I covered earlier, I had to do this through skins not styles (or a custom skin that reads styles). The default skins for the above Spark components provides 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.
 
  1. Continue through the rest of the code and apply the same type of changes to the various components. You can refer to the completed sample files if you need to.
One thing to note when switching from mx:ComboBox to s:ComboBox is that the dataProvider doesn't take an Array value; it has to be an IList. I wrapped the Array into an ArrayList that implements IList. Make sure to import the ArrayList at the top of the Script block using import mx.collections.ArrayList;. 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 the PitchDetection custom view

The PitchDetection custom view is updated much like the previous custom component by adding namespaces and changing all the MX components to their Spark equivalents. You'll need to change the main mx:Canvas to s:Group and change the mx:Canvas with styleName="controlsBox" to s:SkinnableContainer with a value of skinClass="controls.skins.ControlsBoxSkin", as in the previous SampleMicPanel component migration. Chalk one up for reuse of skins. Here is code of just the content section of PitchDetection.mxml:
 
<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" />
Refer to the sample Flex 4 version of PitchDetection.mxml for a complete example.
 
You can also remove the styleName="mainPaddedBox" from the PitchDetection component in the MicrophoneExamples.mxml file as it is not needed for this component and those styles will not apply to the s:Group view's top component.
 

 
Converting the InformationPanel custom view

In the InformationPanel component you need to change the mx:VBox to s:VGroup, and change verticalGap to gap. You can remove the styleName="mainPaddedBox" from the InformationPanel component in the MicrophoneExamples.mxml file and remove mainPaddedBox from the main CSS file. This is because the Spark component does not do padding through styles but as properties on the s:VGroup component. Apply the old padding style values as properties (paddingLeft="10" paddingTop="8" paddingRight="10") on the InformationPanel declared in the MicrophoneExamples.mxml file.
 
When you change the mx:Label to s:Label, it will affect the padding and size of the text, so you'll need to change .infoText font-size to 12 in the CSS file. The original mx:HRule was basically a line, so you can remove this component and replace it with an s:Line with weight of 2 for the thickness. Here is the code after all the changes have been made:
 
<?xml version="1.0" encoding="utf-8"?> <s:VGroup xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" gap="8"> <fx:Script> <![CDATA[ [Bindable] public var applicationVersion:String = ""; ]]> </fx:Script> <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" /> </s:VGroup>

 
Converting the InputDeviceSelector custom view

The InputDeviceSelector custom component makes use of the controlsBox style, which means you can reuse the controls.skins.ControlsBoxSkin with s:SkinnableContainer to replace the mx:Canvas. You'll also need to make the fx:Script, s:Label, and s:VGroup changes like before. When you change the mx:ButtonRadioGroup to s:ButtonRadioGroup you'll need to move it to a new fx:Declarations code block to keep it separate from the visual declarations. If you compile after those changes you'll see an error that recommends using addElement instead of addChild. Spark container components have to implement the IVisualElement interface and use the new addElement method. Simple change vbButtons.addChild to vbButtons.addElement to address the compiler error and you're all set. 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" />
Refer to the sample Flex 4 version of InputDeviceSelection.mxml for a complete example.Run the application and enjoy; that's it for the migration from Flex 3 to Flex 4.
 

Where to go from here

Applications are rarely straightforward logic, and migrating an application is not a simple task. One huge advantage in the Flex 3 to Flex 4 migration is the ability to mix and match MX and Spark components, which enables you to perform the conversion of an application gradually over time. For a deep dive into the specific Flex 3 and Flex 4 component differences take a look the Differences between Flex 3 and Flex 4. If you're switching IDEs as well migrating, check out Moving existing Flex projects from Flex Builder 3 to Flash Builder 4.