Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Drag and Drop List View

Rate me:
Please Sign up or sign in to vote.
4.65/5 (32 votes)
12 May 20044 min read 210.5K   8.9K   92   33
Drag and Drop list items within list views or to other list views without manual intervention.

Sample Image - DragAndDropListView.gif

Introduction

An upcoming application at work required me to have drag and drop functionality between many different ListViews or DataGrids. When I got word, I decided to start searching the Internet and find a pre-existing free control that I could use. My personal requirements were that the ListView or DataGrid needed to have the ability to move rows around within its own control, but also allow me to move rows to other controls. My initial sorting pointed me towards Drag and Drop ListView row reordering by David Boland. As his control allowed users to reorder items in a ListView, it lacked support for moving to other ListViews as well as letting the user know where the new rows are to be placed. This control is somewhat similar to David's, however, it was rewritten only using David's code as a reference.

Using the Code

The DragAndDropListView control inherits from ListView, and provides native support for dragging and dropping ListItems to reorder them or move them to other DragAndDropListView controls. Because it needs to utilize Drag and Drop functionality, I had to override the OnDragDrop, OnDragOver, OnDragEnter, OnItemDrag, OnLostFocus, and OnDragLeave to provide the functionality.

When an item is selected and started to be dragged, OnItemDrag gets called which starts the Drag and Drop functionality by retrieving the control it originates from as well as the selected items. Both of these are stored in a private class that is passed around in the data of the drag and drop events.

C#
protected override void OnItemDrag(ItemDragEventArgs e)
{
 if(!m_allowReorder)
 {
  base.OnItemDrag(e);
  return;
 }

 base.DoDragDrop(GetDataForDragDrop(), DragDropEffects.Move);

 base.OnItemDrag(e);
}

As the user moves the selected list items over the ListView, OnDragOver is called. This method basically determines if the selected items to be dragged & dropped can actually be dragged & dropped. By checking to see if the item it's currently hovering over is not one of the list items being moved, it either displays the move cursor or denied cursor. If the user is trying to drop the items into an area that doesn't have a list item, it will still allow you, thus allowing you to place and reorder items in any order.

