KnowDotNet Visual Organizer

ListView Drag & Drop With Auto Scrolling in .NET (Upgraded)

.NET ListView Tutorial on Auto Scrolling with Drag & Drop

by Les Smith
Print this Article Discuss in Forums

Need to have your ListView scroll while using Drag and Drop?  This article shows the code.  It's not complex, but the documentation is hard to locate in .NET.  I actually looked, off and on for several days, in Google and MSDN, and never found any .NET code for making the ListView scroll.

Finally, by trial and error, I got the ListView to scroll with Drag and Drop.  I was able to find some VB6 and some C# code, but was never able to make any of it work.  When you actually see the code, there is not much to it.  

I must give credit to Bruce Gordon for providing an upgrade to this code and making the scrolling run much smoother.  Therefore, because of his great tip and  rewritten DragOver event, I am republishing this article with the code that he sent me.  THANKS BRUCE!

This article will give you the code for both Drag and Drop and automatic scrolling of the ListView.  I use it to reorder ListViewItems in the ListView.  First, place a ListView on a Windows Form.  I set the MultiSelect Property to True so that the user can select and drag multiple items.  If you name the ListView "lvCodeElts", you can plug the code and it should work with no problems.  Otherwise, just replace all instances of "lvCodeElts" with the name of your ListView.  Also add a Timer control to the form.  Figure 1 shows the code for beginning the Drag and Drop.

Figure 1 - Begin Drag and Drop.

   Private Sub lvCodeElts_ItemDrag(ByVal sender As Object, _
      
ByVal e As System.Windows.Forms.ItemDragEventArgs) Handles lvCodeElts.ItemDrag
      
'Begins a drag-and-drop operation in the ListView control.
      lvCodeElts.DoDragDrop(lvCodeElts.SelectedItems, DragDropEffects.Move)
  
End Sub

I have two ListViews on my form and I do not want the user to be able to drag from one ListView to the other.  The first two lines of the code shown in Figure 1 allow DragDrop in the ListView that is being Dragged and it prevents the Drop from taking place in the lvRegions ListView.  Obviously, I will have the same two lines, just reversing the value of the Booleans in the ItemDrag Event of the lvRegions Listview.  That will prevent a Drag from it dropping into the lvCodeElts ListView.  Figure 2 shows the code for DragEnter Event.  This event validates the Drag movement and determines the cursor appearance throughout the Drag operation.

Figure 2 - DragEnter Event.

   Private Sub lvCodeElts_DragEnter(ByVal sender As Object, _
      
ByVal e As System.Windows.Forms.DragEventArgs) Handles lvCodeElts.DragEnter
      
Dim i As Integer
      For i = 0 To e.Data.GetFormats().Length - 1
        
If e.Data.GetFormats()(i).Equals _
            (
"System.Windows.Forms.ListView+SelectedListViewItemCollection") Then
            'The data from the drag source is moved to the target.
            e.Effect = DragDropEffects.Move
        
End If
      Next
   End Sub

If the user drags outside of the ListViewItem area, the mouse cursor will chage to a "None", or no drop, appearace.  Figure 4 shows the code for the DragDrop event, which does the moving of the items from where they were being dragged to the drop location.

Figure 3 - DragDrop Event.

   ''' <summary>
   ''' Allow reordering of items within a ListView.  Moves all selected
   ''' items to a point before the item on which the dragged item(s) are dropped.
   ''' Exit if the items are not selected in the ListView control.
   ''' While moving up, the dragged items are inserted above the item on which
   ''' they are dropped.  On dragging down, the dragged items are inserted below
   ''' the item on which they are dropped.  This is by design, otherwise you could
   ''' not insert both above and below the first and last item.
   ''' </summary>
   ''' <param name="sender" ></param>
   ''' <param name="e" ></param>
   ''' <remarks></remarks>
   Private Sub lvCodeElts_DragDrop(ByVal sender As Object, _
      
ByVal e As System.Windows.Forms.DragEventArgs) Handles lvCodeElts.DragDrop
      
If lvCodeElts.SelectedItems.Count = 0 Then Exit Sub

      'Returns the location of the mouse pointer in the ListView control.
      Dim p As Point = lvCodeElts.PointToClient(New Point(e.X, e.Y))

      
'Obtain the item that is located at the specified location of the mouse pointer.
      ' dragItem is the lvi upon which the drop is made
      Dim dragToItem As ListViewItem = lvCodeElts.GetItemAt(p.X, p.Y)

      
If dragToItem Is Nothing Then Exit Sub

      'Obtain the index of the item at the mouse pointer.
      ' dragIndes is the index of the lvi on which the items are dropped
      Dim dragIndex As Integer = dragToItem.Index
      
Dim i As Integer

      
' create an array large enough to hold the selected (dragged) items
      Dim sel(lvCodeElts.SelectedItems.Count) As ListViewItem

      
' the following code moves the dragged selection to their new location.
      
For i = 0 To lvCodeElts.SelectedItems.Count - 1
         sel(i) = lvCodeElts.SelectedItems.Item(i)
      
Next

      For i = 0 To lvCodeElts.SelectedItems.Count - 1
        
'Obtain the ListViewItem to be dragged to the target location.
         Dim dragItem As ListViewItem = sel(i)
        
Dim itemIndex As Integer = dragIndex

        
If itemIndex = dragItem.Index Then Exit Sub

         If dragItem.Index < itemIndex Then
            itemIndex = itemIndex + 1
        
Else
            itemIndex = dragIndex + i
        
End If

         'Insert the item in the specified location.
         Dim insertItem As ListViewItem = CType(dragItem.Clone, ListViewItem)
         lvCodeElts.Items.Insert(itemIndex, insertItem)
        
'Removes the item from the initial location while
         'the item is moved to the new location.
         lvCodeElts.Items.Remove(dragItem)
      
Next
   End Sub

If the selected items are dragged above the original selection location, the items will be inserted above the item upon which they are dropped.  If the selected items are dragged down, they will be inserted after the item upon which they are dropped.  This is by design, otherwise you would not be able to both insert above the top item and below the bottom item.  Finally, Figure 4 shows the DragOver event.  This is the event that causes the ListView to automatically scroll as the user moves to either end of the visible items.  This was the code that was the hardest to figure out and yet it is very simple code.  It's too bad Microsoft did not make scrolling automatic when using Drag and Drop, but then where would be the challenge, right?

Figure 4 - DragOver Event.

   Private Sub ListView1_DragOver(ByVal sender As Object, _
      
ByVal e As System.Windows.Forms.DragEventArgs) Handles ListView1.DragOver

      
Dim position As Point
      position =
New Point(0, 0)

      position.X = e.X
      position.Y = e.Y
      position = ListView1.PointToClient(position)
      mobjHoverItem = ListView1.GetItemAt(position.X, position.Y)

      
If position.Y <= ListView1.Font.Height \ 2 Then
         ' getting close to top, ensure previous item is visible
         mintScrollDirection = 0
         tmrLVScroll.Enabled =
True
      ElseIf position.Y >= ListView1.ClientSize.Height - ListView1.Font.Height \ 2 Then
         ' getting close to bottom, ensure next item is visible
         mintScrollDirection = 1
         tmrLVScroll.Enabled =
True
      Else
         tmrLVScroll.Enabled = False
      End If

      e.Effect = DragDropEffects.Move

      position.X = e.X
      position.Y = e.Y
      position = ListView1.PointToClient(position)
      mobjHoverItem = ListView1.GetItemAt(position.X, position.Y)

      
If IsNothing(mobjHoverItem) Then Exit Sub

      If mintSavedHoverIndex = mobjHoverItem.Index Then Exit Sub

      ListView1.BeginUpdate()
      ClearLVHighlight(ListView1)
      mobjHoverItem.BackColor = Color.DarkBlue
      mobjHoverItem.ForeColor = Color.White
      ListView1.EndUpdate()

      mintSavedHoverIndex = mobjHoverItem.Index

  
End Sub

Figure 5 shows the Item_Drag Event which is fired when you start a Drag operation.

Figure 5 - Item Drag Event.

   Private Sub ListView1_ItemDrag(ByVal sender As Object, _
      
ByVal e As System.Windows.Forms.ItemDragEventArgs) Handles ListView1.ItemDrag
      
'Begins a drag-and-drop operation in the ListView control.
      ListView1.DoDragDrop(ListView1.SelectedItems, DragDropEffects.Move)
  
End Sub

Finally, Figure 6 lists some module level variables and a couple of helper methods provided by Bruce whom I previously mentioned.

Figure 6 - Helper Methods

   Private mintScrollDirection As Integer
   Private mobjHoverItem As ListViewItem
  
Private mintSavedHoverIndex As Integer

   Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
       (
ByVal hwnd As Integer, _
        
ByVal wMsg As Integer, _
        
ByVal wParam As Integer, _
        
ByRef lParam As Object) As Integer

   Private Sub tmrLVScroll_Tick(ByVal sender As System.Object, _
      
ByVal e As System.EventArgs) Handles tmrLVScroll.Tick
      ScrollControl(ListView1, mintScrollDirection)
  
End Sub

   Private Sub ScrollControl(ByRef objControl As Control, ByRef intDirection As Integer)

      
' This function enables a control (e.g. TreeView or ListView) to scroll
      ' during a drag-and-drop operation.
      ' For lngDirection, a value of 0 scrolls up; a value of 1 scrolls down.

      Const WM_SCROLL As Integer = &H115S

      SendMessage(objControl.Handle.ToInt32, WM_SCROLL, intDirection, VariantType.Null)

  
End Sub

   Private Sub ClearLVHighlight(ByVal objLV As ListView)

      
For intX As Integer = 0 To objLV.Items.Count - 1
         objLV.Items(intX).ForeColor = Color.Black
         objLV.Items(intX).BackColor = Color.White
      
Next

   End Sub

That's all there is to it.  Hope it saves you the time it took me to gather it and get it to work.  

Should you find that you cannot adopt the code listed above and get it to work, you can download a full working solution including the test form by clicking
HERE.

Have you tried our newest product, Visual Class Organizer?  You'll be amazed how easy it is to keep the code in your code windows organized.  TRY IT FREE FOR 30 DAYS BY CLICKING HERE.



If you are developing in C# and haven't tried CSharpCompleter, you are wasting valuable time typing hundreds of braces {} daily needlessly.  Try CSharpCompleter for 30 DAYS FREE.



Ask a Question, or give your feedback on my articles or products by going to the KnowDotNet Forum or by clicking on My Blog.
  

Writing Add-Ins for Visual Studio .NET
Writing Add-ins for Visual Studio .NET
by Les Smith
Apress Publishing