Handling native drag and drop in a Flex application

Published Date
19 - May - 2011
| Last Updated
19 - May - 2011
 
Handling native drag and drop in a Flex application

In this tutorial we will explore dragging and dropping files from the desktop to an application running on Adobe AIR.

We will make a very basic application for viewing images that are dropped onto its interface. Here is a basic idea of the user interface of the application:

We have created an application window that has half of the interface dedicated to the list of images, and the other half of the application for displaying the image. When one drops one or more files onto the interface of the application, the application adds them to the list on the left. Clicking on the image on the left will display the image in the right area.

For this basic UI here is the basic MXML code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
                                xmlns:mx="library://ns.adobe.com/flex/mx">
                <s:List id="fileList" left="0" width="200" top="0" bottom="0" />
                <s:Image id="image" scaleMode="letterbox"  left="205" right="0" top="0" bottom="0" />
</s:WindowedApplication>

 As you can see, the application has just two components, a list, and an image; nothing more is required really. The list component, with an id of fileList, has been given a fixed width of 200, and is set with its left, top and bottom edges at zero. So it takes up a fixed horizontal space, but still scales with the application.

The image component starts at 205 pixels, a 5 pixel gap from the image list. This way it too will scale with the application, but leaving space for the list. We have set the scaleMode to “letterbox” since that will ensure that the image being displayed is scaled to fit in the space allotted to the image component.

Next let us look at how the dragging and dropping code works. The code is as follows:

private var fileArray:ArrayCollection;
protected function init(e:FlexEvent):void
{
                addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, dragEnter);
                addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, dragDrop);
                fileArray = new ArrayCollection();
                fileList.dataProvider = fileArray;
}
 
private function dragEnter(e:NativeDragEvent):void
{
                if(e.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT))
                {
                                DragManager.acceptDragDrop(this);
                }             
}
 
private function dragDrop(e:NativeDragEvent):void
{
                var fileList:Array = e.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
                for each (var file:File in fileList)
                {
                                var type:String = file.type.toLowerCase();
                                trace(type);
                                if(type == ".jpg" || type == ".jpeg" || type == ".png" ||  type == ".gif" )
                                {
                                                fileArray.addItem(file);
                                }
                }
}

In the initialization function, which is run at “creationComplete”, we attach event handlers to native drag events. We need to handle dragEnter and dragDrop. We also initialize an ArrayCollection that will serve as the dataProvider for the list.

In the dragEnter event handler we check if the content that is being dragged onto the application is even a file list to begin with. If it is, we handle the drag-drop, else we simply ignore it, and the application does not display an icon depicting that it will accept the drag-drop.

Finally, in the dragDrop handler, we retrieve the list of files that were dragged and dropped onto the interface as an Array using the clipboard object attached to the drag event:

var fileList:Array = e.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;

We iterate over this list of files, and check to see if each image is a JPEG, GIF, or PNG image, which are the file formats that can be displayed natively. If a file really is an image, we add it to the fileArray, thus indirectly injecting it into the list in the interface.

If you want, you can perform a test to check if there are any images in the list of files that have been dragged, and refuse to accept them if there aren’t any images in the list. However to keep it simple we just check if any kind of files are dragged, since we will have to check them once after dropping anyway.

If you test the application at this point, you will get a partially working application that will accept dragged files, and add them to a list. However the list at this point is displaying garbage. We need to specify what to display on the list for each file added, and we do this by setting its labelField to "name". The dataProvider of the list is made up of File objects, and the “name” property of the File object is the name of the file it points to. Thus the application will now display the list of file names when they are drag-dropped onto the application.

Finally, we need to ensure that when someone clicks on an image in the list, the image should be displayed in the image area. For this we add:

protected function fileList_changeHandler(event:IndexChangeEvent):void
{
                image.source = (fileList.selectedItem as File).nativePath;
}

This function when set as a handler for the change event of the file list, will change the source of the Image component to display the image pointed to by the selected file.

Here is the final, complete code for the application:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx"
   creationComplete="init(event)"
   >
		<fx:Script>
			<![CDATA[
				import mx.collections.ArrayCollection;
				import mx.events.FlexEvent;
				import mx.managers.DragManager;
			   
				import spark.events.IndexChangeEvent;
			   
				private var fileArray:ArrayCollection;
			   
				protected function init(e:FlexEvent):void
				{
					addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, dragEnter);
					addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, dragDrop);
					fileArray = new ArrayCollection();
					fileList.dataProvider = fileArray;
				}
			   
				private function dragEnter(e:NativeDragEvent):void
				{
					if(e.clipboard.hasFormat(ClipboardFormats.FILE_LIST_FORMAT))
					{
						DragManager.acceptDragDrop(this);
					}             
				}
			   
				private function dragDrop(e:NativeDragEvent):void
				{
					var fileList:Array = e.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array;
					for each (var file:File in fileList)
					{
						var type:String = file.type.toLowerCase();
						trace(type);
						if(type == ".jpg" || type == ".jpeg" || type == ".png" ||  type == ".gif" )
						{
							fileArray.addItem(file);
						}
					}
				}
			   
				protected function fileList_changeHandler(event:IndexChangeEvent):void
				{
					image.source = (fileList.selectedItem as File).nativePath;
				}
						   
			]]>
	</fx:Script>
	<s:List id="fileList" left="0" width="200" top="0" bottom="0" labelField="name" change="fileList_changeHandler(event)"/>
	<s:Image id="image" scaleMode="letterbox"  left="205" right="0" top="0" bottom="0" />
</s:WindowedApplication>

Run it and you will find a fully working application that adds images dragged onto it to a list, and lets one browse this list afterwards.

<