Drag and drop ListView items, with fade/transparent effect.

Bodekaer
Bodekaer used Ask the Experts™
on
How do I create a listview item dragger... I want to be able to move arround the icons inside a listview.

Is it also possible to use the cool XP effect where the icons become transparent when moving files/folders arround in Windows.

Thanks in advance,

Michael
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Do you want to move the items around inside the same listview or do you want to be able to drag items between different listviews?
Here is how to do a simple drag for a single list view item...

1. Subscribe to the following events...

this.listView1.DragOver += new System.Windows.Forms.DragEventHandler(this.listView1_DragOver);
this.listView1.DragDrop += new System.Windows.Forms.DragEventHandler(this.listView1_DragDrop);
this.listView1.DragEnter += new System.Windows.Forms.DragEventHandler(this.listView1_DragEnter);
this.listView1.ItemDrag += new System.Windows.Forms.ItemDragEventHandler(this.listView1_ItemDrag);

2. Implement the following event handlers...

private void listView1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
     // Convert screen position into listview position...
     Point curPoint = listView1.PointToClient(new Point(e.X, e.Y));              

     // Determine destination item based on mouse position...
     ListViewItem destItem = listView1.GetItemAt(curPoint.X, curPoint.Y);

     // Retrieve source item from event data...
     ListViewItem srcItem = e.Data.GetData(typeof(ListViewItem)) as ListViewItem;

     // Create copy of list view items (for some reason removing an item from
     // listview1.Items and inserting it again DOES NOT reorder the items!!)...
     ArrayList items = new ArrayList(listView1.Items);

     // Get index of destination item...
        int index = items.IndexOf(destItem);
         
     // Remove source item...
     items.Remove(srcItem);    
         
     // Insert source item in new position...
     items.Insert(index, srcItem);          
               
     // Repopulate list view...
     listView1.Items.Clear();
     listView1.Items.AddRange((ListViewItem[])items.ToArray(typeof(ListViewItem)));
               
     // Set source item to be selected...
     listView1.SelectedItems.Clear();
     srcItem.Selected = true;
}

private void listView1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
{
     // Set to move...
     e.Effect = DragDropEffects.Move;          
}

private void listView1_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
     // Convert screen position into listview position...
     Point curPoint = listView1.PointToClient(new Point(e.X, e.Y));              

     // Determine destination item based on mouse position...
     ListViewItem destItem = listView1.GetItemAt(curPoint.X, curPoint.Y);

     // Retrieve source item from event data...
     ListViewItem srcItem = e.Data.GetData(typeof(ListViewItem)) as ListViewItem;
               
     if(destItem != null)
     {
          // Select both source and destination items...
          listView1.SelectedItems.Clear();                    
          srcItem.Selected = true;
          destItem.Selected = true;
     }
}

private void listView1_ItemDrag(object sender, System.Windows.Forms.ItemDragEventArgs e)
{
     // Start drag operation with selected item...
     listView1.DoDragDrop(e.Item, DragDropEffects.All);
}


Performing the "cool XP effect" is slightly harder, I'll post some additional code shortly which demonstrates how that can be done.
The following method can be used to make a drag image, it's not really transparent but unless you look really closely you can hardly tell...

(C#)
private Image CreateDragImage(ListViewItem item)
{
     // Ensure the node is completely visible...
     item.EnsureVisible();

     // Calculate node rectange (including item icon)...
     Rectangle itemRect = new Rectangle(
          item.Bounds.Left - 20, item.Bounds.Top,
          item.Bounds.Width + 20, item.Bounds.Height);

     // Set selected node to null & redraw...
     listView1.SelectedItems.Clear();
     listView1.Update();

     // Get reference to tree control graphics...
     Graphics listviewGraphics = listView1.CreateGraphics();

     // Create bitmap and get reference to it's graphics...
     Bitmap   memBitmap   = new Bitmap(itemRect.Width, itemRect.Height);
     Graphics memGraphics = Graphics.FromImage(memBitmap);
               
     // Get references to device context handles...
     IntPtr treeDC = listviewGraphics.GetHdc();
     IntPtr memDC  = memGraphics.GetHdc();
               
     // Copy required region of screen to bitmap...
     BitBlt(
          memDC, 0, 0, itemRect.Width, itemRect.Height,
          treeDC,itemRect.Left, itemRect.Top, 0xCC0020);

     // Release device contexts...
     listviewGraphics.ReleaseHdc(treeDC);
     memGraphics.ReleaseHdc(memDC);

     // Make it look faded...
     memGraphics.FillRectangle(new SolidBrush(Color.FromArgb(128, listView1.BackColor)), 0, 0, itemRect.Width, itemRect.Height);

     // Make selection color transparent, leaving text only...
     memBitmap.MakeTransparent(listView1.BackColor);              
                   
     // Return image...
     return memBitmap;
}

You will also need to import the following method;

(C#)
[DllImport("gdi32.dll")]
public static extern bool BitBlt(
     IntPtr hdcDest, // handle to destination DC
     int nXDest, // x-coord of destination upper-left corner
     int nYDest, // y-coord of destination upper-left corner
     int nWidth, // width of destination rectangle
     int nHeight, // height of destination rectangle
     IntPtr hdcSrc, // handle to source DC
     int nXSrc, // x-coordinate of source upper-left corner
     int nYSrc, // y-coordinate of source upper-left corner
     System.Int32 dwRop // raster operation code
);

The following describes how to integrate it into the drag and drop example...

1. Define some member variables

(C#)
private Image m_dragImage = null;
private Point m_dragPosition;

2. Change the "listView1_ItemDrag" handler as follows.

(C#)
private void listView1_ItemDrag(object sender, System.Windows.Forms.ItemDragEventArgs e)
{
     m_dragImage = CreateDragImage(e.Item as ListViewItem);

     // Start drag operation with selected item...
     listView1.DoDragDrop(e.Item, DragDropEffects.All);              
}

3. Add the following code to the END of the "listView1_DragOver" handler.

if(m_dragImage != null && m_dragPosition != curPoint)
{
     // Redraw last image position...
     listView1.Invalidate(new Rectangle(m_dragPosition.X, m_dragPosition.Y, m_dragImage.Width, m_dragImage.Height));
     listView1.Update();
         
     // Determine new image position...
     Point imagePos = new Point(curPoint.X, curPoint.Y);
         
     // Draw image...
     Graphics.FromHwnd(listView1.Handle).DrawImage(m_dragImage, imagePos);

     m_dragPosition = curPoint;          
}

4. You might also need a call to "listView1.Refresh()" at the end of the "listView1_DragDrop" handler

Author

Commented:
Ok, looks great. What if I want it to work with VB.NET ? :)

(it was within the same listview)
PAQ'd and points NOT refunded.

SpideyMod
Community Support Moderator @Experts Exchange

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial