Creating an Image Viewer in AIR With Drag and Drop

WARNING: This was written for a BETA version of AIR and may not be valid for the most recent release

Of the more popular kinds of recreational, or even professional "applications" you see out there today, either desktop based or web based, you will find image viewers or image galleries at the top of the list. These can be XML-based, or reading from a directory of images on a local or remote drive. Some even show or manage files that have been dragged and dropped into the viewer itself. Apple Macintosh's OS X's Preview application, for example, lets you drag and drop and number of files into the Preview application window. Those files will show up in a drawer to the left which can then be selected to be viewed.

This tutorial will make an application somewhat to Preview in that respect. Here, you will see how an AIR application can be created to accept image files using drag and drop and then individually display those images within the application window.

Requirements:

Note: Though this tutorial uses Flash CS3, the same ActionScript could be applied to an AIR project in Flex Builder.

Source files:

The Basics of Drag and Drop

The process of dragging and dropping content into an AIR application may not be as simple as you think. The process includes some flexibility which requires attention. Namely, dragging and dropping content is not application based. It's target based. In other words, you don't necessarily drag and drop files into the AIR application, you drag and drop files onto a target display object within that application.

With dragging and dropping, you also have essentially 2 phases of events that ultimately lead into the content being dropped onto a target. First, a target must accept the drag indicating to the application (and the operating system) that the dragged content will find a home on the current target if dropped. Usually this will be visually indicated by a change in the cursor. Then, after the target is accepting of the dragged content, only then can the target receive the content when its dropped.

In AIR display objects will dispatch different events when dragged content is dragged over or dropped onto them. These events are defined in the NativeDragEvent class (flash.events.NativeDragEvent) and include:

For accepting basic drag and drop content, all you really need are NATIVE_DRAG_ENTER and NATIVE_DRAG_DROP. The NATIVE_DRAG_ENTER event indicates when dragged content enters a display object. In this event it would be up to the display object to determine whether or not it is a valid drop target. If so, and the content is dropped, the NATIVE_DRAG_DROP event will fire.

For a display object to indicate that it is a valid drop target, you would use the static acceptDragDrop() method of the DragManager class (flash.desktop.DragManager). This is called passing in the display object as an argument. As long as the dragged content remains over that display object, it can be dropped and NATIVE_DRAG_DROP event will be called for that object. Once the mouse leaves the display object (NATIVE_DRAG_EXIT), the content is automatically rejected for that object and would need to be accepted again in the next NATIVE_DRAG_ENTER event.

In the NATIVE_DRAG_DROP event, if called for a display object, the dragged content can be accessed from the NativeDragEvent event object using the transferable property. This property provides a TransferableData (flash.desktop.TransferableData) instance containing the data being dropped onto the display object.

Examining the Drag and Drop Image Viewer Application

The example for this tutorial consists of an image viewer capable of viewing multiple images in succession when those images are dragged from a users folder onto an empty area within the application.

  1. Open dragAndDropImageViewer.fla from the source files

You will notice a large box filling the stage. This box will represent the drop target for image files being dragged into the application. There are also some arrow buttons which will allow you to navigate between the different images added in that manner. A text field is also used to display which image is currently being viewed.

  1. Select the first frame of the actions layer
  2. If not already open, open the Actions panel using F9 or Window > Actions to show the ActionScript code for this application

The code is broken down into 4 parts. At the top you have imports and some variable definitions. This is followed by a section of code that handles the drag and drop actions. Next, there are actions relating to the loading of image files that were dropped into the Flash Player. Finally, at the end of the script are the necessary commands for navigating through the images using the arrow buttons.

Variables and Setup

Here's how this application will work: a drop target called dropBox will sit on the stage awaiting for 1 or more system files to be dragged on to it. When that happens, the files are stored into an array and a position value is set to reference the beginning of that array (the first file). That file will then be loaded into a loader instance and displayed on the screen. If there are more than one file in the file(s) dragged onto the dropBox instance, the arrow buttons can be clicked to change the position value to point to either the next or previous picture in the array. Then, the file at that location in the array will be loaded into the loader and displayed on the screen.

The setup for this includes the definitions for the image array, the position for that array and the loader to load the images.

var imageList:Array = [];
var imageListPosition:int = 0;
var imageLoader:Loader = new Loader();

The imageLoader instance will need to be added to the display list so that the images to be seen. The images being loaded into the application however, are going to be scaled to fill the screen. Because they are filling the screen, they are going to be obscuring the dropBox instance. Additional images would then not be able to be added to the application. To get around this, the imageLoader instance can be made a child of dropBox so that the loaded images themselves are part of it and can have additional images dropped onto them to be loaded into the application.

dropBox.addChild(imageLoader);

Drag and Drop

To obtain dropped content dragged into an AIR application, dragged content has to first be accepted as valid content for a particular display object, and then acquired from the resulting drop event when that content is dropped on that object. In this example, the dropBox instance is the desired display object target for a drag and drop. The NATIVE_DRAG_ENTER and NATIVE_DRAG_DROP events will be used to accept and handle the dragged and dropped content.

dropBox.addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, acceptDrag);
dropBox.addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, dragDrop);

