Creating ActionScript 3.0 components in Flash CS3 ...

anthropologistbarrenSoftware and s/w Development

Jul 4, 2012 (5 years and 19 days ago)

486 views

Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 1
Creating ActionScript 3.0 components in Flash CS3
Professional – Part 5: Styles and skins
Jeff Kamerer
Adobe
Welcome to Part 5 of the article series on creating components using ActionScript 3.0. If
you didn’t get a chance to read the articles leading up to this part, you might want to
begin with Part 1 of the series. On the first page of Part 1 you can download the sample
files for the entire series. Or if you prefer, you can download the sample files for Part 5
to use as a reference as you read this part of the series.
The motivation for adding styles to the MenuBar component is to give the component
user a way to modify the behavior of the
draw()
method without subclassing the
component and overriding the method. After all, creating a component and overriding
the
draw()
method is pretty complex work for a Flash developer who just wants to
update a Button component to be green instead of blue! Styles parameterize the values
used when drawing a component and styles can effect how the pieces of a component
will layout, how the text will be formatted and determine which symbols should be
created for different states of the component.
In this article we’ll see how this is accomplished and also provide you with some best
practices for working with styles when building components.
Working with advanced FLA structure
If you’ve been following along with the previous parts of this article series, you’ll
remember that I placed the TileList and List components on the assets layer of the
MenuBar movie clip. If you open the earlier version of the MenuBar movie clip from the
full set of sample files and double-click either of those component instances, Flash
displays Frame 2. You’ll see where each editable, labeled skin symbol is located on the
Stage. Furthermore, you can click on the CellRenderer movie clip to edit those skins.
For the next part of the component development process, I decided to alter MenuBar
component so that it would have its own editable skin symbols on the Stage, similar to
these other components.
Duplicating skin symbols
First, it was necessary to create new skin symbols for the MenuBar component. I did not
want the MenuBar component to share the skin assets with the TileList and List
components, primarily because I wanted to alter the default look of the MenuBar
component myself—but I also wanted the users of the MenuBar component to be able
to customize the MenuBar skins separately from the skins of the List and TileList
components.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 2
I went into the Component Assets/CellRenderer skin folder and made copies for half of
the symbols. I didn’t copy the selected skins. They were not needed since I set the
selectable
property to false.
Here are the steps I took to make the duplicates of the skin symbols:

First, I right-clicked each movie clip and selected Duplicate in the context menu.

In the Duplicate Symbol dialog box, I changed the symbol name to be prefixed
with MenuBar_ instead of CellRenderer_.

Then after changing the symbol name, I clicked the option to Export for
ActionScript, which filled in the Class field with the symbol name.

I also made sure to uncheck the option to Export on first frame. See the section
on the Export on first frame setting for more details about why this setting is
important.

Finally, I clicked OK.
I repeated the same steps for every symbol in the CellRenderer folder again, but this
time I changed the prefix of the linkage name to Menu_. Then, I moved all of these
duplicate skin symbols into a new folder under Component Assets called
MenuBarSkins. Next, I duplicated List_skin from the ListSkins folder, calling it
Menu_skin, and duplicated TileList_skin from the TileListSkins folder, calling it
MenuBar_skin. Then I moved both of these duplicate symbols into the MenuBarSkins
folder.
9-Slice for skin movie clips
I also edited all of the skin symbols to give them custom designs. I won't go over all the
details of how I changed the skins, but I do want to point out one important thing; while I
was editing them I removed the black border rectangle from all of the Menu_ and
MenuBar_ skin movie clips based on the CellRenderer_ movie clips. Then, I unchecked
the option to Enable guides for 9-slice scaling for each movie clip in the Symbol
Properties dialog box (see Figure 1).
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 3

Figure 1. Deselect the checkbox option to Enable guides for 9-slice scaling
Usually when you are developing components you’ll want to enable the 9-slice scaling
on a skin movie clip, because it allows the component to scale the skin movie clip
without distorting the sides and the corners. So when you create your own skin symbols
you will want to make sure the 9-slice scaling option is checked most of the time. If you
look through the skin symbols for the User Interface components, you will find that the
majority of them use 9-slice scaling. However, since these particular skins are simply
filled rectangles, scaling them will not cause any distortion and the 9-slice scaling is not
needed.

Assets Layer
Next, I began modifying the assets layer of the MenuBar movie clip.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 4
Here are the steps I took to update the assets layer:

I removed the List and TileList components from the Stage. These two
components are no longer needed. In previous iterations of the MenuBar
component I was using these components to pull in their assets, but going
forward I will pull in all the necessary asset symbols directly.

I dragged out all of the skins from the MenuBarSkins folder onto the Stage.

I also placed the focusRectSkin symbol from the Shared folder onto the Stage,
since the MenuBar component (along with many others) will share it.
Asset names layer
The asset names layer will be a guide layer. This is the layer that contains the
background rectangle that sits behind the skin symbols on the Stage and the labels that
explain the skins. The assets names layer should be set as a guide layer because guide
layers are not published to the SWF file and none of the assets in this layer are
necessary at runtime. This layer is locked, since the user of the component does not
need to edit it. In fact, locking the asset names layer is a best practice, because it will
prevent the user from selecting the background rectangle or the labels by mistake.
Here are the steps I took to set up the asset names layer:

First I created a new layer below the assets layer and named it asset names.

Next, I created a blank keyframe on Frame 2.

