1

在我使用 Flex 构建的应用程序中,我有一个以 XML 作为其数据提供者的 Flex 树。用户需要能够创建图层和文件夹来保存这些图层(想想 Photoshop)。用户还需要能够随意重新排列这些项目,并将图层拖到文件夹中(如果需要,还可以将文件夹拖到其他文件夹中,等等)。

这是我制作的一个测试应用程序,展示了我正在尝试做的事情。为简单起见,我已将其简化为尽可能基本。

(注意:我在我的代码中创建带有一个虚拟节点的 XML,然后在运行时删除该节点。这是我发现的唯一方法,它会使 Tree 在启动时完全空白。(只是有一个空的根节点会导致该根节点在树中可见,即使 showRoot 为 false。)如果有更好的方法来实现该效果,请告诉我。)

<?xml version="1.0" encoding="utf-8"?>
<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" minWidth="955" minHeight="600">

    <fx:Script>
        <![CDATA[
            import com.custom.model.service.XMLService;

            import mx.events.DragEvent;
            import mx.events.FlexEvent;
            import mx.utils.ObjectUtil;

            protected function treeInit(event:FlexEvent):void
            {
                layersTree.expandChildrenOf(layers_xml, true);

                /*delete the dummy XML node so that the tree is clean on startup 
                and doesn't show the root folder*/
                XMLService.deleteNodeById(layers_xml, 'dummy');
            }

            protected function newFolderClick(event:MouseEvent):void
            {
                // Form New XML Node..
                var xmlObj:Object = XMLService.formFolderXML();

                //...and add it to the XML file.
                if(!layers_xml.children()[0]){
                    //if there are no child nodes, add normally
                    layers_xml.source = xmlObj.xmlNode;
                }else{
                    //if there are child nodes, add them before the previous node
                    layers_xml.prependChild(xmlObj.xmlNode);
                }

                /*immediately expand the folders to allow them to be dragged into,
                closed folders cannot be dragged into*/
                layersTree.expandChildrenOf(layers_xml, true);
            }

            protected function newTextLayerClick(event:MouseEvent):void
            {
                // Form New XML Node..
                var xmlObj:Object = XMLService.formTextLayerXML();

                //...and add it to the XML file.
                if(!layers_xml.children()[0]){
                    //if there are no child nodes, add normally
                    layers_xml.source = xmlObj.xmlNode;
                }else{
                    //if there are child nodes, add them before the previous node
                    layers_xml.prependChild(xmlObj.xmlNode);
                }

                //programatically select THIS item so it highlights in the tree
                layersTree.selectedIndex = (layersTree.showRoot) ? 1 : 0;
            }

            protected function onDragDrop(event:DragEvent):void
            {
                trace('action:', event.action);
                trace('dragDrop', ObjectUtil.toString(layers_xml));
            }

            protected function onDragComplete(event:DragEvent):void
            {
                trace('action:', event.action);
                trace('dragComplete', ObjectUtil.toString(layers_xml));
            }

        ]]>
    </fx:Script>

    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->

        <fx:XML id="layers_xml">
            <layers label="root" id="root">
                <layer label="DummyLayer2" type="layer" id="dummy" isVisible="false" />
            </layers>
        </fx:XML>

    </fx:Declarations>

    <s:VGroup gap="0" width="100%">

        <mx:Tree id="layersTree" width="100%" minHeight="41" allowMultipleSelection="false"
                 borderVisible="false" creationComplete="treeInit(event)"
                 dataProvider="{layers_xml}" dragEnabled="true" dropEnabled="true" 
                 focusColor="#FFFFFF" labelField="@label"
                 rollOverColor="#D1EEEE" selectionColor="#D1EEEE" showRoot="false" 
                 dragDrop="onDragDrop(event)" dragComplete="onDragComplete(event)"/>

        <s:Group width="100%">
            <s:HGroup id="layerButtons" horizontalCenter="0">
                <s:Button id="newFolderBtn" label="Folder" buttonMode="true"
                         click="newFolderClick(event)"/>
                <s:Button id="newTextFieldBtn" label="TextLayer" buttonMode="true"
                    click="newTextLayerClick(event)" />
            </s:HGroup>
        </s:Group>

    </s:VGroup>

</s:Application>

这是我用来生成要插入到树中的 XML 节点的类 (XMLService)。

