2012/06/29

To Reduce the Vertical Space Between the Separator and the Texts

Recently I have seen some articles introducing how to realize the PopUpButton control’s pop up menu. Till now, there are quite some ways to realize it. Now let’s first have a look at them:
  1. TWaver’s earliest way to realize it is the use of flash’s own pop-up menu which is set through ContextMenu. However, there is a shortage in this way: it has flash’s information on the menu with itself, which cannot be deleted. So it’s inconvenient.
  2. Menu can also be popped out at the click of the left button. This way is agreeable as it has no requirement for the edition of flash.
  3. It can be realized by Flash Player 11.2’s new function. Although this way is good, we have to use flash of higher edition and many users in our project are still using old editions of flash. So this way is not that suitable.
Of the above three methods, we have finally chosen the second one. But I came across a problem in the process of realizing the pop-up menu through Menu—the classifications and layering of the menu in actual applications.
This effect, in general, is enough. But we have found that the vertical space between the separator and the above & below texts is too big. In order to reduce it, we have searched for solutions and found out that a parameter can make it: variableRowHeight. Here’s the detailed example: Reducing the vertical space around a separator in a Flex PopUpButton control’s pop up menu by enabling variable row heights.
Let’s see the effects of the menu. But it turns out to be disagreeable after setting variableRowHeight=true in menu.
The vertical space has been reduced on the first-level menu, but not on the second-level. Why? On google we only find that many viewpoints saying that the problem will be solved as long as this attribute value has been set. It can’t be a bug of Adobe. You will find the crux of the problem on seeing the following source code.


mx_internal function openSubMenu(row:IListItemRenderer):void
{
supposedToLoseFocus = true;

var r:Menu = getRootMenu();
var menu:Menu;

// check to see if the menu exists, if not create it
if (!IMenuItemRenderer(row).menu)
{
menu = new Menu();
menu.parentMenu = this;
menu.owner = this;
menu.showRoot = showRoot;
menu.dataDescriptor = r.dataDescriptor;
menu.styleName = r;
menu.labelField = r.labelField;
menu.labelFunction = r.labelFunction;
menu.iconField = r.iconField;
menu.iconFunction = r.iconFunction;
menu.itemRenderer = r.itemRenderer;
menu.rowHeight = r.rowHeight;
menu.scaleY = r.scaleY;
menu.scaleX = r.scaleX;

// if there's data and it has children then add the items
if (row.data &&
_dataDescriptor.isBranch(row.data) &&
_dataDescriptor.hasChildren(row.data))
{
menu.dataProvider = _dataDescriptor.getChildren(row.data);
}
menu.sourceMenuBar = sourceMenuBar;
menu.sourceMenuBarItem = sourceMenuBarItem;

IMenuItemRenderer(row).menu = menu;
PopUpManager.addPopUp(menu, r, false);
}

It seems that the main menu’s attribute value of variableRowHeight hasn’t been copied to the second-level menu. So I define an itemRenderer by myself, and then set the attribute value in itemrenderer.

public class CustomMenuItemRenderer extends MenuItemRenderer {
public function CustomMenuItemRenderer() {
}
override protected function measure():void {
super.measure();
(this.owner as Menu).variableRowHeight = true;
}
}

Next, we set the renderer we just defined: menu.itemRenderer = new ClassFactory(CustomMenuItemRenderer);

Now let’s run it to see the result:

TWaver Has Realized Elements’ Dynamic Rotation

Recently a client has made a requirement on the dynamic rotation of elements. It is quite convenient to satisfy it with TWaver.
Main functions that have been realized:
  1. The dynamic rotation of elements;
  2. The effect of the element which is selected: the selected element will move quickly to the front and then stop rotating. It will start to rotate again automatically once it is not selected;
  3. Able to adjust the direction of the rotation by itself according to the position of the mouse;
  4. Able to change fw/bw/fh/bh in the program to adjust the presentation of the images.
The effect is presented in the folimage image:
The code is as follows:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                layout="absolute"
                creationComplete="test()"
                width="100%"
                height="100%"
                xmlns:ns="http://www.servasoftware.com/2009/twaver/flex">
    <mx:Script>
        <![CDATA[
            import mx.events.ResizeEvent;

            import twaver.Collection;
            import twaver.Consts;
            import twaver.ElementBox;
            import twaver.IData;
            import twaver.IElement;
            import twaver.Layer;
            import twaver.Node;
            import twaver.SelectionChangeEvent;
            import twaver.SelectionModel;
            import twaver.Styles;
            import twaver.SubNetwork;
            import twaver.Utils;
            import twaver.core.util.l.u;
            private var box:ElementBox = new ElementBox();

            private var _rotationList:Collection = new Collection();
            private var rectIndexMap:Object = {};
            private var indexRectMap:Object = {};
            private var rList:Collection = new Collection();
            private var _startAngle:Number = 90;
            private var timer:Timer;
            private var _selectNode:Node;

            private var _clockSise:Boolean = false;

            private var _stopAngle:Number = 0;
            private var _needStop:Boolean = false;

            [Embed(source="images/1.png")]
            public static const p1:Class;
            [Embed(source="images/2.png")]
            public static const p2:Class;
            [Embed(source="images/3.png")]
            public static const p3:Class;
            [Embed(source="images/4.png")]
            public static const p4:Class;
            [Embed(source="images/5.png")]
            public static const p5:Class;
            [Embed(source="images/6.png")]
            public static const p6:Class;
            [Embed(source="images/7.png")]
            public static const p7:Class;
            [Embed(source="images/8.png")]
            public static const p8:Class;

            public function get clockSise():Boolean
            {
                return _clockSise;
            }

            public function set clockSise(value:Boolean):void
            {
                if(this.clockSise==value){
                    return;
                }
                _clockSise = value;
            }
            private function resetClockSize( point:Point):void {
                if (_needStop == true) {
                    return;
                }
                if (point.x > this.network.width / 2) {
                    clockSise = false;
                } else {
                    clockSise = true;
                }
            }

            public function get selectNode():Node
            {
                return _selectNode;
            }

            public function set selectNode(value:Node):void
            {
                if(this.selectNode==value){
                    return;
                }
                _selectNode = value;
                if(this.selectNode==null){
                    _needStop = false;
                    timer.delay = 30;
                    if(!timer.running){
                        timer.start();
                    }
                }else{
                    var rect:Rectangle = indexRectMap[this.selectNode] as Rectangle;
                    var left:Boolean = (rect.x+rect.width/2)<network.width/2;
                    if(left){
                        clockSise = false;
                    }else{
                        clockSise = true;
                    }
                    var index:int = rotationList.getItemIndex(this.selectNode);

                    var size:int = this.rotationList.count;
                    var step:Number = 360/size;
                    _stopAngle = 90 - index * step;
                    _needStop = true;
                    timer.delay = 5;
                    if(!timer.running){
                        timer.start();
                    }
                }
            }

            public function start():void{
                timer.start();
            }
            public function stop():void{
                if(timer!=null){
                    timer.stop();
                }
            }

            public function get startAngle():Number
            {
                return _startAngle;
            }

            public function set startAngle(value:Number):void
            {
                _startAngle = (value+360)%360;
                this.measureRotation();
            }

            public function get rotationList():Collection
            {
                return _rotationList;
            }

            public function set rotationList(value:Collection):void
            {
                _rotationList = value;
                this.box.layerBox.clear();
                var size:int = this.rotationList.count;
                for(var i:int = 0;i<size;i++){
                    var l:Layer = new Layer("r:"+i);
                    box.layerBox.add(l);
                }
                this.measureRotation();
            }
            private function measureRotation():void{
                rectIndexMap = {};
                indexRectMap = {};
                rList.clear();
                var size:int = this.rotationList.count;
                if(size==0){
                    return;
                }
                var w:Number = this.network.width;
                var h:Number = this.network.height;

                var fw:Number = 1.0 / 3 * w;
                var bw:Number = 1.0 / 6 * w;
                var fh:Number = h / 2.5;
                var bh:Number = h / 7.0;

                var m:Number = (fw - bw) / 4;
                var cw:Number = m * 2 + bw;
                var halfcw:Number = cw / 2;
                var x:Number = halfcw + 15;
                w = w - halfcw * 2 - 30;
                var y:Number = bh / 2 + 10;
                h = h - fh / 2 - bh / 2 - 20;

                var step:Number = 360.0 / size;
                for(var i:int = 0;i<size;i++){
                    var n:Node =  this.rotationList.getItemAt(i) as Node;
                    var p:Point = this.getPointAtEllipse(x,y,w,h,startAngle+step*i);
                    var px:Number = p.x;
                    var py:Number = p.y;
                    var pm:Number = (py - y) / h * (fw - bw) / 2;
                    var ww:Number = pm * 2 + bw;
                    var hh:Number = (py - y) / h * (fh - bh) + bh;
                    var rect:Rectangle = new Rectangle(px - ww / 2, py - hh / 2, ww, hh);
                    n.setSize(rect.width,rect.height);
                    n.setCenterLocation(px,py);
                    rectIndexMap[rect] = n;
                    indexRectMap[n] = rect;
                    rList.addItem(rect);
                }
                rList.sort(rectSort);

                for(var j:int = 0;j<size;j++){
                    var rr:Rectangle = rList.getItemAt(j) as Rectangle;
                    var nn:Node = rectIndexMap[rr];
                    nn.layerID = "r:"+j;
                }
            }

            private function rectSort(r1:Rectangle,r2:Rectangle):int{
                if (r1.width> r2.width) {
                    return 1;
                }
                if (r1.width < r2.width) {
                    return -1;
                }
                return 0;
            }

            public function  getPointAtEllipse(x:Number,   y:Number,   w:Number,   h:Number,   angled:Number):Point {
                var angle:Number =angled / 180.0 * Math.PI;
                var px:Number = x + (Math.cos(angle) * 0.5 + 0.5) * w;
                var py:Number = y + (Math.sin(angle) * 0.5 + 0.5) * h;
                return new Point(px, py);
            }

            private function init():void{
                timer=new Timer(30);
                timer.addEventListener(TimerEvent.TIMER,function():void{
                    if(clockSise){
                        startAngle = startAngle+1;
                    }else{
                        startAngle = startAngle-1;
                    }
                    if(_needStop){
                        var abs:Number = Math.abs(startAngle-(_stopAngle+360)%360);
                        if(abs<2){
                            timer.stop();
                        }
                    }
                });
                network.addEventListener(MouseEvent.MOUSE_MOVE,function(e:MouseEvent):void{
                    var p:Point = network.getLogicalPoint(e);
                    resetClockSize(p);
                });
                network.addEventListener(MouseEvent.MOUSE_DOWN,function(e:MouseEvent):void{
                    var p:Point = network.getLogicalPoint(e);
                    var element:IElement = network.getElementByMouseEvent(e);
                    selectNode = element as Node;
                    resetClockSize(p);
                });
                network.addEventListener(ResizeEvent.RESIZE,function():void{
                    measureRotation();
                });
            }

            private function test():void{
                init();
                Utils.registerImageByClass("p1", p1);
                Utils.registerImageByClass("p2", p2);
                Utils.registerImageByClass("p3", p3);
                Utils.registerImageByClass("p4", p4);
                Utils.registerImageByClass("p5", p5);
                Utils.registerImageByClass("p6", p6);
                Utils.registerImageByClass("p7", p7);
                Utils.registerImageByClass("p8", p8);
                var list:Collection = new Collection();
                for(var i:int = 1;i<9;i++){
                    var sub:Node = new Node();
                    sub.setStyle(Styles.SELECT_STYLE,Consts.SELECT_STYLE_BORDER);
                    sub.setStyle(Styles.IMAGE_STRETCH,Consts.STRETCH_FILL);
                    sub.setStyle(Styles.SELECT_COLOR,"0x00ff00");

                    sub.setStyle(Styles.LABEL_POSITION,Consts.POSITION_CENTER);
                    sub.image = "p"+i;
                    sub.name = "sub"+i
                    list.addItem(sub);
                    box.add(sub);
                }
                this.rotationList = list;
                network.elementBox = box;
                this.start();
            }

        ]]>
    </mx:Script>
    <ns:Network id="network" width="100%" height="100%" backgroundColor="0xffffff">

    </ns:Network>
</mx:Application>

2012/06/19

The Demonstration of equipments with 3D

All the requirements for 3D application we have received up till now mainly focus on 4 aspects:
  1. The recurring of the outdoor scenes;
  2. The representation of indoor layout;
  3. The presentation of single equipments with 3D;
  4. Spatial interactions.
As to the third one, many people ask for the opening-up act and closing one of the equipment. Recently, TWaver’s developers have made out a demo specialized for demonstrating this equipment. If you have taken interest in it, you could contact us through tw-service@servasoft.com for related materials and Demo codes for free.

2012/06/17

How to Connect Logical Data and Its Application on Geography

Those friends who are never engaged in GIS development and application but have quite some knowledge of TNetwork often feel confused about how to develop a system interface involving GIS application when they are using TWaver GIS development.
General TNetwork components are used to display the logical relationship between nodes in network, that is, for example, the connecting relationship between node a and node b from n nodes in an Internet network. TWaver GIS is used to help users to display the physical meaning of nodes in network through TNetwork components. In brief, it is used to correspond the latitude and longitude of a specific node in real world with its position on the screen of the interface of a computer system, hence the real presentation of the physical position of network nodes.
In a network, the interactive modes in the logical relationship and physical meaning of nodes are separate from and independent of each other. For example, interactive acts like pan and zoom in/out of general TNetwork components cannot be applied on GIS interaction and the same as those of GIS application scenes.  Because of that, many users are confused about the two interactive scenes, mixing them up. So they have no idea how to orderly establish the interactions of interfaces in practice. Now I introduce a common way of interaction to you to help reduce puzzlement during early stages in development.
This typical application mode is actually very simple as you can just use a page particular for displaying physical network (involving Map, GIS application of longitude and latitude). When users need to see logical network (like the structure of service network, equipment panels, etc.) due to their needs in business, they can open another new page to display these logical network. Based on such basic thoughts, we could choose JTabbedPane as the main container to meet our requirements.


Here's the details:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTabbedPane;
import twaver.GeoCoordinate;
import twaver.Link;
import twaver.Node;
import twaver.PopupMenuGenerator;
import twaver.SubNetwork;
import twaver.TDataBox;
import twaver.TView;
import twaver.gis.GeographyMap;
import twaver.gis.GisNetworkAdapter;
import twaver.gis.TWaverGisConst;
import twaver.network.TNetwork;

public class MapAndLogicNetwork {
    private JFrame frame;
    private Random random = new Random();
    private JTabbedPane tabbed = new JTabbedPane();
    public MapAndLogicNetwork(){
        initLayout();
        initContents();
    }
    private void initLayout(){
        frame = new JFrame();
        Container container = frame.getContentPane();
        container.setLayout(new BorderLayout());
        container.add(tabbed,BorderLayout.CENTER);
        frame.setSize(800,600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    private void initContents(){
        initMap();
    }
    private void removeLogicNetworkComponent(){
        TNetwork source = (TNetwork)tabbed.getComponentAt(1);
        tabbed.removeTabAt(1);
    }
    private void insertLogicNetwork(TDataBox box){
        if(tabbed.getTabCount()>1){
            removeLogicNetworkComponent();
        }
        final TNetwork logicNetwork = new TNetwork(box);
        logicNetwork.setPopupMenuGenerator(new PopupMenuGenerator() {
            public JPopupMenu generate(TView tview, MouseEvent mouseEvent) {
                JPopupMenu pop = new JPopupMenu();
                JMenuItem item = new JMenuItem("close");
                item.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        removeLogicNetworkComponent();
                    }
                });
                pop.add(item);
                return pop;
            }
        });
        tabbed.add("LogicNetwork "+box.getName(),logicNetwork);
        tabbed.setSelectedIndex(1);
    }

    private void initMap(){
        TNetwork network = new TNetwork();
        GisNetworkAdapter adapter = new GisNetworkAdapter(network);
        adapter.installAdapter();
        GeographyMap map = adapter.getMap();
        map.addLayer("Googlemap", TWaverGisConst.EXECUTOR_TYPE_GOOGLEMAP);
        map.setZoom(2);
        TDataBox box = network.getDataBox();
        Node node = new Node();
        node.putLabelColor(Color.RED);
        node.setName("Double click on me");
        node.putClientProperty(TWaverGisConst.GEOCOORDINATE, new GeoCoordinate(-90,40));
        box.addElement(node);
        tabbed.addTab("Map Container", network);
        network.addElementDoubleClickedActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                TDataBox box = new TDataBox();
                SubNetwork n1 = new SubNetwork();
                n1.setDataSource("/data.xml");
                n1.setName("Logic n1");
                Node n2 = new Node();
                n2.setName("Logic n2");
                Link l = new Link(n1,n2);
                n1.setLocation(Math.abs(random.nextInt(500)), Math.abs(random.nextInt(500)));
                n2.setLocation(Math.abs(random.nextInt(50)), Math.abs(random.nextInt(400)));
                box.addElement(n1);
                box.addElement(n2);
                box.addElement(l);
                box.setName("Random box "+Math.abs(random.nextInt()));
                insertLogicNetwork(box);
            }
        });
    }
    public void showup(){
        frame.setVisible(true);
        frame.setLocationRelativeTo(null);
    }
    public static void main(String[] args){
        new MapAndLogicNetwork().showup();
    }
}

