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.
''' ''' 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:
Post a Comment