简体   繁体   English

使用XML的Flex Tree在将其拖出文件夹节点后生成项目的副本

[英]Flex Tree using XML makes a copy of item after dragging it out of a folder node

In an application I'm building with Flex, I have a Flex Tree with XML as its data provider. 在我使用Flex构建的应用程序中,我有一个带有XML的Flex Tree作为其数据提供者。 The user needs to be able to create layers and folders to hold those layers (think Photoshop). 用户需要能够创建图层和文件夹来保存这些图层(想想Photoshop)。 The user also needs to be able to rearrange those items at will, and drag layers into folders (and folders into other folders if so desired, etc.). 用户还需要能够随意重新排列这些项目,并将图层拖动到文件夹中(如果需要,还可以将文件夹拖动到其他文件夹中等)。

Here is a test application I've made showcasing what I'm trying to do. 这是一个测试应用程序,我已经做了展示我正在尝试做的事情。 I've stripped it down to be as basic as possible for simplicity's sake. 为简单起见,我已将其剥离为尽可能基本。

(NOTE: I'm creating the XML in my code with a dummy node inside, and then deleting that node at runtime. This is the only way I've found that will have the Tree completely blank on startup. (Just having an empty root node causes that root node to be visible in the tree, even if showRoot is false.) If there's a better way to achieve that effect, please let me know.) (注意:我在我的代码中创建了XML,内部有一个虚拟节点,然后在运行时删除该节点。这是我发现启动时树完全空白的唯一方法。 root节点导致root节点在树中可见,即使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>

Here is the class (XMLService) I'm using to generate the XML nodes to be inserted into the tree. 这是我用来生成要插入树中的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++;
        }
    }

}
}

The problem I'm having is that when a folder and layer is created, and a layer is dragged into a folder, once that folder is dragged back out of the folder, a copy of the layer is created instead of the layer being moved. 我遇到的问题是,当创建文件夹和图层,并将图层拖到文件夹中时,一旦将该文件夹拖回文件夹,就会创建图层的副本而不是正在移动的图层。

A folder and a layer being dynamically created: 正在动态创建的文件夹和图层:

正在动态创建的文件夹和图层

Dragging a layer inside of the folder: 拖动文件夹内的图层:

拖动文件夹内的图层

What happens if the layer is now dragged OUTSIDE (and above) the folder: 如果现在将图层拖到文件夹的外部(和上方),会发生什么情况:

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

You can see this happen in the XML while tracing layers_xml on a dragComplete. 在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>

The text layer ("Text Layer 1") that was in "Folder 1" was copied outside of the folder node instead of being moved outside the folder node. “文件夹1”中的文本图层(“文本图层1”)被复制到文件夹节点之外,而不是移动到文件夹节点之外。

Now here's the weird part. 现在这里是奇怪的部分。

This issue goes away if showRoot is set to true in the Tree Flex component. 如果在Tree Flex组件中将showRoot设置为true,则此问题就会消失。 The problem is that the root node being visible in the tree is undesirable. 问题是树节点中可见的根节点是不合需要的。

We make this small change to the Tree component: 我们对Tree组件进行了这么小的改动:

showRoot="true"

Now take a look at layers_xml being traced if the very same steps are taken while showRoot is 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>

Have I run into a bug in Flex? 我是否遇到过Flex中的错误? Or is something in my code causing this to happen? 或者我的代码中有什么导致这种情况发生?

While any response or explaination of why this is happening is appreciated, answers that suggest I using any type of dataProvider other than XML won't be of much help, as the application I'm building with this is too far into its final stages to make such a drastic change. 虽然对于为什么会发生这种情况有任何回应或解释,但是建议我使用除XML以外的任何类型的dataProvider的答案都没有多大帮助,因为我正在构建的应用程序已经过了最后阶段做出如此剧烈的改变。

Thanks for the help! 谢谢您的帮助!

After some more research, the issue has been solved. 经过一番研究,这个问题已经解决了。

The cause of this issue is indeed a bug in Flex, as Flex gets wonky when dynamically adding items to the Tree control (as opposed to having a fixed data set which you can rearrange at will). 这个问题的原因确实是Flex中的一个错误,因为Flex在向Tree控件动态添加项目时会变得不稳定(而不是拥有可以随意重新排列的固定数据集)。

The way around this is to capture a snapshot of the current openItems and scrollPosition , then manually reattach the dataProvider and re-validate the whole thing, so Flex doesn't get confused on the current structure of the tree. 解决这个问题的方法是捕获当前openItemsscrollPosition的快照,然后手动重新附加dataProvider并重新验证整个事物,这样Flex就不会对树的当前结构感到困惑。

I've built that function and called the function on a dragComplete event. 我已经构建了该函数并在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;
}

Problem magically solved, fluffy bunnies are frolicking and all is right with the world. 问题神奇地解决了,蓬松的兔子嬉戏,一切都与世界是对的。

All credit goes to the answer posted here: Flex: Updating a Tree control 所有功劳都归功于此处发布的答案: Flex:更新Tree控件

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM