Drag and Drop
Byron asked me to look into drag and drop in Flex for a project he is working on. I was really surprised how easy and fun the drag and drop functionality is to use in Flex. I found a few issues I thought were interesting along the way.
First the good stuff. Here is a working sample of what I came up with. You can drag friends from the list on the right to a table. Admittedly the graphics need work, but it's just a prototype.
View Drag and Drop Prototype
Turning on D&D in a List control is pretty easy, just by setting:
dragEnabled="true"
The first thing that threw me was COPY vs. MOVE. When you turn on D&D, by default it uses a copy operation from the List when you drag an item out. You can turn on move by setting:
dragMoveEnabled="true"
So now, when you drag an item from the List it does remove the original item from the List. However, copy is still enabled. The user just has to hold down the control key. Unfortunately, there does not seem to be a way to turn off copy (at least no easy way I could see). The prototype is therefore currently bugged in that it allows copies of the List items. I will probably end up sub-classing the List control to do that, which is not a huge deal since I have already sub-classed it for other changes I will discuss below.
The next thing I had to deal with with what is called the Drag Proxy. This is more or less the image under the cursor that is displayed when you drag an item out of the List . The cool thing is, by default Flex creates a Drag Proxy that is an exact image of the entire row being dragged out of the List - very cool. However, for my Drag Proxy I wanted just the image within the row.
Checking out the List control source I found a couple of protected methods I could override to establish the Drag Proxy. I started out doing it that way, but realized there were a few quirks (things like I could not set the alpha of the Drag Proxy and my custom image offsets were being ignored, causing the image under the cursor to be way off alignment). I ended up just overriding the dragStartHandler method in List that kicks off the Drag operation. It turns out that was a useful excercise, because to create the chairs in the table as droppable targets I had to dive into the DragManager API anyway. The main code I need to mess with was:
var dragProxy:Image = new Image();
var renderer:FriendItemRenderer = this.indexToItemRenderer(this.selectedIndex) as FriendItemRenderer;
var xOffset:int = -1 * mouseX + (actualImage.width / 2);
var yOffset:int = -1 * mouseY + (actualImage.height / 2);
dragProxy.source = actualImage.source;
dragProxy.setActualSize(actualImage.width, actualImage.height);
DragManager.doDrag(this, dragSource, event, dragProxy, xOffset, yOffset, 0.5, dragMoveEnabled);
FriendItemRenderer is my custom render for the List rows, which is absolutely nothing special - just a Canvas with an Image (id of actualImage) and some Labels on it. So basically I just ask for the currently selected row, get the renderer for that row, and get the Image control off the renderer so I can get at its Image source. Then I put this into the new Drag Proxy image. Finally, I mess with the offsets of where the dragProxy should be to center the Drag Proxy Image under the cursor.
Finally, I needed to make the Chairs droppable. The Chairs are just a Canvas with an Image and Button that have the dragEnter, dragExit, and dragDrop events handled. I did not need to mess with these events on my List because those are all handled by default. List, DataGrids, and several other controls natively support drag and drop. With custom controls you just have to handle certain events and call the DragManager API to make it all work.
When the Chair handles the dragDrop event it just stores the data being dropped (passed into the dragDrop event), gets the Image source off the data being dropped, and sets it on the Chair's own Image.
When the Chair's Button is clicked, it removes the Image's source and tells the List to add the data it holds on to back into the List.

Comments