Flex组件_Tree树控件_动态增删数据

今天我们来看一下如何在Tree控件中动态增删数据。
我们先初始化一棵简单地树,代码如下:
// 程序1
<?xml version="1.0" encoding="utf-8"?> 
<mx:Application xmlns:mx=" http://www.adobe.com/2006/mxml
minWidth="955" minHeight="600" 
creationComplete="application1_creationCompleteHandler(event)" 
layout="vertical"> 
<mx:Script> 
<![CDATA[ 
import mx.collections.ArrayCollection; 
import mx.events.FlexEvent; 

private var d:ArrayCollection; 

protected function application1_creationCompleteHandler(event:FlexEvent):void 

d = new ArrayCollection(); 

for(var i:int = 0;i < 5;++i) 

var n:Object = new Object(); 
n.label = "节点" + (i + 1); 
d.addItem(n); 


t.dataProvider = d; 

]]> 
</mx:Script> 
<mx:Tree id="t" width="200" height="200"/> 
</mx:Application> 
此代码将会在程序启动的最后创建有5项数据的数组,并用Tree展现。效果:

现在我们假设,通过点击一个按钮,往第一个节点下添加5个子节点。

增加子节点数据
添加子节点的代码如下,直接设置第一个节点的children值为一个新的数组。
// 程序2
protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
   }
运行,点击按钮,并没有效果。
猜想原因应该是,虽然控件的数据刷新了,但控件并没有把新数据刷新到界面上,也就是控件中的可视化子控件(item  renderer)并不知道数据改变了。

利用Tree的滚动条
在进行进一步分析前,我们先看一个有趣的现象, 我们做一点小调整:将Tree控件的高度从200改为100,这样,初始化的Tree就会出现滚动条:
然后我们再点击按钮,把新数据添加到第一个节点“节点1”下,没有任何变化。但我们拖动滚动条:
可以看到“节点1”已经变成了一个非叶子,展开节点,一切正常。

那拖动滚动条时究竟发生了什么呢?

我们知道Tree的每一行都是用item renderer进行渲染的,而且item renderer是使用了缓冲池的原理,也就是说item renderer不是固定用于显示某一行,而是根据需要显示的数据,同一个item renderer可能会渲染不同的数据,例如拖动滚动条的时候。当将滚动条往下拖动时,上面的部分节点会“隐藏”,这些item renderer将会被用于渲染下面“新出现”的节点,当把滚动条拖回到原来的位置时,这些item renderer重新渲染被“隐藏“的节点。由于拖动滚动条时,因为第一个节点会先“隐藏”再“显示”,所以第一个节点会被强制刷新,也就是第一个节点的item renderer在刷新时会显示最新的数据,所以会显示为一个非叶子节点。
从代码层面看,Flex是从Tree.scrollHandler到List.scrollVertically,再调用makeRowsAndColumns进行刷新。
那如果自动修改 verticalScrollPosition是不是就可以达到效果呢?我们试一试这个看起来不怎么优雅的方式:
// 程序3
 protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
    t.verticalScrollPosition += 1;
    t.verticalScrollPosition -= 1;
   }
结果竟然是有效!这里无需担心 verticalScrollPosition进行了+1,再减1,在下一帧还是使用原来的值的问题,下面再讲原因。

增加第一层节点数据会如何?
如果我们修改一下我们的任务,把添加子节点改为添加第一层节点。我们将上述代码稍微改动一下:
// 程序4
protected function button1_clickHandler(event:MouseEvent):void
   {
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "节点" + (i + 6);
     d.addItem(n);
    }
   }
运行,点击按钮,结果:
为什么往第一层添加数据就会马上看到效果呢?而往第一层节点添加子节点就没有效果呢?

collectionChange事件
我们追溯一下Tree源码:
// Tree.commitProperties
super.dataProvider = wrappedCollection = (_dataDescriptor is ITreeDataDescriptor2) ?
               ITreeDataDescriptor2(_dataDescriptor).getHierarchicalCollectionAdaptor(
                   tmpCollection != null ? tmpCollection : _rootModel,
                   itemToUID,
                   _openItems) :
               new HierarchicalCollectionView(
                  tmpCollection != null ? tmpCollection : _rootModel,
                 _dataDescriptor,
                   itemToUID,
                   _openItems);
                // not really a default handler, but we need to be later than the wrapper
                wrappedCollection.addEventListener(CollectionEvent.COLLECTION_CHANGE,
                                          collectionChangeHandler,
                                          false,
                                          EventPriority.DEFAULT_HANDLER, true);
Tree会监听dataProvider(已根据实际传入的dataProvider进行转换)的 CollectionEvent.COLLECTION_CHANGE事件,当dataProvider派发此事件(如d.addItem(n); ),监听后Flex会调用Tree.invalidateDisplayList方法,而在updateDisplayList方法会调用makeRowsAndColumns方法,将item renderer进行刷新。

