My Contents

Monday, June 15, 2009

ListView Drag & Drop With Auto Scrolling in .NET (Upgraded) .NET ListView Tutorial on Auto Scrolling with Drag & Drop - DOT NET

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.

'''
''' 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.
'''

'''
'''
'''
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 <>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.

No comments: