by Joe Ward

Joe Ward

Created

21 March 2011

Requirements
Prerequisite knowledge

General experience of building applications
with Flex Builder or the Flex SDK is
suggested. For more details on getting
started with this Quick Start, refer to
Building the Quick Start sample
applications with Flex
.
Required products

Sample files

User level

Intermediate
 
The Scrappy example application shows the following features of the drag-and-drop and copy-and-paste APIs in Adobe AIR:
 
  • Preparing data and objects to be transferred through drag-and-drop or copy-and-paste operations.
  • Starting a drag operation.
  • Receiving dropped information or objects.
  • Rendering the standard data formats using Adobe AIR and Flash objects.
  • Writing to the system clipboard.
  • Reading from the system clipboard.
  • Using the standard keyboard shortcuts for copy and paste.
Scrappy sample application
Figure 1. This Scrappy sample application demonstrates drag, copy, and paste functionality in Adobe AIR.
 
Note: This is a sample application provided, as is, for instructional purposes.
 
This sample application includes the following files and classes:
 
  • Scrappy-app.xml: The AIR application descriptor file.
  • Scrappy.as: The main application file in ActionScript.
  • Scrap.as: The base class for displaying the dragged or pasted information.
  • TextScrap.as: Extends Scrap to display text.
  • BitmapScrap.as: Extends Scrap to display images.
  • HTMLScrap.as: Extends Scrap to display HTML and XML documents.
  • FileScrap.as: Extends Scrap to load and display files.
  • about.html: Describes this sample application and provides some items to drag into the main window.
  • AIRAliases.js: Defines shorter JavaScript alias names for the AIR and Flash classes. Used by about.html.
  • FlexLoader.html: Used to load non-application SWF files without triggering security exceptions.
  • file.tiff: An example file to drag. Used by about.html.
  • Sample AIR icon files

 
Testing the application

To test the application download and run the installer file (Scrappy.air). To use Scrappy, drag files, bitmaps, URLs, and text to the window. The application accepts dragged items in any of the standard formats supported by AIR. You can also paste (Ctrl+V or Insert) items into the window, as well as cut (Ctrl+X), copy (Ctrl+C), replace (Ctrl+V or Insert), and delete (Del) existing items.
 
Note: Not all applications post bitmap data to the clipboard in a format supported by AIR.
 

 
Understanding the code

The Scrappy example allows you to drop and paste most of the information formats supported by AIR. When data is dragged or pasted into the window, Scrappy checks the format and if recognized, creates an object to display the data. This article does not describe all of the ActionScript classes used in the application. For more information, see the ActionScript 3 Reference for the Adobe Flash Platform.
 
Note: This article demonstrates the use of the flash.desktop.NativeDragManager class in an ActionScript project. If you are building a Flex application, you should first determine whether the flash.desktop.NativeDragManager or the Flex mx.managers.DragManager class best suits your needs.
 
 
Creating the drag target
Scrappy uses a full-window sprite that acts as both the window backdrop and the target for drag-and-drop operations. To allow a Sprite, or other interactive object to act as a receiver for a drag gesture, you must listen for the NativeDragEvents dispatched from that object.
 
The Scrappy class uses the following code to create the window, the drop target sprite and the event listeners:
 
public var dragTarget:Sprite = new Sprite(); public var about:NativeWindow; public function Scrappy():void{ super(); addEventListener(Event.ADDED_TO_STAGE, onStaged); dragTarget.focusRect = false; addChild(dragTarget); } private function onStaged(event:Event):void{ stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; stage.stageFocusRect = false; drawBackground(); addMenu(); //Drag-and-drop listeners dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER,onDragIn); dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_OVER,onDragOver); dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_DROP,onDrop); dragTarget.addEventListener(NativeDragEvent.NATIVE_DRAG_EXIT,onDragExit); //UI listeners stage.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown); stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown); stage.nativeWindow.addEventListener(NativeWindowBoundsEvent.RESIZE, onResize); }
The important events for the drag target are:
 
  • nativeDragEnter, which tells you when a drag gesture enters within the display object area
  • nativeDragExit, which tells you when a drag gesture leaves the display object area
  • nativeDragDrop, which tells you that the drop has occurred on the display object
You can also listen for the nativeDragOver event instead of the nativeDragEnter event if you need to track the position of the mouse over the target. The target dispatches nativeDragOver events whenever the mouse moves (as well as on a short interval). Scrappy uses the nativeDragOver event to constantly update the drop action to override the default action.
 
The keyDown event on the stage is used to check for the paste command. The event listener is attached to the stage because a sprite only receives keyboard events when it has focus.
 
 
Dropping data onto the target
To allow a user to drop data onto a target, you typically:
 
  1. Check the format of the data.
  2. Accept the drag gesture with NativeDragManager.acceptDrop().
  3. Handle the nativeDragDrop event.
Checking the dragged data
 
To check the data being dragged into the target, access the clipboard property of the NativeDragEvent object. The Scrappy class defines the following handler for the nativeDragEnter event:
 
private function onDragIn(event:NativeDragEvent):void{ var transferable:Clipboard = event.clipboard; if(transferable.hasFormat(ClipboardFormats.BITMAP_FORMAT) || transferable.hasFormat(ClipboardFormats.FILE_LIST_FORMAT) || transferable.hasFormat(ClipboardFormats.TEXT_FORMAT) || transferable.hasFormat(ClipboardFormats.URL_FORMAT) || transferable.hasFormat(Scrap.SCRAP_FORMAT)){ NativeDragManager.dropAction = NativeDragActions.MOVE; NativeDragManager.acceptDragDrop(dragTarget); } else {trace("Unrecognized format");} }
The Scrappy application can accept each of the four standard formats. It also defines a custom format, specified with the string constant: Scrap.SCRAP_FORMAT, which contains a reference to a Scrap object created within the Scrappy application itself.
 
Accepting the gesture
 
The call to NativeDragManager.acceptDrop() allows the passed in interactive object to receive the drop. The NativeDragManager changes the display cursor to indicate that the drop is possible. If the user releases the mouse button while over the same interactive object (and before a nativeDragExit event occurs), then the designated display object dispatches a nativeDragDrop event.
 
Setting a drop action
 
If you do not set a drop action, a default action is determined by the actions allowed by the drag initiator object and any modifier keys pressed by the user. For example, if the drag initiator allows all three possible actions, copy, move, and link, then copy is the default action. The user can modify the action by pressing the Ctrl, Command, or Shift keys.
 
If you set a drop action before the drop event, the specified action is used instead of the default and the user cannot choose a different action with keyboard modifiers. Because a user is more likely to expect to move an object when dragging within the window rather than to copy it, Scrappy sets a drop action of move when objects are dragged within the application. When objects are dragged into Scrappy from the operating system or from another application, the default behavior is respected.
 
Taking the drop
 
After the drop occurs on an eligible interactive object, you can access the data through the clipboard property of the nativeDragDrop event object.
Scrappy passes the Clipboard object from the nativeDragDrop event to the Scrap class factory method, which creates new Scrap objects based on the data format, and adds the returned Scrap object to the stage.
 
private function onDrop(event:NativeDragEvent):void{ var scrap:Scrap = Scrap.createScrap(event.clipboard, event.stageX, event.stageY, event.dropAction); dragTarget.addChild(scrap); }
 
Displaying information with the Scrap class
The classes in the scrap package act as containers for information dropped or pasted into the Scrappy window. The base Scrap class extends Sprite to add functions for handling user interaction with scrap objects, including dragging out of the window, deleting, and copying. The Scrap class also implements a static factory method for creating new scrap objects.
 
Scrap subclasses
 
The Scrap subclasses extend the base Scrap class to handle the standard formats of data that can be transferred. To render the data, these classes add child display objects to the scrap, such as a TextField or an HTMLLoader. They also add data to the Clipboard object in the correct format when a scrap is dragged out of the application. The Scrap subclasses are:
 
  • TextScrap: Uses a TextField to display text data.
  • BitmapScrap: Uses a Bitmap object to display bitmap data.
  • HTMLScrap: Uses an HTMLLoader to load and display URLs (including file URLs).
  • FileScrap: Loads and displays the file contents, or displays the filename and icon. For the handful of file types Scrappy understands, the FileScrap constructor loads the data and creates one of other types of Scrap object to render it.
Scrap Factory
 
The scrap factory class, Scrap.createScrap(), takes a Clipboard object, checks the data formats available, and then creates the appropriate type of scrap object to display the data.
 
public static function createScrap(data:Clipboard, placeX:int, placeY:int, action:String):Scrap{ var scrap:Scrap; trace(data.formats.length + " formats found: " + " " + data.formats); if(data.hasFormat(Scrap.SCRAP_FORMAT)){ scrap = data.getData(Scrap.SCRAP_FORMAT, ClipboardTransferMode.ORIGINAL_ONLY) as Scrap; if((action == NativeDragActions.COPY) || (scrap == null)){ var clipping:Clipboard = new Clipboard(); for each (var format:String in data.formats){ if(format != Scrap.SCRAP_FORMAT){ clipping.setData(format, data.getData(format), false); } } scrap = Scrap.createScrap(clipping, placeX, placeY, NativeDragActions.COPY); } } else if(data.hasFormat(ClipboardFormats.BITMAP_FORMAT)){ scrap = new BitmapScrap(BitmapData(data.getData(ClipboardFormats.BITMAP_FORMAT))); } else if(data.hasFormat(ClipboardFormats.FILE_LIST_FORMAT)){ var dropfiles:Array = data.getData(ClipboardFormats.FILE_LIST_FORMAT) as Array; for each (var file:File in dropfiles){ scrap = new FileScrap(file); } } else if(data.hasFormat(ClipboardFormats.TEXT_FORMAT)){ scrap = new TextScrap(String(data.getData(ClipboardFormats.TEXT_FORMAT))); } else if(data.hasFormat(ClipboardFormats.URL_FORMAT)){ scrap = new HTMLScrap(String(data.getData(ClipboardFormats.URL_FORMAT))); } scrap.x = placeX + offset.x + highlightOffset; scrap.y = placeY + offset.y + highlightOffset; return scrap; }
If the Clipboard object contains the format, specified by the Scrap.SCRAP_FORMAT string constant, then the drag must have originated from the Scrappy application itself (or from another AIR application that uses the SCRAP format name). Scrappy attempts to retrieve the Scrap object from the clipboard. If the Scrap object on the clipboard is null (it may have been deleted between copy and paste operations, for example) or the drop action is copy, then Scrappy makes a copy of the object instead. A copy is made by creating a Clipboard object and copying all the data except the SCRAP format to the new object. The createScrap() method then calls itself with the new Clipboard to create a copy of the scrap using the original data.
 
 
Dragging data
To allow a user to drag data or an object originating within an AIR application, you must:
 
  1. Respond to a mouseDown or mouseMove event.
  2. Create a Clipboard object containing the data or object to drag.
  3. Create a drag proxy image, if desired.
  4. Start the drag operation from the mouseDown or mouseMove event handler by calling NativeDragManager.doDrag()
Note: To drag Sprite objects within an application, you can use the startDrag() method of the Sprite class instead of the NativeDragManager. The Sprite-style drag does not allow you to drag the sprite to other applications, but is better for changing the position of objects within a window.
 
Starting the drag
 
The methods and event handlers for dragging objects in the Scrappy application are defined in the Scrap class. When the Scrap object detects a mouseDown event, it calls the following event handler:
 
private function onMouseDown(event:MouseEvent):void{ var transferObject:Clipboard = addTransferableData(); var allowedActions:NativeDragOptions = new NativeDragOptions(); allowedActions.allowLink = false; var proxyImage:BitmapData = getImage(); Scrap.offset.x = -mouseX; Scrap.offset.y = -mouseY; NativeDragManager.doDrag(this, transferObject, getImage(), offset, allowedActions); grabFocus(); event.stopPropagation(); }
This event handler does the following:
 
  • Creates a Clipboard object
  • Creates a NativeDragOptions object to specify the allowed actions
  • Creates a BitmapData object for the drag proxy image
  • Sets the coordinates of a Point object to prevent the proxy image from appearing to jump when the drag begins
  • Starts the drag
  • Manages the focus within the window
Creating the Clipboard object
 
The mouseDown event handler prepares for the drag by calling the Scrap function, addTransferableData(). The addTransferableData() function creates a Clipboard object and adds a reference to the current Scrap object by using a custom format name:
 
protected function addTransferableData():Clipboard{ var transfer:Clipboard = new Clipboard(); transfer.setData("SCRAP",this,true); return transfer; }
The addTransferableData() function is overridden in each of the Scrap subclasses to add the data in the format appropriate to that subclass in addition to the reference format. The BitmapScrap class, for example, overrides the addTransferableData() function with the following definition:
 
protected override function addTransferableData():Clipboard{ var transfer:Clipboard = super.addTransferableData(); transfer.setData(ClipboardFormats.BITMAP_FORMAT,picture.bitmapData,true); return transfer; }
Specify the allowed actions
 
After creating the Clipboard object, the handler creates a NativeDragOptions object to define the actions allowed for the clipboard data. By default, all actions are allowed by a newly constructed NativeDragOptions object. Because the link action has no meaning to Scrappy, the allowLink property is set to false. The properties controlling the copy and move actions are not changed.
 
Create the drag proxy image
 
Scrappy takes a snapshot of the object being dragged by drawing the object to a BitmapData object. The Scrap class defines a getImage() function for this purpose:
 
public function getImage():BitmapData{ var image:BitmapData; try { image = new BitmapData(width + 2 * highlightOffset, height + 2 * highlightOffset, true, 0x00ffffff); image.draw(this, new Matrix(1,0,0,1,highlightOffset,highlightOffset)); } catch (e:ArgumentError){ image = null; } return image; }
The function creates a BitmapData object slightly larger than the current size of the Scrap object (to allow for the size of the highlight rectangle). The function sets the transparent argument of the BitmapData constructor to true and sets the fillColor parameter with the ARGB-format color value: 0x00ffffff, which has the alpha byte set to zero, to make the background of the drag proxy image transparent.
 
By default, the origin of the drag proxy image is positioned at the hotspot of the cursor. This can make the image appear to jump when the drag starts, especially when using an image resembling the object being dragged. To compensate, you can supply offset coordinates to reposition the image relative to the cursor. The MouseEvent object provides the coordinates of the mouse position relative to the origin of the object in the mouseX and mouseY properties. Offsetting the drag proxy image with these coordinates aligns the dragged object and the proxy image so that no jump appears:
 
Scrap.offset.x = -mouseX; Scrap.offset.y = -mouseY;
Unfortunately, the offset value is not reported when the object is dropped. This can lead to a jump in the opposite direction when the object is dropped. The Scrap class saves the offset values in a static Point object and uses the values to adjust the x and y coordinates of the dropped object.
 
Starting the drag operation
 
The call to doDrag(), starts the NativeDragManager-controlled part of the drag gesture:
 
NativeDragManager.doDrag(dragInitiator:InteractiveObject, clipboard:Clipboard, dragImage:BitmapData = null, offset:Point = null, allowedActions:NativeDragOptions = null)
The dragInitiator parameter of the method designates the interactive object that dispatches the nativeDragStart , nativeDragUpdate , and nativeDragComplete events. The Scrap object sets the dragInitiator to itself (this), but any interactive object can be used. In many cases, it makes sense to have a container component serve as the drag initiator, rather than the dragged items themselves.
 
The clipboard parameter is the Clipboard object containing the information to be transferred. Once doDrag() is called, the object can only be accessed in the event handlers of the NativeDragEvents.
 
Focus management
 
Managing the focus is not part of the drag operation. However, it is necessary to allow you to select objects to copy, cut, paste, and delete.
 
To manage the focus, the handler sets the stage focus on the object with the grabFocus() function and then stops event propagation to prevent the event from bubbling up to the parent display container. (The parent display container responds to mouseDown events by clearing the focus, so event propagation must be stopped here, or the object could never be focused.)
 
Completing the drop
 
When the drop occurs, the object passed to NativeDragManager.doDrag() as the initiator dispatches a nativeDragComplete event. The action selected by the user or drop target from the allowed actions set by the initiator is reported in the dropAction property of the event.
 
Copying and pasting data
 
Compared to drag and drop, copy and paste are simple operations. The static property Clipboard.generalClipboard represents the operating system clipboard. You can write data directly to the clipboard with Clipboard.generalClipboard.setData() and you can read data with Clipboard.generalClipboard.getData().
 
The copy-and-paste commands are invoked when the relevant command is selected on the edit menu. Key equivalents are defined on the menu commands, so the standard keyboard shortcuts can also be used.
 
Paste
 
Scrappy implements a paste command on the main window. In response to a paste command, the application passes the clipboard data to the factory method Scrap.createScrap(), which is also used to create scraps for drop operations.
 
public function doPaste():void{ var scrap:Scrap = Scrap.createScrap(Clipboard.generalClipboard, stage.stageWidth/4, stage.stageHeight/4); addChild(scrap); }
The pasted scrap is always placed in the same spot on the stage. A similar command, doReplace() is defined by the Scrap base class. This method deletes the selected object in addition to pasting the clipboard contents.
 
Copy
 
Copy is implemented by the Scrap base class. The function calls the addTransferable() function to get the data from the Scrap object (this is the same function used for drag-and-drop). You cannot assign a Clipboard object directly to the Clipboard.generalClipboard property, so you must copy each of the formats individually:
 
public function doCopy():void{ var transferObject:Clipboard = addTransferableData(); for each( var format:String in Clipboard.generalClipboard.formats){ Clipboard.generalClipboard.setData(format, transferObject.getData(format),true); } }
Cut
 
Cut just copies the scrap object to the clipboard, then deletes it.