A Custom Drag-n-Drop List Control

The List control supports drag-n-drop support out of the box - just not the way I want. What it does is allow you to drag something from another list or other type control and drop onto it there by transfering the object dragged into the list control itself. What I want is for something to be dropped onto an item in the list in question. This is how I ended up doing that.


By looking at the implementation of ListBase.as in the flex framework source code, I found that I needed to override some methods. So I create a MyList.as file which subclasses the List control. The methods I first needed to override were dragEnterHandler and dragOverHandler. Both of these methods in ListBase.as look almost identical, here is the source for dragOverHandler:
    protected function dragEnterHandler(event:DragEvent):void
    {
        if (event.isDefaultPrevented())
            return;

        lastDragEvent = event;

        if (enabled && iteratorValid && event.dragSource.hasFormat("items"))
        {
            DragManager.acceptDragDrop(this);
            DragManager.showFeedback(event.ctrlKey ? DragManager.COPY : 
		DragManager.MOVE);
            showDropFeedback(event);
            return;
        }

        hideDropFeedback(event);
        
        DragManager.showFeedback(DragManager.NONE);
    }
The showDropFeedback method draws a line in the List control that indicates where the new item would be inserted into the List. This doesn't apply to us anymore, so I overrode the showDropFeedback method, to just highlight the item under the cursor instead:
        override public function showDropFeedback(event:DragEvent):void{
            var item = findItemForDragEvent(event);
            var uid:String = itemToUID(item.data);
            if (item){
                drawItem(item, isItemSelected(item.data), true, 
			uid == caretUID);
            }
        }
Moreover, now we want to accept the drop only if the cursor is over a list item, whereas before it allowed for drops on empty parts of the List area. This what I did:
        override protected function dragEnterHandler(event:DragEvent):void{
            _dragOverHandler(event);
        }
        
        override protected function dragOverHandler(event:DragEvent):void{
            _dragOverHandler(event);
        }
        
        public function findItemForDragEvent(event:DragEvent):Object{
            var item;
            var lastItem;
            var pt:Point = new Point(event.localX, event.localY);
            pt = DisplayObject(event.target).localToGlobal(pt);
            pt = listContent.globalToLocal(pt);
            var rc:int = listItems.length;
            for (var i:int = 0; i < rc; i++)
            {
                if (listItems[i][0])
                    lastItem = listItems[i][0];

                if (rowInfo[i].y <= pt.y && 
			pt.y < rowInfo[i].y + rowInfo[i].height)
                {
                    item = listItems[i][0];
                    break;
                }
            }
            return item;
        }
        
        protected function _dragOverHandler(event:DragEvent):void{
            var item = findItemForDragEvent(event);
            if (item){
                DragManager.acceptDragDrop(this);
                DragManager.showFeedback(
			event.ctrlKey ? DragManager.COPY : 
				DragManager.MOVE);
                showDropFeedback(event);
            }else{
                DragManager.showFeedback(DragManager.NONE);
            }
        }
I wrote a findItemForDragEvent method to find the item that the cursor is currently under or null if none exist - the code was mostly stolen from the calculateDropIndex method in ListBase.as - then I accept the drop if I get a non-null value from it.
I think that's pretty much it. Here's the demo and the code.
blog comments powered by Disqus