When dragged content enters the dropBox instance, the NATIVE_DRAG_ENTER event will fire calling the acceptDrag() method. Here, to allow NATIVE_DRAG_DROP to occur if that content is dropped onto dropBox, the static acceptDragDrop() method of the DragManager class must be used. In the case of this application, we will want to make sure that only computer files (potentially image files) are dropped, so within the NATIVE_DRAG_ENTER, the dragged content (represented in Flash as a TransferableData instance) will be checked for the value of TransferableFormats.FILE_LIST_FORMAT. If that kind of data is within the TransferableData instance (the NativeDragEvent's transferable property), the data will be accepted using the dropBox instance - the currentTarget of the event - as the target.

function acceptDrag(event:NativeDragEvent):void {
	if (event.transferable.hasFormat(TransferableFormats.FILE_LIST_FORMAT)) {
		DragManager.acceptDragDrop(DisplayObject(event.currentTarget));
	}
}

Once accepted, the files can be dropped and retrieved by the application. This is handled within the dragDrop() method. If files were dropped onto the dropBox instance without the data being accepted through acceptDragDrop(), the NATIVE_DRAG_DROP event would never occur for dragBox and dragDrop() would never be called.

In dragDrop the dropped data, coming in as an array of File (flash.filesystem.File) instances, is taken from the transferable property of the NativeDragEvent event object and assigned to the imageList array to contain the images to be viewed in the viewer. In addition, imageListPosition, is reset to 0, and the first image is loaded and displayed on the screen. The text display is also updated.

function dragDrop(event:NativeDragEvent):void {
	imageList = event.transferable.dataForFormat(TransferableFormats.FILE_LIST_FORMAT) as Array;
	imageListPosition = 0;
	displayCurrentImage();
	updateCurrentImageTextDisplay();
}

Just like that, the dragged and dropped data is in Flash. All that remains is doing something with that data.

Loading and Displaying the Images

Just as with files viewed from the internet, a Loader instance is used to load and display files loaded into the AIR Flash Player from the file system. The start of this process is started in the displayCurrentImage() method.

function displayCurrentImage():void {
	var imageFile:File = File(imageList[imageListPosition]);
	imageLoader.load(new URLRequest(imageFile.url));
}

This method uses imageListPosition to get a File instance from imageList and load its reference (via the url property) into imageLoader. Event listeners are used to handle the completion and errors associated with this process.

imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, currentImageLoaded);
imageLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, currentImageLoadError);

The currentImageLoadError() handler, for this example, is doing nothing. Ideally proper attention would be made to react appropriately.

function currentImageLoadError(event:Event):void {
}

More important is the currentImageLoaded() method signalling that the file was loaded completely, successfully. This method's purpose is to fit the image centered in the application window.

function currentImageLoaded(event:Event):void {
	imageLoader.scaleX = 1;
	imageLoader.scaleY = 1;
	var imageRatio:Number = imageLoader.height/imageLoader.width;
	var stageRatio:Number = stage.stageHeight/stage.stageWidth;
	var scale:Number;
	if (imageRatio > stageRatio) {
		scale = stage.stageHeight/imageLoader.height;
	}else{
		scale = stage.stageWidth/imageLoader.width;
	}
	imageLoader.scaleX = scale;
	imageLoader.scaleY = scale;
	
	imageLoader.x = (stage.stageWidth - imageLoader.width)/2;
	imageLoader.y = (stage.stageHeight - imageLoader.height)/2;
	
	if (imageLoader.content is Bitmap) {
		Bitmap(imageLoader.content).smoothing = true;
	}
}

By checking the ratio of sizing between the image and the stage, the appropriate scale can be determined to scale the loaded image to fit exactly within the application window. After resized, the images are placed in the center of the screen. In addition to sizing and positioning the loader content is also, if a bitmap, smoothed to ensure the best quality is used - something especially helpful when an image is not being viewed at 100% of its original size.

Because there are no checks to validate the fact that a dropped file is an image (or not), non-image files will still be "loaded" in the application. They just won't be visible. A way that prevent against this could include removing any files within the imageList array that contain invalid extensions.

Navigation

Once image files have been dragged and dropped onto the application, the arrow buttons can be clicked to cycle through them.

These buttons simply increase or decrease the value of imageListPosition and redisplay the image loading the file at that position. Special attention is taken to make sure the position does not escape the bounds of the imageList array.

prevButton.addEventListener(MouseEvent.CLICK, previousImage);
nextButton.addEventListener(MouseEvent.CLICK, nextImage);

function previousImage(event:MouseEvent):void {
	if (imageList.length > 1) {
		imageListPosition--;
		if (imageListPosition < 0) {
			imageListPosition = imageList.length - 1;
		}
		displayCurrentImage();
		updateCurrentImageTextDisplay();
	}
}

function nextImage(event:MouseEvent):void {
	if (imageList.length > 1) {
		imageListPosition++;
		if (imageListPosition >= imageList.length) {
			imageListPosition = 0;
		}
		displayCurrentImage();
		updateCurrentImageTextDisplay();
	}
}

Conclusion

Supporting drag and drop with your AIR applications can greatly increase their ease of use. We see this behavior so much in applications today, it's almost expected by users. And if you are working with files that are not associated with your application, drag and drop support provides an easy way to open files in your application without opening the default viewer or editor and without having to go through any extra menus or dialogs.

But also don't forget that even though this application used drag and drop to handle file data, drag and drop does not stop there. You can also drag and drop, for instance, selected text (depending on the application). Not only that, but using the TransferableData class, you can drag and drop all kinds of data, even data specific to your application, data that may only be usable when dragged and dropped between other AIR applications. The flexibility is there for you to use it however you see fit.