C#
protected override void OnDragOver(DragEventArgs drgevent)
{
 if(!m_allowReorder)
 {
  base.OnDragOver(drgevent);
  return;
 }
 if(!drgevent.Data.GetDataPresent(typeof(DragItemData).ToString()))
 {
  drgevent.Effect = DragDropEffects.None;
  return;
 }

 if(base.Items.Count > 0)
 {
  Point clientPoint = base.PointToClient(new Point(drgevent.X, drgevent.Y));
  ListViewItem hoverItem = base.GetItemAt(clientPoint.X, clientPoint.Y);

  Graphics g = this.CreateGraphics();

  if(hoverItem == null)
  {
   drgevent.Effect = DragDropEffects.Move;

   if(m_previousItem != null)
   {
    m_previousItem = null;
    Invalidate();
   }

   hoverItem = base.Items[base.Items.Count - 1];

   if(this.View == View.Details || this.View == View.List)
   {
    g.DrawLine(new Pen(m_lineColor, 2), new Point(hoverItem.Bounds.X, 
     hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
     new Point(hoverItem.Bounds.X + 
     this.Bounds.Width, hoverItem.Bounds.Y + 
     hoverItem.Bounds.Height));
    g.FillPolygon(new SolidBrush(m_lineColor),
     new Point[] {new Point(hoverItem.Bounds.X, 
     hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5), 
     new Point(hoverItem.Bounds.X + 5, hoverItem.Bounds.Y + 
     hoverItem.Bounds.Height), 
     new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + 
     hoverItem.Bounds.Height + 5)});
    g.FillPolygon(new SolidBrush(m_lineColor), 
     new Point[] {new Point(this.Bounds.Width - 4, 
     hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5), 
     new Point(this.Bounds.Width - 9, 
     hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
     new Point(this.Bounds.Width - 4, 
     hoverItem.Bounds.Y + hoverItem.Bounds.Height + 5)});
   }
   else
   {
    g.DrawLine(new Pen(m_lineColor, 2), 
     new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width, hoverItem.Bounds.Y), 
     new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width, hoverItem.Bounds.Y + 
     hoverItem.Bounds.Height));
    g.FillPolygon(new SolidBrush(m_lineColor), 
     new Point[] {new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width - 5, hoverItem.Bounds.Y), 
     new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width + 5, hoverItem.Bounds.Y), 
     new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width, hoverItem.Bounds.Y + 5)});
    g.FillPolygon(new SolidBrush(m_lineColor), 
     new Point[] {new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width - 5, hoverItem.Bounds.Y + 
     hoverItem.Bounds.Height), 
     new Point(hoverItem.Bounds.X + 
     hoverItem.Bounds.Width + 5, hoverItem.Bounds.Y + 
     hoverItem.Bounds.Height), 
     new Point(hoverItem.Bounds.X + hoverItem.Bounds.Width, 
     hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5)});
   }

   base.OnDragOver(drgevent);

   return;
  }

  if((m_previousItem != null && 
    m_previousItem != hoverItem) || m_previousItem == null)
  {
   this.Invalidate();
  }

  m_previousItem = hoverItem;

  if(this.View == View.Details || this.View == View.List)
  {
   g.DrawLine(new Pen(m_lineColor, 2), new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + this.Bounds.Width, 
    hoverItem.Bounds.Y));
   g.FillPolygon(new SolidBrush(m_lineColor), 
    new Point[] {new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y - 5), new Point(hoverItem.Bounds.X + 5, 
    hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y + 5)});
   g.FillPolygon(new SolidBrush(m_lineColor), 
    new Point[] {new Point(this.Bounds.Width - 4, 
    hoverItem.Bounds.Y - 5), new Point(this.Bounds.Width - 9, 
    hoverItem.Bounds.Y), new Point(this.Bounds.Width - 4, 
    hoverItem.Bounds.Y + 5)});
  }
  else
  {
   g.DrawLine(new Pen(m_lineColor, 2), 
    new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y), 
    new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height));
   g.FillPolygon(new SolidBrush(m_lineColor), 
    new Point[] {new Point(hoverItem.Bounds.X - 5, 
    hoverItem.Bounds.Y), 
    new Point(hoverItem.Bounds.X + 5, hoverItem.Bounds.Y), 
    new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + 5)});
   g.FillPolygon(new SolidBrush(m_lineColor), 
    new Point[] {new Point(hoverItem.Bounds.X - 5, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
    new Point(hoverItem.Bounds.X + 5, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
    new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5)});

  }

  foreach(ListViewItem itemToMove in base.SelectedItems)
  {
   if(itemToMove.Index == hoverItem.Index)
   {
    drgevent.Effect = DragDropEffects.None;
    hoverItem.EnsureVisible();
    return;
   }
  }

  hoverItem.EnsureVisible();
 }

 drgevent.Effect = DragDropEffects.Move;

 base.OnDragOver(drgevent);
}

One of the really cool things that I wanted to do, was to add a line either above or below the hovered list item, however, this really didn't pose an easy feat at first. To figure this out, I had 2 actual implementations of creating the line, 1 for when you were hovering over a list item, and 1 for when you were in an empty area. When you were hovering over a list item, the line needed to be added between that item and the item above it. If the user is currently over the empty area, and items existed, then the line would be added to the bottom of the very last item. Below is the code that shows how to add the line and arrows on both side:

C#
[Add the Line to the Last Item on Bottom]
g.DrawLine(new Pen(Brushes.Red, 2), new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
    new Point(hoverItem.Bounds.X + this.Bounds.Width, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height));
g.FillPolygon(Brushes.Red, new Point[] {new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5), 
    new Point(hoverItem.Bounds.X + 5, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
    new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height + 5)});
g.FillPolygon(Brushes.Red, new Point[] {new Point(this.Bounds.Width - 4, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height - 5), 
    new Point(this.Bounds.Width - 9, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height), 
    new Point(this.Bounds.Width - 4, 
    hoverItem.Bounds.Y + hoverItem.Bounds.Height + 5)});
C#
[Add the Line to the Top of the Current Item]
g.DrawLine(new Pen(Brushes.Red, 2), new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y), new Point(hoverItem.Bounds.X + 
    this.Bounds.Width, hoverItem.Bounds.Y));
g.FillPolygon(Brushes.Red, new Point[] {new Point(hoverItem.Bounds.X, 
    hoverItem.Bounds.Y - 5), new Point(hoverItem.Bounds.X + 5, 
    hoverItem.Bounds.Y), 
    new Point(hoverItem.Bounds.X, hoverItem.Bounds.Y + 5)});
