by Joe Ward

Joe Ward

Created

16 November 2008

Operating systems provide built-in (or native) facilities for creating menus. These native menus include application menus (on the Mac), window menus (on Windows), system tray and dock icon menus, and context menus. A native menu is managed and drawn by the operating system rather than by the AIR runtime or the code in your application. The AIR NativeMenu classes provide an interface for creating and modifying native operating system menus as well as for adding event listeners to handle menu events. 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

The AIRMenus example application, shown in Figure 1, illustrates how to create the various kinds of native menus supported by AIR. In addition, the example demonstrates how to implement an Edit menu using the edit commands provided by the AIR NativeApplication class.

Note: This is an example application provided, as is, for instructional purposes.

This sample application includes the following files:

  • AIRMenusFlash.as: The main application file in ActionScript; includes the code discussed in this article
  • AIRMenusFlash.fla: Flash document for use with Adobe Flash CS3
  • AIRMenusFlex-app.xml: The AIR application descriptor file
  • Sample AIR icon files

Important

If you use Flex 3.0.2 or Flex SDK 3.2 or later to build this Quick Start, you must change the XML namespace in the second line of the AIRMenusFlex-app.xml file, to this:

xmlns="http://ns.adobe.com/air/application/1.5"

To test the application, compile the source code or install and run the example AIR file (AIRMenusFlash.air).

Understanding the code

Note: For more information about using Flash classes, such as the TextField used by AIRMenus, refer to the ActionScript 3 Reference for the Flash Platform.

Creating the menus

You create the native menus objects and their child submenu and command items in ActionScript. In this example, the ActionScript code is in the class file, AIRMenusFlash.as, associated with the main document.

A native menu typically consists of a set of nested NativeMenu objects. A NativeMenu object has child NativeMenuItem objects. An item in a menu can be a command, a separator, or a submenu. To nest one menu as a submenu of another, you create an item in the parent menu, and assign the NativeMenu object of the child menu to the submenu property of that item. To create a separator line, you set the isSeparator parameter to true in the NativeMenuItem constructor function. If an item is nether a submenu or a separator, it is a command. Typically, you respond to user menu commands by listening for the select event on either the item itself, or one of its parent menus.

To create a menu, start with a new NativeMenu object and add command, submenu and separator items to it. The top level menu of application and window menus should only contain items that reference submenus. Command and separator items in the top level menu will not be displayed at all on Mac OS X. On Windows, the item will appear, but will not open a submenu, which will probably confuse users. For other kinds of menus, like context, pop-up, system tray, and dock icon menus, you can put command and separator items directly in the top-level menu object.

The AIR Menus example uses the function, createRootMenu(), to create the root menu. The function creates two example submenus, labeled File and Edit. The NativeMenu objects for these submenus are, in turn, created by the functions, createFileMenu() and createEditMenu():

private function createRootMenu():NativeMenu{ var menu:NativeMenu = new NativeMenu(menuType:String); menu.addSubmenu(createFileMenu(menuType),"File"); menu.addSubmenu(createEditMenu(menuType),"Edit"); return menu; }

The functions which create the submenus, use the addItem() method to add commands and separators. The following function creates the File menu:

private function createFileMenu(menuType:String):NativeMenu{ var temp:NativeMenuItem; var menu:NativeMenu = new NativeMenu(); var newCommand:NativeMenuItem = menu.addItem(new NativeMenuItem("New")); newCommand.keyEquivalent = 'n'; newCommand.data = menuType; newCommand.addEventListener(Event.SELECT, newWindow); var closeCommand:NativeMenuItem = menu.addItem(new NativeMenuItem("Close window")); closeCommand.keyEquivalent = 'w'; closeCommand.data = menuType; closeCommand.addEventListener(Event.SELECT, closeWindow); var quitCommand:NativeMenuItem = menu.addItem(new NativeMenuItem("Exit")); quitCommand.keyEquivalent = 'q'; quitCommand.data = menuType; quitCommand.addEventListener(Event.SELECT, exitApplication); for each (var item:NativeMenuItem in menu.items){ item.addEventListener(Event.SELECT,itemSelected); } return menu; }

A keyboard shortcut can be assigned to a command by setting the item’s keyEquivalent property. AIR automatically adds a standard modifier key to the keyboard shortcut. On Mac OS X, the modifier is the command key, on Windows, it is the control key. In addition, if you set keyEquivalent with an upper-case letter, the shift key will be also be added to the key modifier array. To use a shortcut with no modifiers, use a lower-case letter and set the keyEquivalentModifiers property to an empty array, as follows:

item.keyEquivalentModifiers = [];

Note: Key equivalents can only be used to select commands in application or window menus. Although they can be assigned, and may even be displayed, in other types of menus, pressing the key combination will have no effect.