那为什么添加子节点时界面没有刷新呢?
通过研究ArrayCollection源码发现, CollectionEvent.COLLECTION_CHANGE只在ArrayCollection对象进行add/remove操作时才会被派发,而对于ArrayCollection对象中的每个item,也就是第一层节点进行add/remove操作,尽管对于item也会派发这个事件,但对于ArrayCollection则不会派发这个事件。也就是说ArrayCollection的CollectionEvent.COLLECTION_CHANGE事件只对第一层数据进行增删时才会出现,即浅操作,而对于深操作,即对ArrayCollection的第二层,乃至第n层进行增删,事件都不会上升到第一层,只会停留在进行增删的那一层。
所以,对第一层节点进行增删,Tree是可以监听到的,而对于增删子节点则无能为力。

那我们手动派发CollectionEvent.COLLECTION_CHANGE事件会怎么样呢?
// 程序5
protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
     (t.dataProvider as IEventDispatcher).dispatchEvent(new CollectionEvent(
     CollectionEvent.COLLECTION_CHANGE, false, false, CollectionEventKind.ADD));
  }
那我们手动派发CollectionEvent.COLLECTION_CHANGE事件会怎么样呢?
// 程序5
protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
     (t.dataProvider as IEventDispatcher).dispatchEvent(new CollectionEvent(
     CollectionEvent.COLLECTION_CHANGE, false, false, CollectionEventKind.ADD));
  }
运行,有效!只是这种代码看起来不那么优雅。

那我们手动派发CollectionEvent.COLLECTION_CHANGE事件会怎么样呢?
// 程序5
protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
     (t.dataProvider as IEventDispatcher).dispatchEvent(new CollectionEvent(
     CollectionEvent.COLLECTION_CHANGE, false, false, CollectionEventKind.ADD));
  }

invalidateList
下面我们看一下Flex是怎么处理 CollectionEvent.COLLECTION_CHANGE事件的:
由上面分析可知,CollectionEvent.COLLECTION_CHANGE的事件监听器会调用Tree.invalidateDisplayList方法,而最终导致item renderer的刷新。那改成这样又如何呢:
// 程序6
protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
    t.invalidateDisplayList();
   }
很遗憾,没有任何效果!
那就证明了只是简单地调用 invalidateDisplayList并不能刷新item renderer。

我们回头再仔细研究一下CollectionEvent.COLLECTION_CHANGE的事件监听器(collectionChangeHandler)的处理逻辑。
// ListBase
 protected function collectionChangeHandler(event:Event):void
    {
        // 其它代码
        itemsSizeChanged = true;
        invalidateDisplayList();
    }

protected override function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
       
        if (oldUnscaledWidth == unscaledWidth &&
            oldUnscaledHeight == unscaledHeight &&
            ! itemsSizeChanged && !bSelectionChanged &&
            !scrollAreaChanged)
        {
            return;
        }
        // 其它代码
    }
很容易我们就可以发现是 itemsSizeChanged 变量在作怪,由于考虑到性能问题,不能每次调用invalidateDisplayList,都进行item renderer的刷新,所以需要itemsSizeChanged变量检测dataProvider中的item长度是否发生改变,如果没有改变,则不会进行update.

那很明显,我们需要下面一段代码:
        itemsSizeChanged = true; 
        invalidateDisplayList();
我们回到刚才不优雅的程序3,ListBase的源码,无论怎么设置,都会调用上面的两行代码,所以有效。
// ListBase
override public function set verticalScrollPolicy(value:String):void
    {
        super.verticalScrollPolicy = value;
        itemsSizeChanged = true;
        invalidateDisplayList();
    }
当然,这两行代码不用自己再度封装,Flex已经实现了,其实ListBase.invalidateList()就是这样的!
// 程序7
protected function button1_clickHandler(event:MouseEvent):void
   {
    var p:Object = d.getItemAt(0);
    p.children = new ArrayCollection();
   
    for(var i:int = 0;i < 5;++i)
    {
     var n:Object = new Object();
     n.label = "子节点" + (i + 1);
     p.children.addItem(n);
    }
   t.invalidateList();
   }
运行,点击按钮,一切都运行得很好:

总结
通过本文,我们可以了解动态添加树空间的数据,可以有几种方式:
  1. verticalScrollPosition +-  
  2. (t.dataProvider as IEventDispatcher).dispatchEvent( new CollectionEvent(CollectionEvent.COLLECTION_CHANGE, false, false, CollectionEventKind.ADD)); 
  3. invalidateList
并理解invalidateList方法的作用,以及Tree(或者List)的dataProvider的运行机制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值