Adobe
Products
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
Student and Teacher Editions
More products
Solutions
Creative tools for business
Digital marketing
Digital media
Education
Financial services
Government
Web Experience Management
More solutions
Learning Help Downloads Company
Buy
Home use for personal and home office
Education for students, educators, and staff
Business for small and medium businesses
Licensing programs for businesses, schools, and government
Special offers
Search
 
Info Sign in
Welcome,
My cart
My orders My Adobe
My Adobe
My orders
My information
My preferences
My products and services
Sign out
Why sign in? Sign in to manage your account and access trial downloads, product extensions, community areas, and more.
Adobe
Products Sections Buy   Search  
Solutions Company
Help Learning
Sign in Sign out My orders My Adobe
Preorder Estimated Availability Date. Your credit card will not be charged until the product is shipped. Estimated availability date is subject to change. Preorder Estimated Availability Date. Your credit card will not be charged until the product is ready to download. Estimated availability date is subject to change.
Qty:
Purchase requires verification of academic eligibility
Subtotal
Review and Checkout
Adobe Developer Connection / Flash Developer Center /

Creating ActionScript 3.0 components in Flash – Part 5: Styles and skins

by Jeff Kamerer

Jeff Kamerer

Content

  • Working with advanced FLA structures
  • Understanding the ComponentShim
  • Using the StyleManager class
  • Understanding the different levels of styles
  • Implementing getStyleDefinition() and UIComponent methods
  • Calling getStyleValue() via the draw() method
  • Examining renderer styles
  • Working with TextField styles

Created

9 June 2008

Page tools

Share on Facebook
Share on Twitter
Share on LinkedIn
Bookmark
Print
9-slice ActionScript best practice components Flash Professional styling

Requirements

Prerequisite knowledge

To get the most from this article series, you should be familiar with Flash Professional, including how to manipulate the Timeline, the Property inspector, the Components panel, the Components inspector, the Library, and the Actions panel.

User level

Beginning

Required products

  • Flash Professional (Download trial)

Sample files for Part 5

  • component_styles_skins.zip (714 KB)

Welcome to the fifth part of this article series on creating components with ActionScript 3.0. 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 affect 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.

For your reference, here are all the parts in this series:

  • Part 1: Introducing components
  • Part 2: MenuBar component prototype
  • Part 3: From prototype to component
  • Part 4: Events
  • Part 5: Styles and skins
  • Part 6: Invalidation model
  • Part 7: Focus management
  • Part 8: Keyboard support
  • Part 9: Shim compiled clip

Working with advanced FLA structures

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.

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:

  1. Right-clicked each movie clip and selected Duplicate in the context menu.
  2. In the Duplicate Symbol dialog box, I changed the symbol name to be prefixed with "MenuBar_" instead of "CellRenderer_".
  3. Only after changing the symbol name, I clicked the option to Export for ActionScript, which filled in the Class field with the symbol name.
  4. Made sure to uncheck the option to Export on first frame. See the section on "Export on first frame setting" for more details about why this setting is important.
  5. 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).

Deselecting the check box option to Enable guides for 9-slice scaling
Figure 1. Deselecting the check box 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. Here are the steps I took to update the assets layer:

  1. 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.
  2. Dragged out all of the skins from the MenuBarSkins folder onto the Stage.
  3. 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:

  1. Created a new layer below the assets layer and named it asset names.
  2. Created a blank keyframe on frame 2.
  3. Locked the assets layer for the moment so that I would not edit it by mistake.
  4. Created the labels and the background rectangle in the same style used in the other User Interface components.
  5. Right-clicked on the asset names layer in the Timeline and selected Guide in the context menu.
  6. Locked the assets name layer.
  7. 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:

  1. Created a ComponentShim layer below all the other layers.
  2. Created a blank keyframe on frame 2 and dragged an instance of the ComponentShim from the Library onto the Stage.
  3. 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 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 linked in only if they are needed. As I discussed in an earlier sidebar, "Compiling from source vs. from ComponentShim" in Part 3, 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 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 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. Table 1 shows where you can find it.

