by Joe Ward

Joe Ward

Created

10 June 2010

Requirements
Prerequisite knowledge

General experience of building applications
with Flex Builder or the Flex SDK is
suggested. For more details on getting
started with this Quick Start, refer to
Building the Quick Start sample
applications with Flex
.
Required products

Sample files

User level

Intermediate
 
Operating systems provide built-in (or native) facilities for creating menus. You can create menus in Flex using the FlexNativeMenu class. Native menus can be used for:
 
  • Application menus, which appear on the operating system menu bar (such as on Mac OS X)
  • Window menus, which appear immediately below the window title bar (such as on Microsoft Windows)
  • Application icon menus for the dock and system tray icons
  • Context menus
  • Pop-up menus
In addition, Flex provides the MenuBar class for creating non-native menus that are drawn by Flex using display objects rather than by the operating system. When a menu is outside a window—and this is the case for application, system tray and dock icon menus—you must use a native menu. When the menu is part of a window that has system chrome, then you can use either native menus or the Flex MenuBar control. When a menu is part of a window without system chrome, you cannot use a native window menu. The standard context and pop-up menus are drawn by the operating system, so you use the native menu class for these as well.
 
The AIRMenus example application, shown in Figure 1, illustrates how to create the various kinds of native menus supported by AIR.
 
Air menus
Figure 1. The AIR Menus example application on Microsoft Windows. 
 
Note: This is an example application provided, as is, for instructional purposes.
 
This sample application includes the following files:
 
  • AIRMenusFlex.mxml: The main application file in MXML for Flex; includes the code discussed in this article
  • AIRMenusFlex-app.xml: The AIR application descriptor file
  • Sample AIR icon files
To test the application, compile the source code or install and run the example AIR file (AIRMenusFlex.air).
 

 
Understanding the code

The AIR Menus example uses MXML to define the menu objects. The menus are defined in two parts. An XML object defines the structure of the menu, as well as properties such as the menu item labels and key equivalents. The XML menu definition is assigned as the data provider for FlexNativeMenu and MenuBar controls. The controls parse the XML definition to create the underlying menus.
 
Note: This article does not describe all of the Flex components used in the MXML code for the file. For more information, see the ActionScript 3 Reference for the Adobe Flash Platform.
 
 
Defining menus with XML
To define the elements of the menus, the AIR Menus example uses XML objects. The main menu used for the application, window, system tray and dock icon menus is defined with the following XML object:
 
<mx:XML id="applicationMenu" format="e4x"> <menubar> <menu label="Edit"> <menuitem label="Cut" command="cut" key="x"/> <menuitem label="Copy" command="copy" key="c"/> <menuitem label="Paste" command="paste" key="v"/> <menuitem label="Select all" command="selectAll" key="a"/> <menuitem label="Clear" command="clear"/> </menu> <menu label="Menus"> <menuitem label="Menu bar" command="toggleMenuBar" type="check" toggled="true"/> <menuitem label="Icon menu" command="toggleIconMenu" type="check" toggled="true"/> </menu> </menubar> </mx:XML>
When you use an XML object as the data provider for a menu, the structure of the menu is derived from the structure of the XML document. If an element has children, that element is treated as a submenu and the children as the items in the submenu. The elements in the structure can have any name, a fact you can use to improve the readability of your code. The above example uses menubar for the root of the menu, menu for the main menus on the menu bar and menuitem for menu commands.
 
The attributes of an XML element define the menu properties, such as the label, the key equivalent, and so forth. For some properties, you can use any attribute name. The attribute name is identified by a property of the menu object. For example, the FlexNativeMenu and MenuBar controls let you specify name used for the label attribute with the labelField property. Other menu item properties, like type and toggled, do not have a similar identifier property on the menu controls, so you must use the standard attribute name.
 
You can add additional, custom attributes. These attributes are not used by Flex or AIR, but the attribute values are available to event handlers. In the menu example above, the custom command attribute is used with a switch statement the action to carry out in response to a menu command. Similarly, the colorContextMenu uses a custom color attribute to store the RGB number representing the color choices in the menu.
 
 
Declaring the menu controls
The XML menu definitions are used as data providers for the menu controls. AIR Menus uses the FlexNativeMenu control for the application, window, system tray and dock icon menus. It also creates a separate menu bar for the window with the MenuBar control. Both these control classes can take an XML definition as a data provider.
 
