Saturnboy
 8.5

Drag-and-Drop in Flex 4

, ,

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):

Flash is required. Get it here!
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):

Flash is required. Get it here!
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):

Flash is required. Get it here!
Files

NOTE: All code was built with Flash Builder 4 Beta 1.




Comments

9.2.2009

1

Saturnboy, well done. Clearest, most concise treatment of d&d I’ve come across. Looking forward to more articles…

Michel

11.10.2009

2

Nice. Looking forward to more.

I’m using Beta 2 and the DragDropProxy Square and Circle didn’t showed-up at first. Seem that the 100% for width and height were the cause. Set them to 80 and it worked. I’ll see if there is another way to correct that.

By the way. Custom Drag And Drop support isn’t unchanged. We are missing some methods like dragExit() ans dragOver(). We can declare them in object but they are not fired. I have some Flex 3 components that used dragExit to set their visual state back to normal.

Michel

11.10.2009

3

And also.

When adding a element to a container it removes it from it’s origin container. So, we can’t copy an element by dragging it to an other container.

11.10.2009

4

@Michel:

Thanks for reading, but I must be missing something, because when I compare the “Manually adding drag-and-drop support” pages (Flex 4 vs. Flex 3) they look very similar to me, including the dragExit and dragOver events.

Also, both Flex 3 and Flex 4 do not support copy. The Flex 3 docs say here, “The list-based controls can automate all of the drag-and-drop operation except for when you copy the drag data to the drop target…Flex leaves it to you to implement object copying…”

Lastly, Flex 4 isn’t fully baked yet, so who really knows what works and what does not.

Michel

11.11.2009

5

My mistake. Reading back on your posts and answer to my comment I realize that my component wasn’t pointing the dragExit event to the right function in my test application.

Now… Upon validation of the dragSource the drop target state goes from normal to dropYes, and dropNo if it doesn’t validate.

I’ll check your others posts to see if I can manage that.

Thanks.

11.11.2009

6

Glad to help, and thanks for the feedback.

Santiago

11.25.2009

7

Excellent. Now I’m starting to build an application, and I want to use Panels to contain mostly grids. I want those Panels to be “movable” (using drag & drop) as any program window in Windows (or Mac or any graphical OS).

Can you tell me how to achieve this?

11.26.2009

8

@Santiago:

Well, following my example above, the Panel are the drag source elements, so they would listen for mouseDown. You would need to have some parent container to act as the drag target that listens for dragEnter and dragDrop. Lastly, I’d recommend a custom layout applied to the parent container to handle the positioning of the dropped Panels.

Good luck.

Steve

3.17.2010

9

Saturnboy have you stumbled upon any other ways of copying fxg graphics besides creating class proxy?

3.19.2010

10

@Steve: If I’m understanding you, the answer is no. The lack of deep copy really hurts in this case. You have to instantiate something to be the drag proxy.

6.19.2010

11

Saturnboy, well done. Clearest, most concise treatment of d&d I’ve come across. Looking forward to more articles…

thank you very very ………………very much

12.1.2010

12

I’m trying to use a Flex 4 list as a component to drag stuff from, but only for the visual effect (I have some code that handles the business logic of a drop). However, the list item dragged disappears after a drop, even though I’m not doing anything with it. Any insight into why this might happen?

Thanks for any thoughts, Justin. As always, thanks for the great posts.

vasu

4.18.2011

13

is it possible to add easing effects?

4.18.2011

14

@vasu: The short answer is you can use the events mouseDown, dragEnter, dragDrop to do whatever you want.

The long answer is it’s not gonna be easy. Depending on how tightly you want to control the UI, you might need to create a completely custom d&d impl.

Steve

6.9.2011

15

Any easy way to detect if the whole image is over the drop container instead of the mouse cursor?

For instance, if I click on the middle of the square, once my mouse cursor is over the target, Flex accepts it even though the entire image of the square isn’t over the target.

6.9.2011

16

@Steve: You are off in custom world…but it is probably not too bad.

In mouseDownHandler() on the object you are dragging you’ll need to figure out the bounds of the object relative to the mouse. Then in your dragEnterHandler() on the target, don’t call acceptDragDrop(), instead you'll need to use a mouseMove() handler (probably on the stage) to accept only when the object completely inside your drop target container.

Good luck.

Alek

8.8.2011

17

i am curious how you would go about using a reset button with this example

8.8.2011

18

@Alek: you just need some way to track state, then a reset button could revert everything back to the saved state.

For a simple impl, I’d probably use a hash of arrays, like this:

_saved = { colA:[0,1,2], colB:[3] };

Then a reset button would just walk the hash and make each column match the saved state.

fox

9.21.2011

19

How could we use this to sort or reorder a list? At the moment the dragged items ar always added as last item. How could we add a drag target indicator?

fox

9.21.2011

20

found in your follow up post, Drag-and-Drop Revisited, thank you very much!!!

lu

10.31.2011

21

I want to add a text in a List on position X, anybody know how to do it?

pk

8.14.2012

22

I need drag & drop comparisions of two images and i want to get error message when i try to compare the same images.

dev

9.7.2012

23

good tutorial shaniboy,
I want to display objects in panel like icon but after dragging start, how it will become in its actual width and height?

© 2017 saturnboy.com