by Adobe

adobe_logo_bio

Created

23 March 2010

Requirements
 
User level Required products
All
Flex (Download trial)
A Tree control is a hierarchical structure of branch and leaf nodes. Each item in a tree is called a node and can be either a leaf or a branch. A branch node can contain leaf or branch nodes, or can be empty (have no children). A leaf node is an end point in the tree. This quick start covers some of the challenges developers frequently encounter when working with Tree controls.
 
The Flex Tree control is in the mx.controls.* package. There is no Spark equivalent of the MX Tree control in the Spark component set. As a result, regardless of whether your application uses the Spark, MX, or both component sets, you use the MX Tree control.
 
This Quick Start describes the following topics:
 

 
Comparing use of XMLListCollection and ArrayCollection objects

You might wonder whether you should use an XMLListCollection object or an ArrayCollection object as the data provider for a Tree control when working with data that changes dynamically; this includes data returned from a remote data service or data in local objects that are modified at run time. Whether you use an XMLListCollection object or an ArrayCollection object depends on the form of the data.
 
If you are working with a data source that provides well formed XML and you want to manipulate the data as XML in the Tree control, you should use an XMLListCollection object as the data provider. When working in MXML tags, if the data source is an XMLList object, you should bind that object to the source property of an XMLListCollection object, and then bind the XMLListCollection object to the dataProvider property of the Tree control. Do not bind an XMLList object or XML object directly to the dataProvider property of the Tree control when you expect these objects to change dynamically.
 
When your data source is the lastResult object of an RPC service call and you want to work with XML data, ensure that the resultFormat property on the RPC component is set to "e4x"; when you use the E4X result format, the lastResult object is an XMLList object that you can bind to the source property of an XMLListCollection object. Here is an example that uses the E4X result format.
 
For object data that changes dynamically, use an ArrayCollection object as the Tree control's data provider. When working with MXML tags, you should not bind an Array object directly to the dataProvider property of a Tree control when you expect the Array to change dynamically. Instead, you should bind the Array to the source property of an ArrayCollection object and bind that to the dataProvider property of the Tree control.
 
When the data source is the lastResult object of an RPC service call and the resultFormat property of the RPC component is set to "object", you should use the ArrayUtil.toArray() method to ensure that the object is an Array when you bind it to an ArrayCollection object, as the following example shows:
 
<s:ArrayCollection id="employeeAC" source="{ArrayUtil.toArray(employeeSrv.lastResult.employees.employee)}"/>

 
Adding and removing leaf nodes at run time

You can add and remove leaf nodes from a Tree control at run time. The following example contains code for making these changes. The application initializes with predefined branches and leaf nodes that represent company departments and employees. You can add leaf nodes to one of the branches at run time. You can also remove the predefined leaf nodes and the ones you create yourself at run time.
 
The XML in this example contains two different element names, department and employee. The Tree control's label function, treeLabel(), determines what text is displayed for these types of elements. It uses E4X syntax to return either the title of a department or the name of an employee. Those values are then used in the addEmployee() and removeEmployee() methods.
 
To add employees to the Operations department, the addEmployee() method uses E4X syntax to get the Operations department node based on the value of its title attribute and stores it in the dept variable, which is of type XMLList. It then appends a child node to the Operations node by calling dept.appendChild().
 
The removeEmployee() method stores the currently selected item in the node variable, which is of type XML. The method calls the node.localName() method to determine if the selected item is an employee node. If the item is an employee node, it is deleted.
 
 
Example
<?xml version="1.0" encoding="utf-8"?> <!-- working_with_tree_controls/TreeAddRemoveNodes.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Script> <![CDATA[ import mx.collections.XMLListCollection; [Bindable] private var company:XML = <list> <department title="Finance" code="200"> <employee name="John H"/> <employee name="Sam K"/> </department> <department title="Operations" code="400"> <employee name="Bill C"/> <employee name="Jill W"/> </department> <department title="Engineering" code="300"> <employee name="Erin M"/> <employee name="Ann B"/> </department> </list>; [Bindable] private var companyData:XMLListCollection = new XMLListCollection(company.department); private function treeLabel(item:Object):String { var node:XML = XML(item); if ( node.localName() == "department" ) return node.@title; else return node.@name; } private function addEmployee():void { var newNode:XML = <employee/>; newNode.@name = empName.text; var dept:XMLList =company.department.(@title == "Operations"); if ( dept.length() > 0 ) { dept[0].appendChild(newNode); empName.text = ""; } } private function removeEmployee():void { var node:XML = XML(tree.selectedItem); if ( node == null ) return; if( node.localName() != "employee" ) return; var children:XMLList = XMLList(node.parent()).children(); for (var i:Number=0; i < children.length(); i++) { if ( children[i].@name == node.@name ) { delete children[i]; } } } ]]> </fx:Script> <mx:Tree id="tree" top="72" left="50" dataProvider="{companyData}" labelFunction="treeLabel" height="224" width="179"/> <s:HGroup> <s:Button label="Add Operations Employee" click="addEmployee()"/> <s:TextInput id="empName"/> </s:HGroup> <s:Button label="Remove Selected Employee" click="removeEmployee()"/> </s:Application>

 
Adding an empty branch node at run time

You can add empty branches to a Tree control at run time. The following example shows how to add branch nodes through the data provider API and through the data descriptor API. Generally, the preferred way to add empty branch nodes is through the data provider API.
 
The addEmptyBranchDP() method adds an empty branch through the data provider API for an XML data provider. This method creates a variable of type XML for a new node and sets the node's isBranch attribute to true to create a branch node. The method then calls the Tree control's dataProvider.addItemAt() method to add a new item to the Tree control's data provider.
 
The addEmptyBranchDP2() method adds an empty branch through the data provider API for a Tree control with an object data provider. This method creates an object with a children property, which makes the isBranch() method return true. The method then calls the Tree control's dataProvider.addItemAt() method to add the new object to the Tree control's data provider.
 
The addEmptyBranchDD() method adds an empty branch through the data descriptor API. This method creates a variable of type XML for a new Tree node and sets the node's isBranch attribute to true to create a branch node. The method then calls the Tree control's dataDescriptor.addChildAt() method to add a new child to the Tree control's data descriptor.
 
 
Example
<?xml version="1.0" encoding="utf-8"?> <!-- working_with_tree_controls/TreeAddEmptyBranch.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Script> <![CDATA[ [Bindable] private var dataX:XML = <item label="Top"> <item label="Child One"/> <item label="Child Two" /> </item>; [Bindable] private var dataObj:Object = [{label:"Top", children: [{label:"Child One"}, {label: "Child Two"}]}]; /* Adding a branch by going through the Tree's dataProvider. This is the preferred method. Toggling the isBranch attribute to true causes DefaultDataDescriptor.isBranch() to return true and the Tree treats the node as a branch. */ private function addEmptyBranchDP():void { var newNode:XML = <item label='Middle' isBranch="true"></item>; xmlBound2Tree.dataProvider.addItemAt(newNode, 1); } /* For an object graph, the key point is that the children property needs to be defined, even if it is empty. This causes isBranch() to return true and the Tree treats the new node as a branch. */ private function addEmptyBranchDP2():void { var newObj:Object = {label:"Middle", children:[]}; objGraphBound2Tree.dataProvider.addItemAt(newObj, 1); } /* Adding a branch by going through the Tree's dataDescriptor. */ private function addEmptyBranchDD():void { var newNode:XML = <item label='Child 4' isBranch="true"></item>; xmlBound2Tree.dataDescriptor.addChildAt(dataX, newNode, 2, dataX); } ]]> </fx:Script> <s:Label text="Tree with XML data"/> <mx:Tree id="xmlBound2Tree" dataProvider="{dataX}" labelField="@label" showRoot="true" width="200"/> <s:Button label="Add Empty Branch through the dataProvider" click="addEmptyBranchDP();"/> <s:Button label="Add Empty Branch through the dataDescriptor" click="addEmptyBranchDD();"/> <mx:Spacer height="10"/> <s:Label text="Tree with object data"/> <mx:Tree id="objGraphBound2Tree" dataProvider="{dataObj}" width="200"/> <s:Button label="Add Empty Branch through the dataProvider" click="addEmptyBranchDP2();"/> </s:Application>

 
Opening a Tree to a specific node

