Drag-and-drop is one of the many features that set Flex applications apart from other types of applications. As you’ll see in the next few sections, it is extremely simple to enable drag-and-drop functionality for some standard components, and with a little additional work you can enable drag-and-drop functionality to any type of component.
The simplest way to implement drag-and-drop functionality is to use the built-in features of many of
the components, including List
,
Tree
, DataGrid
, Menu
, HorizontalList
, PrintDataGrid
, and TileList
. Each of these components enables
drag-and-drop in the same way. They each have a dragEnabled
property and a dropEnabled
property. The two properties are false
by default. When you set the dragEnabled
property to true
for a component, the user can click and
drag items. Of course, in most cases enabling a component so that a user
can click and drag an item is not very useful until the user can also
drop the item somewhere in the application. Typically, this is
accomplished by setting the dropEnabled
property of another component to
true
. When the dropEnabled
property is set to true
for a component, the user can drop an
item on the component that he dragged from another component. This
causes the data from that item to be added to the drop target component.
Example 10-7 illustrates both a
dragEnabled
and a dropEnabled
component working in conjunction.
The first data grid contains data about the user’s music collection. The
user can drag items from the music collection to the second data grid to
create a playlist.
Example 10-7. A simple drag-and-drop application
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:HBox width="100%"> <mx:VBox height="100%"> <mx:Label text="My Music"/> <mx:DataGrid dragEnabled="true"> <mx:columns> <mx:DataGridColumn headerText="Song Title" dataField="title"/> <mx:DataGridColumn headerText="Artist" dataField="artist"/> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Object songId="0" title="Astronaut" artist="David Byrne" /> <mx:Object songId="1" title="Rio" artist="Duran Duran" /> <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche Mode" /> <mx:Object songId="3" title="Mesopotamia" artist="B-52s" /> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> <mx:VBox height="100%"> <mx:Label text="Playlist"/> <mx:DataGrid dropEnabled="true"> <mx:columns> <mx:DataGridColumn headerText="Song Title" dataField="title"/> <mx:DataGridColumn headerText="Artist" dataField="artist"/> </mx:columns> </mx:DataGrid> </mx:VBox> </mx:HBox> </mx:Application>
When you test this you’ll see two data grids side by side. You can click and drag an element in the My Music data grid, and you’ll see that it creates a copy of that element that moves with the mouse. A small red circle with a white X appears next to the mouse cursor until the mouse is over a drop-enabled component, at which point the red circle becomes a green circle with a white +. If you drop the item anywhere in the application that is not drop-enabled, no further action occurs. However, if you drop the item on a drop-enabled component, the item is added to the drop target at the location where you dropped the object.
The default behavior for drag-enabled components is to allow the user to copy
elements from the component. However, you can also allow the user to
move elements rather than copy them by setting the dragMoveEnabled
property to true
. By itself, the dragMoveEnabled
property will not have any effect. You must also ensure that
dragEnabled
is set to true
for the component. Example 10-8 uses dragMoveEnabled
to create a simple application
that allows the user to move an email message from his inbox to the
trash.
Example 10-8. A drag-and-drop example that moves items
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:HBox width="100%"> <mx:VBox height="100%"> <mx:Label text="Inbox"/> <mx:DataGrid dragEnabled="true" dragMoveEnabled="true"> <mx:columns> <mx:DataGridColumn headerText="From" dataField="from"/> <mx:DataGridColumn headerText="To" dataField="to"/> <mx:DataGridColumn headerText="Subject" dataField="subject"/> <mx:DataGridColumn headerText="Date" dataField="date"/> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Object emailId="0" from="[email protected]" to="[email protected]" subject="Important New Message" date="10/1/2010" /> <mx:Object emailId="1" from="[email protected]" to="[email protected]" subject="All Items On Sale" date="10/1/2010" /> <mx:Object emailId="2" from="[email protected]" to="[email protected]" subject="Amazing New Stock" date="10/1/2010" /> <mx:Object emailId="3" from="[email protected]" to="[email protected]" subject="Blatant Chain Letter" date="10/1/2010" /> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> <mx:VBox height="100%"> <mx:Label text="Trash"/> <mx:DataGrid dropEnabled="true"> <mx:columns> <mx:DataGridColumn headerText="From" dataField="from"/> <mx:DataGridColumn headerText="To" dataField="to"/> <mx:DataGridColumn headerText="Subject" dataField="subject"/> <mx:DataGridColumn headerText="Date" dataField="date"/> </mx:columns> </mx:DataGrid> </mx:VBox> </mx:HBox> </mx:Application>
In this example, you can move items only one way: from the inbox
to the trash. In a real email application, you’d probably want to allow
users to move messages back to the inbox from the trash if they wanted.
In such a case, you can simply set dragEnabled
and dropEnabled
to true
for both components.
When you set dragMoveEnabled
to
true
for a component, it makes moving
the default behavior. However, if the user holds down the Ctrl key while
dragging and dropping, the item will be copied rather than
moved. In some cases, that is acceptable behavior. In other cases, you
want to ensure that the items always are moved rather than copied. You
cannot make such a change via MXML. As we’ll see in the next section,
you can use ActionScript and event listeners to handle that
behavior.
When using drag-and-drop functionality, the framework utilizes a handful of events behind the scenes. Understanding those events can be very helpful when you want to modify the default behavior or write completely customized drag-and-drop elements.
Table 10-1 lists all the events, the targets of the events, and what those events mean.
Table 10-1. Drag-and-drop events
Event | Target | Description |
---|---|---|
| Drag initiator | This is usually the event that triggers the start of a drag-and-drop operation. |
| Drag initiator | In some cases, the
drag-and-drop operation does not occur until a |
| Drop target | The mouse has entered the drop target while still dragging an object. |
| Drop target | The mouse is moving
within the drop target. This is analogous to a |
| Drop target | The mouse has moved outside the drop target while still dragging the object. |
| Drop target | The user has dropped an object on the target. |
| Drag initiator | This occurs anytime the
user drops the object, whether over a drop target or not. You
can use the |
The mouseDown
and mouseMove
events are standard Flash Player
events of type flash.events.MouseEvent
. The rest of the events are part of the Flex framework, and
they are of type mx.events.DragEvent
. DragEvent
objects have
a dragInitiator
property, which
references the component that originated the drag-and-drop behavior.
They also have a dragSource
property
which is of type mx.core.DragSource
,
and it contains the data that was copied from the
initiator.
DragSource
objects have a
dataForFormat()
method, which requires a string parameter and returns the data
that was stored for the format specified by the string parameter. We’ll
look at DragSource
formats further in
the next section. For now, all you need to know is that list-based drag
initiators always create DragSource
objects with one format called items, and the data
returned for that format is an array of all the elements from the data
provider of the drag initiator corresponding to the selected items. For
example, if the user drags one element from a data grid, the DragSource
object will contain an array with
just one element.
DragSource
objects also have an
action
property, which reports a value of copy
, move
,
or none
. You can use the mx.managers.DragManager COPY
, MOVE
, and NONE
constants for comparison tests. The
action
property tells you what the
expected action is for the drag-and-drop operation. This value is
automatically set when using the built-in drag-and-drop behavior of
list-based components.
In the preceding section, you saw how there’s no simple MXML-based way to ensure that the user cannot copy elements rather than move them. Using events and ActionScript you can put such safeguards into place, as illustrated in Example 10-9.
Example 10-9. Using ActionScript for drag-and-drop behavior
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import mx.controls.DataGrid; import mx.events.DragEvent; import mx.managers.DragManager; private function dragCompleteHandler(event:DragEvent):void { if(event.action != DragManager.NONE) { var grid:DataGrid = event.dragInitiator as DataGrid; var data:ArrayCollection = grid.dataProvider as ArrayCollection; var item:Object = event.dragSource.dataForFormat("items")[0]; data.removeItemAt(data.getItemIndex(item)); } } ]]> </mx:Script> <mx:HBox width="100%"> <mx:VBox height="100%"> <mx:Label text="Inbox"/> <mx:DataGrid dropEnabled="true" dragEnabled="true" dragComplete="dragCompleteHandler(event)"> <mx:columns> <mx:DataGridColumn headerText="From" dataField="from"/> <mx:DataGridColumn headerText="To" dataField="to"/> <mx:DataGridColumn headerText="Subject" dataField="subject"/> <mx:DataGridColumn headerText="Date" dataField="date"/> </mx:columns> <mx:dataProvider> <mx:ArrayCollection> <mx:Object emailId="0" from="[email protected]" to="[email protected]" subject="Important New Message" date="10/1/2010" /> <mx:Object emailId="1" from="[email protected]" to="[email protected]" subject="All Items On Sale" date="10/1/2010" /> <mx:Object emailId="2" from="[email protected]" to="[email protected]" subject="Amazing New Stock" date="10/1/2010" /> <mx:Object emailId="3" from="[email protected]" to="[email protected]" subject="Blatant Chain Letter" date="10/1/2010" /> </mx:ArrayCollection> </mx:dataProvider> </mx:DataGrid> </mx:VBox> <mx:VBox height="100%"> <mx:Label text="Trash"/> <mx:DataGrid dropEnabled="true" dragEnabled="true" dragComplete= "dragCompleteHandler(event)"> <mx:columns> <mx:DataGridColumn headerText="From" dataField="from"/> <mx:DataGridColumn headerText="To" dataField="to"/> <mx:DataGridColumn headerText="Subject" dataField="subject"/> <mx:DataGridColumn headerText="Date" dataField="date"/> </mx:columns> </mx:DataGrid> </mx:VBox> </mx:HBox> </mx:Application>
You’ll notice that in this example, both data grids have dropEnabled
and dragEnabled
set to true
. This in and of itself allows the user to
copy contents from one data grid to the other. However, as we saw in
Example 10-8, this does
not ensure the type of behavior we require in this case. To achieve the
move-only behavior each data grid also listens for dragComplete
events. The dragCompleteHandler()
method handles the
events by either dismissing the event if the event action is set to
none
, or deleting the element from
the drag initiator’s data provider.
The built-in drag-and-drop functionality will work for many use cases. However, there are also many use cases in which you will want to employ drag-and-drop functionality not supported by the standard, built-in features of the handful of drag-and-drop-enabled components. For these cases, you can create custom drag-and-drop elements.
You can create custom drag-and-drop elements using the events
discussed in the preceding section in conjunction with mx.managers.DragManager
. The DragManager
class
has several static methods you can use to handle drag-and-drop
functionality.
The doDrag()
method allows you
to start a drag-and-drop operation. The doDrag()
method requires that you specify the
following parameters: the drag initiator, a DragSource
object specifying the data to copy
from the initiator, and the mouse event used to start the drag
operation. In addition, in most cases you’ll need to pass it a reference
to an object to use as the drag proxy image (the object that actually
drags).
Before we can look at an example using doDrag(),
we first have to discuss the details
of working with DragSource
.
The DragSource
object
you pass to doDrag()
is what is
passed along to event handlers for drag events. This object contains
data that you can use when copying, moving, or comparing data. That
means you should generally store whatever data you want to pass along to
the drag event handlers in the DragSource
object. DragSource
objects allow you to save many
groups of data, each with a unique key (a string value), which is called
a format. You can use the addData()
method to add data to a DragSource
object. The first parameter is the
data to store, and the second parameter is the format, which is an
arbitrary string:
var dragSource:DragSource = new DragSource(); dragSource.addData(initiator.dataProvider.getItemAt(index), "item");
The DragManager
class also
dictates the behavior of the drag proxy image when the user moves it
over the drop target and when the user drops the object. Normally, the
proxy indicates that it cannot be dropped successfully by displaying a
small red circle with a white X. You can remove
that icon by calling DragManager.acceptDragDrop()
and passing it a
reference to the drop target for which the user can drop the object.
Typically, you call this method in response to a dragEnter
event.
Example 10-10 illustrates how to create custom drag-and-drop elements. This simple application uses a column of colored canvases and a grid of canvases with the same colors. The canvases from the column are draggable. When the user drops one of the canvases over the canvas with the same color in the grid, the canvas is removed from the column and the canvas in the grid is lowered in opacity.
Example 10-10. A customized drag-and-drop application
<?xml version="1.0"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.core.DragSource; import mx.containers.Canvas; import mx.events.DragEvent; import mx.managers.DragManager; private function beginDragAndDrop(event:MouseEvent):void { var canvas:Canvas = event.currentTarget as Canvas; var dragSource:DragSource = new DragSource(); var color:uint = canvas.getStyle("backgroundColor"); // Add the color value to the drag source using the backgroundColor key dragSource.addData(color, "backgroundColor"); // Create the (temporary) canvas that will be dragged var proxy:Canvas = new Canvas(); proxy.width = 50; proxy.height = 50; proxy.setStyle("backgroundColor", color); DragManager.doDrag(canvas, dragSource, event, proxy); } private function dragEnterHandler(event:DragEvent):void { var target:Canvas = event.currentTarget as Canvas; var initiator:Canvas = event.dragInitiator as Canvas; // If the target and the initiator have the same color // then accept the drag drop operation if(matches(target, initiator)) { DragManager.acceptDragDrop(target); } } private function dragDropHandler(event:DragEvent):void { var target:Canvas = event.currentTarget as Canvas; var initiator:Canvas = event.dragInitiator as Canvas; // If the target and initiator have the same color then // remove the initiator and lower the alpha of the target if(matches(target, initiator)) { vbox.removeChild(initiator); target.alpha = .25; } } private function matches(a:Canvas, b:Canvas):Boolean { return a.getStyle("backgroundColor") == b.getStyle("backgroundColor"); } ]]> </mx:Script> <mx:HBox width="100%"> <mx:VBox id="vbox" height="100%"> <mx:Canvas width="50" height="50" backgroundColor="#00ff80" mouseDown="beginDragAndDrop(event)" /> <mx:Canvas width="50" height="50" backgroundColor="#ff8040" mouseDown="beginDragAndDrop(event)" /> <mx:Canvas width="50" height="50" backgroundColor="#80ffff" mouseDown="beginDragAndDrop(event)" /> <mx:Canvas width="50" height="50" backgroundColor="#ffff80" mouseDown="beginDragAndDrop(event)" /> </mx:VBox> <mx:VRule height="213"/> <mx:Grid> <mx:GridRow width="100%" height="100%"> <mx:GridItem width="100%" height="100%"> <mx:Canvas width="50" height="50" backgroundColor="#00ff80" dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:Canvas width="50" height="50" backgroundColor="#ff8040" dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" /> </mx:GridItem> </mx:GridRow> <mx:GridRow width="100%" height="100%"> <mx:GridItem width="100%" height="100%"> <mx:Canvas width="50" height="50" backgroundColor="#80ffff" dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" /> </mx:GridItem> <mx:GridItem width="100%" height="100%"> <mx:Canvas width="50" height="50" backgroundColor="#ffff80" dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" /> </mx:GridItem> </mx:GridRow> </mx:Grid> </mx:HBox> </mx:Application>