Tutorial: Context menus are our friends if we use them correctly
In my quest to blur the line between native apps and AIR apps, I decided to fully incorporate context menus (right-click) in the next release of DestroyTwitter. In my initial tests, I came to realize that the ContextMenu class is the roommate who is really nice and optimistic before you move in, then a nightmare to get along with once you’re finally settled. It screams to be extended, but ironically it’s a final class (not extendable). After a few hours, I found a solution. But first, the problem.
Let’s say I have 100 icons. I want each icon to have five actions accessible from its context menu. This results in 100 ContextMenu instances and 500 NativeMenuItem instances, as seen below:

This is not pretty. The solution: share. In programming, sharing is one big step towards drastically reducing memory usage. If I share a single context menu, I will have one instance instead of 100 and five item instances instead of 500.

As expected, memory usage is divided by 100. The problem with sharing the ContextMenu class using a display object’s contextMenu property is that it can’t be done. On paper it can be, but not the way you want. To share a context menu, you’d have to listen on Event.DISPLAYING and use the hitTest() method to check which object the user is right-clicking. From a developer’s standpoint, this is simply unethical. Also, Flash content doesn’t update the mouse position between sequential right-clicks, so you could have a major issue with power users.
The solution is to forget the ContextMenu class altogether. While you’re at it, forget the contextMenu property as well. If you want something done right, you have to do it yourself. Add a listener to the MouseEvent.RIGHT_CLICK event, share a single NativeMenu instance, and call the NativeMenu.display() method in the right-click handler. For the purpose of this demonstration, I construct the NativeMenu in the Icon class. Since we’d ideally share the menu, you’d construct it outside of the class that’s using it, then reference it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package { // import classes public class Icon extends Sprite { public var _menu:NativeMenu; public function Icon () { __construct (); __init (); } private function __construct ():void { _menu = new NativeMenu (); // construct NativeMenu items } private function __init ():void { addEventListener (MouseEvent.RIGHT_CLICK, __rightClick__, false, 0, true); } private function __rightClick__ ($event:MouseEvent):void { // change NativeMenu items' attributes _menu.display (stage, stage.mouseX, stage.mouseY); } } } |
There is a second chapter to the context menu nightmare, which is the API for managing items. It’s clumsy, excessive, and requires the developers to write some pretty gross-looking code. I’ll save the solution to that for another time.
This is a great way to share context menus! I’ve personally had a lot of trouble with them, but more releated to how the player handles them. It seems as if your app is CPU intensive, like a game, then context menus start to have a lot of issues. One of the biggest ones is that you can’t seem to close the context menu after you’ve opened it. You can left and right click all around, but closing seems up to chance. The movie continues to update and animate in the background though. The other is that sometimes a context menu on the root of the stage seems to not apply to some loaded SWFs. In my current app I’ve got dozens of loaded SWFs at once, but there are one or two here and there that just don’t seem to pick up the context menu. I haven’t thoroughly explored this, but it’s still annoying!
Thanks again for your continued research into context menus. I’m always intereseted to hear your findings.
[...] position from updating. This results in a ContextMenu displaying in the wrong place. With my new-found method for displaying ContextMenus, I was able to set this issue to [...]