By default, the Tree control is collapsed when it initializes, and you might be uncertain how to initialize the Tree so that it is expanded with a specific node selected. The following example shows how to do that. In this application, the initTree() method is called after the Tree control is created. This method expands the root node of the Tree control and sets its selectedIndex property to the index number of a specific node.
 
 
Example
<?xml version="1.0"?> <!-- working_with_tree_controls/TreeOpenToNode.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Declarations> <mx:XMLListCollection id="MailBox"> <fx:XMLList> <node label="Mail" data="100"> <node label="Inbox" data="70"/> <node label="Personal Folder" data="10"> <node label="Business" data="2"/> <node label="Demo" data="3"/> <node label="Saved Mail" data="5" /> </node> <node label="Sent" data="15"/> <node label="Trash" data="5"/> </node> </fx:XMLList> </mx:XMLListCollection> </fx:Declarations> <fx:Script> <![CDATA[ import flash.events.*; import mx.events.*; import mx.controls.*; private function initTree():void { XMLTree1.expandItem(MailBox.getItemAt(0), true); XMLTree1.selectedIndex = 2; } ]]> </fx:Script> <mx:Tree id="XMLTree1" width="150" height="170" dataProvider="{MailBox}" labelField="@label" creationComplete="initTree();"> </mx:Tree> </s:Application>

 
Reading an XML document with multiple node names

You can populate a Tree control from an XML document that has multiple node names. The following example shows how to do this. The Tree control's data provider is an XMLListCollection populated from an XMLList that contains folder and Pfolder elements. The Tree control's labelField property is set to the label attribute, which is common to all of the XMLList elements, regardless of the element names.
 
 
Example
<?xml version="1.0"?> <!-- working_with_tree_controls/TreeDiffNodeNames.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <fx:Declarations> <mx:XMLListCollection id="MailBox"> <fx:XMLList id="Folders"> <folder label="Mail"> <folder label="INBOX"/> <folder label="Personal Folder"> <Pfolder label="Business"/> <Pfolder label="Demo"/> <Pfolder label="Saved Mail"/> </folder> <folder label="Sent"/> <folder label="Trash"/> </folder> </fx:XMLList> </mx:XMLListCollection> </fx:Declarations> <mx:Tree id="tree1" dataProvider="{MailBox}" labelField="@label" showRoot="true" width="160"/> </s:Application>

 
Keeping a Tree control open when the data provider is updated

By default, the Tree control collapses when its data provider is updated with new data. The following example shows one way to keep the Tree control expanded when its data provider is updated.
 