package com.custom.model.service
{
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;

public class XMLService extends EventDispatcher
{

    private static var _folderLayerCount:int = 0;
    private static var _textLayerCount:int = 0;
    private static var _layerName:String;
    private static var _layerId:String;


    public function XMLService(target:IEventDispatcher=null)
    {
        super(target);
    }


    private static function randomId(length:int = 9):String{
        //function to generate randomId for XML
        var chars:String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        var num_chars:Number = chars.length - 1;
        var randomChar:String = "";

        for (var i:Number = 0; i < length; i++){
            randomChar += chars.charAt(Math.floor(Math.random() * num_chars));
        }
        return randomChar;
    }


    public static function formTextLayerXML(name:String = null):Object {
        //this static function forms the xml node for text layers.
        _textLayerCount++;
        _layerId = randomId();
        _layerName = (name != null) ? name : ('Text Layer ' + _textLayerCount);

        var textNode:XML = new XML('<layer label="' + _layerName + '" type="textlayer" id="'+ _layerId +'" isVisible="true" />');

        //returns the node and the unique id as a string    
        return {xmlNode : textNode, id : _layerId};
    }

    public static function formFolderXML():Object {
        //this static function forms the xml node for folders.
        _folderLayerCount++;
        _layerId = randomId();
        _layerName = 'Folder ' + _folderLayerCount;

        var folderNode:XML = new XML('<folder label="' + _layerName + '" type="folder" id="'+ _layerId +'" isBranch="true" isVisible="true" />');
        //isBranch = true makes the node behave as a folder, even if it's empty

        //returns the node and the unique id as a string
        return {xmlNode: folderNode, id: _layerId}; 
    }

    public static function deleteNodeById(xml:XML, idToDelete:String):void {

        var count:int = 0; /*have to keep track of our own count
        because targeting will not work for xml deletes */
        var descendNodes:XMLList = xml.descendants();

        for each (var layer:XML in descendNodes) {
            if(descendNodes[count].@id == idToDelete) {
                delete descendNodes[count];
            }
            count++;
        }
    }

}
}

我遇到的问题是,当创建文件夹和图层并将图层拖入文件夹时,一旦将该文件夹拖出文件夹,就会创建图层的副本而不是移动图层。

动态创建的文件夹和图层:

动态创建的文件夹和图层

在文件夹内拖动一个图层:

在文件夹内拖动图层

如果现在将图层拖到文件夹外部(及上方)会发生什么情况:

如果现在将图层拖到文件夹外部(及上方)会发生什么情况

当在 dragComplete 上跟踪 layers_xml 时,您可以在 XML 中看到这种情况。

<layers label="root" id="root">
  <layer label="Text Layer 1" type="textlayer" id="GMccFXr5m" isVisible="true"/>
  <folder label="Folder 1" type="folder" id="JfoN1yo1I" isBranch="true" isVisible="true">
    <layer label="Text Layer 1" type="textlayer" id="GMccFXr5m" isVisible="true"/>
  </folder>
</layers>

“文件夹 1”中的文本层(“文本层 1”)被复制到文件夹节点之外,而不是移动到文件夹节点之外。

现在这是奇怪的部分。

如果在 Tree Flex 组件中将 showRoot 设置为 true,此问题就会消失。问题是在树中可见的根节点是不可取的。

我们对 Tree 组件进行了这个小改动:

showRoot="true"

如果在 showRoot 为真时采取了相同的步骤,现在看看正在跟踪的 layers_xml:

<layers label="root" id="root">
  <layer label="Text Layer 1" type="textlayer" id="nwdwAwlML" isVisible="true"/>
  <folder label="Folder 1" type="folder" id="TdVbaPvPB" isBranch="true" isVisible="true"/>
</layers>

我在 Flex 中遇到过错误吗?或者我的代码中有什么导致这种情况发生?

虽然感谢任何回应或解释为什么会发生这种情况,但建议我使用除 XML 之外的任何类型的 dataProvider 的答案不会有太大帮助,因为我正在使用它构建的应用程序离最后阶段太远了做出如此剧烈的改变。

谢谢您的帮助!

4

1 回答 1

3

经过一番研究,问题已经解决。

这个问题的原因确实是 Flex 中的一个错误,因为 Flex 在向 Tree 控件动态添加项目时会变得不稳定(而不是拥有一个可以随意重新排列的固定数据集)。

解决此问题的方法是捕获当前openItemsand的快照scrollPosition,然后手动重新附加dataProvider并重新验证整个事物,因此 Flex 不会对树的当前结构感到困惑。

我已经构建了该函数并在dragComplete事件上调用了该函数。

protected function onDragComplete(event:DragEvent):void {
     //resetting the entire tree after every drag completes
    forceTreeRedraw(layersTree, layers_xml);
}

private function forceTreeRedraw(tree:Tree, dataProvider:Object):void {
    var scrollPosition:Number = tree.verticalScrollPosition;
    var openItems:Object = tree.openItems;
    tree.dataProvider = dataProvider;
    tree.openItems = openItems;
    tree.validateNow();
    tree.verticalScrollPosition = scrollPosition;
}

问题神奇地解决了,毛茸茸的兔子正在嬉戏,世界一切都很好。

所有功劳归功于此处发布的答案: Flex:更新树控件

于 2012-12-02T15:40:55.197 回答