g.FillPolygon(Brushes.Red, new Point[] {new Point(this.Bounds.Width - 4, 
    hoverItem.Bounds.Y - 5), new Point(this.Bounds.Width - 9, 
    hoverItem.Bounds.Y), 
    new Point(this.Bounds.Width - 4, hoverItem.Bounds.Y + 5)});

Previous versions of this article stated that lines would only be drawn if it was in Details or Line mode, however this updated version includes both Small Icon List and Large Icon List replacing horizontal lines with vertical lines. The demo has also been updated, so you can test this method out as well.

As the user is dragging the selected items around the form, OnLostFocus and OnDragLeave are utilized to invalidate the form so the drawn lines are erased.

Lastly, OnDragDrop is called whenever the user drops the selected items into a DragAndDropListView. This method determines if the items to be dropped are from the current DragAndDropListView or from another DragAndDropListView. At which point, the items are appended to the end, or above the hovered item. Finally, the selected items are removed from the respective DragAndDropListView.

C#
protected override void OnDragDrop(DragEventArgs drgevent)
{
 if(!m_allowReorder)
 {
  base.OnDragDrop(drgevent);
  return;
 }

 Point clientPoint = base.PointToClient(new Point(drgevent.X, drgevent.Y));
 ListViewItem hoverItem = base.GetItemAt(clientPoint.X, clientPoint.Y);

 if(!drgevent.Data.GetDataPresent(typeof(DragItemData).ToString()) || 
  ((DragItemData) 
   drgevent.Data.GetData(typeof(DragItemData).ToString())).ListView == null ||
  ((DragItemData) 
   drgevent.Data.GetData(typeof(DragItemData).ToSTring())).DragItems.Count == 0)
     return;
  
 DragItemData data = 
   (DragItemData) drgevent.Data.GetData(typeof(DragItemData).ToString());

 if(hoverItem == null)
 {
  for(int i=0; i<insertItems.Count; i++)
  {
   ListViewItem newItem = (ListViewItem) insertItems[i];
   base.Items.Add(newItem);
  }
 }
 else
 {
  int hoverIndex = hoverItem.Index;

  if(this == data.ListView)
  {
   if(hoverIndex > base.SelectedItems[0].Index)
    hoverIndex++;
  }

  for(int i=data.DragItems.Count - 1; i >= 0; i--)
  {
   ListViewItem newItem = (ListViewItem) data.DragItems[i];
   base.Items.Insert(hoverIndex, newItem);
  }
 }

 if(data.ListView != null)
 {
  foreach(ListViewItem itemToRemove in data.ListView.SelectedItems)
  {
   data.ListView.Items.Remove(itemToRemove);
  }
 }


 if(m_previousItem != null)
 {
  m_previousItem = null;
 }

 this.Invalidate();

 base.OnDragDrop (drgevent);

}

Known Problems

  • I've fixed all known problems so far.

Version History

  • May 17, 2004
    • Fixed inability to have lines shown for Large or Small Icon listings.
    • Fixed ability to fully move ListViewItems without losing data.
    • Removed hack to find the corresponding DragAndDropListView control.
  • May 14, 2004
    • Fixed ListView not scrolling.
    • Added AllowReorder to allow turning row reordering and row transfer off.
    • Added LineColor to allow you to set the color of the line drawn.
  • May 13, 2004
    • Initial launch showing functionality.

References

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionNice work, but scrolling up while draf doesn't work with "HeaderStyle = None" Pin
Member 1072351723-Oct-14 2:18
Member 1072351723-Oct-14 2:18 
AnswerRe: Nice work, but scrolling up while draf doesn't work with "HeaderStyle = None" Pin
Member 1113136825-Oct-14 4:46
Member 1113136825-Oct-14 4:46 
QuestionLicensing Pin
Member 104455314-Dec-13 12:15
Member 104455314-Dec-13 12:15 
QuestionMy vote is 5 Pin
pige28-Jun-13 1:36
pige28-Jun-13 1:36 
GeneralMy vote of 5 Pin
Paul Bible27-Mar-13 4:23
Paul Bible27-Mar-13 4:23 
QuestionHow about Double Click support Pin
rewrewewrerw21-May-12 12:14
rewrewewrerw21-May-12 12:14 
BugJUMPY when doing drap drop Pin
rewrewewrerw21-May-12 12:12
rewrewewrerw21-May-12 12:12 
GeneralGet Drop Data Pin
Laserson1-Oct-10 20:49
Laserson1-Oct-10 20:49 
GeneralLiscensing request Pin
nickfas8-Jun-10 14:01
nickfas8-Jun-10 14:01 
GeneralRetaining tag or name property of item after drag-drop reorder Pin
honcho2612-Jun-08 9:41
honcho2612-Jun-08 9:41 
GeneralFOUND THE ANSWER! Re: Retaining tag or name property of item after drag-drop reorder Pin
honcho2612-Jun-08 10:45
honcho2612-Jun-08 10:45 
General"Jumping" at the bottom on drag and drop [FIX] - dragging down 2 nodes instead of one [FIX] Pin
paintor11-Feb-08 1:37
paintor11-Feb-08 1:37 
I was recieving a jumping of the selector when dragging downward. This appeared to be related to the dragging over the horizontal scrollbar. I fixed this by detecting if the jump should be applied or not (not tested for multiple listviews)

