在Flex/AIR中使用Drag And Drop. Using Drag And Drop in Flex/AIR

关于Drag和Drop的基础知识与基本应用可以参见:http://livedocs.adobe.com/flex/3/html/help.html?content=dragdrop_1.html png-0048

1. 设计目标

实现Flex/AIR中的Tree,DataGrid自身或相互之间的拖动.

本例仍旧使用NoteManagement,关于NoteManagement的相关信息请参见:
1.完整Flex程序+详细解释之便条管理系统(Tree/回溯/XML/Event) Annotated Flex Sample Application: Note Management
2.使用Air与SQLite开发NoteManagement
3.Flex树形菜单动态加载 Flex Tree Dynamic Loading
通过以上三篇文章,您将对NoteManagement有一个概括性的了解.

2.概要设计:

image 

2.1 Tree内目录拖动的实现:
如果使用Tree自带的Drag与Drop属性设定Tree,则目录之间只能实现十分局限的拖动,如果目录不含有子目录,则其他目录无法拖动到该目录下.要解决这个问题,就必须使用自定义的itemRenderer,该itemRenderer继承自TreeItemRenderer从而不影响Tree的其他功能的正常使用,相对来说,自定义的itemRenderer应该具有以下功能:1.在有目录drag到itemRenderer上时他能够acceptDragDrop. 2:可以接受并处理dragDrop事件.

2.2 Note移动的实现:
使用DataGrid的属性dragEnabled属性设定DataGrid可以被drag. 当drag到目录中时,dragEnter的target acceptDragDrop,通过对相关事件的监听进行相应处理.

3.详细实现:

3.1 Tree内目录拖动:
设置Tree的属性为:dragEnabled="true"  dropEnabled="false"
在Tree初始化完成后监听DRAG_COMPLETE事件;DragtreeCategory.addEventListener(DragEvent.DRAG_COMPLETE, onCatDragComplete);
通过使用onCatDragComplete可以阻止其默认行为preventDefault(),这样我们获得对DRAG_DROP的控制.
为Tree建立一个自定义的itemRenderer,该itemRenderer监听DragEvent.DRAG_ENTER与DragEvent.DRAG_DROP,通过对DRAG_ENTER的监听,控制itemRenderer是否接受Drag,通过对DRAG_DROP的监听,将DRAG的目录加入到DROP目录中,并删除原目录,以完成整个过程.
3.2 Note的移动
设置DataGrid的属性为dragEnabled="true"
同3.1在DataGrid初始化完成后监听DRAG_Complete,阻止默认行为并获得控制
当Drag的Note Enter到Tree中时,通过itemRenderer的监听, 控制itemRenderer是否接受DragDROP,并控制后续过程.
自定义itemRenderer的代码如下:

package component
{
import com.insprise.common.sql.Connection;
import com.insprise.common.utility.LogUtils;
import com.insprise.common.utility.SQLUtils;
import com.insprise.nmair.Category;
import com.insprise.nmair.Note;

import flash.data.SQLConnection;
import flash.events.SQLEvent;

import mx.controls.treeClasses.TreeItemRenderer;
import mx.events.DragEvent;
import mx.managers.DragManager;

public class TreeCatItemRender extends TreeItemRenderer
{
	public static var dragAndDropClass:int;  //辨别Drag来自于Tree还是DataGrid.
	
	private var conn:SQLConnection = new SQLConnection();
	private var catMoved:Category;
	private var catMoveInto:Category;
	private var catEnter:Category;
	private var noteMoved:Note;
	private var noteMoveIntoCat:Category;
	private var noteEnter:Category;
	/**
	 * 构造函数,每次使用该Renderer时会自动添加EventListener;
	 */ 
	public function TreeCatItemRender() {
		super();
		this.addEventListener(DragEvent.DRAG_ENTER, dragEnterHandler);
		this.addEventListener(DragEvent.DRAG_DROP, dragDropHandler);
	}

	//在拖动到某一个Render上时,判断是否允许接受Drop;
	private function dragEnterHandler(e:DragEvent):void {
		if(e.dragSource.hasFormat("treeItems")) {
			dragAndDropClass = 0;
			catMoved = e.dragSource.dataForFormat("treeItems")[0] as Category;
			catEnter = this.data as Category;
			LogUtils.defaultLog.info("Draged Item: " + catMoved.label);
			LogUtils.defaultLog.info("Enter Cat: " + catEnter.label);
			if(catMoved.isAncestor(catEnter) || catMoved == catEnter || catMoved.parent == catEnter) {
				LogUtils.defaultLog.info("上级目录不可移动到下级目录, 也不可移动到期自身内部");
				return;
			}
			DragManager.acceptDragDrop(e.currentTarget as TreeCatItemRender);
		}
		
		else if(e.dragSource.hasFormat("items")) {
			dragAndDropClass = 1;
			noteMoved = e.dragSource.dataForFormat("items")[0] as Note;
			noteEnter = this.data as Category;
			if(noteMoved.parent == noteEnter) {
				LogUtils.defaultLog.info("没有进行移动,不进行任何操作");
				return;
			}
			DragManager.acceptDragDrop(this);
			trace("Note parent.id: " + noteMoved.parent.id );
			trace(" Note Enter Cat id: " + noteEnter.id);
		}
	}
	
	//在Drop之后 ,建立与数据库的连接,准备更新数据库.
	private function dragDropHandler(e:DragEvent):void {
		if(dragAndDropClass == 0) {
			e.preventDefault();
			catMoveInto = this.data as Category;
			LogUtils.defaultLog.info("移动进: " + catMoveInto.label);
		} 
		else if(dragAndDropClass == 1) {
			e.preventDefault();
			noteMoveIntoCat = this.data as Category;
			LogUtils.defaultLog.info("Note: " + noteMoved.title + " 被拖进了: " + noteMoveIntoCat.label);
		}
		
		if(conn.connected) {
			LogUtils.defaultLog.info("DataBase have Connected. Call UpdateDB Directly");
			updateDB();
			return;
		}
		LogUtils.defaultLog.info("Connect Db.....");
		conn.addEventListener(SQLEvent.OPEN, updateDB);
		conn.openAsync(Connection.dbFile);		
	}
	//建立 与数据的 连接之后Update改纪录.
	private function updateDB(e:SQLEvent=null):void {
		if(dragAndDropClass == 0) {
			var updateCatSql:String = "UPDATE Cat SET parent_ID='" + catMoveInto.id + "' WHERE cat_ID=" + catMoved.id;
			SQLUtils.createAndExecuteStatement(conn, updateCatSql, null, updateDBSucess)
		}
		else if(dragAndDropClass == 1) {
			var updateNotesql:String = "UPDATE Note SET parent_ID='" + noteMoveIntoCat.id + "' WHERE note_ID=" + noteMoved.id;
			SQLUtils.createAndExecuteStatement(conn, updateNotesql, null, updateDBSucess)
		}
	}
	
	//Update成功后,移动目录或note
	private function updateDBSucess(e:SQLEvent):void {
		if(dragAndDropClass == 0) {
			LogUtils.defaultLog.info(catMoved.label + " 的parent_ID已成功更新为: " + catMoveInto.id);
			catMoveInto.addSubCat(catMoved);
			LogUtils.defaultLog.info("目录: " + catMoveInto.label + " 成功添加子目录: " + catMoved.label);
		}
		else if(dragAndDropClass == 1) {
			LogUtils.defaultLog.info(noteMoved.title + "的parent_ID已成功更新为:  " + noteMoveIntoCat.id);
			noteMoveIntoCat.addNote(noteMoved);
			LogUtils.defaultLog.info("目录: " + noteMoveIntoCat.label + " 成功添加Note: " + noteMoved.title);
		}
	}
	
}
}

