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






4.87/5 (18投票s)
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
使用
ColorRowA
和ColorRowB
来交替行颜色Public Property ColorRowA() As Color
当
ColorRows = True
时,Item 的Color
在ColorRowA
和ColorRowB
之间交替Public Property ColorRowB() As Color
当
ColorRows = True
时,Item 的Color
在ColorRowA
和ColorRowB
之间交替
参考链接
此项目中使用的(稍后引用的)一些概念已在以下文章中得到解释:
- gCursor [^]
- UITypeEditorsDemo [^]
拖动到列表上方
列表中拖放的最大问题是,在松开鼠标按钮时,无法精确确定项目将最终放在哪里。为了确定在哪里绘制指针和参考插入点,您需要知道:
- 鼠标是在项上还是在空白处?
View
是否处于LargeIcon
模式?- 插入点是在项的上方还是下方?
- 指针是否已移动到新位置?
- 鼠标是否靠近边缘以触发滚动?
添加插入指针

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
之间切换。然后,在获取鼠标下方的项之后,调用 CheckScroller
和 PaintPointer
例程。
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
属性中的下拉列表,将 gCursor
与 gListView
关联。

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