65.9K
CodeProject 正在变化。 阅读更多。
Home

gListView - 具有视觉拖放反馈的 ListView 控件 (VB.NET)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.87/5 (18投票s)

2009 年 4 月 1 日

CPOL

4分钟阅读

viewsIcon

101441

downloadIcon

2855

ListView 具有放置指针、交替行着色和自定义拖动光标。

引言

我需要一个 ListView 来提供更多的拖放反馈。我找到的几个示例对我的需求来说不够完整。我开始着手自己开发来填补空白。我需要内置的重新排序功能,带有视觉插入指针,自动滚动,以及更好的正在拖动的项的示例。我实现了初始阶段,并且在开发了 gCursor [^] 后,一切都变得完美了。gListView 继承自 ListView 并具有以下附加属性:

属性和枚举

    Enum eAutoScroll
        None
        All
        Vertical
        Horizontal
    End Enum

以下是主要属性列表

  • Public Property gCurrCursor() As gCursor

    设置 gCursor

  • Public Property gCursorVisible() As Boolean

    使用或不使用 gCursor

  • Public Property DropBarColor() As Color

    插入点的指针的 Color

  • Public Property AutoScroll() As eAutoScroll

    要使用的自动滚动类型

  • Public Property MatchFont() As Boolean

    当放置 ListViewItem 时,它会保留其原始字体还是更改为匹配此 ListView 的字体?

  • Public Property ColorRows() As Boolean

    使用 ColorRowAColorRowB 来交替行颜色

  • Public Property ColorRowA() As Color

    ColorRows = True 时,Item 的 ColorColorRowAColorRowB 之间交替

  • Public Property ColorRowB() As Color

    ColorRows = True 时,Item 的 ColorColorRowAColorRowB 之间交替

参考链接

此项目中使用的(稍后引用的)一些概念已在以下文章中得到解释:

拖动到列表上方

列表中拖放的最大问题是,在松开鼠标按钮时,无法精确确定项目将最终放在哪里。为了确定在哪里绘制指针和参考插入点,您需要知道:

    1. 鼠标是在项上还是在空白处?
    2. View 是否处于 LargeIcon 模式?
    3. 插入点是在项的上方还是下方?
    4. 指针是否已移动到新位置?
    5. 鼠标是否靠近边缘以触发滚动?

添加插入指针

    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

自动滚动列表

尤其是在重新排序列表时,当拖动时,您需要能够让 gListView 自动滚动。首先确定滚动的方向以及是否允许滚动。

    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

然后使用 Timer SendMessage 来滚动控件。如果鼠标离边缘越来越远,则如果允许,加快滚动速度,或者如果太远则停止滚动。

    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

在 DragOver 事件中整合一切

DragOver 事件 中,检查 KeyState 以查看是否按下了 Control 键,以在 Move 和 Copy DragDropEffects 之间切换。然后,在获取鼠标下方的项之后,调用 CheckScrollerPaintPointer 例程。

    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

为行着色

为了交替行的颜色,我重写了 OnDrawItem 来检查行索引是奇数还是偶数,以便相应地更改 BackColor

    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

gCursor

我喜欢看到项目要去哪里,以及什么项目要去。gCursor [^] 允许您显示正在拖动的项,以便对拖动内容进行即时反馈。我将 gCursor 设置为 gListView 的内置属性。它的属性可以以编程方式设置,但每次都要运行程序来测试 gCursor 的外观可能会很麻烦。我添加了一个 UITypeEditor [^],以使此过程更轻松。因此,当您将 gListViewControl.dll 添加到项目中时,也要添加 gCursor.dll。将 gListView 添加到 Form,将 gCursor 添加到组件托盘。然后,就像将 ImageList 添加到 ListView 一样,从属性网格中的下拉列表中选择 gCursor,将其添加到 gListView。您可以在那里更改其大多数属性,但如果选择组件托盘中的 gCursor,则可以打开属性编辑器以获得更丰富、更完整的体验。

gCursorUIEditor

打开 gCurrCursor 属性中的下拉列表,将 gCursorgListView 关联。

打开编辑器后,您进行的任何调整都会反映在示例中。您甚至可以拖动它以获得更好的感觉。关闭编辑器以设置新的 gCursor

将项目放到列表中

由于您无法使用 Items.Insert 方法将项添加到列表末尾或列表为空时,您必须确定使用 Add 还是 Insert。然后确定您有一个项还是多个项,以及它们是移动还是复制。

    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

使用 gListView

gListView 放置到您的 Form 上之后,转到 ItemDrag Event,通常您会在那里放置 DoDragDrop Call。如果在拖放之前,您正在使用 gCursor,请在此之前添加对 gCursor 的任何更改。看看 Form2,了解它的使用有多简单。

    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

历史

  • 版本 1.0 - 2009 年 3 月
© . All rights reserved.