Tree 中的拖放 第一部分
这篇文章是关于如何放Tree节点——或者更确切地说是如何拖放Tree节点的。
因为这是一个有点复杂的主题而blog的文章不应该长达几页,所以我将这个主题分成了几篇文章。我将会讲述在一个Tree控件内的拖放,从一个Tree控件到其他地方的拖放,以及从其他地方到一个Tree控件的拖放。
如果你正在使用一个Tree控件那么你肯定已经确定了使用哪一种类型的数据:XMLListCollection 或者 ArrayCollection(请看Tree 控件的DataProviders,(这篇我已经翻译过了,译注)).不管你使用哪一种Tree的结构都是分等级的。这对XML来说非常理想,而对于ArrayCollections你则需要使用包含ArrayCollections子节点的ArrayCollections来提供这种结构。
我之所以提到这些,是为了让那些以前没有使用过Tree控件的人熟悉一下如何为Tree提供数据。由于我相信多数程序都使用XML作为Tree的数据,所以这些拖放的例子也将使用XML。
在一个Tree内进行拖放
让一个Tree控件允许用户进行拖拽节点来重新排列是非常简单的事情。想一下这样一个文件浏览器,你可以将文件从一个文件夹拖拽到另一个文件夹中。
这样设定你的Tree控件:
···程序代码
<mx:Tree x="162" y="122" width="279" height="278"
dataProvider="{treeDataList}"
labelField="@label"
dragEnabled="true"
dragMoveEnabled="true"
dropEnabled="true"
/>
通过将dragMoveEnabled 和 dropEnabled 设定为 true,Tree中的节点就可以被拖拽了。
DragSource
在讨论拖放之前,理解DragSource是很重要的。在拖拽事件处理器的事件(mx.events.DragEvent)参数中提供了一个该类的实例。DragSource包含了很多东西,其中包括对拖拽操作的启动控制,数据的格式以及通过格式对数据的访问。
比如,如果你从一个目录里拖拽了一个条目,DragSource的数据可能是该条目的一个图像以及关于该条目的数据。所以DragSource有两种格式:图像和数据项(items)。
在下面的这些例子中你将会看到 ”items” 被用来作为格式的名字来访问DragSource中的数据。像Tree,DataGrid,以及List等Flex控件在自动创建一个DragSource的时候,都是用 ”item” 作为格式的名字的。
拖拽到一个Tree
使用 dropEnabled="true" 可以使得Tree控件接收拖拽事件并且监听下面的事件:
···程序代码
<mx:Tree x="162" y="122" width="279" height="278"
dataProvider="{treeDataList}"
labelField="@label"
dropEnabled="true"
dragEnter="onDragEnter(event)"
dragOver="onDragOver(event)"
dragDrop="onDragDrop(event)"
/>
当鼠标将对象拖拽到Tree上时会分派dragEnter事件。Tree必须确定是否接受该拖拽。这是因为设定了dragEnabled并不是说所有的拖拽都会被接受。此外,dragEnter事件还可以利用事件中的信息——比如拖拽的启动控制(叫做dragInitiator)或者被拖拽的数据。
这里有一个简单的dragEnter事件处理器:
···程序代码
private function onDragEnter( event:DragEvent ) : void
{
DragManager.acceptDragDrop(UIComponent(event.currentTarget));
}
当鼠标将对象拖拽进入Tree的框架内的时候会分派dragOver事件。你可以忽略这个事件,但是你也可以使用它来为用户提供反馈。比如,可以将鼠标下的节点设置为高亮。你甚至可以查看正在被拖拽的节点和鼠标下节点的数据来判断这次拖拽是否被允许。例如,如果一个Tree是关于汽车经销的,现在你将一辆跑车拖到Tree上面,如果将跑车拖到了小型货车目录上面的话你可以改变拖拽的鼠标指针来通知用户那里不允许进行此次拖拽。
下面是一个dragOver事件触发器,它判定哪一个节点在鼠标下并给出相应信息:
···程序代码
private function onDragOver( event:DragEvent ) : void
{
// r is the visible index in the tree
var dropTarget:Tree = Tree(event.currentTarget);
var r:int = dropTarget.calculateDropIndex(event);
tree.selectedIndex = r;
// retrieving the newly selected node, you can examine it and decide to tell
// the user the drop is invalid by changing the feedback.
var node:XML = tree.selectedItem as XML;
if( node.@type == "minivan" ) {
DragManager.showFeedback(DragManager.NONE);
return;
}
// the type of drop - copy, link, or move can be reflected in the feedback as well.
// Here the control and shift keys determine that action.
if (event.ctrlKey)
DragManager.showFeedback(DragManager.COPY);
else if (event.shiftKey)
DragManager.showFeedback(DragManager.LINK);
else {
DragManager.showFeedback(DragManager.MOVE);
}
}
当放开鼠标的时候会分派dragDrop事件。Tree也可以忽略这个事件(因为用户可能错误地在小型货车目录上放开了鼠标),但是这个事件将被拖拽的数据复制给Tree。你可以利用全部或部分数据,也可以不利用。你可以创建节点或者替换节点甚至创建新的分支——这些取决于你的程序设计。
···程序代码
private function onDragDrop( event:DragEvent ) : void
{
var ds:DragSource = event.dragSource;
var dropTarget:Tree = Tree(event.currentTarget);
// retrieve the data associated with the "items" format. This will be the data that
// the dragInitiator has copied into the DragSource.
var items:Array = ds.dataForFormat("items") as Array;
// determine where in the tree the drop occurs and select that node by the index; followed by
// retrieving the node itself.
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();
}
// taking all of the items in the DragSouce, insert them into the
// tree using parent p.
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);
}
}
dragComplete事件是在拖拽启动器(例如:一个列表)中注册的,当拖拽操作结束的时候会调用它 —— 在拖拽被接受并处理时,或拖拽被拒绝时,或者在用户将对象拖拽到一个不允许拖拽的区域中时。
在下面的例子中,所作的唯一一件事情就是清除dragOver 和dragDrop 操作在tree中做的选择。
private function onDragComplete( event:DragEvent ) : void
{
tree.selectedIndex = -1;
}
总结
将信息拖拽到一个Tree控件时需要确定该拖拽是否会被接受。这个可以在dragEnter事件的开始检测,也可以在dragOver事件中被拖拽对象移动到Tree控件上面时动态地检测。
Tree 中的拖放 第二部分
在 Tree 中的拖放第一部分(译注:此文我已经翻译)中我讲述了如何在一个Tree控件内拖拽元素以及如何将元素从Tree控件外部拖拽到Tree中。这篇文章我会讲述如何从Tree中拖拽元素(到其它的控件)。
运行示例
要从一个Tree中拖拽元素你需要将Tree的dropEnabled属性设定为false并且将它的dragEnabled属性设定为true:
···程序代码
<mx:Tree x="34" y="81" width="181" height="189"
dataProvider="{treeData.node}"
labelField="@label"
dropEnabled="false"
dragEnabled="true"
dragMoveEnabled="false"
/>
在这个例子中,假定Tree中的数据是美国的各州,州中的城市以及城市中的饭店。进一步地,我们要拖拽到的目标是一个这样定义的DataGrid:
···程序代码
<mx:DataGrid x="291" y="81" height="189"
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>
在所有的拖放操作中,那些事件处理器是操作成功的关键所在。
DragEnter事件(DataGrid中的)决定了拖拽是否可以进行。在这个例子中,只有那些表示饭店的节点可以被DataGrid接受。一个典型的tree节点会像这样:
···程序代码
<node label="Lobster Pot" type="restaurant" />
其中@type属性可以被用决定被拖拽的节点是否可以被接受。DragEnter事件可以这样定义:
···程序代码
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 passes or the dragInitiator is not a tree, accept the drop
DragManager.acceptDragDrop(UIComponent(event.currentTarget));
}
如果开始拖拽操作的是一个Tree控件,dragSource就会检查它是否包含 "treeItems" 以及这些元素的 @type 是否为"restaurant"。如果检测结果为成功,则DataGrid(即event.currentTarget)将会接受这次拖拽.
当用户移动拖拽的元素时dragOver事件将处理其反馈(feedback):
···程序代码
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);
}
}
}
当dragInitiator是Tree的时候,反馈总是被设定为COPY。当然,你也可以将反馈设定为任何你喜欢的东西,但是在这个例子中反馈是一个copy操作,这样Tree中的信息就不会被删除。
当鼠标移动到DataGrid上并松开时会触发DataGrid的dragDrop事件。数据将会被检查并添加到DataGrid中。
···程序代码
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);
}
当一个控件将拖拽操作初始化完毕的时候,dragSource就会被检查然后treeitems会被取出。接下来会创建一个循环来将新的元素添加到DataGrid中。最后,会调用onDragExit方法:
···程序代码
private function onDragExit( event:DragEvent ) : void
{
var dropTarget:ListBase=ListBase(event.currentTarget);
dropTarget.hideDropFeedback(event);
}
这个函数只是用来确保可视化的反馈被删除。
从Tree中拖拽(到其他控件)非常简单——只需要检查包含"treeItems"的dragSource并且依照程序中的行为来处理他们。在这个例子中,只有 "restaurant" 类型的tree节电被允许拖拽到DataGrid中并且在开始的DragEnter方法中对其进行处理,这样在以后的步骤里就不用再次检测了。