In this application, the changeProvider() method refreshes the data provider when a user clicks on a Button control. Normally, this results in the Tree control collapsing. However, the Tree control's render event handler, the renderTree() method, prevents that from happening. When the changeProvider() method is called, the current state of open items is saved in an object that is assigned to the variable named open. A Boolean variable named refreshData, is set to true when the data is refreshed. The renderTree() method calls the Tree control's invalidateList() method to refresh the Tree rows. It then sets the refreshData property to false and resets the Tree control's openItems property to the open object, which contains the state of open items before the data provider was refreshed.
 
 
Example
<?xml version="1.0" encoding="utf-8"?> <!-- working_with_tree_controls/TreeKeepOpen.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" initialize="initTree()"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Script> <![CDATA[ [Bindable] public var open:Object = new Object(); [Bindable] public var refreshData:Boolean = false; [Bindable] public var switchObj:Object = new Object(); [Bindable] public var firstObj:Object = new Object(); [Bindable] public var firstObj1:Object = new Object(); [Bindable] public var firstObj2:Object = new Object(); [Bindable] public var provider:String = "firstObj"; private function initTree():void { firstObj = new Object(); firstObj.label = "Foods"; firstObj.children = new Array(); firstObj1.label = "Fruits"; firstObj1.children = new Array(); firstObj2.label = "Oranges"; firstObj1.children[0] = firstObj2; firstObj.children[0] = firstObj1; switchObj = firstObj; } public function changeProvider():void { open = SampleTree.openItems; refreshData = true; if (provider == "firstObj") { provider = "switchObj"; SampleTree.dataProvider = switchObj; } else { provider = "firstObj"; SampleTree.dataProvider = firstObj; } } public function renderTree():void { if (refreshData){ /* Refresh Tree on update. */ SampleTree.invalidateList(); refreshData = false; SampleTree.openItems = open; /* Validate and update properties of the Tree and redraw it if necessary. */ SampleTree.validateNow(); } } ]]> </fx:Script> <mx:Tree id="SampleTree" render="renderTree()" width="250" dataProvider="{firstObj}" labelField="label"/> <s:Button label="Change Data Provider" click="changeProvider()"/> </s:Application>

 
Dragging and dropping items to and from a Tree control

