UPDATE: I have another Drag-and-Drop Revisited post that covers even more drag-and-drop functionality available in Flex 4.
The Flex 4 gods were kind to us developers when they made the great decision to leave the custom drag-and-drop support unchanged. We just do what we’ve always done: detect the user is trying to drag something via mouseDown
or mouseMove
and then add both dragEnter
and dragDrop
event handlers to the drop target. So there is nothing in this post that’s not basically identical to Flex 3, except the coolness of FXG (which you can easily mimic with Degrafa in Flex 3).
Simple Drag-and-Drop
I’ll begin with a basic Flex 4 application. We have two draggable graphics (a Rect
and an Ellipse
) in the left panel, and a target panel on the right:
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"> <fx:Script> <![CDATA[ imports... private function mouseDownHandler(e:MouseEvent):void { DragManager.doDrag(e.currentTarget as IUIComponent, null, e); } private function dragEnterHandler(e:DragEvent):void { DragManager.acceptDragDrop(e.currentTarget as IUIComponent); } private function dragDropHandler(e:DragEvent):void { e.currentTarget.addElement(e.dragInitiator); } ]]> </fx:Script> <s:Panel title="src" width="100" minHeight="133" x="10" y="10"> <s:Graphic width="80" height="80" mouseDown="mouseDownHandler(event)"> <s:Rect ... </s:Rect> </s:Graphic> <s:Graphic width="80" height="80" mouseDown="mouseDownHandler(event)"> <s:Ellipse ... </s:Ellipse> </s:Graphic> ... </s:Panel> <s:Panel title="target" width="100" minHeight="133" x="120" y="10" dragEnter="dragEnterHandler(event);" dragDrop="dragDropHandler(event);"> ... </s:Panel> </s:Application>
Reading the code above, each draggable Graphic
has a mouseDown
handler that calls DragManager.doDrag()
to initiate dragging. And the target Panel
calls DragManager.acceptDragDrop()
on dragEnter
and addElement()
on dragDrop
. Note that since Panel
is a Spark container we must use addElement()
to re-parent the dropped graphic (the familiar addChild()
is still used for Halo containers).
Our simple drag-and-drop app (view source enabled):
Using a FXG Drag Proxy
In the example above, the moment you start dragging a component the drag proxy is displayed. By default, the proxy is just a bounding rectangle with an alpha value of 0.5. This is particularly noticeable and lame when you try to drag the pink circle. Thankfully, we can use any display object for the drag proxy (more specifically any component that implements IFlexDisplayObject
which includes UIComponent
and any of its descendants). Why not use a FXG graphic? It is, after all, a major piece of the new hotness that is Flex 4.
First, we need to modify the mouseDownHandler
to instantiate our FXG graphic. Then we just add it as the forth parameter to the DragManager.doDrag()
call.
private function mouseDownHandler(e:MouseEvent):void { var star:Star = new Star(); DragManager.doDrag(e.currentTarget as IUIComponent, null, e, star); }
And here is Star.mxml
:
<?xml version="1.0" encoding="utf-8"?> <s:Graphic xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" width="80" height="80"> <s:Path data="M 119,0 L 148,86 238,86 166,140 192,226 119,175 46,226 72,140 0,86 90,86 Z" y="2" scaleX="0.3361345" scaleY="0.3361345"> <s:fill> <mx:SolidColor color="#FFCC00" /> </s:fill> </s:Path> </s:Graphic>
Bang! We have a gold star as our drag proxy. Try it out for yourself (view source enabled):
Dynamic Drag Proxy
Dragging a gold star is pretty cool, but how can we make the drag proxy look exactly like the drag source? For that we need a dynamic drag proxy.
First, for reasons which will become clear in a moment, we need to revise our app to make each of the drag source graphics into a separate component. So the left Panel
changes to contain these custom components:
<s:Panel title="src" width="100" minHeight="133" x="10" y="10"> <graphics:Square fillColor="#6666FF" mouseDown="mouseDownHandler(event)" /> <graphics:Circle fillColor="#FF66FF" mouseDown="mouseDownHandler(event)" /> <graphics:Star fillColor="#FFCC00" mouseDown="mouseDownHandler(event)" /> <graphics:Square fillColor="#66FF99" mouseDown="mouseDownHandler(event)" /> <s:layout> <s:VerticalLayout gap="10" horizontalAlign="center" paddingTop="10" paddingBottom="10" /> </s:layout> </s:Panel>
Next, we again modify the mouseDownHandler
, this time we instantiate our drag proxy dynamically. We use reflection on the incoming drag source to get its name, instantiate it, adjust a few properties, and pass it to the DragManager.doDrag()
call.
private function mouseDownHandler(e:MouseEvent):void { var dragSrc:Graphic = e.currentTarget as Graphic; //create a proxy by creating a new "copy" of the drag src var className:String = getQualifiedClassName(dragSrc); var klass:Class = getDefinitionByName(className) as Class; var proxy:* = new klass(); //set the proxy's properties to match the src + sexy drop shadow proxy.width = dragSrc.width; proxy.height = dragSrc.height; proxy.fillColor = (dragSrc as IDraggableGraphic).fillColor; proxy.filters = [new DropShadowFilter()]; DragManager.doDrag(dragSrc, null, e, proxy); }
The real reason for the reflection gymnastics, plus the need to package each drag source as a custom component, is the lack of a deep copy operation on UIComponent
. If we could just clone the drag source, we’d be golden. Alas, for many reasons, the most obvious of which is that it is a huge pain in the ass, there is no such thing as deep copy. In our case, my work around was to package all the visual stuff of each draggable item into a single custom component with a known API (all the custom graphics implement IDraggableGraphic
). Then I reflect, instantiate, configure, and pass to doDrag()
.
Our dynamic drag proxy now matches the drag source, plus the sexy drop shadow. Check it out (view source enabled):
Files
NOTE: All code was built with Flash Builder 4 Beta 1.