Building Windows Classic Menus in Flex



Flex MenusLooking to give your Flex based applications a more native look and feel? To that end we'll show you how to go about creating 'windows classic' menus.


First, let’s examine how menus work in Flex. We’ll create a simple menu using the mx:MenuBar component. Then we'll move on to styling it to match the 'windows classic' skin. Lastly, we'll inject some ActionScript code to fix some deficiencies in the styling.



<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.events.MenuEvent;
public function menuProcessor(event:MenuEvent):void
{
}
]]>
</mx:Script>
<mx:Panel x="0" y="0" width="100%" height="100%">
<mx:MenuBar width="100%" labelField="@label" id="MyMenuBar" itemClick="menuProcessor(event)">
<mx:XMLList>
<menuitem label="File">
<menuitem label="Open" />
<menuitem label="Save" />
<menuitem label="Exit" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Cut"/>
<menuitem label="Copy"/>
<menuitem label="Paste"/>
</menuitem>
</mx:XMLList>
</mx:MenuBar>
</mx:Panel>
</mx:Application>


There are a few items above that you’ll find in most Flex applications-the mx:Application tag, followed by a root panel. After the panel, we dive into our menuing code.

In the mx:MenuBar tag, you'll see we set the itemclick equal to a function called 'menuProcessor'. This means if any menu item belonging to this menu is clicked, that function will be called (this is a little different than some other GUI libraries where you need to set event listeners on each individual menu item).

The real interesting part is the 'labelField' attribute. It basically tells the menubar that each menu item's name will held in an attribute called 'label' in the XMLList that the MenuBar encompasses. We could have set that field to anything, but 'label' will suffice.

Speaking of XMLLists, you'll see the one that we've included lays out a fairly typical menu data structure, with a 'File' menu, an 'Edit' menu, etc....

If you compile this down with your Flex compiler, you'll see we get the following:



This is a good start, but it doesn't look anything like a 'Windows Classic' theme. To fix this, we'll need to do some stylin'!

Using CSS

Flex gives you the ability to style your components with CSS. We'll see what we can do with CSS below.


<mx:Style>
Menu
{
color:#000000;
rollOverColor:#08246B;
selectionColor:#08246B;
textRollOverColor:#FFFFFF;
textSelectedColor:#FFFFFF;
}

MenuBar
{
backgroundColor:#D6D3CE;
backgroundSkin: Embed("MBSkin.gif");
color:#000000;
itemDownSkin: Embed("MBSelSkin.gif");
itemOverSkin: Embed("MBSelSkin.gif");
itemUpSkin: Embed("MBSkin.gif");
rollOverColor:#08246B;
selectionColor:#08246B;
}

MenuBarItem
{
color:#000000;
}
</mx:Style>


[The astute among you may be asking why we're using skins and background colors above. The reason why is for some reason I was having trouble getting the background colors to take, so I created single pixel images of the specified color and used it as a skin-this seemed to work fine. If you can get this to work without the skins, so much the better.]

If you add the above code to right below your mx:Application tag, you'll see we've made definite progress. See below.



The obvious problem here is that the menubar item 'File' is black when it is open-this shouldn't be the case-it should be white. You'll notice it doesn't change to white when you mouse over it either. Partly this is caused by a lack of a textRollOver style associated with menubar items-this style only applies to Menus. What we really need to do is detect when a mouse over event has occurred for the menubar item and change the color dynamically.

Adding Behaviors

We'll start off by inserting the following code:


for (i = 0; i < MyMenuBar.menuBarItems.length; i++)
{
MyMenuBar.menuBarItems[i].addEventListener(MouseEvent.ROLL_OVER, menuBarItemActivate);
MyMenuBar.menuBarItems[i].addEventListener(MouseEvent.ROLL_OUT, menuBarItemInactivate);
}
MyMenuBar.addEventListener(MenuEvent.MENU_SHOW, menuActivate);
MyMenuBar.addEventListener(MenuEvent.MENU_HIDE, menuInactivate);


I'd recommend adding this code to an initialization function that you call from say, a creationComplete on the mx:Application tag. The tricky part here is we just can't change the color of the menubar item when a mouse moves over the item or out of the menubar item-we must also leave the menubar item white when the associated menu is open and close it when the associated menu is closed (this is easier in some languages where the menubar item doesn't really exist, but is a part of the associated menu instead of it being a separate entity like it is in Flex).

To accomplish this we'll also track when menus are opened and closed on the menubar. Now for the event handling code:


private var MenuOpen:Boolean=false;

public function menuActivate(event:MenuEvent):void
{ /* Function menuActivate */
var i:int;

MenuOpen = true;
for (i = 0; i < MyMenuBar.menus.length; i++)
if (MyMenuBar.menus[i] == event.menu)
MyMenuBar.menuBarItems[i].setStyle("color", "#ffffff");
} /* Function menuActivate */

public function menuBarItemActivate(event:MouseEvent):void
{ /* Function menuBarItemActivate */
var i:int;

/* Inactive All Menu Items */
for (i=0; i < MyMenuBar.menuBarItems.length; i++)
MyMenuBar.menuBarItems[i].setStyle("color", "#000000");

event.currentTarget.setStyle("color", "#ffffff");
} /* Function menuBarItemActivate */

public function menuBarItemInactivate(event:MouseEvent):void
{ /* Function menuBarItemInactivate */
var i:int;

/* Inactivate All Menu Items */
for (i = 0; i < MyMenuBar.menuBarItems.length; i++)
MyMenuBar.menuBarItems[i].setStyle("color", "#000000");
if (MenuOpen)
event.currentTarget.setStyle("color", "#ffffff");
} /* Function menuBarItemInactivate */

public function menuInactivate(event:MenuEvent):void
{ /* Function menuInactivate */
var i:int;

MenuOpen = false;
for (i = 0; i < MyMenuBar.menus.length; i++)
if (MyMenuBar.menus[i] == event.menu)
MyMenuBar.menuBarItems[i].setStyle("color", "#000000");
} /* Function menuInactivate */


And wallah:



To see a live demo of this, check out ComCenter-it's used there.

This is completely functional, but you can't help but wonder if there was a better way to do this. Its much easier to give other components the windows classic skin-menus (due to the additional functionality) turn out to be one of the harder ones.


7 comments :

Biby Cletus said...

Cool blog, i just randomly surfed in, but it sure was worth my time, will be back

Deep Regards from the other side of the Moon

Biby Cletus

Anonymous said...

this is just grate, it solved my problem

Coke said...

Is it possible to add icons to the menu items
can you explain how
please i haver surf the web days and couldnt find anything on this

regards

Jorge Gomez
respaldo@dualplanet.com.mx

Anonymous said...

Haven't tried this with icons, but to get more comprehensive menuing components(and UI components in general) for Flash I would seriously recommend checking out aswing.

-WebDev Central

tgha said...

dear Sir

1st let me say thank you for great article, I have tried but when the menubaritem color is set the skin is gone, I copy paste your code and again the same issue,could you comment a demo url with source code or just put the mxml here , I would really appreciate that, thanks

Anonymous said...

Even we are stuck as the skin effect goes away when you apply text color to menubaritem. Have downloaded your latest project, even that shows the same effect (skin goes away) at my end. Is there something that we are missing or not doing right. Please assist.

Anonymous said...

Loved the "wallah".
Solved all my problems, Thanks - God bless.