Native Drag and Drop in Adobe Air with example

In this tutorial I will teach you how to create a simple adobe air application with native drag and drop support using a file system manager as example, that can copy/move files dragged to/from the OS desktop.

What does it mean to create an application with native drag and drop support?
The standard drag & drop functionality provided in Adobe Flex only allows you to drag & drop interactive objects within the Flex application itself. However with Adobe Air, using the NativeDragManager allows you to drag & drop objects between the OS, or from other applications installed in your operating system and the Air app.

For example in Adobe Air you can create a simple mp3 player which accepts mp3 files dragged directly from the desktop into the mp3 player window. Or like in the example included you can create an adobe air app that copies files between the OS desktop and the air app window.

Native drag and drop, like the standard flex drag and drop is divided has 3 phases – initiation, dragging and dropping.

The initiation is when the user clicks on an interactive component while keeping the mouse button pressed. In this case the component that the user clicks on is the drag initiator. Any air component that supports dragging will handle either the mouseDown() or mouseMove() event to initiate the native drag & drop operation. The event handler will then create a Clipboard object which contains data related to the object being dragged. For example if you are dragging a file between two components, the Clipboard object will contain a reference to a File object. Once the Clipboard object is constructed the NativeDragManager.doDrag() method is called where the first argument is a reference to the object that initiated the drag, the second argument is the Clipboard object, the third argument is an optional drag image/proxy image that can be displayed while dragging the object on the screen(e.g. a semi-transparent version of the drag initiator).

In the Dragging stage the user moves the flex component across the screen up to the drop target. If you specify a drag proxy image, that is what is shown on the screen when dragging the component. If not specified a rectangle will be shown instead.

In the dropping stage when the component being dragged arrives to a potential drop target, a nativeDragEnter event is raised. The event handler that handles this event should check whether the Clipboard object contains data that is of an acceptable format.  If that’s the case then NativeDragManager.acceptDragDrop() is called indicating that the drop target is happy in accepting the dragged data.

If the user actually releases the mouse while hovering over a drop target and that target has accepted the drag object by invoking NativeDragManager.acceptDragDrop(), the nativeDragDrop event is dispatched. The event handler registered with this event then does what it is supposed to do, like copying a file from the desktop to a destination directory, or play an mp3 file.

Below is the source code for our Air File Manager with drag and drop support:

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" width="1200" height="400">
<mx:Script>
<![CDATA[
import mx.events.DragEvent;
import mx.events.CloseEvent;
import mx.core.DragSource;
import mx.controls.Alert;
import mx.managers.DragManager;
import mx.core.UIComponent;
import mx.core.IUIComponent;
import mx.managers.CursorManager;
import flash.desktop.NativeDragManager;

/**
* This function handles the case where the user drags in an icon from the desktop
*
*/
private function handleNativeDragEnter(event:NativeDragEvent):void {

NativeDragManager.dropAction = NativeDragActions.COPY;
if(event.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)){
NativeDragManager.acceptDragDrop(InteractiveObject(event.currentTarget)); //'this' is the receiving component
}

}

/**
* We signal the start of the drag
*
*/
private function handleMouseDown(event:MouseEvent):void {
var fs:FileSystemDataGrid =FileSystemDataGrid(event.currentTarget);
if (fs.selectedItem != null) {
var transferObject:Clipboard = createClipboard(fs.selectedItem as File);
NativeDragManager.doDrag(InteractiveObject(event.target),
transferObject,
null,
new Point(0,0));

}
}

public function createClipboard( sourceFile:File):Clipboard {
var transfer:Clipboard = new Clipboard();
transfer.setData(ClipboardFormats.FILE_LIST_FORMAT,
new Array(sourceFile),
false);
// Standard file list format
return transfer;
}

/**
* In this case we copy the file to the folder specified by the fs
*
*/
private function handleNativeDragDrop(event:NativeDragEvent):void {
var files:Array = event.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT, ClipboardTransferMode.ORIGINAL_PREFERRED) as Array;
var fs:FileSystemDataGrid = event.currentTarget as FileSystemDataGrid;

/**
* copies each file to the file system
*/
for each (var file:File in files) {

var fileDest:File = fs.directory.resolvePath(file.name)
file.addEventListener(Event.COMPLETE, refreshFileSystems);
// if the user has pressed the shift key then he just
// wants to copy the file
if (event.shiftKey)
{
this.copyFile(file, fileDest);

}
else {
this.moveFile(file, fileDest);
}

}

}

/**
* Refreshes the file system data grid after a copy/move operation
*
*/
private function refreshFileSystems(event:Event):void
{
fs1.refresh();
fs2.refresh();

}

/**
* Moves the file to a new location
*
*/
private function moveFile(fileOrig:File, fileDest:File):void {
if (!fileDest.exists) {
fileOrig.moveToAsync(fileDest);
}
else {
mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this,
function(event:CloseEvent):void {
// overwrite file
if (event.detail== Alert.YES) {
fileOrig.moveToAsync(fileDest, true);
}
else {
fs1.refresh();
fs2.refresh();
}
});
}

}

/**
* Copies the file to a new location
*
*/
private function copyFile(fileOrig:File, fileDest:File):void {
if (!fileDest.exists) {
fileOrig.copyToAsync(fileDest);
}
else {
mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this,
function(event:CloseEvent):void {
// overwrite file
if (event.detail== Alert.YES) {
fileOrig.copyToAsync(fileDest, true);
}
else {
fs1.refresh();
fs2.refresh();
}
});
}

}

/**
* Checks that there is at least one item selected and asks the user whether
* he really wants to delete the file.
*
*/
private function handleDelete(fs:FileSystemDataGrid):void {
mx.controls.Alert.show("Do you really want to delete this file? Please be aware that the delete actually deletes... not part of the demo.","Delete confirmation",Alert.YES|Alert.CANCEL,
this, function(event:CloseEvent):void {
deleteConfirmation(event, fs );
});
}

/**
* If the user really wants to delete the file we do what they ask
*
*/
private function deleteConfirmation(event:CloseEvent, fs:FileSystemDataGrid):void {
if (event.detail==Alert.YES) {
if (fs.selectedItem != null) {
var file:File= fs.selectedItem as File;
if (file.isDirectory) {
file.deleteDirectory(false);
}
else {
file.deleteFile();

}
fs.refresh();
}
}
}

]]>
</mx:Script>

<mx:Panel layout="vertical" height="100%" title="Copy From">
<mx:ControlBar >
<mx:FileSystemHistoryButton label="Back"  click="fs1.navigateBack()" enabled="{fs1.canNavigateBack}" itemClick="fs1.navigateBack(event.index)" dataProvider="{fs1.backHistory}"/>
<mx:FileSystemHistoryButton label="Forward" enabled="{fs1.canNavigateForward}" dataProvider="{fs1.forwardHistory}" itemClick="fs1.navigateForward(event.index)" dragStart="mx.controls.Alert.show('Drag Start')"/>
<mx:Button label="Up" enabled="{fs1.canNavigateUp}" click="{fs1.navigateUp()}"/>
<mx:Button label="Delete" enabled="{fs1.selectedItem!= null}" click="handleDelete(fs1)"/>

</mx:ControlBar>
<mx:FileSystemDataGrid id="fs1"  nativeDragEnter="handleNativeDragEnter(event)" mouseDown="handleMouseDown(event)" directory="{File.documentsDirectory}" nativeDragDrop="handleNativeDragDrop(event)">

</mx:FileSystemDataGrid>
</mx:Panel>
<mx:Panel layout="vertical" height="100%" title="Copy To">
<mx:ControlBar >
<mx:FileSystemHistoryButton label="Back" click="fs2.navigateBack()" enabled="{fs2.canNavigateBack}" itemClick="fs2.navigateBack(event.index)" dataProvider="{fs2.backHistory}"/>
<mx:FileSystemHistoryButton label="Forward" enabled="{fs2.canNavigateForward}" dataProvider="{fs2.forwardHistory}" itemClick="fs2.navigateForward(event.index)"/>
<mx:Button label="Up" enabled="{fs2.canNavigateUp}" click="{fs2.navigateUp()}"/>
<mx:Button label="Delete" enabled="{fs2.selectedItem!= null}"/>
</mx:ControlBar>
<mx:FileSystemDataGrid id="fs2" nativeDragEnter="handleNativeDragEnter(event)"  mouseDown="handleMouseDown(event)" nativeDragDrop="handleNativeDragDrop(event)"  directory="{File.applicationStorageDirectory}">

</mx:FileSystemDataGrid>
</mx:Panel>
</mx:WindowedApplication>




Click here to download the file manager with native drag and drop support