Implementing Drag and Drop Between the OS and AIR
- Drag and Drop in an Application (DragManager Class)
- Drag and Drop Between the OS and the Application (NativeDragManager)
- Drag and Drop Items into an AIR Application from the OS
- Drag Items into the dpTimeTracker Application
- Drag Items from an AIR Application to the OS
- Drag Items into the OS
- Next Steps
The previous chapter explored the rich integration that AIR has with the Operating System (OS) clipboard. In this chapter, you will further explore integration possibilities using drag and drop, a technique familiar to most desktop application users. This technique, combined with others you will learn throughout this book, allows you to create AIR applications that have the look and feel of more traditional desktop applications.
The following sections explain the DragManager and NativeDragManager classes, and include several examples of using each. You will also explore the drag-and-drop operations used within the dpTimeTracker to gain an understanding of how these pieces can be used together in a larger application.
To use drag and drop successfully, you will need familiarity with two main classes, namely DragManager and NativeDragManager. These classes are responsible for handling all elements dragged within an application. Both are responsible for initiating, accepting, and presenting feedback for drag-and-drop operations, application wide. You will learn about the DragManager first to give you an understanding of basic drag-and-drop operations within an application. Then you will explore the NativeDragManager to learn about dragging items in and out of the application and interacting with the OS.
Drag and Drop in an Application (DragManager Class)
The DragManager is native to the Flex API and handles all the internal drag-and-drop actions in an application, but it has no effect outside of the application window. All Flex components have some level of support for drag-and-drop operations. It is up to the developer to handle the specific user actions (such as mouse down, drag enter, and so on) and properly use the DragManager to handle them. The drag-and-drop process has a few important terms and events worth noting. Keep in mind that the NativeDragManager also uses similar events and terms with slight variances, which we will discuss in the next section.
- Drag Initiator—The interactive object that begins the drag action and also dispatches the dragStart and dragComplete events.
- Drag Proxy—The visual representation of the item being dragged that follows your cursor. Usually it is depicted as a faded silhouette of the object, but it can be customized by the user as well. DragManager can assign any InteractiveObject to be the proxy.
- Drop Target—A visual object where a dragged item can be dropped. The drop target makes the final decision on whether the type of object being dragged can be dropped at this location.
- dragEnter—The event dispatched when an item is dragged over a possible drop target. As you will see, this event is often used with the DragManager.acceptDrop() method to grant an object permission to be dropped.
- dragOver—The event dispatched repeatedly as the item is dragged over an interactive object.
- dragExit—The event dispatched when the dragged item leaves an interactive object.
- dragDrop—The event dispatched when the mouse is released over an eligible drop target. The event handler will be able to access the dropped data by using the event.dragSource object.
- dragComplete—The event dispatched from the drag initiator when the drop is completed. This event allows you to gather feedback on the success of the drop as well as clean up data in the drag initiator.
This first sample shows dragging from a simple list control to another. When initiating a drag between two drag-enabled components (such as List, Tree, and DataGrid, to name a few), it is a simple process to configure the controls. Listing 1 shows the MXML tags for the List control; take note of the dragEnabled, dropEnabled, and dragMoveEnabled attributes.
Listing 1.
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" > <mx:List id="list1" height="50%" dragEnabled="true" dropEnabled="true" dragMoveEnabled="true"> <mx:dataProvider> <mx:String>AIR</mx:String> <mx:String>Flex</mx:String> <mx:String>Flash</mx:String> <mx:String>PhotoShop</mx:String> <mx:String>Fireworks</mx:String> </mx:dataProvider> </mx:List> <mx:List id="list2" height="50%" dragEnabled="true" dropEnabled="true" dragMoveEnabled="true"> <mx:dataProvider> <mx:String>LiveCycle ES</mx:String> <mx:String>Blaze DS</mx:String> <mx:String>ColdFusion</mx:String> </mx:dataProvider> </mx:List> </mx:WindowedApplication>
With dragEnabled, dropEnabled, and dragMoveEnabled attributes set to true, the Lists will allow users to move items from one List control to the other by clicking it and dragging it. If you want the user to copy from one to the other, instead of moving, change the dragMoveEnabled attribute from true to false, or remove the attribute entirely (false is the default value). The List will automatically update the drop target’s (the destination List’s) data provider. Using this technique, you do not have control over the appearance of the item as it is being dragged, or any fine-grained control over the operation. However, using this approach you can very quickly enable basic drag-and-drop operations in an application.
Listing 2 shows dragging from a List to any other type of UI control, in this case, a Label. Much like the List control from the previous example, the dragEnabled and dragMoveEnabled properties remain true on the List. However, the dropEnabled property is no longer needed and has been removed (so it will have the default value of false).
Listing 2.
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.managers.DragManager; import mx.events.DragEvent; protected function handleDragEnter( event : DragEvent ) : void{ DragManager.acceptDragDrop( event.target as Label ); } protected function handleDrop ( event : DragEvent ) : void{ var dropItem:Object = event.dragSource.dataForFormat( "items" ); dropLbl.text = dropItem.toString(); } ]]> </mx:Script> <mx:Label id="dropLbl" text="Drop on Me" dragDrop="handleDrop( event )" dragEnter="handleDragEnter( event )"/> <mx:List id="list1" height="50%" dragEnabled="true" dragMoveEnabled="true"> <mx:dataProvider> <mx:String>AIR</mx:String> <mx:String>Flex</mx:String> <mx:String>Flash</mx:String> <mx:String>PhotoShop</mx:String> <mx:String>Fireworks</mx:String> </mx:dataProvider> </mx:List> </mx:WindowedApplication>
Unlike the List from Listing 1, the Label class does not have a dropEnabled property (only a few classes, such as List, DataGrid, and Tree do), so for it to accept the drop, the user must create the event handlers to deal with this action. Notice that a dragEnter event handler has been added to the Label. This event fires when the user drags a component over the Label. When this happens, the handleDragEnter() function will execute.
In the handleDragEnter() function, the DragManager is instructed to allow the drop on this target. To do this, handleDragEnter() calls DragManager.acceptDragDrop() and passes the element on which the drop will be allowed (in this case the Label). In more complex examples, a conditional statement would determine if the item being dragged is the proper type and if it is allowed by the current target. In this simple example, all dragged elements are allowed to be dropped.
When dropping an element on the target, the target’s dragDrop event fires. In this example, the handleDrop() function is registered to handle the dragDrop event. In the handleDrop() function you will change the text displayed in dropLbl, which is the name of your Label control in Listing 2. The text property will be set using the data from the drag event.
Internally, DragManager keeps all the data being dragged in an instance of the DragSource class. This DragSource instance contains one or more copies of the data in different formats. For example, the same data could be dragged as text, as an image, or perhaps as HTML. When the dragged item is dropped, the instance of the DragSource class containing this data is available via the dragSource property of the DragEvent event.
The data inside dragSource is retrieved by using the dataForFormat() function of the dragSource, which accepts a string as an argument. When data is dragged from a dragenabled List control, it is available in a format named “items.” In this case, the data provider contains only a collection of strings, so it can easily be set as the text property of the Label.
In this last example for this section, you will learn to use the DragManager to copy a simple “person” object, created from data in a Label, into a List control without using any of the native drag-and-drop behaviors of the List.
Listing 3.
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import mx.core.IUIComponent; import mx.core.DragSource; import mx.managers.DragManager; import mx.events.DragEvent; private function handleDragBegin( event : MouseEvent ):void { var person : Object = new Object(); person.nameAge = Label(event.currentTarget).text; person.comment = "We are dragging " + person.nameAge; var dSource : DragSource = new DragSource(); dSource.addData( person, "people" ); var label:Label = new Label(); label.text = person.comment; DragManager.doDrag( event.currentTarget as Label, dSource, event, label, 0 , 0, .5); } private function handleDragEnter( event:DragEvent ):void { if ( event.dragSource.hasFormat("people") ) { DragManager.acceptDragDrop( IUIComponent( event.currentTarget ) ); DragManager.showFeedback( DragManager.COPY ); } } private function handleDrop( event : DragEvent ):void { if(!list1.dataProvider){ list1.dataProvider = new ArrayCollection(); } list1.dataProvider.addItem( event.dragSource.dataForFormat( "people") ); } ]]> </mx:Script> <mx:HBox> <mx:Label text="Frank, 26" mouseDown="handleDragBegin( event )"/> <mx:Label text="Mike, 33" mouseDown="handleDragBegin( event )"/> <mx:Label text="Jeff, 37" mouseDown="handleDragBegin( event )"/> </mx:HBox> <mx:List id="list1" height="50%" labelField="nameAge" dragEnabled="false" dropEnabled="false" dragDrop="handleDrop( event )" dragEnter="handleDragEnter( event )"/> </mx:WindowedApplication>
The handleDragBegin() function is registered to handle the mouseDown event on any of the three labels. When a mouseDown event occurs on one of these labels, the dragging process begins. Look at the following handleDragBegin() function from Listing 3.
private function handleDragBegin( event : MouseEvent ):void { var person : Object = new Object(); person.nameAge = Label(event.currentTarget).text; person.comment = "We are dragging " + person.nameAge; var dSource : DragSource = new DragSource(); dSource.addData( person, "people" ); var label:Label = new Label(); label.text = person.comment; DragManager.doDrag( event.currentTarget as Label, dSource, event, label, 0 , 0, .5); }
In the handleDragBegin() function, several different things need to take place to prepare the drag. First, an object is created and the nameAge property is set to the text of the label that is being dragged. Next, the comment property is set to the string “We are dragging” followed by whatever the text was in the selected label, so it will read “We are dragging Mike, 33,” when the middle label is dragged. Next a DragSource instance called dSource is created. As the previous example explained, this is where the data related to the drag operation will be stored.
var dSource : DragSource = new DragSource(); dSource.addData( person, "people" );
The addData() function on dSource adds the person object. The addData() function requires an argument of type Object and a format of type String. In this case, the person object will be the data and the format will be the string “people”.
The next piece of this function involves making a drag proxy image that will represent the drag when you’re moving your cursor. This step is optional.
var label:Label = new Label(); label.text = person.comment;
If you decide to create a proxy, you’ll need a visual component to represent the dragged data. This example uses a label, with the text “you are dragging” followed by whatever text the dragged label contained.
Now you’re ready to start the drag, is done by using the static function DragManager.doDrag().
DragManager.doDrag( event.currentTarget as Label, dSource, event, label,0 ,0,.5);
Notice that the doDrag() function requires several arguments. The first argument is the dragInitiator—the item that is being dragged; this can usually be obtained by using the event.currentTarget. The second argument is the DragSource; this can simply be set to the dSource instance discussed earlier. The third argument is the mouseEvent that started the drag; this would simply be the event parameter passed into this function.
protected function handleDragBegin(event : MouseEvent):void
The next four arguments are for the dragImage, xOffset, yOffset, and imageAlpha, and all are optional. These are all related to the proxy that is shown when the label is being dragged around. In this example, the dragImage is the newly created label “you are dragging.” The xOffset and yOffset determine where to place the image in relation to your mouse cursor as you are dragging. Lastly, the imageAlpha defines the opacity of the proxy image, with 1 indicating that it should be fully opaque and 0 indicating that it should be completely transparent (to the point of invisibility). The value of .5 offers a half–faded-out label as the proxy.
Figure 1 By using a drag proxy, the elements shown during a drag are customized.
That covers the events to begin the drag operations. Next you will explore the events on the drop target, dragEnter and dragDrop. Look at the drop target section of Listing 3 that follows.
<mx:List id="list1" height="50%" labelField="nameAge" dragEnabled="false" dropEnabled="false" dragDrop="handleDrop( event )" dragEnter="handleDragEnter( event )"/>
Notice that the dragEnabled and dropEnabled properties are disabled and that event handlers for dragEnter and dragDrop have been added. The handleDragEnter() is the listener function for the dragEnter event. This function determines if the item being dragged is allowed to be dropped onto this List. It does so by checking the format (type) of data in the dragSource. If it matches an acceptable format, it will accept the drop.
Listing 4.
private function handleDragEnter(event:DragEvent):void { if ( event.dragSource.hasFormat("people") ) { DragManager.acceptDragDrop( IUIComponent( event.currentTarget ) ); DragManager.showFeedback( Dragmanager.COPY ); } }
In this case, the conditional statement is looking for data with the format “people”. If the dragSource has a format (type) of “people”, then DragManager.acceptDrop() is called and passes the currentTarget (the List) as the drop target. The List will now accept the drop should you release the mouse.
The next statement marks the first of a few divergences between Flex applications running in the web browser and in AIR. When running from a web browser, the DragManager.showFeedback( feedback : String) changes the mouse cursor. This technique is often used along with keyboard shortcuts (such as the Ctrl or Shift key) to indicate if the data is to be copied or moved. Flex offers four built-in cursors related to drag operations to show that the item will be moved, copied, linked, or not allowed to be dropped. In this example, passing DragManager.COPY to the showFeedback() method changes the mouse pointer to a green plus sign, giving you a visual confirmation that the drop is allowed. The four possible cursors are DragManager.COPY, DragManager.MOVE, DragManager.LINK, and DragManager.NONE. The following figures shows the possible cursors.
Figure 2 The DragManager.COPY feedback
Figure 3 The DragManager.MOVE feedback
Figure 4 The DragManager.LINK feedback
Figure 5 The DragManager.NONE feedback, indicating you are not over an acceptable drop target
Unfortunately, at this time, these feedback options do not work by default in AIR, only in the web browser, due to a difference in the DragManager implementation used in these two environments. This information has been included as it is likely this limitation will disappear in future AIR releases.
The final step to complete the drag-and-drop operation is to handle the drop event using the handleDrop listener function. Listing 4 adds the person object to the dragSource using the “people” format. This object will be copied into the list’s dataProvider when the drop operation completes successfully. Using the event.dragSource.dataForFormat("people"), you can retrieve the person object that was created when the mouse was first clicked and moved above the Label. The following function is from Listing 3.
private function handleDrop ( event : DragEvent ) : void{ if(!list1.dataProvider){ list1.dataProvider = new ArrayCollection(); } list1.dataProvider.addItem( event.dragSource.dataForFormat("people") ); }
This function checks if list1’s dataProvider exists. If it does not, the function creates a new ArrayCollection and provides it to the dataProvider property of the list. Lastly, it adds the data from the dragged label to the list’s dataProvider.
The drag operation is currently making a copy of the data, meaning that you could perform this operation multiple times and have duplicate data in the list. If you are interested in moving the item rather than copying it, you could use the dragComplete event to remove the drag initiator from its parent. The following code sample shows you how to go about removing the dragged label using the dragComplete handler.
private function dragCompleteHandler(event:DragEvent):void { var draggedLabel:Label = event.dragInitiator as Label; var dragParent: Object = draggedLabel.parent; if (event.action == DragManager.MOVE) dragParent.removeChild( draggedLabel ); }
You now have an understanding of how to drag simple objects using the DragManager, using both drag-enabling properties and custom drag functions.