关于组件拖放
可视化的开发环境就是要允许用户能够在屏幕中通过鼠标选择或者移动程序中的各种物件。拖放就允许用户选择一个物件,比如List控件或者Flex中的Image控件,然后把它拖到另外一个组件(容器),再把这个物件添加到这个组件(容器)中。
你可以添加对所有Flex组件拖放的支持,当然,Flex也本身包含了对拖放的built-in的支持和操作,比如List, Tree 和DataGrid控件,这些空间都能自动处理拖放所需要的操作。
关于拖放操作
拖放的操作包行三个主要的过程:初始化、拖以及放过程。
² 初始化:用户通过按下鼠标选择或者移动一个Flex组件,或者Flex组件中的一项来完成初始化动作。
² 拖:当用户仍然按住鼠标不放的时候,他可以在Flex程序中移动鼠标,拖的过程中所显示一个image就叫着一个drag proxy.它包含了被拖组件(drag source)的数据。
² 放:当用户把drag proxy移动到另外一个组件(容器),这个组件(容器)就成为了一个drop target.这个drop target就会检查drag source物件来判断里头的数据类型或者格式是否恩那个被drag target所接受。如果可以接受,即两种数据类型或者格式符合,就会允许用户放下所拖的组件。否则,则不允许用户放下组件。
执行拖放操作
拖和放都是事件驱动的。要配置一个组件作为drag initiator或者 drag target,你必须为特定的事件书写事件处理,比如dragDrop或者dragEnter事件。
一些经常被拖放的组件,Flex提供了Built-in事件处理来自动执行拖放操作。这些控件都是ListBase这个类的子类,可被认作为list-based控件。
对于移动操作,即你从drag initiator移动一个数据组件到drag target, list-based控件可以处理所有的拖放的时间处理。但是你如果要拷贝一个拖放组件数据到drop target,那么你就必须书写事件处理无论是list-based控件还是nonlist-based控件。
使用list-based控件
以下的控件都包行了对拖放的bulit-in的支持。
ü DataGrid
ü HorizontalList
ü List
ü PrintDataGrid
ü TileList
ü Tree
这些控件的built-in支持可以让用户从一个支持drag-enabled的控件把items移动到另外一个支持drop-enabled控件中。然而,要拷贝items,用户需要书写额外的逻辑操作。比如:以下的程序允许用户把List控件中的item移动到其他的控件中:
creationComplete="initApp();">
import mx.collections.ArrayCollection;
private function initApp():void {
srclist.dataProvider =
new ArrayCollection(['Reading', 'Television', 'Movies']);
destlist.dataProvider = new ArrayCollection([]);
}
]]>
allowMultipleSelection="true"
dragEnabled="true"
dragMoveEnabled="true"/>
dropEnabled="true"/>
label="Reset"
click="initApp()"
/>
通过设置第一个list的dragEnabled属性为true,以及第二个list中的dropEnabled属性为true,用户就可以从第一个list拖放item到第二个list,而不需要去担心底层是怎么执行的。
对于所有list类的控件除了Tree控件,dragMoveEnabled属性的值为false,所以你只能从一个控件拷贝元素到另外一个控件。通过设置第一个list中的dragMoveEnabled值为ture 你就可以移动或者拷贝数据了。对于Tree 控件,dragMoveEnabled的默认是为true的。
当dragMoveEnabled的值为ture的时候,拖放的默认执行操作是移动数据,如果要执行拷贝操作,在移动的过程中,通过按下Control 这个键就可以完成拷贝的操作。
在执行拖放的操作过程中,唯一的要求就是数据(data provider)的结构必须与两个控件要求的结构相符。否则将不能正确显示拖放的数据内容。
你可以更改托拖放的数据类型使得他们完成兼容的操作。以下的例子通过设置两个list-baed控件的dragEnabled,dropEnabled,和drogMoveEnabled属性值为ture来运行双向的拖放动作。
creationComplete="initApp();">
import mx.collections.ArrayCollection;
private function initApp():void {
srcgrid.dataProvider = new ArrayCollection([
{Artist:'Carole King', Album:'Tapestry', Price:11.99},
{Artist:'Paul Simon', Album:'Graceland', Price:10.99},
{Artist:'Original Cast', Album:'Camelot', Price:12.99},
{Artist:'The Beatles', Album:'The White Album', Price:11.99}
]);
destgrid.dataProvider = new ArrayCollection([]);
}
]]>
allowMultipleSelection="true"
dragEnabled="true"
dropEnabled="true"
dragMoveEnabled="true">
allowMultipleSelection="true"
dragEnabled="true"
dropEnabled="true"
dragMoveEnabled="true">
label="Reset"
click="initApp()"
/>
同个控件中执行拖放操作
在同个控件中执行拖放操作一般是用来list-based控件中的item重组。下个例子当中,定义个Tree控件,允许用户通过拖放重组里面的节点。这个例子当中,你只需设置dragEnabled和dropEnabled两个属性为trure,因为Tree控件中的dragMoveEnabled的属性默认值为true.
// Initialize the data provider for the Tree.
private function initApp():void {
firstList.dataProvider = treeDP;
}
]]>
showRoot="false"
labelField="@label"
dragEnabled="true"
dropEnabled="true"
allowMultipleSelection="true"
creationComplete="initApp();"/>
当然,list-based控件的拖放动作拥有很多属性值,具体可以参考development guide.这不多介绍了。
获取拷贝中的类型信息
当你利用list-based控件的built-in支持从一个list-based控件拷贝数据到另外一个list-based控件。你会碰见三种情形可能造成你的数据类型信息丢失。
Ø 当你在两个list-based控件中执行拷贝操作,但是在移动操作中并没有执行拷贝操作。
Ø 你的数据类型不是基本的AS数据类型,比如:Date,Number…
Ø 被拷贝的数据类型不是DispalyObject类,或者它的子类。
举例:你定义了一个如下的类,Car.as:
package
{
// dragdrop/Car.as
[RemoteClass]
public class Car extends Object
{
// Constructor.
public function Car()
{
super();
}
// Class properties.
public var numWheels:int;
public var model:String;
public var make:String;
public function get label():String
{
return make + " " + model;
}
}
}
注意到文件当中的元数据(metadata)标记 [RemoteClass].这个标记是用来注册一个Car数据类型,并且这个类型信息可以在拷贝过程中保留的。如果删除了这个标记,类型信息就会丢失了。然后你就可以在程序中利用这个类了:
layout="absolute" xmlns="*">
public function changeit():void
{
if (list2.dataProvider != null)
{
msg.text += list2.dataProvider[0]
if(list2.dataProvider[0] is Car)
msg.text += " Is Car\n";
else
msg.text += " Is NOT Car\n";
}
}
]]>
x="10" y="45"
width="160" height="120"
dragEnabled="true"
dragMoveEnabled="true">
x="200" y="45"
width="160" height="120"
dropEnabled="true"/>
x="10" y="200"
width="400" height="100"/>
手动增加拖放操作支持
Built-in的支持只适合于list-based的控件。但是Flex允许所有组件支持拖放操作。即一些nonlist-based的控件也可以执行拖放操作。这就需要处理拖放事件了。
支持拖放操作的类很多:包括DragManger,DragSource以及DragEvent。具体功能参考development guide. 同样对于draginitiator来说,也有很多drag-and-drop 事件:mouseDown,mouseMove,dragStart,dragComplete,dragEnter等。具体功能参考development guide。当为nonlist-based控件增加拖放操作支持时,必须实现事件处理:dragEnter和dragDrop两个事件。其他事件处理可选。对于list-based控件来说,当你设置了dropEnabled属性为true的时候,flex就自动增加了所有事件的处理。
拖放操作
以下的步骤定义了一个拖放操作:
Ø 一个组件成为一个课可拖放的initiator有两种方式:
ü List-based控件其dragEnabled值为ture.
ü Nonlist-based控件或者list-based控件其dragEnabled值为false的。程序必须检测用户的开始拖放的意图,一般的通过mouseMove或者mouseDown来开始一个拖放操作。接着组件必须创建一个—mx.core.DragSource 类实例包含要移动的数据以及数据格式。组件最后条用mx.manager.DragManager.doDrog()方法来出示一个拖放操作。
Ø 当鼠标仍然处于按下的状态,用户可以围绕应用程序界面移动鼠标,Flex将会在程序中显示一个代理镜像(proxy image)。DragManager.defaultDragImageSkin负责设置代理镜像的默认值。注意:当代理镜像没有在drag target上方时,若释放鼠标,flex将在drag initiator产上一个DragComplete事件,并且由DragManager.getFeedback()方法返回DragManger.NONE.
Ø 假如用户移动drag proxy经过一个flex组件,那么flex将调用dragEnter事件:
ü 对于dropEnabled=ture的list-base控件,flex检查看看组件时候能成为一个drop target.
ü 对于nonlist-based控件或者list-based控件其dropEnabled=false的。组件必须为dropEnter事件定义一个事件处理。这个事件处理能检查DragSource对象来决定是否数据可被接受与否。如果可以接受,事件处理将调用DragMnager.accetpDragDrop()方法。你必须为drag target调用这个方法来接收dragOver,dragEixt,dragDrop事件。
ü 如果drop target不接受,drop target组件的的父链将被检查是否可以任何组件可以接受这个drop data.
ü 如果drop target 或者其父链中的组件接受了drop。Flex将在用户移动proxy 到 drop target时候调用dragOver方法。
Ø 如果用户释放了鼠标执行放的操作。Flex将在drop target调用dragDrop事件.
ü 对于dropEnabled=ture的list-base控件,flex将自动添加drop data到drop target中。
ü 对于nonlist-based控件或者list-based控件其dropEnabled=false的, 在drop target必须定义对dragDrop事件的监听来处理增加drop data到drop target中。
例子:Nonlist-based组件的拖放操作
以下的例子是允许用户通过拖放两种颜色框来改变Canvas的背景颜色。
backgroundColor="white">
import mx.core.DragSource;
import mx.managers.DragManager;
import mx.events.*;
import mx.containers.Canvas;
// Initializes the drag and drop operation.
private function mouseMoveHandler(event:MouseEvent):void {
// Get the drag initiator component from the event object.
var dragInitiator:Canvas=Canvas(event.currentTarget);
// Get the color of the drag initiator component.
var dragColor:int = dragInitiator.getStyle('backgroundColor');
// Create a DragSource object.
var ds:DragSource = new DragSource();
// Add the data to the object.
ds.addData(dragColor, 'color');
// Call the DragManager doDrag() method to start the drag.
DragManager.doDrag(dragInitiator, ds, event);
}
// Called when the user moves the drag proxy onto the drop target.
private function dragEnterHandler(event:DragEvent):void {
// Accept the drag only if the user is dragging data
// identified by the 'color' format value.
if (event.dragSource.hasFormat('color')) {
// Get the drop target component from the event object.
var dropTarget:Canvas=Canvas(event.currentTarget);
// Accept the drop.
DragManager.acceptDragDrop(dropTarget);
}
}
// Called if the target accepts the dragged object and the user
// releases the mouse button while over the Canvas container.
private function dragDropHandler(event:DragEvent):void {
// Get the data identified by the color format
// from the drag source.
var data:Object = event.dragSource.dataForFormat('color');
// Set the canvas color.
myCanvas.setStyle("backgroundColor", data);
}
]]>
width="30" height="30"
backgroundColor="red"
borderStyle="solid"
mouseMove="mouseMoveHandler(event);"/>
width="30" height="30"
backgroundColor="green"
borderStyle="solid"
mouseMove="mouseMoveHandler(event);"/>
width="100" height="100"
backgroundColor="#FFFFFF"
borderStyle="solid"
dragEnter="dragEnterHandler(event);"
dragDrop="dragDropHandler(event);"/>
label="Clear Canvas"
click="myCanvas.setStyle('backgroundColor', '0xFFFFFF');"
/>
简单描述鼠标mouseDown的事件处理:
1. 创建一个DragSource对象来初始化被拖放的数据以及类型。
2. 调用DragManger.doDrag()方法来开始拖放操作。
DragEnter事件处理:通过DataSource对象来判断被拖放的数据是否于drop target要求的类型兼容。
DragDrop事件处理:当释放鼠标的时候触发该事件,并且dragEnter事件已经掉哟个DragManger.acceptDragDrop()来接受drop.你必须定一个事件处理来把drop data添加到drop target中。
例子:处理list-based控件的拖放事件
当你设置了dragEnabled或者dropEnabled属性为ture时,Flex自动定义了默认的事件处理来执行拖放操作。当然你可以利用这些默认的事件处理,也可以自行定义你自己的事件处理。如果要定义自己的事件处理,必须在drag initiator设置dragEnabled属性为false,或者在drag target中设置dropEnabld为false.那如果要使用这些自己定义的事件处理,除了事先定义好之外,你最好需要显式停止flex默认的事件处理:即在你自己的事件处理中调用Event.preventDefault().注意:你如果在Tree控件中,当在移动一个data从一个tree控件到另外一个时候,你调用了Event.preventDefault()方法来处理dragComplete或者dragDrop事件,那么程序就会阻止drop操作。
以下的例子为dragDrop定义了一个事件处理。这个事件处理在默认的事件处理执行前执行来显式Alert信息。
creationComplete="initApp();">
import mx.events.DragEvent;
import mx.controls.Alert;
import mx.collections.ArrayCollection;
private function initApp():void {
srcgrid.dataProvider = new ArrayCollection([
{Artist:'Carole King', Album:'Tapestry', Price:11.99},
{Artist:'Paul Simon', Album:'Graceland', Price:10.99},
{Artist:'Original Cast', Album:'Camelot', Price:12.99},
{Artist:'The Beatles', Album:'The White Album', Price:11.99}
]);
destgrid.dataProvider = new ArrayCollection([]);
}
// Define the event listener.
public function dragDropHandler(event:DragEvent):void {
// dataForFormat() always returns an Array
// for the list-based controls
// in case multiple items were selected.
var dragObj:Array=
event.dragSource.dataForFormat("items") as Array;
// Get the Artist for all dragged albums.
var artistList:String='';
for (var i:Number = 0; i < dragObj.length; i++) {
artistList+='Artist: ' + dragObj[i].Artist + '\n';
}
Alert.show(artistList);
}
]]>
allowMultipleSelection="true"
dragEnabled="true"
dropEnabled="true"
dragMoveEnabled="true">
allowMultipleSelection="true"
dragEnabled="true"
dropEnabled="true"
dragMoveEnabled="true"
dragDrop="dragDropHandler(event);">
label="Reset"
click="initApp()"
/>
注意到了dataFormat()方法指定了一个参数名称items.这是因为list-based控件已经事先定义了drag data的数据格式。所有的list控件除了Tree控件,其格式字符为”items”,对于Tree控件来说其格式字符值为”treeItems”.还需要注意的是,dataFormat()方法返回的值是一个数组,这是因为list-based控件允许用户选择读多个items的缘故。
运行在AIR中的Flex应用程序的拖放
运行在AIR的中Flex程序可以使用Flex drag manager或者AIR drag Manger. Flex drag manager由 mx:mangers.DragManger类实现,而AIR drag Manger由flash.desktp.NativeDragManager类实现。默认的,Flex中由定义的程序使用Flex drag-and drop manager.即使程序运行在AIR中,当然运行在AIR中的程序也可以制定使用AIR的drag-and-drop manager.这就需要配置Flex的 mx.managers.DragManger类,指定使用AIR中的drag-and-drop manger. 若使用的Flex程序默认使用AIR的drag-and-drop 管理器。以下举例在程序指定使用AIR中的drag-and-drop管理器:
initialize="initDandD();">
// Ensure that the NativeDragManagerImpl class is linked in to your
application.
import mx.managers.NativeDragManagerImpl;
var placeholder:NativeDragManagerImpl;
// Handle the initialize event to load the DragManager.
public function initDandD():void
{
// Ensure the DragManager is loaded, so that dragging in an AIR works.
DragManager.isDragging;
}
]]>
...
拖放实例
例子1:用Canvas作为drop target
//Import classes so you don't have to use full names.
import mx.managers.DragManager;
import mx.core.DragSource;
import mx.events.DragEvent;
import flash.events.MouseEvent;
// Embed icon image.
[Embed(source='assets/globe.jpg')]
public var globeImage:Class;
// The mouseMove event handler for the Image control
// initiates the drag-and-drop operation.
private function mouseMoveHandler(event:MouseEvent):void
{
var dragInitiator:Image=Image(event.currentTarget);
var ds:DragSource = new DragSource();
ds.addData(dragInitiator, "img");
DragManager.doDrag(dragInitiator, ds, event);
}
// The dragEnter event handler for the Canvas container
// enables dropping.
private function dragEnterHandler(event:DragEvent):void {
if (event.dragSource.hasFormat("img"))
{
DragManager.acceptDragDrop(Canvas(event.currentTarget));
}
}
// The dragDrop event handler for the Canvas container
// sets the Image control's position by
// "dropping" it in its new location.
private function dragDropHandler(event:DragEvent):void {
Image(event.dragInitiator).x =
Canvas(event.currentTarget).mouseX;
Image(event.dragInitiator).y =
Canvas(event.currentTarget).mouseY;
}
]]>
width="500" height="500"
borderStyle="solid"
backgroundColor="#DDDDDD"
dragEnter="dragEnterHandler(event);"
dragDrop="dragDropHandler(event);">
source="@Embed(source='assets/globe.jpg')"
mouseMove="mouseMoveHandler(event);"/>
例子2:指定drag proxy
在事件处理mouseDown或者mouseUp中,你可以通过doDrag()来制定drag proxy.如果你不自己指定,哪么程序将使用默认值。你可以自行定义drag proxy以下属性:dragImage, xoffset, yoffset, imageAlpha.你必须制定drag proxy的大小,否则将不会被显示出来。举例:以下将设置proxy的长宽为15 pixel.
//Import classes so you don't have to use full names.
import mx.managers.DragManager;
import mx.core.DragSource;
import mx.events.DragEvent;
import flash.events.MouseEvent;
// Embed icon image.
[Embed(source='assets/globe.jpg')]
public var globeImage:Class;
// The mouseMove event handler for the Image control
// initiates the drag-and-drop operation.
private function mouseOverHandler(event:MouseEvent):void
{
var dragInitiator:Image=Image(event.currentTarget);
var ds:DragSource = new DragSource();
ds.addData(dragInitiator, "img");
// The drag manager uses the Image control
// as the drag proxy and sets the alpha to 1.0 (opaque),
// so it appears to be dragged across the Canvas.
var imageProxy:Image = new Image();
imageProxy.source = globeImage;
imageProxy.height=15;
imageProxy.width=15;
DragManager.doDrag(dragInitiator, ds, event,
imageProxy, -15, -15, 1.00);
}
// The dragEnter event handler for the Canvas container
// enables dropping.
private function dragEnterHandler(event:DragEvent):void {
if (event.dragSource.hasFormat("img"))
{
DragManager.acceptDragDrop(Canvas(event.currentTarget));
}
}
// The dragDrop event handler for the Canvas container
// sets the Image control's position by
// "dropping" it in its new location.
private function dragDropHandler(event:DragEvent):void {
Image(event.dragInitiator).x =
Canvas(event.currentTarget).mouseX;
Image(event.dragInitiator).y =
Canvas(event.currentTarget).mouseY;
}
]]>
width="500" height="500"
borderStyle="solid"
backgroundColor="#DDDDDD"
dragEnter="dragEnterHandler(event);"
dragDrop="dragDropHandler(event);">
source="@Embed(source='assets/globe.jpg')"
mouseMove="mouseOverHandler(event);"/>
例子3:为drop target处理dragOver和dragExit事件
当用户移动鼠标到drop target,而它的dragEnter的事件已经调用DragManger.acceptDragDrop()方法时将会触发dragOver事件。当然这个事件是可选的。
dragOver事件相当有用尤其是当用户移动鼠标到drop target时能返回一个可视化的回馈。常使用的有:DragManager.COPY,DragManager.LINK,DragManager.MOVE,DragManager.NONE等。用户可以通过DragManger.showFeedback()方法来指定具体的显示形式。
dragExit是在用户在drop target放下drag proxy,但还没有放入数据时调度的。你可以利用这个方法来恢复之前因为dragOver事件所作的可视化的改变。
creationComplete="initApp();">
import mx.managers.DragManager;
import mx.events.DragEvent;
import mx.collections.ArrayCollection;
private function initApp():void {
firstList.dataProvider = new ArrayCollection([
{label:"First", data:"1"},
{label:"Second", data:"2"},
{label:"Third", data:"3"},
{label:"Fourth", data:"4"}
]);
secondList.dataProvider = new ArrayCollection([]);
}
// Variable to store original border color.
private var tempBorderColor:uint;
// Flag to indicate that tempBorderColor has been set.
private var borderColorSet:Boolean = false;
private function dragOverHandler(event:DragEvent):void {
// Explpicitly handle the dragOver event.
event.preventDefault();
// Since you are explicitly handling the dragOver event,
// call showDropFeedback(event) to have the drop target
// display the drop indicator.
// The drop indicator is removed
// automatically for the list controls by the built-in
// event handler for the dragDrop event.
event.currentTarget.showDropFeedback(event);
if (event.dragSource.hasFormat("items"))
{
// Set the border to green to indicate that
// this is a drop target.
// Since the dragOver event is dispatched continuosly
// as you move over the drop target, only set it once.
if (borderColorSet == false) {
tempBorderColor =
event.currentTarget.getStyle('borderColor');
borderColorSet = true;
}
// Set the drag-feedback indicator based on the
// type of drag-and-drop operation.
event.currentTarget.setStyle('borderColor', 'green');
if (event.ctrlKey) {
DragManager.showFeedback(DragManager.COPY);
return;
}
else if (event.shiftKey) {
DragManager.showFeedback(DragManager.LINK);
return;
}
else {
DragManager.showFeedback(DragManager.MOVE);
return;
}
}
// Drag not allowed.
DragManager.showFeedback(DragManager.NONE);
}
private function dragDropHandler(event:DragEvent):void {
dragExitHandler(event);
}
// Restore the border color.
private function dragExitHandler(event:DragEvent):void {
event.currentTarget.setStyle('borderColor', tempBorderColor);
borderColorSet = true;
}
]]>
dragEnabled="true"
dragMoveEnabled="true"/>
borderThickness="2"
dropEnabled="true"
dragOver="dragOverHandler(event);"
dragDrop="dragExitHandler(event);"
dragExit="dragExitHandler(event);"/>
label="Reset"
click="initApp()"
/>
移动和拷贝数据
移动和拷贝数据都是拖放操作的一部分。
关于移动数据
当你移动数据,是在drop target新增它,然后在drag initiator删除它来完成的。你利用dragDrop事件为drop target添加数据,利用dragComplete为drag initiator移除数据。而这个过程中你需要做多少工作取决于你使用的是list-based还是nonlist-based组件。
关于拷贝数据
List-based控件默认的是自动处理移除动作,所以你要做拷贝动作,你必须为drop target显式的处理dragDrop事件。