Converting Flex 3 Microphone application to Flex 4 (part 2)
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:
<!--
////////////////////////////////////////////////////////////////////////////////
//
// 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:
<!--
////////////////////////////////////////////////////////////////////////////////
//
// 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:
<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:
{
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:
<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:
{
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
.
Pingback: Renaun's thoughts on Converting Flex 3 Microphone application to Flex 4 (part 1)
Pingback: Renaun's thoughts on Converting Flex 3 Microphone application to Flex 4 (part 3)