I locked the assets layer for the moment, so that I would not edit it by mistake.

Then I created the labels and the background rectangle in the same style used in
the other User Interface components.

I right-clicked on the asset names layer in the Timeline and selected Guide in the
context menu.

Then I locked the assets name layer.

And finally, I unlocked the assets layer.
In the next part of this article, we’ll take a look at the componentShim to learn more
about how it works and how to use it.
Understanding the ComponentShim
The ComponentShim provides the precompiled definitions of the User Interface
Component Infrastructure definitions so that the ActionScript source does not need to
be added in the classpath. In our earlier iterations of the MenuBar component, the
precompiled definitions of the User Interface Component infrastructure were included
via the TileList and List components. However, since I’ve removed these components, it
was necessary to include it directly now.
Here are the steps I took to add the ComponentShim to the project:
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 5

First, I created a ComponentShim layer below all the other layers.

Next, I created a blank keyframe on Frame 2 and dragged an instance of the
ComponentShim from the Library onto the Stage.

Then I locked the ComponentShim layer.
Even though the ComponentShim layer does not contain any visible symbols in it, the
layer should not be hidden. You should never hide layers in your component movie clip
because the option to Export hidden layers in the Publish setting could be unchecked in
the component user's FLA file.
The mysterious ComponentShim
It can be a little disorienting working with the ComponentShim on the Stage. The
ComponentShim’s height and width values are set to zero and it is completely invisible;
even when you select it, no selection handles are drawn. You can select it by unlocking
the ComponentShim layer and selecting all. Once it is selected you can at least see its
information displayed in the Property inspector. The good news is that most Flash
developers do not have to interact with the ComponentShim at all. Many will use it
without ever realizing it is there. But even a component developer can be confused by it.
So what is it? What does it do? Why do you need it? And where does it come from?
What is it and what does it do?
The ComponentShim is a compiled clip that has all of the User Interface Component
Infrastructure classes and definitions compiled into it. It does not contain any of the
visual assets, such as the skins, the avatars, etc. The ComponentShim only contains
the compiled ActionScript 3.0 byte code, which is also known as ABC. The
ComponentShim symbol itself is linked to a class called
ComponentShim
, which is an
auto-generated class only used to shim the rest of the definitions into your Library.
Like the other symbols required by the MenuBar component, the ComponentShim
symbol does not have the option Export on first frame checked and it is located on
Frame 2 of the MenuBar movie clip. This ensures that the ComponentShim will be
added into a Flash user's Library at the same time the MenuBar component is dragged
into a FLA file. At the same time, it also ensures that the ComponentShim symbol is
exported into the resulting SWF file. When the ComponentShim symbol is exported into
the SWF file, the empty movie clip and the ComponentShim class are automatically
exported into your SWF file. The empty movie clip and the ComponentShim class are
not really ever needed, but they do not take up many bytes in the SWF file, and
exporting them enables the functionality that is necessary for the MenuBar component
to work successfully.
When a compiled clip is output to a SWF file, whether it is exported because it is on the
Stage or because the option to Export on first frame is checked, all of the ABC
definitions within that compiled clip become available to the ActionScript 3.0 compiler.
The ABC definitions can also be output to the SWF file, although they will not
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 6
necessarily be output. An ABC definition will only be exported to the SWF file if it is
referenced by some ActionScript definition that is being exported or if it is linked to an
exported symbol as set in the Linkage dialog box. In other words, even though the
ComponentShim contains every class definition for every component, only the classes
that are required by the components that you use will be included in your SWF file.
Here’s another way to think about it. The ABC definitions in the ComponentShim are
added to your classpath. Just as the definitions in ActionScript files are only compiled in
if they are needed, ABC definitions are only linked in if they are needed. As I discussed
in an earlier sidebar
, all ActionScript source files in your classpath are always checked
for definitions before a precompiled ABC definition from a compiled clip would be used.
If you are familiar with the Adobe Flex 2 compiler, you can think of exporting the
ComponentShim for the User Interface components as very similar to having
framework.swc in your build path for the Flex 2 components.
Why do you need the ComponentShim?
Using precompiled ABC definitions makes SWF publishing much faster when the
ActionScript source files are not in the classpath. The User Interface component source
is included for reference and for component development, and there are situations when
you may need them in your classpath, but they are not in the default classpath.
As part of your component development, creating compiled clips like the
ComponentShim enable you to provide FLA-based components without distributing the
source files.
Where did the ComponentShim come from?
The FLA file used to generate the ComponentShim, ComponentShim.fla, is installed
with Flash CS3. You can find it in the following location:
Windows Macintosh
C:\Program Files\Adobe\Adobe Flash
CS3\language\Configuration\Component
Source\ActionScript 3.0\User Interface\
/Applications/Adobe Flash
CS3/Configuration/Component
Source/ActionScript 3.0/User
Interface/
If you make any custom edits to the User Interface Component Infrastructure code, you
can recreate ComponentShim and replace it in your FLA files.
Here are the steps I took to recreate ComponentShim:

First, open ComponentShim.fla.

Right-click the Library symbol ComponentShim source and select Convert to
Compiled Clip from the context menu.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 7

Rename the resulting compiled clip from the ComponentShim source SWF to
ComponentShim.

Next, open the Linkage dialog box for the ComponentShim compiled clip and
uncheck the option to Export on first frame.

Drag ComponentShim into the Component Assets/_private folder in the Library of
your FLA file.

In the Resolve Library Conflict dialog box, select the option to Replace existing
items and click OK. If you do not see this dialog box, it means that you did not
drag ComponentShim into the _private folder correctly.

Finally, close the ComponentShim.fla without saving changes.
You can also generate the ComponentShim by exporting the ComponentShim source
as an SWC file and dragging the file over from the Components panel. However, I do
not recommend this approach, because when you use this method of generating the
ComponentShim it shows up as a component under the User Interface folder. It is a
best practice to use the steps outlined above.
If you regenerate the ComponentShim, you will need work carefully, because every time
you drag a component from the Components panel the ComponentShim from the
component will write over your custom ComponentShim. If you are concerned, you can
replace ComponentShim in the User Interface.fla file to avoid this problem.
Creating a compiled clip like the ComponentShim is a technique you can use in your
own components. The instructions for doing this are detailed in the section titled
Creating a Shim Compiled Clip.
Setting the Edit frame
To take a user straight to Frame 2 where all the skins are, (instead of Frame 1), I
opened the Component Definition dialog box again by right-clicking the MenuBar movie
clip in the Library and selecting Component Definition from the context menu. The only
change I made was updating the Edit frame text field to 2 (see Figure 2).
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 8

Figure 2. In the Component Definition dialog box, change the Edit frame field from 1 to
2
After I made these changes, I opened the MenuBar component again and the Timeline
view reflected the recent updates (see Figure 3).
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 9
Figure 3. The MenuBar FLA file displays as expected after changing the Edit frame to
Frame 2

Library Cleanup
At this point in my development process, I had accumulated a large quantity of symbols
in the Library that were not necessary. I thought it was time for some housekeeping, so I
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 10
went through the Library and deleted them. If you are unsure which symbols are needed
for your project, here’s a good method for identifying them: Create a brand new FLA file,
then drag the MenuBar component on to the Stage and note which symbols are
imported with it. Any symbol in the Library that is not imported into the new FLA file is
not used for the MenuBar component.
Here’s the list of the unnecessary symbols I deleted for this project:

TileList

List

Component Assets/_private/Component_avatar

Component Assets/CellRenderer

All symbols in the Component Assets/CellRendererSkins folder

Component Assets/ListSkins/List_skin

Component Assets/ScrollBar

All symbols in the Component Assets/ScrollBarSkins folder

Component Assets/Shared/arrowIcon

Component Assets/TileListSkins/TileList_skin
After doing this cleanup, I reviewed the list of symbols in my Library (see Figure 4).
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 11

Figure 4. After deleting the unneeded symbols, the Library of MenuBar.fla contained a
more manageable list of items
Now it was time to update the Library of test.fla. Before dragging the new version of the
MenuBar component into the Library of test.fla, I cleaned up that Library by deleting the
entire Component Assets folder, the List component and the TileList component.
Completing this step assured me that the assets I just deleted from the Library of
MenuBar.fla did not linger in the Library of test.fla. When I dragged the MenuBar
component over from MenuBar.fla, the Component Assets folder reappeared in the
Library of test.fla. Since I previously used the Button component in test.fla and it also
contained assets in the Component Assets folder, I dragged the Button component back
into the Library from the Components panel to recreate its asset symbols in the Library
of test.fla.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 12
Finally, I had to do some troubleshooting in test.fla to get the code working as desired.
After making all of these changes to the Library, running Control > Test Movie resulted
in many, many runtime errors!
MenuBarTileList, MenuList and NoScrollBar
The removal of the ScrollBar skins caused some problems. To resolve the issues, I
created three classes in the package
fl.example.menuBarClasses
:
MenuBarTileList
,
MenuList
and
NoScrollBar
. The only changes required in the
MenuBar
code to use these
classes was to create a new
MenuBarTileList
instance instead of a
TileList
instance in
configUI(),
and to create a new
MenuList
instance instead of a
List
instance in
createMenu()
. I discuss these three classes in depth in the sidebar titled Replacing
Display Objects in configUI().
Using the StyleManager class
The class
fl.manager.StyleManager
defines the StyleManager, which manages all
styles on all component instances. In order to use styles, one of the first things a
component needs to do to is call
StyleManager.registerInstance(this)
—which
initializes all styles on the instance and also registers the instance with the
StyleManager so that it will update when the styles are changed. Styles can be changed
globally, per component type or per component instance. It is not necessary to write any
code to register with the StyleManager because the
UIComponent
constructor does it for
you.
The StyleManager's main task is managing the different levels of styles to ensure that
each component instance uses the correct styles. For example, there are default styles
(predefined by the component’s ActionScript code) and there are customized styles,
which the component user defines with ActionScript. There are four levels of styles, and
they are listed below in the following order of priority, from highest to lowest:

Customized instance level styles

Customized component level styles

Global styles

Default component level styles
Developers using components can customize styles at three different levels: the
instance level, the component level and the global level. There are symmetric APIs to
set, clear and get styles at each level.
Note that since
StyleManager
is a class, the code
StyleManager.setStyle()
calls a
static method on a class. This means that you must include
import
fl.managers.StyleManager
in your code to avoid compile errors.
Here’s a list of the methods you can use to set, get or clear styles:
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 13
• instance.setStyle(styleName, styleValue)

• instance.clearStyle(styleName)

• instance.getStyle(styleName)

• StyleManager.setComponentStyle(componentClass, styleName, styleValue)

• StyleManager.clearComponentStyle(componentClass, styleName)

• StyleManager.getComponentStyle(componentClass, styleName)

• StyleManager.setStyle(styleName, styleValue)

• StyleManager.clearStyle(styleName)

• StyleManager.getStyle(styleName)

All of the set methods take a string name and a value parameter. Each component
understands a predefined list of styles. The style value parameter passed into the set
methods is
Object
so that a value of any type (
Boolean
,
Number
,
Class
,
Object
,
flash.text.TextFormat
, etc.) can be passed in. Depending on the style being set, a
specific type is required. All of the styles supported by each component and all of the
types required for each style are listed in the online ActionScript 3.0 Language and
Components Reference
.
Generally speaking, the process you would use involves calling a clear method to undo
a previous setting to a set method, and this acts as though the style for that instance or
component had never been customized. Similarly, the get methods return the value set
at that level. To get the active style for an instance based on the settings for all style
levels and the priorities of those levels, call the method
getStyleValue()
.
Understanding the different levels of styles
In the previous section of this series, we looked at the available methods for setting,
getting or clearing styles. Now let's take a closer level at the style levels and the APIs
that operate on them. I’ll describe these in order of priority level, beginning with the
lowest priority.
Default component styles
Each component has default styles, which are defined by the return value of
getStyleDefinition()
for the component class. If a component class does not
implement the static method
getStyleDefinition(),
then the StyleManager walks up
its inheritance chain, starting with its base class, until it finds an implementation. The
object returned from this call defines both the list of styles that the component uses and
the default values for these styles. When a style from this list is updated, StyleManager
will update every instance of this component, causing an invalidation. The
StyleManager keeps track of the settings and it will not notify a component instance
when a style updates that it does not use.
The set, get and clear methods for component styles do not operate on the default
component style level. There are in fact no set, get and clear methods that operate on
the default component styles. The default component styles are immutable and cannot
be changed by the component user’s code.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 14
To see how this works, let’s try the following example. This illustrates how the get and
clear component level methods do not interact with the default component styles. Drag
a Button component onto the Stage and put the following code on Frame 1:
import fl.managers.StyleManager;
import fl.controls.Button;
trace(StyleManager.getComponentStyle(Button, "textFormat"));
StyleManager.clearComponentStyle(Button, "textFormat");
Test Movie traces
null
to the Output panel and the Button’s appearance displays the
default values. The
getComponentStyle()
method call returned
null
because there isn’t
a customized component level style, and the
clearComponentStyle()
method call did
not alter the display of the Button component for the same reason.
Global styles
The
StyleManager.setStyle()
method sets styles globally for all components. For this
example, drag two Button components and a Checkbox onto the Stage. Then, put the
following code on Frame 1 to see all three instances displayed with italic labels:
import fl.managers.StyleManager;
var tf:TextFormat = new TextFormat();
tf.italic = true;
StyleManager.setStyle("textFormat", tf);
The previous example changed one of the default global styles initialized by
UIComponent.getStyleDefinition()
, but any style, including a new style used by your
custom component, can be set at the global level, and when it is updated every
component that uses that style will be updated. For example, the Button, RadioButton
and CheckBox components all use the style named upIcon, but have different default
values. Drag each of these three components to the Stage and put the following code
on Frame 1:
import fl.managers.StyleManager;
StyleManager.setStyle("upIcon", RadioButton_upIcon);
In contrast to component level styles, the StyleManager does not track customized and
default global styles separately. So calling
StyleManager.getStyle()
will return values
set by calls to
StyleManager.setStyle()
and will also return the default values initialized
by
UIComponent.getStyleDefinition()
. Calls to
StyleManager.clearStyle()
are
similarly able to clear styles set by the user and can also clear out the default values.
For example, drag out a Button component onto the Stage and add the following code
to Frame 1. When you select Control > Test Movie, the SWF file will trace the default
value, 2, to the Output panel:
import fl.managers.StyleManager;
trace(StyleManager.getStyle("focusRectPadding"));
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 15
The result of clearing the focusRectSkin style is an interesting situation that occurs
because there are no separate customized global styles and default global styles levels.
All components that use the focusRectSkin style set it to
null
in their
defaultStyles

variable to register it with the StyleManager for updates to that style. The reason the
value is always set to
null
is because the global style value has precedence over the
default component level style, so the default component level style would never be used
in a normal situation. So, if the user clears the global focusRectSkin, there are no
component default levels to fall back on. Component instances will not have a valid
focusRectSkin, resulting in runtime errors every time focus tabs from one component to
another. To see this effect in action, drag a couple of Button component instances onto
the Stage. Then put the following code on Frame 1. When you select Control > Test
Movie make sure that the keyboard shortcuts are disabled under the Control menu:
import fl.managers.StyleManager;
StyleManager.clearStyle("focusRectSkin");
You could avoid this situation by setting the styles at the component level or at the
instance level. For example, the following code would work if you’ve only dragged
Button component instances onto the Stage, but the same code will break if a
CheckBox is also placed on the Stage:
import fl.managers.StyleManager;
import fl.controls.Button;
StyleManager.clearStyle("focusRectSkin");
StyleManager.setComponentStyle(Button, "focusRectSkin", "focusRectSkin");
Finally, while the StyleManager does not track default global styles, the defaultTextField
and defaultDisabledTextField are special styles that are treated as default global styles.
They are only used when the effective value for textField or disabledTextField for a
component instance is
null
. As you can see in the code snippet in the section of this
article on TextField Styles
, the component code always calls
UIComponent.getStyleDefinition()
to get these values, so customizing their values in
the StyleManager has no effect.
Customized component styles
The
StyleManager.setComponentStyle()
method sets the custom style for all instances
of a particular component. For example, to put italic labels on two Button component
instances and leave a plain text label on a CheckBox component, drag two Button
components and one CheckBox component to the Stage. Then put the following code
on Frame 1:
import fl.managers.StyleManager;
import fl.controls.Button;
var tf:TextFormat = new TextFormat();
tf.italic = true;
StyleManager.setComponentStyle(Button, "textFormat", tf);
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 16
As I mentioned in the default component styles section of this article, the get and clear
methods for component styles only operate on the customized styles and do not get or
clear default component styles.
When component styles are customized for a class, they are customized for the
component implemented by that class—but not for any components that inherit from
that class. For example, if you created a custom component with a class named
MyButton
that extended
Button
, when a user set custom component styles for
Button
, it
would not affect instances of your component. A user would need to specify
MyButton

when customizing component styles to affect instances of your component.
Customized instance styles
The
setStyle()
method called on an instance sets a custom style for a single instance.
For example, to make the label of one Button component display with italic text and
leave one label displaying normal text, drag two Button component instances onto the
Stage. Then, give one of the buttons the instance name myButton in the Property
inspector and put the following code on Frame 1:
var tf:TextFormat = new TextFormat();
tf.italic = true;
myButton.setStyle("textFormat", tf);
Calling
getStyle()
on an instance before calling
setStyle()
for the same style will
always return
null
. Use the protected method
getStyleValue()
to get the proper active
style for an instance, and use
getStyle()
to learn whether the style is customized at the
instance level.
Similarly, calling
clearStyle()
on an instance before calling
setStyle()
for the same
style does nothing. Clearing a custom instance level style is only useful once it has
been set.
An example of setting multiple style levels
The priority of different types of styles is explained in the StyleManager section. To see
how these three different ways of setting components interact with one another, drag
two Button components onto the Stage. Then, give one of them the instance name
myButton in the Property inspector. Next, drag a RadioButton and a CheckBox
component onto the Stage and put the following code on Frame 1:
import fl.managers.StyleManager;
import fl.controls.CheckBox;
var globalTF:TextFormat = new TextFormat();
globalTF.italic = true;
StyleManager.setStyle("textFormat", globalTF);
var myButtonTF:TextFormat = new TextFormat();
myButtonTF.color = 0xFF0000;
myButton.setStyle("textFormat", myButtonTF);
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 17
var checkBoxTF:TextFormat = new TextFormat();
checkBoxTF.bold = true;
StyleManager.setComponentStyle(CheckBox, "textFormat", checkBoxTF);
When you select Control > Test Movie you will see one Button component with a red
label, another Button component with an italic label, a RadioButton component with an
italic label and a CheckBox component with a bold label.
Implementing getStyleDefinition() and UIComponent methods
The first step I took to ensure that styles were supported in
MenuBar
was to implement
the static method
getStyleDefinition()
. The first time the StyleManager encounters a
type of component, it calls
getStyleDefinition()
on that component's class to get the
default styles for the component. The static method
getStyleDefinition()
returns an
object with style names and default values. Every style that a component uses must
appear on this object, because the StyleManager assumes that if a style is not in this
list, then instances of this component do not need to be updated when that style
changes.
MenuBar
implements
getStyleDefinition()
in the standard way, returning a private
defaultStyles
static object.
private static var defaultStyles:Object = {
// styles set on the TileList
menuBarCellRenderer: fl.example.menuBarClasses.MenuBarCellRenderer,
menuBarSkin:"MenuBar_skin",
menuBarContentPadding:1,

// styles set on menu Lists
menuCellRenderer: fl.example.menuBarClasses.MenuCellRenderer,
menuSkin:"Menu_skin",
menuContentPadding:1,

// both List and TileList support disabledAlpha, but we will never show
// a drop-down menu when the MenuBar is disabled, so we don't put a
prefix
// on it and only pass it through to the TileList
disabledAlpha:0.5,

// These styles are in the list of default global styles defined by
// UIComponent.getStyleDefinition(). We list them to tell the
StyleManager
// that MenuBar uses these styles. We leave the values as null because
// the global styles defined by UIComponent would override the default
// component style anyways
focusRectSkin: null,
focusRectPadding: null
};

public static function getStyleDefinition():Object {
return defaultStyles;
}
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 18
I got this specific list of styles by simply mimicking the styles supported by
List
and
TileList
, minus the
ScrollBar
skins. I made this list of skins by looking at the
defaultStyles
declarations for
List
and
TileList
, and then I also reviewed their
superclasses,
SelectableList
and
BaseScrollPane
; this was actually easier than
looking at the documentation, because there were so many
ScrollBar
skins to weed
out. I made two sets of styles: one for the menu bar and one for the drop-down menus.
UIComponent.mergeStyles()
I created two new classes, both extending
CellRenderer
to change its default styles.
The cell renderer styles for
MenuBar
default to these classes:
MenuBarCellRenderer
and
MenuCellRenderer
. I put these classes in the
fl.example.menuBarClasses
package,
following the same pattern the User Interface components use. Generally speaking,
auxiliary classes for a specific component should be put into a package following this
naming convention. These two classes are very similar, so I'll just look at one of them.
package fl.example.menuBarClasses {

import fl.core.UIComponent;
import fl.controls.listClasses.CellRenderer;

public class MenuCellRenderer extends CellRenderer {

public function MenuCellRenderer() {
super();
}

private static var defaultStyles:Object = {
upSkin:"Menu_upSkin",
downSkin:"Menu_downSkin",
overSkin:"Menu_overSkin",
disabledSkin:"Menu_disabledSkin"
};

public static function getStyleDefinition():Object {
return UIComponent.mergeStyles(defaultStyles,
CellRenderer.getStyleDefinition());
}

}

}
The
getStyleDefinition()
implementation by
MenuCellRenderer
uses the static
mergeStyle()
method defined in
UIComponent
. The
mergeStyles()
method takes two or
more objects and puts all name/value pairs found in any of the objects into a single
object, which is returned. If multiple objects define the same name/value pair, then the
value found on the first object is used unless it is
null
. So it was critical that I passed
defaultStyles
as the first parameter into
mergeStyles()
, because I was overriding
styles that are also defined by
CellRenderer
.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 19
While usually it makes sense to merge in the styles of the base class, in some cases
you may not and in some cases you may merge in some other styles. For example,
CheckBox
and
RadioButton
simply return
defaultStyles
without merging in
LabelButton.getStyleDefinition()
. Another example is the
BaseScrollPane
, which
merges in the styles from
ScrollBar
, which is not a superclass but is instead a
subcomponent of
BaseScrollPane
.
UIComponent.getStyleDefinition() and Global Styles
The StyleManager calls
UIComponent.getStyleDefinition()
to initialize the global
styles level.
Here’s a list of the global styles that are initialized by
UIComponent
:

focusRectSkin

focusRectPadding

textFormat

disabledTextFormat

defaultTextFormat

defaultDisabledTextFormat
You should never merge the return value of
UIComponent.getStyleDefinition()
into
your component's styles definition. Instead, you should add any global style your
component uses to your
defaultStyles
variable with a value of
null
. You should never
add the style defaultTextFormat or defaultDisabledTextFormat to your component's list
of styles. I added two global styles initialized by
UIComponent
to the
defaultStyles
of
MenuBar
: focusRectSkin and focusRectPadding.
Calling getStyleValue() via the draw() method
When your
draw()
method needs to get a style's value, it should call the protected
method
getStyleValue()
. It returns the proper style, whether per instance, per
component or global, for the component instance as determined by the StyleManager.
There is also a public
getStyle()
method, so be sure not to get the two confused; it
returns a value if the instance's style has been customized, otherwise
getStyle()

returns
null
.
The
draw()
method for
MenuBar
is pretty simple since it just gets the styles and passes
them on to the proper subcomponents. There were also some other changes to the
draw()
method that altered the layout code to account for the content padding style, but
I won't cover those changes in detail. Very similar code was added for forwarding styles
to the menu bar
TileList
instance and the drop-down menu
List
instances. The
following code was added near the beginning of the
draw()
method to set up the
TileList
instance:
// set styles
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 20
myMenuBar.setStyle("skin", getStyleValue("menuBarSkin"));
myMenuBar.setStyle("cellRenderer", getStyleValue("menuBarCellRenderer"));
var menuBarPadding:Number = getStyleValue("menuBarContentPadding") as Number;
myMenuBar.setStyle("contentPadding", menuBarPadding);
myMenuBar.setStyle("disabledAlpha", getStyleValue("disabledAlpha"));
getDisplayObjectInstance()
Most components do not exclusively forward styles to subcomponents, but instead
create skin instances in their
draw()
methods—and to do this they use
getDisplayObjectInstance()
. A great example of this is the
drawBackground()
method
from
BaseButton
, which is called by the
draw()
method.
protected function drawBackground():void {
var styleName:String = (enabled) ? mouseState : "disabled";
if (selected) { styleName =
"selected"+styleName.substr(0,1).toUpperCase()+styleName.substr(1); }
styleName += "Skin";
var bg:DisplayObject = background;
background = getDisplayObjectInstance(getStyleValue(styleName));
addChildAt(background, 0);
if (bg != null && bg != background) { removeChild(bg); }
}
First, the code above includes some logic with the current mouse state, enabled state
and selected state to determine the style name for the correct skin. It then caches the
current
background
instance in the
bg
variable and calls
getDisplayObjectInstance(getStyleValue(styleName))
to get the correct instance.
Your code does not have to worry about how the skin instance is created; this method
just does the right thing. It adds the new
background
instance to the display list at the
back (of course). Finally, before it removes the cached
bg
instance from the display list,
it checks to make sure it is not the same exact instance that was just added. It is not
common, but there are situations where a call to
getDisplayObjectInstance()
could
return the same instance you were already using for a skin, so as a best practice you
should always handle this case.
Skin styles
Skin styles are all declared to take the type
Class
, but the reality is a bit more
complicated than that, which is why you should always use
getDisplayObjectInstance()
to get the proper
DisplayObject
instance for a skin. A
valid value for a skin style is not in fact only a
Class
instance, but can be any of the
following three types:
• Class

• String

• flash.display.DisplayObject

Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 21
A
Class
parameter specifies the class to be instantiated to create the skin. The class's
constructor must accept zero arguments. For example, if you had a symbol in your
Library called MyPurpleFocusRectSkin and it was exported for ActionScript as
MyPurpleFocusRectSkin
, then you could call
StyleManager.setStyle("focusRectSkin",
MyPurpleFocusRectSkin)
to set the focusRectSkin style for all components to use your
symbol. (You can try this by duplicating the Component Assets/Shared/focusRectSkin
symbol and using that code. Remember that if you are in Test Movie you must disable
keyboard shortcuts under the Control menu in order to tab among components and see
the focus rect behavior.)
A
String
parameter also specifies the class for the skin symbol, but as a string instead
of a direct reference to the class. All default skin styles in the User Interface
components use strings, and all your default skin styles should do the same. It is
important to ensure your component’s default skin styles use strings, because otherwise
the user of your component might delete some of the default skin symbols from the
Library—in which case they would encounter a runtime or a compile error. (The type of
error depends on whether the class for the removed skin symbol was automatically
generated or not and whether the component source was compiled from source or
provided precompiled, as it is with the ComponentShim.)
A
DisplayObject
parameter specifies a specific instance to be used as a skin. The
DisplayObject
instance passed into
setStyle()
could be a movie clip that you placed
on the Stage and assigned an instance name in the Property inspector, or it could be an
instance created dynamically with code like
new MyPurpleFocusRectSkin()
, or it could
even be an instance created with the Drawing API. The following code demonstrates
how to create a gradient ellipse with the Drawing API and use it as a Button component
instance's upSkin:
var skinSprite:Sprite = new Sprite();
var matrix:Matrix = new Matrix();
matrix.createGradientBox(100, 22);
skinSprite.graphics.beginGradientFill(GradientType.LINEAR, [0xFF0000,
0x00FF00, 0x0000FF], [1, 1, 1], [0, 128, 255], matrix);
skinSprite.graphics.drawEllipse(0, 0, 100, 22);
skinSprite.graphics.endFill();
theButton.setStyle("upSkin", skinSprite);
There is a significant restriction on
DisplayObject
style values for skins, which is that
they can only be applied to a component instance with the syntax
instance.setStyle(styleName, styleValue)
and they can never be used as global or
component styles. This restriction occurs because it is not possible for multiple
instances to share a single skin instance. For example, if you try to set the global style
for the Button upSkin with an instance on the Stage using the code
StyleManager.setStyle("upSkin", mySkinInstance)
, you’ll discover that if you have
two Button components on the Stage, only one button will draw correctly and the other
will be missing its upSkin. One notable exception to this restriction is the focusRectSkin;
since only one component has the focus at any given time, a single instance can be
shared by all components on the Stage.
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 22
Examining renderer styles
The components based on
SelectableList
—DataGrid, List and TileList—support
renderer styles. Renderer styles can only be set and cleared at the instance level with
the methods
setRendererStyle()
and
clearRendererStyle()
. Renderer styles allow the
component user to set styles on all cell renderers for a component instance. For
example, to make the labels in each cell of a List component display with italic text, you
can drag a List component onto the
Stage, give it the instance name myList in the Property inspector and put the following
code on Frame 1:
import fl.data.DataProvider;
var dp:DataProvider = new DataProvider();
dp.addItem({label: "one"});
dp.addItem({label: "two"});
dp.addItem({label: "three"});
dp.addItem({label: "four"});
myList.dataProvider = dp;
var tf:TextFormat = new TextFormat();
tf.italic = true;
myList.setRendererStyle("textFormat", tf);
It is important to note that the style cellRenderer determines which class does the cell
rendering, which is not set with the
setRendererStyle()
method, but using the ordinary
methods of setting styles at the instance, component or global level. The cellRenderer
style should always be set to a class which implements
fl.controls.listClasses.ICellRenderer
. The value of the cellRenderer style
determines what styles are available via
setRendererStyle()
.
MenuBar renderer styles
To support customizations of the cell renderers for the menu bar and for the drop-down
menus, I implemented the following four public methods:
• setMenuBarCellRenderer()

• clearMenuBarCellRenderer()

• setMenuCellRenderer()

• clearMenuCellRenderer()

The implementation of these methods was largely copied from
SelectableList
. A case-
insensitive search through SelectableList.as on "rendererstyle" found everything I
needed. To implement most of the changes I could have just copied the code directly,
but it was necessary to use search and replace to change rendererStyle to
menuBarRendererStyle and also to menuRendererStyle. The biggest change was that
rather than actually calling
setStyle()
on the individual
ICellRenderer
instances, the
MenuBar
version only has to call
setRendererStyle()
on the
TileList
instance and the
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 23
List
instances. The code example below shows how this works. I’ve only included the
changes for the menu renderer styles, since the changes are so similar:
// support for setMenuRendererStyles(), etc
protected var menuRendererStyles:Object;
protected var updatedMenuRendererStyles:Object;

public function MenuBar() {
...

// init renderer styles info
menuRendererStyles = new Object();
updatedMenuRendererStyles = new Object();
}

...

override protected function draw():void {
...

// if we have menus, then we set up the rowHeights, heights, widths and
locations of everything
if (myMenus.length > 0) {
// distribute the menus evenly across the menu bar
myMenuBar.columnWidth = ((myMenuBar.width - (menuBarPadding * 2) - 1)
/ myMenus.length);
// get menu styles
var menuSkin:Object = getStyleValue("menuSkin");
var menuCellRenderer:Object = getStyleValue("menuCellRenderer");
var menuPadding:Number = getStyleValue("menuContentPadding") as
Number;
// update all the renderer styles before we call drawNow() on each
updateMenuRendererStyles();
...
}
...
}

...

public function setMenuRendererStyle(name:String, style:Object):void {
if (menuRendererStyles[name] == style) { return; }
updatedMenuRendererStyles[name] = style;
menuRendererStyles[name] = style;
invalidate(InvalidationType.RENDERER_STYLES);
}

public function getMenuRendererStyle(name:String):Object {
return menuRendererStyles[name];
}

public function clearMenuRendererStyle(name:String):void {
delete menuRendererStyles[name];
updatedMenuRendererStyles[name] = null; // Do not delete, so it can clear
the style from current renderers.
invalidate(InvalidationType.RENDERER_STYLES);
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 24
}

protected function updateMenuRendererStyles():void {
for (var i:int=0; i < myMenus.length; i++) {
var theMenu:List = myMenus[i] as List;
for (var n:String in updatedMenuRendererStyles) {
theMenu.setRendererStyle(n, updatedMenuRendererStyles[n]);
}
}
updatedMenuRendererStyles = {};
}
While testing the movie and clicking on the various buttons, I discovered a problem with
my menu cell renderers. The changes to the display updated after I applied them,
however, if I applied any other change, (like a size change, data change or style
change) the menu cell renderer styles stopped working. After performing a little
debugging, I realized my issue was occurring because when all of the
List
instances
are destroyed, it is necessary to force all of the renderer styles to be set on the new
list
instances.
To resolve this issue, I made the following change to the code:
protected function clearMenus():void {
closeMenuBar();
myMenuBar.dataProvider = new DataProvider();
while (myMenus.length > 0) {
var theMenu:List = (myMenus.shift() as List);
removeChild(theMenu);
}
// This line forces all renderer styles to be refreshed on
// the drop-down menu Lists, which will be necessary
// since brand new ones will be created
updatedMenuRendererStyles = menuRendererStyles;
}
I am pointing out this particular debugging experience to illustrate how important it is to
work with these simple test buttons in test.fla. It is essential that you keep testing the
movie and keep trying to break it in order to discover errors that are easy to make but
hard to catch. Make sure to create some test cases that alter the styles of your
components after initialization!
Working with TextField styles
If your component uses
TextField
instances, it should follow the best practices for
TextField
styles to format these instances. By supporting consistent
TextFormat
styles,
your component will be able to share text-formatting changes with all of the
components. Components that use
TextField
instances support the following styles:

textFormat

disabledTextFormat (optional)
Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 25

embedFonts
The textFormat and disabledTextFormat styles accept a
flash.text.TextFormat

instance and are set on the
TextField
instances with the
setTextFormat()
method. The
disabledTextFormat style is optional because some components do not display text at
all when disabled, (such as the ColorPicker component), and some do not display text
differently when disabled, (such as the Label component). The embedFonts style
accepts a Boolean value and sets the
embedFonts
property on the
TextField
instances.
UIComponent
also defines the styles defaultTextFormat and defaultDisabledTextFormat,
which your component should fall back on if textFormat or disabledTextFormat is
null
.
When applying these properties, you should use code very similar to the following code
from
fl.controls.TextInput
:
protected function drawTextFormat():void {
// Apply a default textformat
var uiStyles:Object = UIComponent.getStyleDefinition();
var defaultTF:TextFormat = enabled ? uiStyles.defaultTextFormat as
TextFormat :
uiStyles.defaultDisabledTextFormat as TextFormat;
textField.setTextFormat(defaultTF);

var tf:TextFormat =
getStyleValue(enabled?"textFormat":"disabledTextFormat") as TextFormat;
if (tf != null) {
textField.setTextFormat(tf);
} else {
tf = defaultTF;
}
textField.defaultTextFormat = tf;

setEmbedFont();
if (_html) { textField.htmlText = _savedHTML; }
}

protected function setEmbedFont() {
var embed:Object = getStyleValue("embedFonts");
if (embed != null) {
textField.embedFonts = embed;
}
}
Many components use code very similar to this, and most of it is boilerplate that you can
copy and paste. Your component must have a
textField
property to which to apply the
format. Also this code uses some private properties,
_html
and
_savedHTML
. If you allow
the
htmlText
of the
textField
to be set (some components, like the Button component,
do not) then you will need code similar to this. This is because calling
setTextFormat()

replaces all previous formatting on the
TextField
instance, including HTML formatting,
and setting the
htmlText
property again reapplies the HTML formatting.

Creating ActionScript 3.0 components in Flash CS3 Professional –
Part 5: Styles and skins 26
Where to go from here
In the next part of this article series we’ll take a look at the invalidation model. We’ll
discuss how to maximize performance of the MenuBar component by consolidating the
events received (mouse events, frame scripts, whenever a new dataProvider is set or
whenever properties are updated) so that the changes to the component are updated at
once. I’ll also cover the different types of invalidation and how they work with the draw()
method to limit the amount of updates made to the component.
If this article has sparked your interest and you’d like to learn more about customizing
the look and feel of ActionScript 3.0 components, be sure to check out these useful
articles to get more details:

Skinning the ActionScript 3.0 FLVPlayback component

Designing Flex 2 skins with Flash, Photoshop, Fireworks or Illustrator



About the author
Jeff Kamerer is a computer scientist at Adobe Systems who has worked on the
Flash authoring team since 2002.