Table 1. Location of ComponentShim.fla

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 to recreate ComponentShim:

  1. Open ComponentShim.fla.
  2. Right-click the Library symbol ComponentShim source and select Convert to Compiled Clip from the context menu.
  3. Rename the resulting compiled clip from the ComponentShim source SWF to ComponentShim.
  4. Open the Linkage dialog box for the ComponentShim compiled clip and uncheck the option to export on the first frame.
  5. Drag ComponentShim into the Component Assets/_private folder in the Library of your FLA file.
  6. 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.
  7. Close the ComponentShim.fla without saving changes.

You can also generate the ComponentShim by exporting the ComponentShim source as a 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 "Creating a shim compiled clip" in Part 9.


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).

Changing the Edit frame field from 1 to 2 in the Component Definition dialog box
Figure 2. Changing the Edit frame field from 1 to 2 in the Component Definition dialog box

After I made these changes, I opened the MenuBar component again and the Timeline view reflected the recent updates (see Figure 3).

 MenuBar FLA file appearing as expected after changing the Edit frame to frame 2
Figure 3. MenuBar FLA file appearing 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 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).

Library of MenuBar.fla containing a more manageable list of items after deleting the unneeded symbols
Figure 4. Library of MenuBar.fla containing a more manageable list of items after deleting the unneeded symbols

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.

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, "Example of 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). This 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 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:

  • 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 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(). See the section, "Calling getStyleValue() via the draw() method," for more information.

This topic is rapidly venturing into advanced territory. To dig deeper, read the next section, "Understanding the different levels of styles." Otherwise, skip to the section after, "Implementing getStyleDefinition() and UIComponent methods."

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.

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. 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"));

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, "Working with 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);

As I mentioned just above in the default component styles section, 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. See the section, "Calling getStyleValue() via the draw() method," for more information.

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.

Example of setting multiple style levels

The priority of different types of styles is explained in the previous section, "Using the StyleManager class." 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); 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; }

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.

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

Caution: 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 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

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 the following to set the focusRectSkin style for all components to use your symbol:

StyleManager.setStyle("focusRectSkin", MyPurpleFocusRectSkin)

(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 following 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.

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 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); } 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. 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)
  • 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.

Where to go from here

In Part 6 you'll take a look at the invalidation model. I'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

More Like This

  • Tips for using Flash efficiently
  • Controlling the appearance of text elements with the Text Layout Framework
  • Using the Flash OSMF Media Player template
  • Web video template: Media presentation with details
  • Preloading TLF runtime shared libraries in Flash Professional CS5.5
  • Getting started with the Adobe SiteCatalyst extension for Flash Professional CS5
  • Optimizing content for Apple iOS devices
  • Using screen orientation APIs for smartphone application development
  • Guide for Apple App Store submissions
  • Saving state in AIR applications for iOS devices

Flash User Forum

More
04/23/2012 Auto-Save and Auto-Recovery
04/23/2012 Open hyperlinks in new window/tab/pop-up ?
04/21/2012 PNG transparencies glitched
04/01/2010 Workaround for JSFL shape selection bug?

Flash Cookbooks

More
02/13/2012 Randomize an array
02/11/2012 How to create a Facebook fan page with Flash
02/08/2012 Digital Clock
01/18/2012 Recording webcam video & audio in a flv file on local drive

Products

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • Mobile Apps
  • Photoshop
  • Touch Apps
  • Student and Teacher Editions

Solutions

  • Digital marketing
  • Digital media
  • Web Experience Management

Industries

  • Education
  • Financial services
  • Government

Help

  • Product help centers
  • Orders and returns
  • Downloading and installing
  • My Adobe

Learning

  • Adobe Developer Connection
  • Adobe TV
  • Training and certification
  • Forums
  • Design Center

Ways to buy

  • For personal and home office
  • For students, educators, and staff
  • For small and medium businesses
  • For businesses, schools, and government
  • Special offers

Downloads

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

Company

  • News room
  • Partner programs
  • Corporate social responsibility
  • Career opportunities
  • Investor Relations
  • Events
  • Legal
  • Security
  • Contact Adobe
Choose your region United States (Change)
Choose your region Close

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

Terms of Use | Privacy Policy and Cookies (Updated)

Ad Choices

Reviewed by TRUSTe: site privacy statement