At first: Master Boot-the interface displayed with maps:
Double click a specific node on the map, thus a logical-network page is added. That page is added on a JTabbed one.
You can choose “close” on the menu appeared at the click of the right button of the mouse to exit the presentation of the logical network and this page to return to the main interface displayed with the map.

Time to Eeplace TWaver’s Tree with FastTree

In TWaver Flex edition 2.1 the latest FastTree has been released, the literal meaning of which is Tree “at a higher speed.” It is similar to Tree in function, not multiplexing Flex’s Tree. FastTree is superior to the ordinary Tree in performance and speed, for it only refreshes the visible part. Its imperfection lies in the lack of some decoratory functions, such as the animation effects when expanding nodes and the highlight in the current line when moving the mouse. In the following codes we will display with an example in which aspects FastTree is fast.
  1. Faster in sequencing and filtering
  2. The following example displays the filtering and sequencing functions of FastTree and Tree respectively: At the moment when 10 thousand nodes have been loaded in FastTree, after inputting a filter condition, the result will show up right away; with 1 thousand nodes loaded in the ordinary Tree, 2-3 seconds bring the result out.

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:twaver="http://www.servasoftware.com/2009/twaver/flex"
                    layout="absolute" creationComplete="init()">

        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var input:String = "";

                private function init():void {
                    // Init Filter.
                    fastTree.visibleFunction = function(node:IData):Boolean{
                        if(input == ""){
                            return true;
                        }
                        return isVisible(node);
                    };

                    // Init Sort.
                    fastTree.compareFunction = function(d1:IData, d2:IData):int {
                        if(d1.name > d2.name){
                            return 1;
                        }else if(d1.name == d2.name){
                            return 0;
                        }else{
                            return -1;
                        }
                    };

                    // Init DataBox
                    var i:int = 100;
                    var box:DataBox = fastTree.dataBox;
                    while(i--){
                        var node:Node = new Node();
                        node.name = 'n-' + i;
                        box.add(node);
                        var ii:int = 100;
                        while(ii--){
                            var child:Node = new Node();
                            child.name =  'n-' + i + '-' +ii;
                            node.addChild(child);
                            box.add(child);
                        }
                    }

                    // Expand All Nodes
                    fastTree.callLater(function():void {
                        fastTree.expandAll();
                    });
                }

                // All parents are visible if child is visible
                private function isVisible(item:IData):Boolean {
                    if(item.name.toLowerCase().indexOf(this.input) >= 0) {
                        return true;
                    }
                    var result:Boolean = false;
                    item.children.forEach(function(child:IData):Boolean {
                        if(isVisible(child)) {
                            result = true;
                            return false;
                        }
                        return true;
                    });
                    return result;
                }

                // Refresh FastTree when input is changed.
                private function handleSearchTextChanged():void {
                    this.input = this.textSearch.text.toLowerCase();
                    fastTree.invalidateModel();
                }
            ]]>
        </mx:Script>

        <mx:VBox width="100%" height="100%" >
            <mx:TextInput id="textSearch" change="handleSearchTextChanged()" />
            <mx:HBox width="100%" height="100%" >
                <twaver:FastTree id="fastTree" width="100%" height="100%"/>
            </mx:HBox>
        </mx:VBox>
    </mx:Application>

    TestTreeSortAndFilter.mxml:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:twaver="http://www.servasoftware.com/2009/twaver/flex"
                    layout="absolute" creationComplete="init()">

        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var input:String = "";

                private function init():void {
                    // Init Filter.
                    tree.visibleFunction = function(node:IData):Boolean{
                        if(input == ""){
                            return true;
                        }
                        return isVisible(node);
                    };

                    // Init Sort.
                    tree.compareFunction = function(d1:IData, d2:IData):int {
                        if(d1.name > d2.name){
                            return 1;
                        }else if(d1.name == d2.name){
                            return 0;
                        }else{
                            return -1;
                        }
                    };

                    // Init DataBox
                    var i:int = 100;
                    var box:DataBox = tree.dataBox;
                    while(i--){
                        var node:Node = new Node();
                        node.name = 'n-' + i;
                        box.add(node);
                        var ii:int = 10;
                        while(ii--){
                            var child:Node = new Node();
                            child.name =  'n-' + i + '-' +ii;
                            node.addChild(child);
                            box.add(child);
                        }
                    }

                    // Expand All Nodes
                    tree.callLater(function():void {
                        tree.expandAll();
                    });
                }

                // All parents are visible if child is visible
                private function isVisible(item:IData):Boolean {
                    if(item.name.toLowerCase().indexOf(this.input) >= 0) {
                        return true;
                    }
                    var result:Boolean = false;
                    item.children.forEach(function(child:IData):Boolean {
                        if(isVisible(child)) {
                            result = true;
                            return false;
                        }
                        return true;
                    });
                    return result;
                }

                // Refresh tree when input is changed.
                private function handleSearchTextChanged():void {
                    this.input = this.textSearch.text.toLowerCase();
                    tree.updateCompareAndVisibility();
                }
            ]]>
        </mx:Script>

        <mx:VBox width="100%" height="100%" >
            <mx:TextInput id="textSearch" change="handleSearchTextChanged()" />
            <mx:HBox width="100%" height="100%" >
                <twaver:Tree id="tree" width="100%" height="100%"/>
            </mx:HBox>
        </mx:VBox>
    </mx:Application>
     
  3. Faster in lazy load
  4. In the following example we have imitated the lazy load. 500 child nodes will be loaded every time you unfold a tree node: it costs only tens of milliseconds to load even when tens of thousands of nodes have already been unfolded under FastTree; it needs to takes over 10 seconds to load nodes when 3 thousand nodes have been expanded under Tree.

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex">
        <mx:Script>
            <![CDATA[
                import twaver.*;
                import twaver.controls.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    // Init DataBox
                    for(var i:int=0; i<40; i++){
                        var data:Data = new Data();
                        data.name = "Data" + i;
                        box.add(data);
                    }
                    fastTree.dataBox = box;

                    // Setup Lazy Load
                    fastTree.branchFunction = function(data:IData):Boolean {
                        return true;
                    };

                    // Load Children when expanding
                    fastTree.addInteractionListener(function(event:FastTreeInteractionEvent):void{
                        if(event.kind == FastTreeInteractionEvent.EXPAND &&
                            event.data.childrenCount == 0){
                            loadDatas(event.data);
                        }
                    });
                }

                // Load 500 Nodes
                private function loadDatas(data:IData):void{
                    var startTime:Date = new Date();
                    for(var j:int=0; j<500; j++){
                        var child:Data = new Data();
                        child.name = data.name + "," + j;
                        child.parent = data;
                        box.add(child);
                    }
                    console.text = "Count:"+box.count+"\tExpand Time:"+(new Date().getTime()-startTime.getTime())+"ms";
                }
            ]]>
        </mx:Script>

        <mx:Label id="console" width="400"/>
        <tw:FastTree id="fastTree" width="400" height="100%" editable="true" backgroundColor="#FFFFFF"/>
    </mx:Application>


    TestTreeLazyLoad.mxml:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init();"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex">
        <mx:Script>
            <![CDATA[
                import twaver.*;
                import twaver.controls.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    // Init DataBox
                    for(var i:int=0; i<40; i++){
                        var data:Data = new Data();
                        data.name = "Data" + i;
                        box.add(data);
                    }
                    tree.dataBox = box;

                    // Setup Lazy Load
                    tree.branchFunction = function(treeData:TreeData):Boolean {
                        return true;
                    };

                    // Load Children when expanding
                    tree.addInteractionListener(function(event:TreeInteractionEvent):void{
                        if(event.kind == TreeInteractionEvent.EXPAND_TREE_DATA &&
                                event.treeData.data != null &&
                                event.treeData.data.childrenCount == 0){
                            loadDatas(event.treeData.data);
                        }
                    });
                }

                // Load 500 Nodes
                private function loadDatas(data:IData):void{
                    var startTime:Date = new Date();
                    for(var j:int=0; j<500; j++){
                        var child:Data = new Data();
                        child.name = data.name + "," + j;
                        child.parent = data;
                        box.add(child);
                    }
                    console.text = "Count:"+box.count+"\tExpand Time:"+(new Date().getTime()-startTime.getTime())+"ms";
                }
            ]]>
        </mx:Script>

        <mx:Label id="console" width="400"/>
        <tw:Tree id="tree" width="400" height="100%" editable="true" backgroundColor="#FFFFFF"/>
    </mx:Application>
     
  5. Faster operational speed of ExpandAll with huge amount of data
  6. When 50 thousand nodes have been loaded by FastTree, clicking all to unfold them will only cost you several milliseconds; by ordinary Tree it turns out to be abnormal after 1 minute. TestFastTreeExpandAll.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex"
                    paddingLeft="0" paddingRight="0" paddingTop="0" paddingBottom="0" backgroundColor="0xFFFFFF"
                    creationComplete="init()">
        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    this.initBox();
                    trace(box.count);
                    fastTree.dataBox = box;
                }

                private function initBox():void {
                    var root:Data = this.addData("Root");
                    for (var k:int = 0; k < 10; k++) {
                        var ip:String = "10.0." + k + ".";
                        var count:int = 0;
                        var data:Data = this.addData(ip + count++);
                        data.parent = root;

                        for (var i:int = 0; i < 10; i++) {
                            var iData:Data = this.addData(ip + count++);
                            iData.parent = data;
                            for (var j:int = 0; j < 100; j++) {
                                var jData:Data = this.addData(ip + count++);
                                jData.parent = iData;
                            }
                        }
                    }
                }

                private function addData(name:String):Data {
                    var data:Data = new Data();
                    data.name = name;
                    data.toolTip = name;
                    this.box.add(data);
                    return data;
                }
            ]]>
        </mx:Script>

        <mx:HDividedBox width="100%" height="100%">
            <tw:FastTree id="fastTree" width="200" height="100%"/>
            <mx:HDividedBox width="100%" height="100%">
                <mx:Canvas id="canvas" width="100%" height="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off">
                    <mx:HBox id="toolbar" top="0" left="0" horizontalGap="0">
                        <mx:Button label="Expand All" click="fastTree.expandAll();"/>
                        <mx:Button label="Collapse All" click="fastTree.collapseAll();"/>
                    </mx:HBox>
                </mx:Canvas>
            </mx:HDividedBox>
        </mx:HDividedBox>
    </mx:Application>
     
    TestTreeExpandAll.mxml:
    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
                    xmlns:tw="http://www.servasoftware.com/2009/twaver/flex"
                    paddingLeft="0" paddingRight="0" paddingTop="0" paddingBottom="0" backgroundColor="0xFFFFFF"
                    creationComplete="init()">
        <mx:Script>
            <![CDATA[
                import twaver.*;

                private var box:DataBox = new DataBox();

                private function init():void {
                    this.initBox();
                    trace(box.count);
                    tree.dataBox = box;
                }

                private function initBox():void {
                    var root:Data = this.addData("Root");
                    for (var k:int = 0; k < 10; k++) {
                        var ip:String = "10.0." + k + ".";
                        var count:int = 0;
                        var data:Data = this.addData(ip + count++);
                        data.parent = root;

                        for (var i:int = 0; i < 10; i++) {
                            var iData:Data = this.addData(ip + count++);
                            iData.parent = data;
                            for (var j:int = 0; j < 100; j++) {
                                var jData:Data = this.addData(ip + count++);
                                jData.parent = iData;
                            }
                        }
                    }
                }

                private function addData(name:String):Data {
                    var data:Data = new Data();
                    data.name = name;
                    data.toolTip = name;
                    this.box.add(data);
                    return data;
                }
            ]]>
        </mx:Script>

        <mx:HDividedBox width="100%" height="100%">
            <tw:Tree id="tree" width="200" height="100%"/>
            <mx:HDividedBox width="100%" height="100%">
                <mx:Canvas id="canvas" width="100%" height="100%" horizontalScrollPolicy="off" verticalScrollPolicy="off">
                    <mx:HBox id="toolbar" top="0" left="0" horizontalGap="0">
                        <mx:Button label="Expand All" click="tree.expandAll();"/>
                        <mx:Button label="Collapse All" click="tree.collapseAll();"/>
                    </mx:HBox>
                </mx:Canvas>
            </mx:HDividedBox>
        </mx:HDividedBox>
    </mx:Application>
     
