Click here to Skip to main content
12,303,777 members (70,823 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

54K views
2.1K downloads
51 bookmarked
Posted

gListView - ListView Control with Visual Drag and Drop FeedBack (VB.NET)

, 7 Apr 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
ListView has a pointer for drop location, alternating row coloring, and custom drag cursor.

Introduction

I needed a ListView that gave me more Drag and Drop feedback. The couple of examples I found were not complete enough for my needs. I began work on my own to fill in the gaps. I needed built-in reordering with a visual insertion pointer, auto scrolling, and a better example of what was being dragged. I had the initial stages working well, and then after developing the gCursor [^], it all came together. The gListView Inherits ListView and has these extra properties:

Properties and Enumerations

Enum eAutoScroll
    None
    All
    Vertical
    Horizontal
End Enum

Here is a list of the primary properties:

  • Public Property gCurrCursor() As gCursor

    Setup the gCursor

  • Public Property gCursorVisible() As Boolean

    Use or not use the gCursor

  • Public Property DropBarColor() As Color

    Color of the pointer for the insertion point

  • Public Property AutoScroll() As eAutoScroll

    What type of Auto Scroll to use

  • Public Property MatchFont() As Boolean

    When dropping the ListViewItem does it keep its original font or change to match this ListView's font

  • Public Property ColorRows() As Boolean

    Use the ColorRowA and ColorRowB to alternate the color of the rows

  • Public Property ColorRowA() As Color

    When ColorRows = True, the Item Color alternates between ColorRowA and ColorRowB

  • Public Property ColorRowB() As Color

    When ColorRows = True, the Item Color alternates between ColorRowA and ColorRowB

Reference Links

Some concepts used in this Project (and referenced later) are already explained in these articles:

Dragging Over the List

The big problem with drag and drop in a list, is pin pointing where exactly the item will end up when you let go of the mouse button. To determine where to draw the pointer and reference the insertion point, you need to know:

    1. Is the mouse over an item or whitespace
    2. Is the View in LargeIcon Mode
    3. Is the insertion point Above or Below the item
    4. Has the pointer moved to a new location
    5. Is the mouse near an edge to trigger scrolling

Add Insert Pointer

Private Sub PaintPointer( _
    ByRef Mpt As Point, _
    ByRef MeItem As ListViewItem, _
    ByRef e As System.Windows.Forms.DragEventArgs)

    'Check if the mouse is over an Item
    If IsNothing(MeItem) Then
        'if there are 0 items let it add
        Me.Invalidate(InvRect)
        If Me.Items.Count > 0 Then e.Effect = DragDropEffects.None
        OffItems = True
    Else
        'Get the old pointer area to Invalidate
        If Me.View = Windows.Forms.View.LargeIcon Then
            InvRect = New Rectangle(LineStartPt.X - 6, LineStartPt.Y, _
                    12, MeItem.Bounds.Height + 15)
        Else
            InvRect = New Rectangle(LineStartPt.X, LineStartPt.Y - 6, _
                    MeItem.Bounds.Width, 13)
        End If

        Dim ItemRect As Rectangle = MeItem.Bounds
        Dim StrtPt As Integer
        Dim LineStartPt_N As Point
        Dim LineEndPt_N As Point
        Dim LineAbove_N As Boolean

        'determine the new pointer location and position
        If Me.View = Windows.Forms.View.LargeIcon Then
            If Mpt.X < ItemRect.Left + (ItemRect.Width / 2) Then
                StrtPt = ItemRect.Left
                LineAbove_N = True
            Else
                StrtPt = ItemRect.Right
                LineAbove_N = False
            End If
            LineStartPt_N = New Point(StrtPt - 1, ItemRect.Top - 1)
            LineEndPt_N = New Point(StrtPt - 1, ItemRect.Bottom - 1)
        Else
            If Mpt.Y < ItemRect.Top + (ItemRect.Height / 2) Then
                StrtPt = ItemRect.Top
                LineAbove_N = True
            Else
                StrtPt = ItemRect.Bottom
                LineAbove_N = False
            End If
            LineStartPt_N = New Point(ItemRect.Left - 1, StrtPt - 1)
            LineEndPt_N = New Point(ItemRect.Right - 1, StrtPt - 1)
        End If

        'if the Pointer has moved clear the old area and paint a new pointer
        If LineStartPt_N <> LineStartPt Or OffItems Then
            Me.Invalidate(InvRect)
            Me.Update()
            OffItems = False

            'Set the new position
            LineStartPt = LineStartPt_N
            LineEndPt = LineEndPt_N
            LineAbove = LineAbove_N

            DrawThePointer(ItemRect)

            LineIndex = MeItem.Index
        End If
    End If
End Sub

Auto Scroll the List

Especially when re-ordering a list, you need to be able to have the gListView scroll itself automatically when dragging. First determine which way to scroll and if it is allowed.

Private Sub CheckScroller( _
    ByRef Mpt As Point, _
    ByRef MeItem As ListViewItem, _
    ByRef e As System.Windows.Forms.DragEventArgs)

    Dim ScrollMargin As Padding = New Padding
    If Me.View = Windows.Forms.View.Details Then
        ScrollMargin.Top = Me.TopItem.Bounds.Top + 5
    Else
        ScrollMargin.Top = Me.ClientRectangle.Top + 5
    End If
    ScrollMargin.Bottom = Me.ClientSize.Height - 5
    ScrollMargin.Left = 5
    ScrollMargin.Right = Me.ClientSize.Width - 5

    If Mpt.Y <= ScrollMargin.Top _
        AndAlso (_AutoScroll = eAutoScroll.All _
        Or _AutoScroll = eAutoScroll.Vertical) Then

        scrollDirection = 0
        scrollHorzVert = sHorzVert.Vert
        ScrollTimer.Start()
        _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollUp
        e.Effect = DragDropEffects.None
        If IsNothing(MeItem) Then _gCurrCursor.MakeCursor()
        Me.Invalidate(InvRect)

    ElseIf Mpt.Y >= ScrollMargin.Bottom _
        AndAlso (_AutoScroll = eAutoScroll.All _
        Or _AutoScroll = eAutoScroll.Vertical) Then

        scrollDirection = 1
        scrollHorzVert = sHorzVert.Vert
        ScrollTimer.Start()
        _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollDn
        e.Effect = DragDropEffects.None
        If IsNothing(MeItem) Then _gCurrCursor.MakeCursor()
        Me.Invalidate(InvRect)

    ElseIf Mpt.X <= ScrollMargin.Left _
        AndAlso (_AutoScroll = eAutoScroll.All _
        Or _AutoScroll = eAutoScroll.Horizontal) Then

        scrollDirection = 0
        scrollHorzVert = sHorzVert.Horz
        ScrollTimer.Start()
        _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollLeft
        e.Effect = DragDropEffects.None
        If IsNothing(MeItem) Then _gCurrCursor.MakeCursor()
        Me.Invalidate(InvRect)

    ElseIf Mpt.X >= ScrollMargin.Right _
        AndAlso (_AutoScroll = eAutoScroll.All _
        Or _AutoScroll = eAutoScroll.Horizontal) Then

        scrollDirection = 1
        scrollHorzVert = sHorzVert.Horz
        ScrollTimer.Start()
        _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollRight
        e.Effect = DragDropEffects.None
        If IsNothing(MeItem) Then _gCurrCursor.MakeCursor()
        Me.Invalidate(InvRect)

    Else
        _gCurrCursor.gScrolling = gCursor.eScrolling.No
        If ScrollTimer.Enabled Then _gCurrCursor.MakeCursor()
        ScrollTimer.Stop()
    End If
End Sub

Then SendMessage using a Timer to scroll the control. If the mouse moves further away, speed up the scrolling if allowed, or stop scrolling if it gets too far away.

Enum sHorzVert
    Horz
    Vert
End Enum
Private scrollHorzVert As sHorzVert
Private scrollDirection As Integer
Const SelLVColl As String = _
    "System.Windows.Forms.ListView+SelectedListViewItemCollection"
Const LVItem As String = _
    "System.Windows.Forms.ListViewItem"

Private WithEvents ScrollTimer As New Timer
Private Const WM_HSCROLL As Integer = &H114S
Private Const WM_VSCROLL As Integer = &H115S

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 ScrollTimer_Tick(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles ScrollTimer.Tick

    Try
        If scrollHorzVert = sHorzVert.Vert Then
            If _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollDn Then
                ScrollTimer.Interval = 300 - (10 * _
                (Me.PointToClient(MousePosition).Y - _
                Me.ClientSize.Height))
            ElseIf _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollUp Then
                ScrollTimer.Interval = 300 + (10 * _
                (Me.PointToClient(MousePosition).Y - _
                (Me.Font.Height \ 2)))
            End If
        Else
            If _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollRight Then
                ScrollTimer.Interval = 300 - (10 * _
                (Me.PointToClient(MousePosition).X - Me.ClientSize.Width))
            ElseIf _gCurrCursor.gScrolling = gCursor.eScrolling.ScrollLeft Then
                ScrollTimer.Interval = 300 + (10 * _
                (Me.PointToClient(MousePosition).X))
            End If
        End If
    Catch ex As Exception
    End Try

    If MouseButtons <> Windows.Forms.MouseButtons.Left Or _
        Me.PointToClient(MousePosition).Y >= Me.ClientSize.Height + 30 Or _
        Me.PointToClient(MousePosition).Y <= Me.ClientRectangle.Top - 30 Or _
        Me.PointToClient(MousePosition).X <= -40 Or _
        Me.PointToClient(MousePosition).X >= Me.ClientSize.Width + 30 _
 Then
        ScrollTimer.Stop()
        _gCurrCursor.gScrolling = gCursor.eScrolling.No
        _gCurrCursor.MakeCursor()
    Else
        ScrollControl(Me, scrollDirection)
    End If

End Sub

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

    ' For intDirection, a value of 0 scrolls up and 1 scrolls down.
    If scrollHorzVert = sHorzVert.Horz Then
        SendMessage(objControl.Handle.ToInt32, _
            WM_HSCROLL, intDirection, VariantType.Null)
    Else
        SendMessage(objControl.Handle.ToInt32, _
            WM_VSCROLL, intDirection, VariantType.Null)
    End If
End Sub

Putting It All Together in the DragOver Event

In the DragOver Event the KeyState is checked to see if the Control Key is being pressed to switch between Move and Copy DragDropEffects. Then after getting the Item under the mouse, call the CheckScroller and PaintPointer routines.

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

    If e.Data.GetDataPresent(SelLVColl, False) _
      Or e.Data.GetDataPresent(LVItem, False) Then

        If (e.KeyState And 8) = 8 Then
            e.Effect = DragDropEffects.Copy
        Else
            e.Effect = DragDropEffects.Move
        End If

        Dim Mpt As Point = Me.PointToClient(New Point(e.X, e.Y))
        Dim MeItem As ListViewItem
        MeItem = CType(sender, ListView).GetItemAt(Mpt.X, Mpt.Y)

        If Not IsNothing(_gCurrCursor) _
          AndAlso _AutoScroll <> eAutoScroll.None Then
            CheckScroller(Mpt, MeItem, e)
        End If

        If IsNothing(_gCurrCursor) _
          OrElse _gCurrCursor.gScrolling = gCursor.eScrolling.No Then
            PaintPointer(Mpt, MeItem, e)
        End If

    Else
        e.Effect = DragDropEffects.None
    End If

End Sub

Coloring the Rows

To alternate the color of the rows, I override the OnDrawItem to check if the row index is odd or even to change the BackColor accordingly.

Protected Overrides Sub OnDrawItem( _
  ByVal e As System.Windows.Forms.DrawListViewItemEventArgs)

    MyBase.OnDrawItem(e)

    e.DrawDefault = True

    If _ColorRows Then
        If e.ItemIndex Mod 2 = 0 Then
            e.Item.BackColor = _ColorRowA
        Else
            e.Item.BackColor = _ColorRowB
        End If
    Else
        e.Item.BackColor = Me.BackColor
    End If

End Sub

Protected Overrides Sub OnDrawColumnHeader( _
  ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs)
    MyBase.OnDrawColumnHeader(e)

    e.DrawDefault = True

End Sub

The gCursor

I like to see not only where the item is going, but what item is going. The gCursor [^] lets you display what is being dragged for immediate feedback to the drag contents. I made the gCursor a built-in property of the gListView. Its properties can be set programmatically, but it can be a pain having to run the program every time to test the look of the gCursor. I added a UITypeEditor [^] to make this process easier. So when you add the gListViewControl.dll to your project, also add the gCursor.dll. Add a gListView to the Form and the gCursor to the component tray. Then just like adding an ImageList to a ListView, choose the gCursor from the dropdown in the property grid to add it to the gListView. You can change most of its properties there, but if you select the gCursor in the Component Tray, you can open the property Editor for a richer more complete experience.

gCursorUIEditor

Open the dropdown in the gCurrCursor property to associate a gCursor with the gListView.

After opening the Editor, any adjustments you make will reflect in the example. You can even drag it around to get an even better feel for it. Close the Editor to set the new gCursor.

Dropping on the List

Since you can't use the Items.Insert method to put an item at the end of the list or if it is empty, you have to determine whether to use the Add or Insert. Then determine if you have one item or multiple items and are they moving or copying.

Private Sub gListView_DragDrop(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.DragEventArgs) Handles Me.DragDrop

    If e.Data.GetDataPresent(SelLVColl, False) _
        Or e.Data.GetDataPresent(LVItem, False) Then

        'Determine where and how to add the item
        Dim insertItem As Boolean = True
        Dim dragItem, dragItem_Clone, InsertAtItem As New ListViewItem
        If Me.Items.Count = 0 Then
            insertItem = False
        Else
            If LineAbove Then
                InsertAtItem = Me.Items(LineIndex)
            Else
                If LineIndex + 1 < Me.Items.Count Then
                    InsertAtItem = Me.Items(LineIndex + 1)
                Else
                    InsertAtItem = Me.Items(LineIndex)
                    insertItem = False
                End If
            End If
        End If

        Me.BeginUpdate()

        'Flip the view to refresh the indexing
        Dim lvViewState As View = Nothing
        lvViewState = Me.View
        Me.View = View.List

        If e.Data.GetDataPresent(SelLVColl, False) Then
            Dim DragItems As SelectedListViewItemCollection = _
                CType(e.Data.GetData(SelLVColl), _
                SelectedListViewItemCollection)
            If Not DragItems.Contains(InsertAtItem) Then
                For Each dragItem In DragItems
                    dragItem_Clone = CType(dragItem.Clone, ListViewItem)
                    If _MatchFont Then
                        dragItem_Clone.Font = Me.Font
                        dragItem_Clone.ForeColor = Me.ForeColor
                    End If
                    If insertItem = False Then
                        Me.Items.Add(dragItem_Clone)
                    Else
                        Me.Items.Insert(InsertAtItem.Index, dragItem_Clone)
                    End If
                    If e.Effect = DragDropEffects.Move Then
                        dragItem.Remove()
                    End If
                Next
            End If

        ElseIf e.Data.GetDataPresent(LVItem, False) Then
            dragItem = CType(e.Data.GetData(LVItem), ListViewItem)
            dragItem_Clone = CType(dragItem.Clone, ListViewItem)
            If _MatchFont Then
                dragItem_Clone.Font = Me.Font
                dragItem_Clone.ForeColor = Me.ForeColor
            End If
            If insertItem = False Then
                Me.Items.Add(dragItem_Clone)
            Else
                Me.Items.Insert(InsertAtItem.Index, dragItem_Clone)
            End If
            If e.Effect = DragDropEffects.Move Then
                dragItem.Remove()
            End If
        End If

        Me.View = lvViewState
        Me.EndUpdate()
        InsertAtItem.EnsureVisible()
    End If

End Sub

Using the gListView

After dropping a gListView on your Form, go to the ItemDrag Event where you would normally put the DoDragDrop Call. Add any changes to the gCursor just before the DoDragDrop, if you are using it. Take a look at Form2 to see how simple it can be to use.

Private Sub GListView4_ItemDrag(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.ItemDragEventArgs) _
    Handles GListView4.ItemDrag

    Dim glist As gListView = CType(sender, gListView)
    Dim glistitem As ListViewItem = CType(e.Item, ListViewItem)
    With glist.gCurrCursor
        .gImage = CType(glist.LargeImageList.Images( _
            glist.SelectedItems(0).ImageKey), Bitmap)
        .gText = glistitem.Text & vbCrLf & glistitem.SubItems(1).Text
        .MakeCursor()
    End With

    glist.DoDragDrop(glist.SelectedItems(0), DragDropEffects.Move)

End Sub

History

  • Version 1.0 - March 2009

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

SSDiver2112
Software Developer
United States United States
I first got hooked on programing with the TI994A. After it finally lost all support I reluctantly moved to the Apple IIe. Thank You BeagleBros for getting me through. I wrote programs for my Scuba buisness during this time. Currently I am a Database manager and software developer. I started with VBA and VB6 and now having fun with VB.NET/WPF/C#...

You may also be interested in...

Comments and Discussions

 
Generalfirst feelings Pin
konikula15-Dec-09 14:03
memberkonikula15-Dec-09 14:03 
GeneralRe: first feelings Pin
SSDiver211215-Dec-09 18:16
memberSSDiver211215-Dec-09 18:16 
GeneralRe: first feelings Pin
konikula16-Dec-09 4:33
memberkonikula16-Dec-09 4:33 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160530.1 | Last Updated 7 Apr 2009
Article Copyright 2009 by SSDiver2112
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid