9 June 2008
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.
Beginning
Welcome to the second installment of this article series on creating components with ActionScript 3.0. If you skipped the first part, you might want to begin with Part 1 where you can download the sample files for the entire series. Or if you prefer, you can download the sample files for Part 2 only to follow along with this article.
For your reference, here are all the parts in this series:
I began my component development process by prototyping the MenuBar component outside of the User Interface Component Infrastructure. This is generally a great way to start any component. The average component is heavily code-driven and creates its assets dynamically at runtime based on its dimensions, styles and parameters. However, to simplify the prototype phase I placed fixed assets on Stage and left the dynamic parameterization for later. I also started by writing small code snippets on frame scripts, rather than putting the code in ActionScript files that define classes.
Before I walk through the creation of the prototype in detail, let's quickly review the steps: I began the prototype by dragging out an instance of the TileList component to the Stage and configuring it to look and act more like a menu bar. Next, I dragged instances of the List component to the Stage to serve as the drop-down menus. Then, I wrote event handler functions to hide and show the menus and added a trace statement to send a message to the Output panel whenever a menu item was selected. Finally, I took all of the code and I changed it from a frame script into an ActionScript class.
If you've downloaded the full set of sample files for this series, you can find examples of my prototype in progress, as well as the finished product, in the MenuBarPrototype folder. Or if you prefer, just download the sample files for the prototyping process using the link above. You can try out the final prototype component in the live example below:
In this article, I'll cover in detail the process I went through to get from nothing to this prototype. If the pace seems slow or if I'm discussing topics you are familiar with, I encourage you to skip ahead to Part 3 where I begin working with the User Interface Component Infrastructure.
I started with a new ActionScript 3.0 FLA file and dragged a TileList component from the Components panel onto the Stage. The TileList component is similar to a traditional list, but it can place its cells out in a grid or horizontally. I selected the TileList component because I thought it would serve well as a menu bar. In the Component inspector for the TileList instance, I double-clicked dataProvider to open the Values dialog box, and then I clicked the plus (+) button four times. I assigned the labels menu1, menu2, menu3, and menu4 to my entries. With Live Preview, the appearance of my TileList instance updated immediately, but at this point it did not really resemble a menu bar (see Figure 1).
So I changed the width of the TileList instance to 100, which spread out the tiles, but they were not taking up the entire width. I noticed that the columnWidth parameter was set to 50, so I changed it to 100 and that made it look better. But the TileList instance was still too tall. I changed the height to 22 but then I could not see my labels at all! I reset the parameters again, and this time I changed rowHeight to 22. Now at least the instance looked like a menu bar (see Figure 2).
Thanks to Live Preview, I was able to customize all of this very quickly and view the results without selecting Control > Test Movie. However, when I did Test Movie and clicked the tiles of my menu bar, I saw that they were selectable. When I clicked one tile, its color changed until I clicked another (see Figure 3).
I did not want the tiles to change color because it was not the way a menu bar should behave. I reviewed the options in the parameters, but none of the options addressed selectability, so I turned to the online Component Reference.
While all of the component reference material is included in the Help panel within Flash CS3, you will find that working with the online ActionScript 3.0 Language and Component Reference is a much better experience. Its three-frame layout allows you to navigate from package to package and class to class quickly and easily. It documents all classes for the ActionScript 3.0 components shipping with Adobe Flash CS3, as well as all of the classes and functions built into Adobe Flash Player, and operators and keywords for the ActionScript 3.0 language itself.
I opened the online reference, clicked the fl.controls package link in the top left frame, and then clicked on the TileList link in the lower left frame. At the top of the TileList class documentation, the entire class hierarchy for the class is shown with links to each class's documentation—including the classes built into Flash Player. These links are extremely useful. I suspected that there must be a property in TileList that controls selectability, so I searched on "select." Initially I did not find anything, but an important trick to know when using the ActionScript 3.0 Language and Component Reference is that inherited properties, events, methods, and styles are hidden by default. At the top of the Public Properties section, there is a Show Inherited Public Properties link, which I clicked. I repeated my search and found the selectable property. If the name of the property did not make it sufficiently clear that it is what I required, the one sentence synopsis confirmed it.
Back in Flash, I gave my TileList instance the name myMenuBar so that I could reference it with code. I opened the Actions panel and wrote the single line of code:
myMenuBar.selectable = false;
Now my TileList instance looked and felt like a menu bar, but with no drop-down menus. We'll see how to make those happen in the next part of this article.
I dragged four instances of the List component onto the Stage and lined them up below the four items in the menu bar. I named the four instances menu1, menu2, menu3, and menu4 to match the labels of the menu items above them, which will be useful later. I customized the dataProvider of each List instance with two to five items. At this point my menu bar looked like every drop-down menu was expanded (see Figure 4).
Next, I trimmed the height of each List instances to dispense with the empty space at the bottom. I selected Control > Test Movie to check the progress so far. When I clicked around the SWF, I found that the List instances were allowing persistent selection and behaving like the TileList instance before I set its selectable property to false. Another quick look at the ActionScript 3.0 Language and Component Reference confirmed that TileList and List both inherit from SelectableList, which defines the selectable property. I copied the code from the reference material and pasted the following to my frame script:
menu1.selectable = false;
menu2.selectable = false;
menu3.selectable = false;
menu4.selectable = false;
These four lines of code fixed the selectability problem, but upon closer inspection I noticed that the labels within the menu bar were not centering in the cell; they were hovering toward the top. I suspected that I could fix this by customizing a style on my TileList instance, so I went back to component reference. In the style section for TileList, there are only two items when inherited styles are hidden. I decided that cellRenderer was the culprit, especially since its default is fl.controls.listClasses.ImageCell. After doing some more research in the reference of the entries for the fl.controls.listClasses package and the fl.controls.List class, I discovered that the cellRenderer style's default value for List is fl.controls.listClasses.CellRenderer. Since I wanted the cells in the TileList instance to lay out like they do in the List component, I guessed that changing the cellRenderer style to fl.controls.listClasses.CellRenderer would solve my problem. To confirm how to change a property, I displayed inherited methods in the TileList entry and searched on "style." Eventually I found the setStyle() method. Finally I wrote the code:
import
fl.controls.listClasses.CellRenderer;
myMenuBar.setStyle("cellRenderer", CellRenderer);
Many definitions are imported for you when you work in frame scripts. However, none of the definitions from the fl.* packages for the User Interface Component Infrastructure are automatically imported, so I had to import them before using them in the first line of code above.
Now I had successfully added drop-down menus to the MenuBar component, but they were all open all of the time. In the next section of this article, I'll add the code to make the instances behave the way a menu bar should and bring the component to life.
After setting up the movie with instances from the TileList and List components, it was time to add the code to make the top-level menu items display the corresponding drop-down menu when the user mouses over it. This part involves defining and creating the functionality. I added mouse event handlers to show and hide the drop-down menus. I also began initial event handling by adding a simple trace statement that displays in the Output panel when a menu item is selected. Rather than explaining each line of code in this article, I've added descriptive comments so that you can read through it yourself.
A great way to understand the flow of the code is to download the prototype (menubar_prototype.zip (ZIP, 1.4 MB)) and step through the code by setting breakpoints and selecting Debug > Debug Movie. It is also helpful to try commenting out lines or changing the code in other ways to better understand what each line of code does.
Here's the script in its entirety:
// turning off selectable makes the TileList instance behave more like a menu bar
myMenuBar.selectable = false;
// turning off selectable makes the List instance behave more like drop-down menus
menu1.selectable = false;
menu2.selectable = false;
menu3.selectable = false;
menu4.selectable = false;
// The default cellRenderer style for TileList is fl.controls.listClasses.ImageCell, which
// lays out a label below an image. In our case this lay out is pushing the label toward
// the top of the cell, not the center, so we use fl.controls.listClasses.CellRenderer
// instead, which centers the label in the cell.
import fl.controls.listClasses.CellRenderer;
myMenuBar.setStyle("cellRenderer", CellRenderer);
// keepMenuOpen is used to signal to the Stage listener events
// that they should not close the menus if the event was received
// by the TileList menu bar or List drop-down menu first
var keepMenuOpen:Boolean = false;
// need to listen for a mouseDown event on the menu bar
// to start the menu handling. Other mouse event handlers will
// be added after the initial mouseDown on the TileList menu bar
// and removed when the menus are closed.
myMenuBar.addEventListener(MouseEvent.MOUSE_DOWN, menuBarMouseHandler);
// ICellRenderer is the interface implemented by fl.controls.listClasses.CellRenderer
// and other classes which can be used for the cellRenderer style. While many
// cellRenderer classes will subclass CellRenderer, they do not need to so you should
// use the interface. In the current prototype form, the cellRenderer will ALWAYS
// be CellRenderer, but it will eventually be exposed via a style and could be any
// class which implements ICellRenderer at that time, so we just use the best practice
// right away to ensure we won't forget later.
import fl.controls.listClasses.ICellRenderer;
/*
* Handler for mouse events for TileList menu bar.
*/
function menuBarMouseHandler(e:MouseEvent):void {
// the target of the mouse event will often be the ICellRenderer
// for the cell being clicked or moused over. By using the as
// keyword, we ensure that we have an instance of a class which
// implements the ICellRenderer interface, and if target is not
// an instance of such a class, we get null. It turns out in
// some cases the target can be the TileList itself, and in this
// case the event is not interesting to us so we check for null
// and return immediately in that case.
var cellRenderer:ICellRenderer = e.target as ICellRenderer;
if (cellRenderer == null) return;
// rather than have a handler for each MouseEvent we handle,
// it simplifies the code to have one handler and switch on the
// event type.
switch (e.type) {
case MouseEvent.MOUSE_DOWN:
// the List drop down menus have instance names
// which match the label for the corresponding item
// in the TileList menu bar, so we can get the correct
// List instance by referencing the label we get from
// the ICellRenderer instance's data property.
openMenuBar(this[cellRenderer.data.label]);
// see MOUSE_UP handling below for discussion of keepMenuOpen
keepMenuOpen = true;
break;
case MouseEvent.MOUSE_OVER:
hideAllMenusExcept(this[cellRenderer.data.label]);
break;
case MouseEvent.MOUSE_UP:
// this event will only be hit on the first mouseUp after the
// first mouseDown which opened the menus. We need to handle
// this to prevent the Stage mouseUp listener from closing the
// menus. We could stop the stage listener from getting the
// event at all by calling e.stopPropagation(), but since this
// will eventually become component code that could be used in
// an arbitrary application, it seems dangerous to stop event
// propagation since we do not know what sort of event handling
// the user might be coding on top of ours. So instead we use
// the keepMenuOpen:Boolean.
myMenuBar.removeEventListener(MouseEvent.MOUSE_UP, menuBarMouseHandler);
keepMenuOpen = true;
break;
}
}
/*
* Handler for mouse events for List drop down menus.
*/
function menuMouseHandler(e:MouseEvent):void {
// see comment in menuBarMouseHandler for explanation of
// next two lines of code
var cellRenderer:ICellRenderer = e.target as ICellRenderer;
if (cellRenderer == null) return;
switch (e.type) {
case MouseEvent.MOUSE_UP:
// we aren't doing any real event dispatching yet, just tracing
trace(cellRenderer.data.label);
// also set the contents of a TextField on stage, just for the
// prototype, we will remove this when we move to component
selectedMenu_txt.text = cellRenderer.data.label;
closeMenuBar();
break;
case MouseEvent.MOUSE_DOWN:
// keep stage listener from closing the menus
// more on use of keepMenuOpen in comment
// for mouseUp event in menuBarMouseHandler()
keepMenuOpen = true;
break;
}
}
/*
* handler for Stage mouse events. mouseUp or mouseDown
* on the Stage closes the menus, unless the event hit
* the TileList menu bar or a List drop-down menu first.
*/
function stageMouseHandler(e:MouseEvent):void {
if (keepMenuOpen) {
// keepMenuOpen is used by the TileList menu bar mouseUp event
// to signal that the next stage mouseUp event should not close
// the menus. We set it to false, so that the subsequent stage
// mouseUp event WILL close the menus, and do nothing else.
keepMenuOpen = false;
} else {
closeMenuBar();
}
}
/*
* Shows the drop down menu passed in as a parameter and adds
* all the events used to track the open menus and decide when
* to close them. Only the mouseDown event listener on the
* TileList menu bar is active until the menus are active.
*/
function openMenuBar(menuToOpen:List):void {
// open the List drop down menu
hideAllMenusExcept(menuToOpen);
// we need to handle mouseOver once a menu is opened to switch
// which menu is dropped down
myMenuBar.addEventListener(MouseEvent.MOUSE_OVER, menuBarMouseHandler);
// we want to handle the first mouseUp event after the first
// mouseDown on the TileList menu bar to prevent the Stage
// mouseUp listener from closing the menus
myMenuBar.addEventListener(MouseEvent.MOUSE_UP, menuBarMouseHandler);
// if we get a mouseDown anywhere on the Stage aside
// from the TileList menu bar or the List drop-down menus
// we will close the menus
stage.addEventListener(MouseEvent.MOUSE_DOWN, stageMouseHandler);
// if the first mouseUp after the initial mouseDown to
// open the menu is not over the TileList menu bar or
// the List drop-down menus, we should close the menus
stage.addEventListener(MouseEvent.MOUSE_UP, stageMouseHandler);
// When we get a mouseUp on a List drop down menu, this means
// the user has made a selection which we must handle
menu1.addEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu2.addEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu3.addEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu4.addEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
// we just need to handle mouseDown on the List drop down
// menus to prevent the stage listener from closing the menus
menu1.addEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
menu2.addEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
menu3.addEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
menu4.addEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
}
/*
* hide all List drop-down menus and remove all of the event
* listeners added in openMenuBar()
*/
function closeMenuBar():void {
// close all menus
hideAllMenusExcept(null);
// reset the state of keepMenuOpen, just for good housekeeping
keepMenuOpen = false;
// remove all the event listeners we added in openMenuBar()
myMenuBar.removeEventListener(MouseEvent.MOUSE_UP, menuBarMouseHandler);
myMenuBar.removeEventListener(MouseEvent.MOUSE_OVER, menuBarMouseHandler);
stage.removeEventListener(MouseEvent.MOUSE_DOWN, stageMouseHandler);
stage.removeEventListener(MouseEvent.MOUSE_UP, stageMouseHandler);
menu1.removeEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu2.removeEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu3.removeEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu4.removeEventListener(MouseEvent.MOUSE_UP, menuMouseHandler);
menu1.removeEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
menu2.removeEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
menu3.removeEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
menu4.removeEventListener(MouseEvent.MOUSE_DOWN, menuMouseHandler);
}
// need to import List to use it as a parameter in hideAllMenusExcept()
import fl.controls.List;
/*
* takes a parameter indicating which List drop down menu
* should be open. All the other menus will be made
* invisible. Pass in null to hide all of the drop down menus.
*/
function hideAllMenusExcept(except:List):void {
menu1.visible = (except == menu1);
menu2.visible = (except == menu2);
menu3.visible = (except == menu3);
menu4.visible = (except == menu4);
}
In the event handling code in the prototype, I casted the event's target property to the type flash.controls.listClasses.ICellRenderer. This interface must be implemented by any class that is passed in as the cellRenderer style for any of the components based on flash.controls.SelectableList, including List, TileList, and DataGrid. Especially in the context of my prototype, in which I explicitly set the cellRenderer property of TileList to be CellRenderer, there is the temptation to cast to that class instead of the interface it implements, and that approach would work fine in the prototype.
However, eventually this prototype code will become a component with its own cellRenderer style—which could be set to any user-defined class which implements ICellRenderer but does not inherit from CellRenderer. In fact, the cellRenderer styles specific to DataGrid, fl.controls.dataGridClasses.DataGridCellEditor and fl.controls.dataGridClasses.HeaderRenderer do not inherit from CellRenderer.
Casting an instance to an interface when possible, rather than to a class, is a best practice not specific to components and should be followed in all of your coding. The specific instance of ICellRenderer vs. CellRenderer is one you will need to remember when creating components using SelectableList.
In the next section of this article, you'll see how to move the frame script code into a full ActionScript 3.0 class.
As the final step for my prototype, I wrapped the TileList menu bar and List drop-down menus into a symbol named MenuBar. I exported that symbol for ActionScript as fl.example.MenuBar and copied all the frame script code into an external ActionScript file. Next, I made some minor changes to that code to get it working in the context of a class. At the very top of the external ActionScript file, I added this line:
package fl.example {
I had to I had to wrap the class in a package declaration, and this is required for all classes. You can put a class in the default package, which has the same syntax without specifying a package name. Because I used the package fl.example, my MenuBar.as file had to be nested in a matching directory structure, fl/example, in the classpath. I put the fl directory next to MenuBarPrototype.fla, since the directory containing the FLA (i.e., .) is in the default classpath.
Next I imported the necessary classes:
import flash.display.Sprite;
import flash.events.MouseEvent;
import fl.controls.listClasses.CellRenderer;
import fl.controls.listClasses.ICellRenderer;
import fl.controls.List;
I gathered all of my import statements together within the package declaration but before the class declaration. This is not necessary, but it is a good practice. I did need to import some additional definitions, however: flash.display.Sprite and flash.display.MouseEvent. When you work in frame scripts, many of the classes and other definitions built into Flash Player are imported for you automatically, including flash.events.* and flash.display.*. However, when defining a class in an ActionScript file, you always need to import all definitions.
public class MenuBar extends Sprite {
// keepMenuOpen is used to signal to the Stage listener events
// that they should not close the menus if the event was received
// by the TileList menu bar or List drop-down menu first
protected var keepMenuOpen:Boolean = false;
I declared a class named MenuBar that extends Sprite. Later I would extend fl.core.UIComponent, but at this point that would add unnecessary complexity to this simple prototype.
All classes linked to movie clip symbols must inherit from flash.display.Sprite. Note that such a class does not need to directly extend Sprite. For example flash.display.MovieClip, which subclasses Sprite, is often used as the base class, and is actually the default base class for movie clips exported for ActionScript with auto-generated classes.
The Sprite class is new to ActionScript 3.0, and it essentially represents a one-frame movie clip. It does not have the methods or properties in MovieClip that are necessary for working with the timeline—like play(), gotoAndPlay(), currentFrame, and currentScene—but it does have all the other APIs necessary for manipulating movie clips, such as startDrag() and graphics. The motivation for inheriting from Sprite instead of MovieClip is a matter of clean design and best practice. When a movie clip will not function as a multi-frame animation, the Timeline-related APIs are an unnecessary distraction, and the restriction against frame scripts enforces the best practice of keeping all code in external ActionScript files. For more on the differences between Sprite and MovieClip classes, consult the ActionScript 3.0 Language and Component Reference.
All of the Flash CS3 components, as well as the Adobe Flex components, inherit from Sprite and not from MovieClip. The class fl.core.UIComponent extends Sprite, so any component built upon the User Interface Component Infrastructure will inherit from Sprite but not from MovieClip.
When a class connected to a movie clip extends Sprite but not MovieClip, it is subject to some restrictions:
stop() action on Frame 1.If any of the first three restrictions are violated, Test Movie will generate compile errors in strict mode or runtime errors with strict mode disabled.
The restrictions on component instances with customized parameters and instances with accessibility information are really corollary restrictions to the frame script restriction. This is because they cause frame scripts to be generated dynamically behind the scenes. It is actually possible to put a component instance on the Stage with customized parameters if the instance is on the Stage for every frame of the Timeline and its parameters remain the same on every frame. In this case, the component instance will not be removed from the Stage and recreated when the Timeline loops and it will never need a modification to the parameters once it is created, so the component initialization is performed in the Timeline class constructor, rather than in a frame script.
Conceptually a movie clip attached to a class that inherits from Sprite but not from MovieClip should have only one frame, since a Sprite conceptually has no Timeline at all. This is why Sprites are suitable to Flex, which has no Timeline. So it may seem that multiple frame Sprites should not be allowed at all. However, Flash ActionScript 3.0 components are structured the same way as the ActionScript 2.0 components, with a second frame that is never meant to be displayed. The two-frame approach is laid out in detail in the sections "Examining the basic two-frame structure of components" and "Working with advanced FLA structures."
Let's take a look at the code:
// keepMenuOpen is used to signal to the Stage listener events
// that they should not close the menus if the event was received
// by the TileList menu bar or List drop-down menu first
protected var keepMenuOpen:Boolean = false;
When I declared keepMenuOpen, I added the modifier protected. Modifiers are not allowed in frame scripts, but it is a best practice to use them in class definitions. In the MenuBar example, I never made anything private, which is often too restrictive for a class that might be extended by another developer, as is the case with components. When developing, I normally use protected, unless the public modifier is necessary. For more information on using these modifiers, consult the ActionScript 3.0 Language and Component Reference.
It is important to note that I still did not add declarations for any of the instances on the Stage: myMenuBar, menu1, menu2, menu3 and menu4. I did not need to do this because the option to "Automatically declare stage instances" was checked in the ActionScript 3.0 Settings dialog box for MenuBarPrototype.fla.
There is a very important check box inside the ActionScript 3.0 Settings dialog box, which is accessed from the Publish Settings dialog box by clicking the Settings button next to the ActionScript version check box in the Flash tab. I'm specifically referring to the "Automatically declare stage instances" check box (see Figure 5). It is important to understand this setting for ActionScript 3.0 development in general, but this option is especially important for component development.
When linking a class to a movie clip in the Library or to the document's Timeline, Flash determines whether symbol instances on the Stage that are assigned instance names will be declared automatically or whether they should be declared explicitly in the ActionScript file defining the class. This check box setting affects only classes that are defined by the user—instances are always automatically declared in auto-generated classes.
In the final step of the MenuBar prototype, I left this option checked in the FLA file, which is why I did not need to make the following declarations in my MenuBar implementation:
public var myMenuBar:TileList;
public var menu1:List;
public var menu2:List;
public var menu3:List;
public var menu4:List;
Since I kept the option checked, I didn't need to import fl.controls.TileList; when instances are automatically declared, the necessary imports are added automatically as well. Declarations of named instances on the Stage are required to be public.
If I added these declarations to MenuBar.as and I did not uncheck the "Automatically declare stage instances" check box, then I would get compile errors for declaring the same variable twice. The errors would look something like this:
1151: A conflict exists with definition myMenuBar in namespace internal.
If I did not add these declarations and I unchecked the "Automatically declare stage instances" check box, then I would get runtime errors for never declaring the variable. The errors would look something like this:
ReferenceError: Error #1056: Cannot create property asdf on fl.example.FooFoo.
at flash.display::Sprite/flash.display:Sprite::constructChildren()
at flash.display::Sprite$iinit()
at flash.display::MovieClip$iinit()
at fl.example::FooFoo$iinit()
Generally speaking, whether this check box is left checked or unchecked is a matter of personal preference when developing. The main thing is to understand what this setting does and then write code to comply with the check box's settings.
In reality, the check box is not actually that important for SWC-based components, but it is critical for FLA-based components. Since FLA-based components are movie clips copied into the user's FLA file, and since this check box may or may not be checked in their FLA file, all FLA-based components must be able to compile correctly regardless of the setting of this check box.
This means that FLA-based components cannot have any symbol instances on the Stage with instance names. They also cannot have any component instances on the Stage with customized parameters, since this check box setting automatically generates instance names. (For more information on this topic, see the earlier sidebar, "Sprite vs. MovieClip").
When testing your FLA-based components, it is very important to test them with the "Automatically declare stage instances" setting both activated and deactivated, to ensure that there are no mistakes in the composition of your component symbol in the FLA file.
Let's review this section of code:
public function MenuBar() {
// turning off selectable makes the TileList instance behave more like a menu bar
myMenuBar.selectable = false;
// turning off selectable makes the List instance behave more like drop down menus
menu1.selectable = false;
menu2.selectable = false;
menu3.selectable = false;
menu4.selectable = false;
// The default cellRenderer style for TileList is fl.controls.listClasses.ImageCell, which
// lays out a label below an image. In our case this lay out is pushing the label toward
// the top of the cell, not the center, so we use fl.controls.listClasses.CellRenderer
// instead, which centers the label in the cell.
myMenuBar.setStyle("cellRenderer", CellRenderer);
// need to listen for a mouseDown event on the menu bar TileList
// to start the menu handling. Other mouse event handlers will
// be added after the initial mouseDown on the TileList menu bar
// and removed when the menus are closed.
myMenuBar.addEventListener(MouseEvent.MOUSE_DOWN, menuBarMouseHandler);
}
All of the initialization code was loose in the frame script, but for the class this would not work. So I pulled all initialization code into the constructor. ActionScript 3.0 requires that all constructors be declared as public.
Other than new indentation, the rest of the code remained almost exactly the same, with the exception of one line in the menuMouseHandler() function:
/*
* Handler for mouse events for List drop down menus.
*/
private function menuMouseHandler(e:MouseEvent):void {
// see comment in menuBarMouseHandler for explanation of
// next two lines of code
var cellRenderer:ICellRenderer = e.target as ICellRenderer;
if (cellRenderer == null) return;
switch (e.type) {
case MouseEvent.MOUSE_UP:
// we aren't doing any real event dispatching yet, just tracing
trace(cellRenderer.data.label);
// also set the contents of a TextField on stage, just for the
// prototype, we will remove this when we move to component
parent["selectedMenu_txt"].text = cellRenderer.data.label;
closeMenuBar();
break;
case MouseEvent.MOUSE_DOWN:
// keep stage listener from closing the menus
// more on use of keepMenuOpen in comment
// for mouseUp event in menuBarMouseHandler()
keepMenuOpen = true;
break;
}
}
...
}
}
Because the instances were no longer on the main Timeline, but instead were wrapped in the MenuBar movie clip, I had to reference selectedMenu_txt through the parent property.
In ActionScript 2.0, I could have used the following line of code:
_parent.selectedMenu_txt.text = cellRenderer.data.label;
to access the dynamic text object with the instance name selectedMenu_txt. However, in ActionScript 3.0, with strict mode on, this line of code:
_parent.selectedMenu_txt.text = cellRenderer.data.label;
will result in a compile error. This is because the declared type parent is flash.display.DisplayObjectContainer, which is not declared dynamic.
Instead, the following code would have worked with no compile errors and no runtime errors:
var theParent:MovieClip = parent as MovieClip;
theParent.selectedMenu_txt.text = cellRenderer.data.label;
This works at compile time because MovieClip is declared as dynamic. This means that even in strict mode you can reference any property or method on a MovieClip instance. There are no runtime errors in this case because the parent actually is of type MovieClip and that parent actually has a property selectedMenu_txt that has a property named text.
I used a lazier approach, which was to avoid casting by using square brackets to reference the property, instead of using a dot. Even in strict mode you can reference any property or method on any object with the square brackets without compile errors. However, if the object does not define that property and the object is not dynamic, you will still get runtime errors.
Since I knew that this was only temporary code, which would be removed soon, I did not see any harm in using the lazy approach. Generally though, I try to keep everything strongly typed in my code and use the dot syntax. I strongly recommend this approach because it helps you leverage all the benefits of strict compile mode.
I hope you will continue on to Part 3 of this article series, where I take the MenuBar prototype and turn it into a real component. This is where the magic really happens, as we begin to work with the User Interface Component Infrastructure.
If the discussions about SWC-based components in this series have piqued your curiosity about how they work and how you can customize them, check out these excellent articles by Dan Carr in the Flash Developer Center:
Also, if you'd like to learn more about creating classes in ActionScript 3.0, see the following Flash Quick Start:
| 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? |
| 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 |