I’ve been working on an AIR application for managing SQLite databases where I’ve been using the Flex Tree component to list out all databases. I’ve been having a hard time finding good resources on working with a ContextMenu (right click menu) on a Tree component.
I tried my luck on adding a ContextMenu to the Tree component itself and managing it there. But that idea went sour because I couldn’t find a way to get the current Tree item selected since right-clicking doesn’t actually select it. After checking out what others have done, this wasn’t getting any easier because I wasn’t convinced by the solutions provided. They seemed more like hacks when I knew this should be pretty simple.
It never occurred to me to try adding the ContextMenu on a Tree ItemRenderer, so when I originally thought of it, I figured eureka, that’s it! Ultimately, though, I didn’t think it through that well since adding a renderer would also remove the folding arrows and folder/leaf icons. Ouch! Although now I know what was needed, I realized that adding a TreeItemRenderer is not the same as another List-based renderer such as one for a DataGrid or List component.
After following the solution on the Flex Cookbook, I dropped in that demo item renderer and was able to attach a ContextMenu object to it in the renderer constructor. So something like this:
public function DatabaseListRenderer()
{
super();
var contextMenu:ContextMenu = new ContextMenu();
var menuItems:Array = [];
var edit:ContextMenuItem = new ContextMenuItem("Edit Name");
edit.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, databases_menuItemSelectHandler);
menuItems.push(edit);
contextMenu.customItems = menuItems;
this.contextMenu = contextMenu;
}
private function databases_menuItemSelectHandler(event:ContextMenuEvent):void {
trace("menu item selected: " + data);
}
Now when I right-click on a particular item and select a menu option, the event handler fires and I see my trace message. Notice I dump out the data to see what’s there (debugger is a better place for this) and I see the data for that specific Tree item as if it were selected. Problem solved!
JT said:
I’m trying to do the same with with a ContextMenu when right clicking on a Tree control in Flex. Can you post a sample showing your complete tree code? Thank you.
Javier Julio said:
JT,
I apologize but I don’t have a sample with just this functionality. All I did was take the sample from the Flex Cookbook article and customized it with a simple a test (which you can see in the code view in this blog post) and that was it.
There really is nothing else to it. Just remember the trick to adding a context menu to the Tree component is to add it on the item renderer.
JT said:
Thanks for the reply.
What is the superclass of example above?
Javier Julio said:
JT,
Sorry about that! It would have helped if the blog post had links! I’ll have to chat with the person here who posts the Quick Tip blog articles. :)
If you follow the links now mentioned in the article above things will be much clearer. The subclass in the example above is TreeItemRenderer. I just followed the same sample on the Flex Cookbook article and everything worked great. Let me know if you have any trouble.
JT said:
Ok I got it figured out. The superclass of the above code is TreeItemRenderer and then you set the itemRenderer property of your Tree instance to your new TreeItemRenderer extended class.
JT said:
Have you tried using an Event Listener to listen for right clicks in the TreeItemRenderer extended class?
For example putting “this.addEventListener(MouseEvent.RIGHT_CLICK, rightClickHandler);” in the DatabaseListRenderer constructor.
It seems like the when you attach the ContextMenu, the ContextMenu consumes the right click event and the rightClickHandler method is never called.
I’m trying to have it setup so when you right click on the a tree item there will be a ContextMenu and that tree item will be selected. So far I can not get both to happen at the same time.
Javier Julio said:
JT,
I’ve had a very similar issue with a component where I was trying to create a line numbered TextArea. I needed a set of utility methods that would get me the line the user was on based on an x and y coordinate. Luckily the flash component TextField has those methods. You’ll need to use the getObjectsUnderPoint method of the TreeItemRenderer.
Scratch that.. I did some more digging through events and I think I got it. At the end of the constructor for the TreeItemRenderer I added an event listener for MouseEvent.CONTEXT_MENU. That will fire once the context menu is displayed.
Since you are in the item renderer you have access to the data property so you already know the item that is to be selected. If you set the selectedItem property of the Tree it will appear selected. Although if a previous selection was made it too is selected. Not sure how to get around that but this should give you plenty to fool around with!
Again add this to the ItemRenderer constructor (I’m following my example above so change accordingly for you):
addEventListener(MouseEvent.CONTEXT_MENU, database_contextMenuHandler);
And then define the event handler function:
private function database_contextMenuHandler(event:MouseEvent):void {
var xmlData:XML = data as XML;
trace( xmlData.toXMLString() );
var tree:Tree = this.owner as Tree;
tree.selectedItem = xmlData;
trace(“new selected item!\n” + tree.selectedItem);
}
Note that I used XML as my dataProvider for the Tree but you would do something very similar if it were an ArrayCollection. Hope this helps in you getting further! Let me know how you make out.
JT said:
What I ended up doing was using “addEventListener(MouseRight.RIGHT_CLICK, rightClickHandler);” in the constructor and not attaching the ContextMenu to the item renderer in the constructor.
Then in the “rightClickHandler” method I set the selected tree node and show the ContextMenu.
private function rightClickHandler(event:MouseEvent):void {
var x:int = event.stageX;
var y:int = event.stageY;
Tree (parent.parent).selectedItem = data;
treeContextMenu.display(stage, x, y); // I declared this ContextMenu in the constructor
}
I found out that if you click on the actual icon of the tree item after setting the selected node the x and y values of the “event.stageX, event.stageY” get screwed up. It is weird if you click on the tree item text they are fine. So I save the x and y values then set the new selected tree node. Then I display the ContextMenu.
So far it is working as I want.
I can right click on a tree node and it will select it and the ContextMenu will display.
Thanks for your help!
Vickram said:
Nice tip! Thanks.
Tom said:
You should create only 1 ContextMenu object, imagine if you have 400 items in your Tree ;-(
Then you listen the itemRollOver event:
function(e:ListEvent)
{
TreeItemRenderer(e.itemRenderer).contextMenu = mm;
}
Keep in mind to make your itemRenderer component as light as possible
Steve said:
Hi,
I know this is an old blog post, but curious if anyone is still watching. I’m having a hell of a time figuring out where you added your above code to in the cookbook example.
I’ve been trying for quite some time and feel like I’m making a silly mistake. Any suggestions would be very helpful, as I’m trying to add in “edit” “delete” and “add” functionality to a context menu for a tree.
(and yes, I’ve specified the renderer in the Tree mxml. I’ve got it showing purple, like the cookbook, just no context menu additions.)
I’m a bit new to flex, so please pardon if this question is foolish.
Javier Julio said:
@Tom,
You are absolutely right. At that time it wasn’t my concern since I was trying to find something that works. Since then I’ve learned a lot better and in that case what I would do is create it as a static variable so it only gets created once.
@Steve,
I had a renderer called DatabaseListRenderer so all the code you see above (minus the last 3 lines) is in the renderer’s constructor. You would do the same for the cookbook example. The last 3 lines is a private function that handles a menu item select event. Simple as that.
liliass said:
Hii,
I am beginner in Flex, and i want when i click right with mouse to post a a component that that I already programmed;
can you help me,
Steve said:
Javier,
Thank you so much! I feel like an idiot. I spent an hour or two trying to copy and paste part of your code into the sample from the cookbook! (so foolish)
Got it working now. Thanks so much for the tip.
Javier Julio said:
No problem Steve! Always glad to help. Make sure to take into consideration that you should only have the Menu instance created once. Might want to consider it creating it as a static variable.
Arun said:
Hi all,
I am working on a flex project that has to trigger opening of a mxml component(which is a canvas), on selection of a tree control item. I tried this with click event handler, but then the page(ie the canvas component) gets displayed even without me clicking on the tree control item. Could you please help me out with this issue.
any help is appreciated.
Javier Julio said:
@Arun,
What you need to do is create a variable (you can make it bindable) and bind the visible property of that canvas. So initially that variable will be set to false because by default you don’t want it displayed. Then when you click on an item in the tree you set that bindable variable to true and your canvas will appear.
ktdsm said:
hi, i need some help … I have a flex screen . It has a datagrid and a tree control.I want to create one context menu for datgrid with COPY option and the other context menu for tree with PASTE.
I want to copy the row from datagrid to tree through copy . paste menu options.
I have created two seperate contextmenus for each control. I dont know exactly how would i retain the copied values from datagrid , when COPY option is clicked on the contextmenu and then paste it to the tree control.
I am a beginner and having a hard time in getting the functionality implemented.
Please help me out…
kumar said:
Hi,
I have read the entire post. I tired what Javier Hulio said, but selecting of Tree item is NOT being selected.
When you say flex cookbook sample, are you referring to ” Create an Item Renderer for a Tree”, page 142 sample code.
Please help
Thanks
kumar said:
Sorry for the typos but selection of tree item is not happening
Javier Julio said:
@Kumar
The Flex cookbook sample I refer too I have linked within the article. You can find it here:
http://cookbooks.adobe.com/index.cfm?event=showdetails&postId=62
I used the renderer there as a starting point because an item renderer for a Tree is very different from a List which we are much more accustomed to working with. My mistake was thinking I could just drop in any renderer but it doesn’t work that way as it will mess up the Tree itself, missing arrow icons, etc. The cookbook sample is what I started off with.
kumar said:
Javier,
Thanks for the response. I tried the with sample and added the contextmenu creation in the constructor, but still could not get working, my bad.
I am not clear if this line below is only adding event handler ?
edit.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, databases_menuItemSelectHandler);
does the event handler function (databases_menuItemSelectHandler) should have some extra logic to select /highlight the item in the tree ?
As per your blog, it seems it does not require, Please throw some light. Thanks for all the help
Below is the sample i have been trying
[code]
<![CDATA[
import mx.events.ListEvent;
import mx.collections.XMLListCollection;
[Bindable]
public var treeMenu:ContextMenu = new ContextMenu();
public var lastTreeItem:ListEvent;
[Bindable]
private var company:XML =
;
[Bindable]
private var companyData:XMLListCollection = new XMLListCollection(company.department);
]]>
[/code]
[code]
package views.renderers
{
import flash.events.ContextMenuEvent;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import mx.collections.*;
import mx.controls.Tree;
import mx.controls.listClasses.IListItemRenderer;
import mx.controls.treeClasses.*;
import views.controls.ModelGrid;
public class ModelVersionNode extends TreeItemRenderer
{
public var isVersion:Boolean = false;
public var isPublished:Boolean = false;
protected var nodeGrid:ModelGrid;
protected var _tree:Tree;
public function ModelVersionNode()
{
super();
var contextMenu:ContextMenu = new ContextMenu();
contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, databases_menuItemSelectHandler);
var menuItems:Array = [];
var edit:ContextMenuItem = new ContextMenuItem("Edit Name");
edit.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, cm_menuItemSelectHandler);
menuItems.push(edit);
contextMenu.customItems = menuItems;
this.contextMenu = contextMenu;
}
override public function set data(value:Object):void
{
if(value != null)
{
super.data = value;
if(TreeListData(super.listData).hasChildren)
{
setStyle("color", 0x660099);
setStyle("fontWeight", 'bold');
}
else
{
setStyle("color", 0x000000);
setStyle("fontWeight", 'normal');
}
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if(super.data)
{
if(TreeListData(super.listData).hasChildren)
{
var tmp:XMLList = new XMLList(TreeListData(super.listData).item);
var myStr:int = tmp[0].children().length();
super.label.text = TreeListData(super.listData).label + "(" + myStr + " objects)";
}
}
}
private function databases_menuItemSelectHandler(event:ContextMenuEvent):void {
var rowNum:int;
if (event.mouseTarget is IListItemRenderer) {
rowNum = this._tree.itemRendererToIndex(event.mouseTarget as IListItemRenderer);
if (rowNum >= 0) this._tree.selectedIndex = rowNum;
}
}
private function cm_menuItemSelectHandler(event:ContextMenuEvent):void
{
}
}
}
[/code]
Javier Julio said:
You can have the event handler do whatever you like. The example was merely to demonstrate how to add a context menu to a Tree but since you have the event listener there you can have the handler do anything. I see now that on an earlier comment I posted about selection:
Define the following in the constructor:
addEventListener(MouseEvent.CONTEXT_MENU, database_contextMenuHandler);
And then define the event handler function where the myData variable would be the object in Tree’s dataProvider that you want selected.
private function database_contextMenuHandler(event:MouseEvent):void {
var tree:Tree = this.owner as Tree;
tree.selectedItem = myData;
trace(”new selected item!\n” + tree.selectedItem);
}
kumar said:
Hi,
Thanks for the response.
You meant, that i should add this to context menu ?
addEventListener(MouseEvent.CONTEXT_MENU, database_contextMenuHandler);
Also the there is no event MouseEvent.CONTEXT_MENU is available in AIR. I am using flex for web applications ( so it runs in flash player). I tried with MouseEvent.CLICK, but it did not help.
One thing i observed from the begining is when i right click the context menu appears, when i select on the options in the menu, the tree item below is getting highlighted/selected.
Please let me know. Thank you
kumar said:
Hi,
I meant MouseEvent.CONTEXT_MENU is available ONLY in AIR
kumar said:
Hi Javier,
I think i got it working.. runnign some tests
contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, database_contextMenuHandler);
Thanks for all the help. Keep you posted