在move情况下,我们把目标加入到Drop target中,并从Drag initiator中删除,使用dragDrop事件的监听函数来加入目标到Drop target, 使用dragComplete事件的监听函数来删除drag initiator中的源数据.

在这个例子中,由于在addCat()与addNote()中以含有删除功能,故我们阻止了dragComplete,使用dragDrop处理增加与删除.addCat()与addNote通过判断源数据的parent是否等于Drop target的id来进行对应处理.

在移动后元数据的parent与Drop target中的Category是不同的,这种情况下先将其删除,修改其parent属性后再加入到Drop target的Category中.完成操作.

代码如下:

		/**
		 * Adds the given child.
		 * If the child has an existing parent, it will be removed from its parent first then added to this. 
		 */
		public function addSubCat(child:Category):void {
  		  	if(this.subCats == null) {
  		  		subCats = new ArrayCollection();
  		  	}
  		  	
  		  	if(child.parent == null) { // initial
  		  		child._parent = this;
  		  		subCats.addItem(child);
  		  	}else{ // not null
  		  		if(child.parent == this) {
  		  			throw new Error("The parent has already been set to this: " + toString() + ", child: " + child);
  		  		}else{ // change parent.
  		  			child._parent.delCat(child);
  		  			child._parent = this;
  		  			subCats.addItem(child);
  		  		}
  		  	}
		}
		
		//删除子目录.须提供一个index,通过findCatIndex函数寻找.
		/**
		 * Remove the given child category and set the removed category's parent to null.
		 * @throw Error if the child's parent is not this.
		 */
		public function delCat(child:Category):void {
			trace("This.id: " + this.id);
			trace("child's Parent ID: " + child.parent.id);
			trace(child._parent.subCats.getItemIndex(child));
			if(child._parent != this) {
				throw new Error("The parent of the child is not this: " + toString() + ", child: " + child);
			}
			
			subCats.removeItemAt(subCats.getItemIndex(child));
			child._parent = null;
		}
		
		/**
		 * 增加Note
		 * 如果category的notes为空,则新建notes
		 * 如果note的parent为空,则将this赋给note的parent  [Note初始化时发生]
		 * 如果note的parent为this,则为重复添加.报错处理      [重复添加Note,报错]
		 * 如果note的parent不等于this,则说明是移动过来的note,需要在该note原先的parent下删除该note,并将该note添加到this的notes中.
		 */ 		
		public function addNote(note:Note):void {
			if(notes == null) {
				notes = new ArrayCollection();
			} else if(note.parent == null) {
				note.parent = this;
				notes.addItem(note);
			} else{ 
				if(note.parent == this) {
					throw new Error(note.title + " 的parent已经被设定为" + this.label);
				} else{
					note.parent.delNote(note);
					note.parent = this;
					notes.addItem(note);
				}
			}
		}
		
		/**
		 * 删除note
		 * 如果note的parent不为this,则报错
		 * 否则删除该note
		 * 并将note parent为空.
		 */ 
		public function delNote(note:Note):void {
			if(note.parent != this) {
				throw new Error(note.title + " note的parent不是this " + this.label);
			}
			notes.removeItemAt(notes.getItemIndex(note));
			note.parent = null;
		}