Wednesday, January 6, 2010

Icons in Flex MenuBar

I came across this strange behavior (I'm not sure if it qualifies to be called a bug, but it definitely is not a feature) in Flex while trying to add icons to the MenuBar control.

The icons are displayed properly if both the MenuBar and the icon-embed variables are declared in the main application class. But if they are nested inside another component, only the top level menu-items' icons are displayed - child menu items' icons are not displayed.

After some debugging I realized that only the top-level menu items are displayed using the itemToIcon method of the MenuBar class; child menu-items are displayed using the itemToIcon method of the Menu class. The problem here is that even though both of these methods use document[data[iconField]] to grab the icon-embed class, the document properties of these two objects point to different items. The document property of the MenuBar points correctly to the enclosing component (MyPanel). Whereas, for some reason that I don't understand, the document property for nested Menu points to the MainApplication class.

In short, while rendering icons of a MenuBar within a component, flex searches the component class for icons of top-level menu items, and the main-application class for icons of its nested menu items. The obvious workaround is to embed all icons to public static const in some class and to keep appropriate references to them in both application and component classes. But that doesn't sound so pretty to me (even though that's what I am doing now). Does anyone know better?

Here is example code and screenshots to demonstrate the issue:

<!--The main Application class-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
   creationComplete="create()" xmlns:local="*">
   <mx:Label text="MenuBar in the Application works fine"/>
   <mx:MenuBar id="menuBar" top="10" left="10"
       dataProvider="{menuBarCollection}" labelField="@label"
       iconField="@icon" />

   <local:MyPanel/>

   <mx:Script>
       <![CDATA[
           import mx.collections.XMLListCollection;

           [Bindable]
           protected var menuBarCollection:XMLListCollection;

           [Embed(source="assets/icon_one.png")]
           public var iconOne_App:Class;

           [Embed(source="assets/icon_two.png")]
           public var iconTwo_App:Class;

           private function create():void
           {
               menuBarCollection = new XMLListCollection(XMLList(
                   '<menuitem label="One" icon="iconOne_App">' +
                       '<menuitem label="Two" icon="iconTwo_App"/>' +
                       '<menuitem label="Three" icon="iconTwo_App"/>' +
                   '</menuitem>'
               ));
           }
       ]]>
   </mx:Script>
</mx:Application>

MenuBar in the Application

<!--MyPanel.mxml-->
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" height="144"
   creationComplete="create();" horizontalAlign="center" verticalAlign="middle">
   <mx:Text text="MenuBar inside a component doesn't display icons properly"/>
   <mx:MenuBar id="menuBar" top="10" left="10"
       dataProvider="{menuBarCollection}" labelField="@label"
       iconField="@icon" />
   <mx:Script>
       <![CDATA[
           import mx.collections.XMLListCollection;

           [Bindable]
           protected var menuBarCollection:XMLListCollection;

           [Embed(source="assets/icon_one.png")]
           public var iconOne_Panel:Class;

           [Embed(source="assets/icon_two.png")]
           public var iconTwo_Panel:Class;

           private function create():void
           {
               menuBarCollection = new XMLListCollection(XMLList(
                   //top level item referring to component
                   //this icon is displayed
                   '<menuitem label="ParentIcon_panel" icon="iconOne_Panel">' +

                       //nested item referring to component
                       //this icon is not displayed
                       '<menuitem label="ChildIcon_panel" icon="iconTwo_Panel"/>' +

                       //nested item referring to application
                       //this icon is displayed
                       '<menuitem label="ChildIcon-app" icon="iconTwo_App"/>' +
                   '</menuitem>' +

                   //top level item referring to Application
                   //this icon is not displayed
                   '<menuitem label="ParentIcon_app" icon="iconOne_App"/>'
               ));
           }
       ]]>
   </mx:Script>
</mx:Panel>

MenuBar inside a Component