Creating an application that drags and drops items to or from a Tree control can seem daunting, particularly because it can require quite a bit of event handling logic. This section provides two examples that demonstrate techniques for both of these scenarios.
 
 
Dragging and dropping from a Tree control
The following example shows how to drag and drop items from a Tree control to a DataGrid control. The Tree control's data provider is an XML object. Use the comments that precede the drag and drop event handler methods to guide you through the application.
 
 
Example
<?xml version="1.0" encoding="utf-8"?> <!-- working_with_tree_controls/TreeDragFrom.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <s:layout> <s:HorizontalLayout/> </s:layout> <fx:Declarations> <fx:XML id="treeData" xmlns=""> <root> <node label="Massachusetts" type="state" data="MA"> <node label="Boston" type="city" > <node label="Smoke House Grill" type="restaurant" /> <node label="Equator" type="restaurant" /> <node label="Aquataine" type="restaurant" /> <node label="Grill 23" type="restaurant" /> </node> <node label="Provincetown" type="city" > <node label="Lobster Pot" type="restaurant" /> <node label="The Mews" type="restaurant" /> </node> </node> <node label="California" type="state" data="CA"> <node label="San Francisco" type="city" > <node label="Frog Lane" type="restaurant" /> </node> </node> </root> </fx:XML> </fx:Declarations> <fx:Script> <![CDATA[ import mx.controls.*; import mx.controls.listClasses.*; import mx.collections.ArrayCollection; import mx.core.DragSource; import mx.events.DragEvent; import mx.containers.Canvas; import mx.managers.DragManager; import mx.core.UIComponent; [Bindable] private var dataGridProvider:ArrayCollection = new ArrayCollection(); /** * Handles the dragEnter event on the DataGrid control. * If the dragInitiator is the Tree, then only nodes of type "restaurant" * are permitted to be dropped. * Here you can see that by examining the dragSource you can determine if * the control should accept the drop. The DataGrid control would not * know how to treat a branch+children from the Tree control, so only leaf (restaurant) * nodes are accepted. */ private function onDragEnter( event:DragEvent ) : void { if( event.dragInitiator is Tree ) { var ds:DragSource = event.dragSource; if( !ds.hasFormat("treeItems") ) return; // no useful data var items:Array = ds.dataForFormat("treeItems") as Array; for(var i:Number=0; i < items.length; i++) { var item:XML = XML(items[i]); if( item.@type != "restaurant" ) return; // not what we want } } // If the Tree control passes or the dragInitiator is not a tree, accept the drop. DragManager.acceptDragDrop(UIComponent(event.currentTarget)); } /** * Handles the dragOver event on the DataGrid control. * If the dragInitiator is the Tree control, only copy is allowed. Otherwise, a move * or link can take place from the List control. */ private function onDragOver( event:DragEvent ) : void { if( event.dragInitiator is Tree ) { DragManager.showFeedback(DragManager.COPY); } else { if (event.ctrlKey) DragManager.showFeedback(DragManager.COPY); else if (event.shiftKey) DragManager.showFeedback(DragManager.LINK); else { DragManager.showFeedback(DragManager.MOVE); } } } /** * Handles the dragExit event on the drop target and just hides the * the drop feedback. */ private function onDragExit( event:DragEvent ) : void { var dropTarget:ListBase=ListBase(event.currentTarget); dropTarget.hideDropFeedback(event); } /** * Handles the dragDrop event on the DataGrid control when the * drag proxy is released. */ private function onGridDragDrop( event:DragEvent ) : void { var ds:DragSource = event.dragSource; var dropTarget:DataGrid = DataGrid(event.currentTarget); var arr:Array; if ( ds.hasFormat("items") ) { arr = ds.dataForFormat("items") as Array; } else if ( ds.hasFormat("treeItems") ) { arr = ds.dataForFormat("treeItems") as Array; } for (var i:Number=0; i < arr.length; i++) { var node:XML = XML(arr[i]); var item:Object = new Object(); item.label = node.@label; item.type = node.@type; dataGridProvider.addItem(item); } onDragExit(event); } /** * Intercepts the dragComplete event on the Tree control * and prevents the default behavior from happening. This is necessary * if the item being dragged from the Tree control is dropped on a non-Tree * object, such as the DataGrid. */ private function onTreeDragComplete(event:DragEvent):void { event.preventDefault(); } /** * Selects all of the items in the List control if Ctrl+A is picked when the List control * has focus. */ private function selectAllMaybe( event:KeyboardEvent ) : void { if ( event.ctrlKey && event.keyCode == 65 ) { var l:List = List(event.currentTarget); var allItems:Array = new Array(l.dataProvider.length); for (var i:Number=0; i < allItems.length; i++) { allItems[i] = i; } l.selectedIndices = allItems; } } ]]> </fx:Script> <s:VGroup> <s:Label x="34" y="40" text="Drag items from this Tree:"/> <mx:Tree x="34" y="81" width="181" height="189" dataProvider="{treeData.node}" labelField="@label" dropEnabled="false" dragEnabled="true" dragComplete="onTreeDragComplete(event)" dragMoveEnabled="false"/> </s:VGroup> <s:VGroup> <s:Label x="291" y="55" text="Drop items from Tree here:"/> <mx:DataGrid x="291" y="81" height="189" dragEnabled="true" dataProvider="{dataGridProvider}" dragEnter="onDragEnter(event)" dragOver="onDragOver(event)" dragDrop="onGridDragDrop(event)" dragExit="onDragExit(event)"> <mx:columns> <mx:DataGridColumn headerText="Label" dataField="label"/> <mx:DataGridColumn headerText="Type" dataField="type"/> </mx:columns> </mx:DataGrid> </s:VGroup> </s:Application>

 
Dragging and dropping to a Tree control

The following example shows how to drag and drop items to a Tree control from a List control. The Tree data provider is an XML object. Use the comments that precede the drag and drop event handler methods to guide you through the application.
 
 
Example
<?xml version="1.0" encoding="utf-8"?> <!-- working_with_tree_controls/TreeDragTo.mxml --> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"> <s:layout> <s:VerticalLayout/> </s:layout> <fx:Declarations> <fx:XML id="treeData" xmlns=""> <root> <node label="Massachusetts" type="state" data="MA"> <node label="Boston" type="city" > <node label="Smoke House Grill" type="restaurant" /> <node label="Equator" type="restaurant" /> <node label="Aquataine" type="restaurant" /> <node label="Grill 23" type="restaurant" /> </node> <node label="Provincetown" type="city" > <node label="Lobster Pot" type="restaurant" /> <node label="The Mews" type="restaurant" /> </node> </node> <node label="California" type="state" data="CA"> <node label="San Francisco" type="city" > <node label="Frog Lane" type="restaurant" /> </node> </node> </root> </fx:XML> <fx:Array id="listData"> <fx:String>Johnny Rocket's</fx:String> <fx:String>Jet Pizza</fx:String> <fx:String>Steve's Greek</fx:String> <fx:String>Sonsie</fx:String> <fx:String>The Border Cafe</fx:String> </fx:Array> </fx:Declarations> <fx:Script> <![CDATA[ import mx.events.DragEvent; import mx.managers.DragManager; import mx.core.DragSource; import mx.core.UIComponent; import mx.controls.Tree; /** * Called as soon as the dragProxy enters the target. You can add logic * to determine if the target will accept the drop based on the * dragInitiator, the data available in the dragSource, or whatever. * Here the drop is blindly accepted. */ private function onDragEnter( event:DragEvent ) : void { DragManager.acceptDragDrop(UIComponent(event.currentTarget)); } /** * Called while the dragProxy is over the drop target. You can * use this function to determine the type of feedback to show. * Because the List is set to allow MOVE (the item is deleted * once dropped), different feedback possibilities are given. * * Also, for this application, the Tree control node the dragProxy is * over is selected. As the dragProxy moves, the Tree control's * selection changes. * * A complexity in this application is that the drop is being allowed * only over nodes whose type is NOT 'state'. * The feedback is removed. */ private function onDragOver( event:DragEvent ) : void { var dropTarget:Tree = Tree(event.currentTarget); var r:int = dropTarget.calculateDropIndex(event); tree.selectedIndex = r; var node:XML = tree.selectedItem as XML; if ( node.@type == "state" ) { DragManager.showFeedback(DragManager.NONE); return; } if (event.ctrlKey) DragManager.showFeedback(DragManager.COPY); else if (event.shiftKey) DragManager.showFeedback(DragManager.LINK); else { DragManager.showFeedback(DragManager.MOVE); } } /** * Called when the dragProxy is released * over the drop target. The information in the dragSource * is extracted and processed. * * The target node is determined and * all of the data selected (the List has allowMultipleSection * set) is added. */ private function onDragDrop( event:DragEvent ) : void { var ds:DragSource = event.dragSource; var dropTarget:Tree = Tree(event.currentTarget); var items:Array = ds.dataForFormat("items") as Array; var r:int = tree.calculateDropIndex(event); tree.selectedIndex = r; var node:XML = tree.selectedItem as XML; var p:*; /* If the selected node has children (it is type==city), then add the items at the beginning. */ if ( tree.dataDescriptor.hasChildren(node) ) { p = node; r = 0; } else { p = node.parent(); } for (var i:Number=0; i < items.length; i++) { var insert:XML = <node />; insert.@label = items[i]; insert.@type = "restaurant"; tree.dataDescriptor.addChildAt(p, insert, r+i); } } /** * Called when the drag operation completes, whether * successfully or not. The tree is cleared of its * selection. */ private function onDragComplete( event:DragEvent ) : void { tree.selectedIndex = -1; } ]]> </fx:Script> <s:Panel x="48" y="125" width="447" height="351" title="Drag onto Tree"> <mx:Tree width="186" left="10" top="10" bottom="10" id="tree" labelField="@label" dataProvider="{treeData.node}" dropEnabled="false" dragMoveEnabled="false" dragEnter="onDragEnter(event)" dragOver="onDragOver(event)" dragDrop="onDragDrop(event)"> </mx:Tree> <mx:List width="188" height="206" right="10" bottom="10" id="list" allowMultipleSelection="true" dataProvider="{listData}" dragEnabled="true" dragMoveEnabled="true" dragComplete="onDragComplete(event)"> </mx:List> <s:Label x="229" y="10" text="Drag from the list below to the tree" width="188" height="39"/> <s:Label x="229" y="69" text="restaurants"/> </s:Panel> </s:Application>

 
For more information