|
|
13. Menu and Menu Bar Components
Using the Menu and Menu Bar components, you can easily create a main
menu that could be mistaken for one in a desktop application. Your menu
items can contain icons and you can set up listeners to react to user
selections. In addition, your menu items can perform like check boxes
(to toggle a state) and like radio buttons (to select one of a number
of options.) You can use the Menu and the Menu Bar independently of each
other. In other words, you can have a Menu Bar without menus (also known
as a Toolbar) and you can have a Menu without a Menu Bar (for example,
a menu that shows when the user clicks a button.)
Menu Example 1: A Simple Menu Bar
The simplest way to get up and running with the Menu Bar is to populate
it through ActionScript. The following example shows you how.
- Create a new FLA. Use the screenshot as a guide when laying out your
movie in the following steps.

- Create a new Layer and call it Actions. Name the lower layer Components.
- Drag a MenuBar component from your Components panel on to the Stage,
into Frame 1 of the Components layer.
- Using the Property Inspector, give the MenuBar instance the instance
name myMenuBar, as show below. Notice how the component only has a single
parameter, labels. For all intents and purposes, you can ignore this
parameter. You will need to use this component through code.

- In this excercise, you will be creating a simple Help menu on the
Menu Bar with two menu items, About and Ultrashock. In a real-world
application, the first might pop up an info box and the second a browser
window with the Ultrashock home page. These are easy to achieve using
the Alert Component and getURL. For the purposes of this tutorial, however,
they will just be tracing information into the Output window.
In Frame 1 of the Actions layer, enter the following code to create
the Help menu:
////////////////////////////////////////////////////////////////
// Help Menu
////////////////////////////////////////////////////////////////
var helpMenu = myMenuBar.addMenu("Help");
helpMenu.addMenuItem({label:"About", instanceName:"aboutMenuItem"});
helpMenu.addMenuItem({label:"Ultrashock", instanceName:"aboutUltrashock"});
- Test the movie (Control -> Test Movie; Ctrl-Enter). At this point
you should see the Menu Bar with the Help menu. Clicking on Help will
open up the menu, with the two menu items you just created. Clicking
on the items themselves will close the menu. To make the menu do something
when an item is selected, you have to set up a listener.
- Add the following code to the script in Frame 1 of the Actions layer:
////////////////////////////////////////////////////////////////
// Help Menu Listener
////////////////////////////////////////////////////////////////
var helpMenuListener = new Object();
helpMenuListener.change = function(eventObject){
var theMenu = eventObject.menu;
var theMenuItem = eventObject.menuItem;
switch (theMenuItem)
{
case theMenu.aboutMenuItem:
// about selected
trace ("Help Menu: About selected");
break;
case theMenu.aboutUltrashock:
// ultrashock selected
trace ("Help Menu: Ultrashock selected");
break;
default:
// error: unknown menu item
trace ("Error: Unknown menu item selected");
break;
}
}
helpMenu.addEventListener("change",helpMenuListener);
- Test the movie (Control -> Test Movie; Ctrl-Enter) and
select and option from the menu to see a message confirming your selection
in the Output window. Notice how you check to see which menu was selected:
The eventObject contains a property called .menu that points to the
Menu object that the event occured on (this is similar to the .target
property in some of the other components.) It also has a property called
.menuItem that points at the specific menu item that fired the change
event.
In the switch statement, you compare the menu item that fired the event
(theMenuItem) with each of the items in the menu (these are actual references
to the menu item instances themselves) and trace out the respective
message. The default clause is in there because it is good practice
to cover your posterior when programming. In this case, it should never
get called but it may save you some time in debugging if you add a menu
item to the menu in the future and forget to add a check for it in the
switch statement. It's nice to have your application break in predictable
ways as you develop, whether through unit tests or well thought-out
error checking or a combination of both.
If you've never seen a switch statement before, it is nothing more than
a different way of stating an if..else if..else if..else statement.
You could have written the same statement as:
if(theMenuItem == theMenu.aboutMenuItem)
{
// about selected
trace ("Help Menu: About selected");
}
else if ( theMenuItem == theMenu.aboutUltrashock )
{
// ultrashock selected
trace ("Help Menu: Ultrashock selected");
}
else
{
// error: unknown menu item
trace ("Error: Unknown menu item selected");
}
Note that my indentation of the switch statement is non-standard. I
add an additional level of indentation between the case statement and
the break, essentially making the case and the break take the place
of curly braces. There are many who do not like the switch statement
and argue that it is a Bad Thing (tm). Personally, I believe that if
it is used right, it is better (more legible, at least) to use a switch
statement in place of a very long string of if..else..if statements.
I am sure that there are others who will argue the opposite.
The above example showed you the simplest way to populate a Menu Bar
and barely scratched the surface of what you can do with the MenuBar and
Menu components. In the next example, you will add a new menu to the Menu
Bar and, in the process, learn about two new types of menu items, the
check box and separator.
Menu Example 2: The Check Box and Separator Menu Items
- Continue with the FLA from the previous example. If you don't have
it, open up menu_bar.fla from your downloads and follow along.
- Add the following code to the top of the code in Frame 1 of the Actions
layer:
////////////////////////////////////////////////////////////////
// Options Menu
////////////////////////////////////////////////////////////////
var optionsMenu = myMenuBar.addMenu ( "Options" );
optionsMenu.addMenuItem({label: "Enable Item 3", type: "check", instanceName: "enableItem3", selected: false});
optionsMenu.addMenuItem({type: "separator" } );
optionsMenu.addMenuItem({label: "Item 3", instanceName: "item3", enabled: false});
Notice that the first menu item has a type property that is set to check.
You are telling the Menu to treat this item as a check box. Visually,
it will display a check mark when toggled on. The selected property
decides whether or not it appears checked by default. Here, you are
setting it to false, so it will appear in the menu without a check mark.
In the previous example, you did not have to specify a type property
since a menu item without a specified type defaults to normal. Both
of the menu items in the Help menu are normal menu items.
The second menu item definition is also interesting. Here, you are creating
a separator. The default appearance for a separator is as a horizontal
rule within a menu. A separator does not need any other properties to
describe it.
Finally, you create the third menu item and disable it by setting the
enabled property to false.
- Enter the following code underneith the batch you just typed in:
////////////////////////////////////////////////////////////////
// Options Menu Listener
////////////////////////////////////////////////////////////////
var optionsMenuListener = new Object();
optionsMenuListener.change = function(eventObject){
var theMenu = eventObject.menu;
var theMenuItem = eventObject.menuItem;
switch(theMenuItem)
{
case theMenu.enableItem3:
// check type menu item toggled
var checked = theMenuItem.attributes.selected;
// if item is checked, enable item 3, if not disable it
theMenu.setMenuItemEnabled ( theMenu.item3, checked );
break;
case theMenu.item3:
// ultrashock selected
trace ("Options Menu: Item 3 selected.");
break;
default:
// error: unknown menu item
trace ("Options Menu Error: Unknown menu item selected");
break;
}
}
optionsMenu.addEventListener("change",optionsMenuListener);
Here, you are setting up the familiar change listener. The difference
is that if the user clicks on the Enable Item 3 menu item, you now have
to check to see if it is selected (checked) or not and enable or disable
Item 3 in the Menu accordingly. You do this by checking the selected
attribute of the menu item. Menu items are nothing more than standard
XML objects and thus have their attributes stored in their attributes
objects. To enable or disable Item 3, you use the setMenuItemEnabled()
method. Since the checked variable will contain either a true or false
(boolean) value, you can pass it directly as the second argument (which
requires a true for enable and false for disable.) There isn't anything
new in the rest of the code.
- Test the movie. Try to click on Item 3 in the Options menu. Notice
that you can't and that it is disabled. Now click the Enable Item 3
menu item in the Options menu. Return to the Options menu and click
Item 3. Notice that it is now enabled and that the change event handler
on your optionsMenuListener gets called when you click the menu item.
In this exercise you saw how to create a check box menu item and a separator
menu item. In the next exercise, you will see the final menu item type,
radio button.
Menu Example 3: The Radio Box Menu Items
- Continue with the FLA from the previous example. If you don't have
it, open up menu_bar2.fla from your downloads and follow along.
- Modify the top of the script in Frame 1 of the Actions layer to add
the code shown in boldface, below. You are adding a separator and two
radio button menu items to the Options menu.
var optionsMenu = myMenuBar.addMenu("Options");
optionsMenu.addMenuItem({label: "Enable Item 3", type: "check", instanceName: "enableItem3", selected: false});
optionsMenu.addMenuItem({type: "separator"});
optionsMenu.addMenuItem({label: "Item 3", instanceName: "item3", enabled: false});
optionsMenu.addMenuItem({type: "separator"});
optionsMenu.addMenuItem({label: "Either choose me", type: "radio", groupName: "myGroup", instanceName: "item5", selected: "true"});
optionsMenu.addMenuItem({label: "Or me (but not both)", type: "radio", groupName: "myGroup", instanceName: "item6"});
Notice how the radio button menu item has an additional property, groupName.
Just like regular Radio Button components, you need to group radio button
menu items. Only one of the items in a given radio button group can
be selected at a time. It is expected behavior that one of the radio
button menu items in a group is selected by default. In this case, you
are setting the Either choose me item as selected.
- Modify the switch statement in the optionsMenu change() event handler
method (in Frame 1 of the Actions layer) by adding the following two
case clauses:
case theMenu.item5:
// first radio button
trace ("Options Menu: First radio button selected.");
break;
case theMenu.item6:
// second radio button
trace ("Options Menu: Second radio button selected.");
break;
- Test the movie. Click on the Either choose me menu item and the Or
me (but not both) menu item and see how the selection (the bullet) moves
between them.
You have now created and used all of the various menu item types that
come with the Menu component. In the next example, you will learn how
to create a menu using an external XML file. Although this is normally
a longwinded process involving either the use of multiple menu files (one
for each menu in a MenuBar) or that of an incorrectly formed XML file.
I will be providing you with two small helper classes to make things much
easier.
Menu Example 4: The Radio Box Menu Items
Up to this point, you have been creating the menus for the Menu Bar using
ActionScript. Although this is possible, and arguably easy, it is messy.
This messiness manifests itself all the more when you have long, complicated
menus. Being a tree-structure itself, XML lends itself greatly to the
display of hierarchical menus. In fact, as you saw earlier, the menu items
are nothing more than XML objects. This is also true for the Menus themselves.
What you were doing in the previous examples was essentially creating
XML nodes using ActionScript. Wouldn't it be nicer if you could have your
whole MenuBar defined in a neat external XML file?
Given the standard Menu and MenuBar components that come out of the box,
this is possible but slightly messy. For one thing, you cannot use a single,
correctly-formed XML file that contains all your various menus (e.g.,
Options, Help) and your Menu Bar structure (e.g., Options first, then
Help). Instead, you need have one XML file per Menu object that you want
to create. After having loaded these in, you have to go and add them to
the MenuBar and then, to add insult to injury, you need to add your listeners
to each of the Menus... I don't know about you but my head spins just
thinking about all that. Of course, it also means more code that really
has nothing to do with our business logic (what our application is actually
trying to achieve). So, to help you out, I've created a MenuBarDecorator
class that handles all of this for you automatically and in a very elegant
manner.
The following exercise demonstrates how to load in a single XML file
to create a MenuBar and all of its associated menus, and set up listeners
for each of them.
- Continue with the FLA from the previous example. If you don't have
it, open up menu_bar3.fla from your downloads and follow along.
- Remove all of the code in the Options Menu and Help Menu sections
in Frame 1 of the Actions layer, but leave the Options Menu Listener
and Help Menu Listener code as they are. You will not be altering the
listeners, just the way in which in you create the menu bar and its
menus.
- Add the following code at the top of the code in Frame 1 of the Actions
layer:
////////////////////////////////////////////////////////////////
// Create Menu Bar Decorator
////////////////////////////////////////////////////////////////
import MenuBarDecorator;
// create a new menu bar decorator instance for myMenuBar
myMenuBarDecorator = new MenuBarDecorator(myMenuBar);
// load the menu structure into our decorated menu bar
myMenuBarDecorator.loadMenuStructure("menubar.xml", this);
Yes, that's it! We've just replaced some 45 or so lines of code with
the few you see above. The magic is in the MenuBarDecorator class that
you are importing with the first statement but you don't have to worry
about how that works at all (you can look in the source for MenuBarDecorator.as,
in your downloads if you're of the curious type!).
In the second line you create a new MenuBarDecorator instance. It's
called a Decorator because it adds functionality to an existing component
without altering the component itself in any way. If you're coming from
an Object-Oriented Programming background, a Decorator pattern implements
a Has A relationship instead of an Is A relationship. I have adapted
the Decorator pattern here to Flash to allow it to function with an
existing component instance on Stage. Although this alters the base
pattern slightly in implementation, it does not in spirit and in fact
adapts it quite nicely to a semi-visual workflow that is well suited
to the semi-visual nature of Flash.
In the third statement, you tell the MenuBarDecorator instance to load
an external menu structure held in a file called menubar.xml. The second
argument specifies where the MenuBarDecorator can find your listeners.
The rest of the magic happens in the menubar.xml file.
- Take a look at the menubar.xml file that is part of your downloaded
material:
<?xml version="1.0" encoding="iso-8859-1"?>
<menuBar>
<menu name="Options" instanceName="options" listener="optionsMenuListener">
<menuItem label="Enable Item 3" type="check" instanceName="enableItem3" selected="false" />
<menuItem type ="separator" />
<menuItem label="Item 3" instanceName="item3" enabled="false" />
<menuItem type="separator" />
<menuItem label="Either choose me" type="radio" groupName="myGroup" instanceName="item5" selected="true"/>
<menuItem label="Or me (but not both)" type="radio" groupName="myGroup" instanceName="item6" />
</menu>
<menu name="Help" instanceName="help" listener="helpMenuListener">
<menuItem label="About" instanceName="aboutMenuItem" />
<menuItem label="Ultrashock" instanceName="aboutUltrashock" />
</menu>
</menuBar>
The complete structure of the MenuBar is contained within this XML file.
You specify each of the menus in your MenuBar using the menu tag. So
far, this is the same as the built-in functionality. What I have done
with the MenuBarDecorator is allowed for three new attributes to the
menu tag: name, instanceName and listener.
The name attribute is where you specify the name of the menu. This is
what will show up visually on the MenuBar component as the label of
the menu.
The instanceName attribute specifies the instance name that the MenuBarDecorator
will give the current Menu object. Although we don't use it in the example
above, the loadMenuStructure() method of the MenuBarDecorator returns
and object with references to each of the created Menu objects, in case
you need to affect them in some way.
The final attribute, listener, is where you specify the name of your
listener object. When the MenuBarDecorator creates each of the menus,
it automatically adds your specified listener, which it searches for
in the timeline (or object) reference that you passed to it as the second
argument of the loadMenuStructure() method.
- Test the movie. Note that it creates exactly the same MenuBar as
in the previous example but the code is simpler, neater and more legible
and thus easier to maintain and scale. If you get any errors when compiling,
make sure the the two required class files, MenuBarDecorator.as and
MenuStructure.as are either in your working directory or available in
your class path.
|
|