Besides, there are no such bugs as listed below:
Finally, the use of FastTree is similar to that of ordinary Tree but attention need to be paid to some differences between them. Most of them are that all TreeData's have been replaced by IData's. Through the example above you can also find some differences.
  1. The methods to make it refresh after the change of Filter or Sequencing Function.
  2. Replacing
    public function updateCompareAndVisibility(treeData:TreeData = null):void
    public function invalidateTreeData(treeData:TreeData = null):void
    with
    public function invalidateModel():void
     
  3. The methods to judge whether nodes are expanded.
  4. Replacing
    public function isOpen(data:IData):Boolean
    public function isDataOpen(data:IData):Boolean
    with
    public function isExpanded(data:IData):Boolean 
     
  5. The methods to judge whether nodes are visible
  6. Replacing
    public function isTreeDataVisible(treeData:TreeData):Boolean
    public function isDataVisible(data:IData):Boolean
    with
    public function isVisible(data:IData):Boolean
     
  7. The method to judge whether nodes can be expanded
  8. Replacing
    public function isBranch(treeData:TreeData):Boolean
    with
    public function isBranch(data:IData):Boolean
     
  9. The methods to spot a particular node
  10. Replacing
    public function getTreeData(data:IData):TreeData
    public function getDataIndex(data:IData):int
    public function getTreeDataByIndex(index:int):TreeData
    public function getTreeDataByMouseEvent(event:MouseEvent):TreeData
    public function getTreeDataByContextMenuEvent(event:ContextMenuEvent):TreeData
    with
    public function getRowIndexByData(data:IData):int
    public function getRowIndexById(id:Object):int
    public function getDataByRowIndex(index:int):IData
    public function getRowIndexByMouseEvent(e:MouseEvent):int
    public function getDataByMouseEvent(e:MouseEvent):IData
    public function getDataByContextMenuEvent(event:ContextMenuEvent):IData
    public function getRowIndexByStagePoint(point:Point):int
    public function getDataByStagePoint(point:Point):int
     
  11. The method to get the root nodes of the tree
  12. Replacing
    public function get rootTreeData():TreeData
    with
    public function get rootData():IData
     
  13. The method to expand and collapse the tree node
  14. Replacing
    public function expandData(data:IData, animate:Boolean = false):void
    public function collapse(data:IData, animate:Boolean = false):void
    with
    public function expand(data:IData):void
    public function collapse(data:IData):void
     
  15. The method to perform an operation for each tree node
  16. Replacing
    public function forEachTreeData(callbackFunction:Function, treeData:TreeData = null):void
    with
    tree.rowDatas.forEach
     
  17. Substituting TreeInteractionEvent with FastTreeInteractionEvent
  18. public function addInteractionListener(listener:Function, priority:int = 0, useWeakReference:Boolean = false):void 
     
  19. The method to get the user-defined components of tree nodes
  20. Replacing
    public function getIconsComponents(treeData:TreeData):Array
    with
    public function getIconsComponents(data:IData):Array
     
  21. The parameter TreeData of the methods iconsComponentsFunction and branchFunction has been replaced by IData.
  22. public function get iconsComponentsFunction():Function
    public function get branchFunction():Function