The function also sets the data property of each menu item. The data property is a convenient place to reference an object relevant to a menu command. In this case, the data property is set to a string describing the parent menu. This string is used in the itemSelected() event handler to report the menu to which a selected command belongs.

Adding application and dock menus

When AIR supports application menus on an operating system, the static NativeApplication.supportsMenu property will be true. The Mac OS X operating system provides a default application menu object. You have the option of using the provided menu (although most of the commands will do nothing unless you add event listeners to them) and perhaps adding new items and submenus, or replacing the menu entirely. The AIR Menus example takes the second approach and replaces the default menu with a new menu object returned by the createRootmenu() function:

if(NativeApplication.supportsMenu){ NativeApplication.nativeApplication.menu = createRootMenu("Application menu"); }

Likewise, when AIR supports dock icons on an operating system, the static NativeApplication.supportsDockIcon will be true. The dock icon is represented by the NativeApplication.nativeApplication.icon property. The icon object is created automatically.

Mac OS X provides a default menu for the dock icon. You can add additional items to the dock menu by adding the items to a NativeMenu object and assigning it to the icon menu property.

if(NativeApplication.supportsDockIcon){ DockIcon(NativeApplication.nativeApplication.icon).menu = createRootMenu("Dock icon menu"); }

Adding window and system tray menus

When AIR supports window menus on an operating system, the static NativeWindow.supportsMenu property will be true. No default window menu is provided by the Windows operating system, so you must assign a new menu object to the window:

if(NativeWindow.supportsMenu){ stage.nativeWindow.menu = createRootMenu("Window menu"); }

When AIR supports system tray icons on an operating system, the static NativeApplication.supportsSystemTrayIcon will be true. The system tray icon is represented by the NativeApplication.nativeApplication.icon property. Although the icon object is created automatically, to display the icon in the notification area of the taskbar, you must assign an array containing the icon image to the bitmaps property of the icon object. (To remove the icon from the taskbar, set bitmaps to an empty array.)

AIRMenus uses a utility class, AIRMenusIcon, that loads the icon images and dispatches a complete event. When the complete event is received, the application sets the icon bitmaps array:

private var icon:AIRMenuIcon = new AIRMenuIcon(); //... icon.addEventListener(Event.COMPLETE,function():void{ application.icon.bitmaps = icon.bitmaps; }); icon.loadImages();

Add a menu to the system tray icon by assigning a NativeMenu object to the icon menu property. You must cast the object to the SystemTrayIcon class to access the menu property.

if(NativeApplication.supportsSystemTrayIcon){ SystemTrayIcon(NativeApplication.nativeApplication.icon).tooltip = "AIR Menus"; SystemTrayIcon(NativeApplication.nativeApplication.icon).menu = createRootMenu("System tray icon menu"); }

Be careful about using SystemTrayIcon properties on the wrong operating system. On Mac OS X, for example, the NativeApplication.nativeApplication.icon object is of type, DockIcon. Attempting to set the tooltip would generate a runtime error.

Responding to menu events

To respond to menu commands, register a handler for the select event on the either the parent menu or the command menu item object. In this example, a separate handler is used for each of the commands in the application, window, system tray, and dock icon menus. For example, the following handler responds to the New window command by creating a window and loading the application SWF file:

private function newWindow(event:Event):void{ var options:NativeWindowInitOptions = new NativeWindowInitOptions(); options.systemChrome = NativeWindowSystemChrome.STANDARD; options.transparent = false; options.maximizable = false; options.minimizable = true; options.resizable = false; var newWindow:NativeWindow = new NativeWindow(options); newWindow.stage.stageWidth = 355; newWindow.stage.stageHeight = 400; newWindow.title = window.title; var reload:Loader = new Loader(); reload.load(new URLRequest("app:/AIRMenusFlash.swf")); newWindow.stage.addChild(reload); }

Also available, but not used in this example, are displaying events. A displaying event is dispatched by a menu just before it is displayed. You can use displaying events to update the menu or items within it to reflect the current state of the application. For example, if your application used a menu to let users open recently viewed documents, you could update the menu to reflect the current list inside the handler for the displaying event.

Adding context and pop-up menus

You can assign context menus to any object of type InteractiveObject with the contextMenu property. When set, a Control+click or right-mouse click on the object will open the menu. You can use either a NativeMenu or a ContextMenu object with the contextMenu property. The context menus behave much the same as they would if running in the Flash Player in the browser, except that there are no built-in items (and also no default context menu).

This AIRMenus example, demonstrates a different technique for displaying context menus, only available in AIR applications. Rather than setting the contextMenu property, AIRMenus listens for the contextMenu event (available to AIR applications) and displays a menu using the display() method of the NativeMenu class. The contextMenu event is dispatched when the user performs the context menu gesture of their operating system, such as right-clicking or Control+clicking the mouse.

The context menu is enabled by adding the contextMenu event listener to the appropriate objects. In this case, the leftWidget and middleWidget objects defined in the FLA file are given context menus:

leftWidget.addEventListener(MouseEvent.CONTEXT_MENU, openContextMenu); middleWidget.addEventListener(MouseEvent.CONTEXT_MENU, openContextMenu);

A pop-up menu is added to the rightWidget in the same way. The only difference is that the menu is displayed in response to a mouseUp event, rather than a contextMenu event:

rightWidget.addEventListener(MouseEvent.MOUSE_UP, openContextMenu);

The menu is very simple, containing only four commands. The same menu object is used for both the context and the pop-up menus and is created with a function, createColorMenu():

private function createColorMenu():NativeMenu{ var colorMenu:NativeMenu = new NativeMenu(); var brown:NativeMenuItem = colorMenu.addItem(new NativeMenuItem("Brown")); brown.data = new ColorTransform(0,0,0,1,0x77,0x52,0x52,0); var blue:NativeMenuItem = colorMenu.addItem(new NativeMenuItem("Blue")); blue.data = new ColorTransform(0,0,0,1,0x6A,0x52,0x77,0); var green:NativeMenuItem = colorMenu.addItem(new NativeMenuItem("Green")); green.data = new ColorTransform(0,0,0,1,0x52,0x77,0x53,0); var purple:NativeMenuItem = colorMenu.addItem(new NativeMenuItem("Purple")); purple.data = new ColorTransform(0,0,0,1,0xaa,0x00,0x97,0); return colorMenu; }

A ColorTransform object for each color is stored in the data property of the NativeMenuItem object. This color transform is used to change the color of appropriate widget when a menu command is selected.

To show the menu, the handler for the contextMenu or mouseUp event calls the menu display() method. Both events are types 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.

One problem to solve is how to get a reference to the object that was clicked when handling the select event of a menu command. AIRMenus solves this problem by handling the select event using an inner function defined in the main event handler:

private function openContextMenu(event:MouseEvent):void{ colorContextMenu.addEventListener(Event.SELECT, changeColor); colorContextMenu.display(stage, event.stageX, event.stageY); function changeColor(menuEvent:Event):void{ colorContextMenu.removeEventListener(Event.SELECT, changeColor); event.target.transform.colorTransform = menuEvent.target.data; log(menuEvent.target.label + " from color menu"); } }

The openContextMenu() function registers the changeColor() function as the handler for the select 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(), changeColor() can access the original MouseEvent event object to determine the display object that was clicked to open the context menu.

Implementing an Edit menu

The TextField and HTMLLoader objects, as well as components such as TextArea that are based on them, implement default behavior for edit operations such as cut, copy, and paste. You can trigger these behaviors with a menu command by calling the edit functions provided by the NativeApplication class. These functions send an internal command to the currently focused interactive object. For example, the following statement triggers the cut command:

NativeApplication.nativeApplication.cut();

The edit behaviors are normally triggered by the standard keyboard shortcuts, but if you add those shortcut keys to a menu command, then the menu command takes priority.The following handler is used by the Cut command on the Edit menu:

private function doCut(event:Event):void{ if(!window.active){ window.addEventListener(Event.ACTIVATE, cut); application.activate(AIRMenusFlash.lastActiveWindow); function cut(event:Event):void{ window.removeEventListener(Event.ACTIVATE, cut); application.cut(); } } else { application.cut(); } }

The handler is not quite as simple as it might be, because the Edit menu is also used in the system tray and dock icon menus. When you use these menus, the window loses focus when the menu command is selected. Therefore, the function must check whether the window is active and, if not, activate it. Because activation is asynchronous, an event listener must be used to wait for the actual activation event. The listener calls an inner function, cut(), that removes the event listener to free the associated memory, and calls the NativeApplication cut() method. The other thing you must do is to set the alwaysShowSelection property of the text field objects to true in order to maintain the current text selection when the window loses focus.

If the focused object does not implement the edit commands internally, then nothing happens when you call the NativeApplication edit commands. The only way to add support for these commands to a custom class or component is to extend or include a class, such as TextField, that already implements them. If you include a TextField object in your own class instead of extending the TextField class, you must also manage the focus so that the TextField object always has the focus when your custom component has the focus.

Requirements

Prerequisite knowledge

General experience of building applications with Flash CS3 is suggested. For more details on getting started with this Quick Start, refer to Building the Quick Start sample applications with Flash.

 

Additional Requirements

User level

Intermediate