private const int WS_HSCROLL = 0x100000;
private const int GWL_STYLE = (-16);

[DllImport("user32", CharSet=CharSet.Auto)]
private static extern int GetWindowLong(IntPtr hwnd, int nIndex);

private bool IsHorizontalScrollBarVisible()
{
if (!this.IsHandleCreated)
return false;

return (GetWindowLong(this.Handle, GWL_STYLE) & WS_HSCROLL) != 0;
}

will detect if the scrollbar is present, and in OnDragOver() before the call to CreateGraphics

if( hoverItem == null )
{
if ( IsHorizontalScrollBarVisible() )
{
// added 2 px to scrollbar to avoid jump in gutter
hoverItem = base.GetItemAt(clientPoint.X, clientPoint.Y -
SystemInformation.HorizontalScrollBarHeight + 2 );
if( hoverItem != null )
{
if ( hoverItem.Index == this.Items.Count - 1 )
{
// found the last one, we want to drop at the bottom
hoverItem = null;
}
else
{
this.EnsureVisible( hoverItem.Index + 1 );
}
}
}
}

This works for horizontal scrollbar visible or not, with arbritrary vertical scrollbar.


Additionally I found the dragging down counter intuitive as it dropped an item 2 nodes below instead of "in the middle" where the selector was (due to the line being at the top of each node). I therefore took out the code below from OnDragDrop()

// determine if the items to be dropped are from
// this list view. If they are, perform a hack
// to increment the hover index so that the items
// get moved properly.

// if(this == data.ListView)
// {
// if(hoverIndex > base.SelectedItems[0].Index)
// hoverIndex++;
//}

Please note these alterations are not tested on multiple list boxes
QuestionProblem with Sorting [modified] Pin
shashank_15610-Feb-08 23:31
shashank_15610-Feb-08 23:31 
GeneralBug Fix - When reordering down and horizontal scroll bar is visible Pin
Steve Marshall29-Oct-07 6:25
Steve Marshall29-Oct-07 6:25 
GeneralVB Code Pin
powermetal1-Oct-07 8:19
powermetal1-Oct-07 8:19 
GeneralBug Fix for LargeIcon or SmallIcon view Pin
huanyi25-Sep-07 3:02
huanyi25-Sep-07 3:02 
GeneralRe: Bug Fix for LargeIcon or SmallIcon view Pin
nullesc19-Nov-08 17:24
nullesc19-Nov-08 17:24 
GeneralDrag&Drop only inside of current List Pin
MichaZ1238-Aug-07 0:30
MichaZ1238-Aug-07 0:30 
GeneralQuestion Pin
loneferret17-Jul-06 12:02
loneferret17-Jul-06 12:02 
QuestionLicensing Pin
mark_ledwich20-Apr-06 19:44
mark_ledwich20-Apr-06 19:44 
QuestionView LargeIcon ? Pin
miha527-Nov-05 22:59
miha527-Nov-05 22:59 
QuestionFlicker when dragging Pin
dgannon3416-Sep-05 18:16
dgannon3416-Sep-05 18:16 
AnswerRe: Flicker when dragging Pin
Member 135915891-Jan-18 6:55
Member 135915891-Jan-18 6:55 
Questionhow to implement drag and drop in the asp.net page ,like list data or multiple select from one to another Pin
Jade_king15-Sep-05 22:13
Jade_king15-Sep-05 22:13 
GeneralListViewItem Image problem Pin
didis1-Dec-04 5:24
didis1-Dec-04 5:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.