The menu object used for native menus is created with the following declaration:
 
<mx:FlexNativeMenu id="nativeMenu" dataProvider="{applicationMenu}" labelField="@label" keyEquivalentField="@key" keyEquivalentModifiersFunction="standardOSModifier" showRoot="false" itemClick="nativeMenuCommand(event)" />
The name of the attribute containing the menu item label is identified with the labelField property. Instead of assigning a label string, you can also define a function to create the labels with the labelFunction property.
 
The key equivalent attribute is identified by the keyEquivalentField property. The key equivalent modifier array is returned by a function, standardOSModifier(), which determines the current operating system and returns the standard modifier keycode.
 
When using an XML object to define the menu, showRoot must be false.
 
The MenuBar object is created with the following declaration:
 
<mx:MenuBar top="0" left="0" right="0" id="menuBar" dataProvider="{applicationMenu}" labelField="@label" showRoot="false" itemClick="menuBarCommand(event)" />
The declaration is similar to the FlexNativeMenu declaration, except the MenuBar does not support key equivalents.
 
 
Adding application and window menus
The menu for either the application menu or the window menu set using the menu attribute of the WindowedApplication MXML declaration. When you assign a FlexNativeMenu object to the WindowedApplication component, the menu object is used as an application menu on the Mac and a Window menu on Microsoft Windows.
 
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" menu="{nativeMenu}">
The Window component also has a menu property that you can use to assign a menu to a window. However, the Window component menu does not affect the application menu.
 
Only windows with system chrome can display a native window menu. For windows without system chrome, use the MenuBar control instead.
 
 
Adding a Flex MenuBar
The MenuBar control is very similar to the FlexNativeMenu control. Instead of being displayed as part of the system chrome of the window, the menu bar can be displayed anywhere in the window — and in windows without system chrome.
 
One challenge with using the menu bar control, is that clicking a menu on the menu bar will take the focus from other objects. If, for example, you have highlighted some text in a TextArea and try to use the cut command, the text is not cut because the menu, not the TextArea, has focus when the event handler is executed.
 
To remedy this problem, AIR Menus implements a simple focus management scheme. A variable, lastFocusedControl, references the last editable control that had focus. Whenever an editable control loses focus, the focusOut event handler updates the lastFocusedControl variable. Before executing a menu command, the handler for the menu events restores the focus to the last focused control.
 
 
Adding system tray and dock icon menus
The WindowedApplication control provides two properties, systemTrayIconMenu and dockIconMenu, that you can use to assign menus to the dock icon on the Mac and the system tray icon on Windows:
 
this.dockIconMenu = nativeMenu; this.systemTrayIconMenu = nativeMenu;
The items in the menu assigned to the dock icon are added to the default menu supplied by the operating system.
 
The system tray icon is not displayed by default. To display the icon, load an image, insert it into an array, and assign it to the bitmaps property of the NativeApplication object:
 
//Load an icon image for system tray, if supported if(NativeApplication.supportsSystemTrayIcon){ var sysTrayImageURL:String = "app:/icons/AIRApp_16.png"; var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE,iconLoaded,false,0,true); loader.load(new URLRequest(sysTrayImageURL)); } } //Show the system tray icon by setting an image private function iconLoaded(event:Event):void{ this.nativeApplication.icon.bitmaps = [event.target.content.bitmapData]; }
 
Adding a context menu
You can use the ContextMenu class for context menus. In AIR, no built-in items are displayed. The ContextMenu class is useful when sharing code between projects that run in AIR and projects that run in the browser, but does not support submenus. You can also use the NativeMenu class for context menus in an AIR application. The NativeMenu class supports submenus, but does not report the item that owns the menu when dispatching native menu events.
 
To set a context menu using the built-in context menu support, assign a ContextMenu or NativeMenu object to the contextMenu property of an interactive object. For example, the following statement assigns a menu defined by a FlexNativeMenu control as a context menu of the window:
 
this.contextMenu = flexNativeMenu.nativeMenu;
This AIR Menus example illustrates a different technique for creating context menus. The example implements context menus using the contextMenu event and the display() method of the FlexNativeMenu control.
 
A contextMenu event is dispatched when an object is right-clicked on Windows or control-clicked on the Mac. To add a context menu to an object using this method, add an event listener to the object. In this example, the listener is added in the MXML declaration for the control:
 
<mx:TextArea id="leftContextWidget" contextMenu="openContextMenu(event)"/>
The handler for the event calls the display() method of the appropriate menu object. The contextMenu event is a type of mouse event so you can get the coordinates at which to display the menu from the stageX and stageY properties of the event object.
 
The remaining problem to solve is how to get a reference to the object that was clicked when handling the itemClicked event of a menu command. AIR Menus solves this problem by handling the itemClick event using an inner function defined in the contextMenu event handler:
 
private function openContextMenu(event:MouseEvent):void{ colorContextMenu.addEventListener(FlexNativeMenuEvent.ITEM_CLICK, changeColor); colorContextMenu.display(stage, event.stageX, event.stageY); function changeColor(menuEvent:FlexNativeMenuEvent):void{ colorContextMenu.removeEventListener(FlexNativeMenuEvent.ITEM_CLICK, changeColor); var widget:TextArea = event.target.parent as TextArea; widget.setStyle("backgroundColor", menuEvent.item.@color); log("Color change from " + widget.id.toString()); } }
The openContextMenu() function registers the changeColor() function as the handler for the itemClick event of the colorContextMenu object. The function then calls the menu display() method. When a user selects a color from the menu, the changeColor() function is called. Because it is defined within the scope of openContextMenu(), the changeColor() function can access the original contextMenu event object to determine the display object that was clicked to open the menu.
 
 
Adding a pop-up menu
A pop-up menu can be added in much the same way as the context menus used by the AIR Menus example. In this case, a mouseUp event is used to call the openContextMenu() method.
 
<mx:TextArea id="popupWidget" mouseUp="openContextMenu(event)"/>
 
Responding to menu events
To respond to menu events, register a handler for the event on the menu object. FlexNativeMenu controls dispatch FlexNativeMenuEvent objects. MenuBar controls dispatch MenuEvent objects.
 
The following handler is used to respond to itemClick events from the FlexNativeMenu control:
 
private function nativeMenuCommand(menuEvent:FlexNativeMenuEvent):void{ var application:WindowedApplication = this; if(!application.nativeWindow.active){ application.addEventListener(AIREvent.WINDOW_ACTIVATE, executeAsync); application.activate(); function executeAsync(event:Event):void{ application.removeEventListener(AIREvent.WINDOW_ACTIVATE, executeAsync); doCommand(menuEvent.item.@command); } } else { doCommand(menuEvent.item.@command); } log(menuEvent.item.@command + " from " + menuEvent.currentTarget); }
The handler uses the item property of the event object to access the value of the custom command attribute specified in the menu XML object. The command string is passed to another function, doCommand(), that uses the value in a switch statement.
 
When you use a dock or system tray icon menu, the window is no longer active when the menu command is selected. The function checks whether the window is active and, if not, activates the application. Because activation is asynchronous, an event listener must be used to detect the actual activation event. The listener calls an inner function, executeAsync(), that removes the event listener to free the associated memory, and calls the doCommand() method.
 
If the window is already active, the function calls doCommand() directly:
 
//Execute the selected command private function doCommand(command:String):void{ switch(command){ case "cut": this.nativeApplication.cut(); break; case "copy": this.nativeApplication.copy(); break; case "paste": this.nativeApplication.paste(); break; case "selectAll": this.nativeApplication.selectAll(); break; case "clear": this.nativeApplication.clear(); break; case "toggleMenuBar": if(menuBarShown){ menuBar.visible = false; } else { menuBar.visible = true; } menuBarShown = !menuBarShown; break; case "toggleIconMenu": if(iconMenuShown){ this.dockIconMenu = new FlexNativeMenu(); this.systemTrayIconMenu = new FlexNativeMenu(); } else { this.dockIconMenu = nativeMenu; this.systemTrayIconMenu = nativeMenu; } iconMenuShown = !iconMenuShown; break; default: log("Unrecognized command: " + command); }}
You can also listen for menuShow events to update the items in a menu immediately before the menu is displayed. This feature is not demonstrated in the AIR Menus example.