适用于 Silverlight 4.0 的通用多选 MVVM 列表框拖放辅助程序,带自定义反馈






4.91/5 (3投票s)
本文档重点介绍为 Silverlight 开发一个兼容 MVVM 的列表框到列表框的拖放辅助程序。
目录
- 引言
- 背景
- 必备组件
- 项目设计
- ListDragDropHelper 类概述
- ListDragDropHelper 类的工作原理
- 为列表框拖放配置 ViewModel
- 连接 XAML
- 示例应用
- 特别提及
- 最终评论
- 历史
引言
本文档重点介绍为 Silverlight 开发一个兼容 MVVM 的列表框到列表框的拖放辅助程序。市面上已经有很多关于 MVVM 模式及其优点的优秀文章,我们不会再让大家感到困惑。
背景
我们在 Silverlight LOB 应用程序中需要一个高度灵活的拖放库,该库需要:
- 符合 MVVM 模式
- 支持
Single
、Multiple
和Extended
列表框选择模式 - 最少的编码要求
- 拖动悬停工具提示,支持 Blend 模板
- 低 CPU 使用率
- 轻量级
- 免费
环顾四周,我们没有找到符合以上标准的解决方案。那些接近的要么功能过多(过于臃肿),要么不支持 MVVM(仅限代码后置)。
我们为该解决方案使用了 .NET 4.0,但将其转换为 .NET 3.5 并不需要太多改动。
先决条件
我们本想让此解决方案不依赖任何第三方框架;但是,我们需要一个针对列表框控件的 MouseMove
事件的包装器。
因此,该解决方案使用了 MVVM 框架 - Laurent Bugnion (GalaSoft) 的 MVVM Light。我们使用 EventToCommand
和 RelayCommand
将 XAML 连接到 ListDragDropHelper
类。
我敢肯定每个人都有自己喜欢的框架;但在这篇文章中,我们希望将重点放在解决方案上。不过,要进行更改非常容易,只需要调整 ListDragDropHelper
类中的一个属性,即名为 ChildMoveCommand
的 RelayCommand
属性,以及 XAML 代码中每个列表框的 MouseMove
事件,以替换为您喜欢的 MVVM 框架的相应部分。
项目设计
为了确保代码符合 MVVM 模式的要求,即使这是一个小型示例应用程序,将代码分解为单独的项目模块也是一个好策略:
- SL MVVM DragDropHelper 示例 - 包含视图的主应用程序
- Models - 所有数据模型
- ViewModels - 所有视图模型和视图模型定位器
- Services - 模拟数据服务
- Helpers - 支持类
- ListDragDropSL - 拖放服务和
DragDropToolTip
控件
下面是包含的示例项目的类图。该示例项目演示了如何使用 ListDragDropHelper 类单独或批量(扩展多选)重新标记和分组产品。
ListDragDropHelper 类概述
在构建 ListDragDropHelper
类时,我们希望尽量减少连接视图 <=> ViewModel <=> ListDragDropHelper 的工作量。因此,我们将过程简化为以下步骤:
- 将
DragDropToolTip
控件放置在包含所有将参与拖放操作的列表框的父Panel
控件(Grid
/Canvas
/等)上,并为其命名; - 在 ViewModel 中为每个可以发起拖放操作的列表框声明一个
ListDragDropHelper
属性; - 为每个列表框的
MouseMove
事件添加一个触发器; - 在 ViewModel 中编写悬停和放置处理程序;
- 样式化
DragDropToolTip
控件。
我们本可以将 DragDropToolTip
控件完全在 ListDragDropHelper
类中生成;但是,我们将其用作一个标记,以便快速识别列表框的 Panel
控件,用于拖放操作。优点是,这使得我们可以在 Visual Studio XAML 编辑器或 Expression Blend 中轻松地为控件设置样式。
ListDragDropHelper 类的工作原理
我们选择该类的关键部分来突出显示其核心功能。要查看完整代码,请下载本文档顶部提供的解决方案。所有源代码都已完全注释,易于理解。
在列表框 MouseMove
事件启动时,ListDragDropHelper
类将执行以下操作:
- 检查鼠标按钮是否按下;
- 根据
DragDropToolTip
控件的定位来确定 XAML 结构; - 定位
DragDropToolTip
控件的 Z-Index(适用于所有面板类型); - 连接父控件(UserControl/Navigation Page)的
MouseMove
和LeftMouseButtonUp
事件;
为什么我们不使用列表框的 LeftMouseButtonDown
事件?这个事件从未触发。我们看到一些解决方案使用了 SelectedItem
或 GotFocus
事件,但这些事件存在问题。SelectedItem
事件仅在项目最初被选中时触发。SelectedItem
事件的另一个问题是,即使用户不想拖动,拖动操作也会在 LeftButtonMouseDown
时启动。您可以通过使用 Timer
或在启动拖放操作之前观察拖动距离来解决此问题;但是,您仍然面临 SelectedItem
事件仅对新选择触发的初始问题。GotFocus
事件对于列表框控件只触发一次。
为了解决这些问题,如上所述,我们监视列表框的 MouseMove
事件,并使用 CaptureMouse
(System.Windows.UIElement
) 方法来确定鼠标按钮是否按下。这样做有一个“窍门”,但我们有一个简单的解决方案,在文章中进行了更详细的说明,演示了我们如何处理这个问题。
Private Function IsMouseDown() As Boolean
If _dragListBox IsNot Nothing AndAlso _dragListBox.CaptureMouse() Then
_dragListBox.ReleaseMouseCapture()
Return True
End If
Return False
End Function
父宿主控件的 MouseMove
和 LeftMouseButtonUp
事件将管理拖放操作直至完成。在拖放操作期间,ViewModel 中的处理程序对 DragDropToolTip
控件中显示的信息内容和方式拥有完全控制权,从而为用户提供反馈。
其中一项要求是最小化 CPU 使用率。监视 MouseMove
事件在鼠标位于事件所属的边界控件内时会快速触发。当拖放操作正在进行时,我们监视父控件的 MouseMove
事件,以便我们能够跨越 XAML 层从一个列表框连接到下一个。如果您有多个列表框,例如包含的示例项目,那么 CPU 将会承受巨大压力,因为多个 ListDragDropHelper
类会跟踪同一边界面板控件中鼠标的位置。为了克服这个问题,我们只在拖放操作开始时监视父 MouseMove
事件,并在完成后移除处理程序。这样,在拖放操作期间,只有一个 ListDragDropHelper
类会跟踪鼠标的位置。使用这种方法的另一个好处是,它将编码量最小化到每个列表框的一个触发器,以及进入辅助类的单一入口点。
'-- Start Drag
Private Sub ChildContainerMouseMove(e As MouseEventArgs)
'-- Are we about to Drag?
If (Not _isBusy) AndAlso (Not _isDragging) Then
If Not _isDragMode Then
'-- Control discovery only when we really need it
If _dragListBox Is Nothing Then
_dragListBox = FindListBoxContainer(e.OriginalSource)
End If
_isDragMode = IsMouseDown()
If _isDragMode Then
...
'-- Only allow DragDrop operation if
If _DragDropToolTip IsNot Nothing Then
...
'-- Only wire up the parent mouse events
' if/when we require them to reduce CPU usage
AddHandler _Host.MouseMove, AddressOf HostContainerMouseMove
AddHandler _Host.MouseLeftButtonUp, _
AddressOf HostContainerMouseLeftButtonUp
End If
End If
End If
End If
End Sub
'-- Stop Drag
Private Sub HostContainerMouseLeftButtonUp(Sender As Object, e As MouseEventArgs)
...
'-- Cleanup after Drag/Drop operation
_isDragMode = IsMouseDown()
If _isDragging Then
...
'-- Don't need to keep listening to parent mouse event. Free up resources
RemoveHandler _Host.MouseMove, AddressOf HostContainerMouseMove
RemoveHandler _Host.MouseLeftButtonUp, _
AddressOf HostContainerMouseLeftButtonUp
End If
End Sub
当您使用单个 ListDragDropHelper
类为多个列表框设置拖放操作(在列表框的 MouseMove
事件期间使用 CaptureMouse
捕获 MouseDown
)时,一个潜在的问题是会触发多个列表框的拖放操作。为了克服这个问题,我们有一个共享(静态)变量来跟踪我们是否处于拖放操作中。这确保在拖放操作期间,只有一个 ListDragDropHelper
类的实例处于活动状态。
'-- Prevent Race condition with multiple handlers
Private Shared _isBusy As Boolean = False
...
'-- Start Drag
Private Sub ChildContainerMouseMove(e As MouseEventArgs)
'-- Are we about to Drag?
If (Not _isBusy) AndAlso (Not _isDragging) Then
...
End If
End Sub
'-- Main Drag/Hover Logic
Private Sub HostContainerMouseMove(Sender As Object, e As MouseEventArgs)
'-- Dragging
If _isDragMode Then
...
If Not _isDragging Then '-- Initiate Drag/Drop operation
...
'-- Set Local and global dragging states -
' only one local can be active at any time
_isDragging = (_isBusy = False)
If _isDragging Then _isBusy = _isDragging
...
End If
End If
End Sub
'-- Stop Drag
Private Sub HostContainerMouseLeftButtonUp(Sender As Object, e As MouseEventArgs)
...
'-- Cleanup after Drag/Drop operation
_isDragMode = IsMouseDown()
If _isDragging Then
_isBusy = False
...
End If
End Sub
DragDropToolTip
控件在拖放操作期间显示;但是,该控件可以放置在 XAML 代码的任何位置。我们通过代码控制 Z-Index,而不是强制特定的放置顺序。Canvas
控件有一个 Z-Index 设置器,可以轻松地将控件置于前面。对于 Grid
控件等其他面板控件,没有 Z-Index 设置器。DragDropToolTip
控件通常在不使用时隐藏。因此,为了克服 Grid
控件等面板控件 Z-Index 设置器的缺失,我们更改了 DragDropToolTip
控件在面板控件子元素中的位置 - 从而有效地将 DragDropToolTip
控件置于前面。
...
If _DragDropToolTip IsNot Nothing Then
'-- Bring to front _DragDropToolTip (DragTip)
With CType(_DragDropToolTip.Parent, Panel).Children
'-- only Canvas has ZIndex Setter.
' So we need to reorder the controls manually just in case
' the DragTip is not on top...
.Remove(_DragDropToolTip)
.Add(_DragDropToolTip)
End With
...
End If
...
对于 ListDragDropHelper
类,我们需要做的最后一件事是公开一个属性供列表框 MouseMove
触发器绑定。
#Region "Relay Commands"
'-- App View Event Hook
Public Property ChildMoveCommand() As RelayCommand(Of MouseEventArgs)
Private Sub _InitCommands()
ChildMoveCommand = New RelayCommand(Of MouseEventArgs)(_
Sub(e) ChildContainerMouseMove(e))
End Sub
#End Region
为列表框拖放配置 ViewModel
为了使视图能够与 ListDragDropHelper
类进行通信,我们需要为每个可以发起拖放操作的列表框公开一个属性。
#Region "View Binding Properties"
'-- Drag Drop Services
Property ProductsDragDropHelper As ListDragDropHelper
Property TagsDragDropHelper As ListDragDropHelper
Property GroupsDragDropHelper As ListDragDropHelper
#End Region
#Region "Constructor"
Sub New()
'-- Wiring up Drag Drop Services
ProductsDragDropHelper = New ListDragDropHelper("DragDropToolTip", _
Function(s, d, fb) HandleHover(s, d, fb), Sub(s, d) HandleDrop(s, d))
TagsDragDropHelper = New ListDragDropHelper("DragDropToolTip", _
Function(s, d, fb) HandleHover(s, d, fb), Sub(s, d) HandleDrop(s, d))
GroupsDragDropHelper = New ListDragDropHelper("DragDropToolTip", _
Function(s, d, fb) HandleHover(s, d, fb), Sub(s, d) HandleDrop(s, d))
End Sub
#End Region
#Region "Event Handlers: Drag Hover / Drop"
Private Function HandleHover(SelectedItems As Object,_
DestItem As Object,_
DragFeedback As ListDragDropHelper.FeedbackStates) _
As ListDragDropHelper.FeedbackStates
'-- Code goes here for controlling DragDropToolTip display
' and feedback icon and tip (see below sample code)
...
End Sub
Private Sub HandleDrop(ByRef SelectedItems As Object, DestItem As Object)
'-- Code goes here for Processing the Drop Event (see below sample code)
...
End Sub
#End Region
连接 XAML
最后,我们需要设置一个触发器来触发列表框的 MouseMove
事件。
<ListBox SelectionMode="Extended">
<i:Interaction.Triggers>
<!-- Trigger for Start Drag (LeftMouseDown does not fire). -->
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand
Command="{Binding DragDropHelper.ChildMoveCommand, Mode=OneWay}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
示例应用程序
包含的示例代码演示了如何为列表框同时使用 Single
和 Extended
选择模式。我们使用了三个列表框:Products、Tags 和 Groups。一个产品可以有多个标签,并且只有一个组。拖放操作允许以下规则:
- 多个产品可以被选中并拖放到单个标签或组上。
- 多个标签可以被拖放到单个产品或组上 - 通过组链接将一组标签应用到单个/多个产品上。
- 将单个组拖放到单个产品上。
这是 ViewModel 中处理悬停事件的代码,该事件根据上述规则向用户提供反馈。它根据源的模型类型(仅 ListBoxItem
(s))识别谁开始了操作,并根据目标模型类型决定显示什么。如果没有找到目标模型类型(仅 ListBoxItem
),则操作必须是非法的。通过将模型从 ListDragDropHelper
类传递给 ViewModel,ViewModel 不会知道模型在视图中是如何显示的。
Private Function HandleHover(SelectedItems As Object,_
DestItem As Object,_
DragFeedback As ListDragDropHelper.FeedbackStates) _
As ListDragDropHelper.FeedbackStates
'-- Drag View Feedback
Dim Move As New ListDragDropHelper.FeedbackStates With _
{.Drag = ListDragDropHelper.DragFeedbackState.Move,
.Drop = ListDragDropHelper.DropFeedbackState.Move}
Dim Allowed As New ListDragDropHelper.FeedbackStates With _
{.Drag = ListDragDropHelper.DragFeedbackState.Copy,
.Drop = ListDragDropHelper.DropFeedbackState.Add}
Dim NotAllowed As New ListDragDropHelper.FeedbackStates With _
{.Drag = ListDragDropHelper.DragFeedbackState.NotAllowed,
.Drop = ListDragDropHelper.DropFeedbackState.None}
If SelectedItems Is Nothing Then Return NotAllowed
Dim Items As ObservableCollection(Of Object) = _
CType(SelectedItems, ObservableCollection(Of Object))
If Items.Count = 0 Then Return NotAllowed
Dim SrcType As Type = SelectedItems(0).GetType
Select Case True
Case SrcType Is GetType(ProductModel)
'-- Products require a Destination Tag/group
If DestItem IsNot Nothing Then
Select Case True
Case DestItem.GetType Is GetType(SummaryTagModel)
With Allowed
.HoverMessage = "BULK RETAG"
.DropMessage = _
String.Format("Set '{0}'{2}for '{1} Products'",
CType(DestItem, SummaryTagModel).Tag,
SelectedItems.count,
vbNewLine)
'.Drag = ListDragDropHelper.DragFeedbackState.None
' Uncomment if no drag icon is required
'.Drop = ListDragDropHelper.DropFeedbackState.None
' Uncomment if no drop tooltip is required
End With
Return Allowed
Case DestItem.GetType Is GetType(SummaryGroupModel)
With Allowed
.HoverMessage = "CHANGE GROUP"
.DropMessage = _
String.Format("Set '{0}'{2}for '{1} Products'",
CType(DestItem, SummaryGroupModel).title,
SelectedItems.count,
vbNewLine)
End With
Return Allowed
Case DestItem.GetType Is GetType(ProductModel)
With Move
.HoverMessage = "SELECT"
.DropMessage = _
String.Format("Drag '{0} Products' to a " & _
"Tag to{1}Bulk Tag Change or{1}to a Group...",
SelectedItems.count,
vbNewLine)
End With
Return Move
End Select
End If
Case SrcType Is GetType(SummaryTagModel)
'-- Tags require a Destination product/group
If DestItem IsNot Nothing Then
Select Case True
Case DestItem.GetType Is GetType(ProductModel)
With Allowed
.HoverMessage = "BULK TAG"
.DropMessage = _
String.Format("Set '{0}'{2}for '{1} Tags'",
CType(DestItem, ProductModel).title,
SelectedItems.count,
vbNewLine)
End With
Return Allowed
Case DestItem.GetType Is GetType(SummaryTagModel)
With Move
.HoverMessage = "SELECT"
.DropMessage = String.Format("Drag '{0}' to a " & _
"product {1}to set the Tag{1}or " & _
"to a Group to Bulk change Groups...",
CType(SelectedItems(0), SummaryTagModel).Tag,
vbNewLine)
End With
Return Move
Case DestItem.GetType Is GetType(SummaryGroupModel)
With Allowed
.HoverMessage = "BULK CHANGE GROUPS"
.DropMessage = _
String.Format("Set '{0}'{2}for '{1}' Products",
CType(DestItem, SummaryGroupModel).title,
CType(SelectedItems(0), SummaryTagModel).num_products,
vbNewLine)
End With
Return Allowed
End Select
End If
Case SrcType Is GetType(SummaryGroupModel)
'-- Groups require a Destination Product
If DestItem IsNot Nothing Then
Select Case True
Case DestItem.GetType Is GetType(ProductModel)
With Allowed
.HoverMessage = "CHANGE GROUP"
.DropMessage = _
String.Format("Set '{0}'{2}for '{1}' Products",
CType(SelectedItems(0), SummaryGroupModel).title,
CType(DestItem, ProductModel).title,
vbNewLine)
End With
Return Allowed
Case DestItem.GetType Is GetType(SummaryGroupModel)
With Move
.HoverMessage = "SELECT"
.DropMessage = _
String.Format("Drag '{0}' to a product " & _
"{1}to set the Group...", _
CType(SelectedItems(0), SummaryGroupModel).title,
vbNewLine)
End With
Return Move
End Select
End If
End Select
Return NotAllowed
End Function
下面是处理放置操作的代码。同样,ListDragDropHelper
类只将模型数据传递给 ViewModel。
Private Sub HandleDrop(ByRef SelectedItems As Object, DestItem As Object)
'-- Drop Data handler
If SelectedItems Is Nothing Then Return
'-- Reset trackers
ProductsNoTagRoom.Clear()
ProductsUpdated.Clear()
ProductsSkipped.Clear()
Dim Items As ObservableCollection(Of Object) = _
CType(SelectedItems, ObservableCollection(Of Object))
If Items.Count = 0 Then Return
Dim SrcType As Type = SelectedItems(0).GetType
Select Case True
Case SrcType Is GetType(ProductModel)
'-- Products require a Destination Tag/group
If DestItem IsNot Nothing Then
ProcessProductMove(Items, DestItem)
Return
End If
Case SrcType Is GetType(SummaryTagModel)
'-- Tags require a Destination product/group
If DestItem IsNot Nothing Then
ProcessTagsMove(Items, DestItem)
End If
Case SrcType Is GetType(SummaryGroupModel)
'-- Groups require a Destination Tag/group
If DestItem IsNot Nothing Then
ProcessGroupMove(Items, DestItem)
End If
End Select
Return
End Sub
Private Sub ProcessProductMove(SelectedItems As _
ObservableCollection(Of Object), DestItem As Object)
Select Case True
Case DestItem.GetType Is GetType(SummaryTagModel)
Dim DestTag As TagModel = CType(DestItem, TagModel)
For Each item As ProductModel In SelectedItems
Dim product As ProductModel = _
Products.Where(Function(l) l.product_id = _
item.product_id).SingleOrDefault
Dim Tag As TagModel = product.tags.Where(Function(t) t.Tag = _
DestTag.Tag).SingleOrDefault
If Tag Is Nothing Then
If product.tags.Count > 10 Then
'-- too many
ProductsNoTagRoom.Add(product)
Else
'-- we have room...
product.tags.Add(DestTag)
ProductsUpdated.Add(product)
End If
Else
'-- already set
ProductsSkipped.Add(product)
End If
Next
'-- Refresh
UpdateTagSumaryList(New ObservableCollection(Of Object) From {DestItem})
Case DestItem.GetType Is GetType(SummaryGroupModel)
Dim Destgroup As GroupModel = CType(DestItem, GroupModel)
For Each item As ProductModel In SelectedItems
Dim product As ProductModel = _
Products.Where(Function(l) l.product_id = _
item.product_id).SingleOrDefault
If product.group.group_id = Destgroup.group_id Then
'-- already set
ProductsSkipped.Add(product)
Else
'-- Ok to change...
product.group = Destgroup
ProductsUpdated.Add(product)
End If
Next
'-- Refresh
UpdateGroupSumaryList(Destgroup)
Case Else
'-- Invalid ListBox - Shouldn't get here!
End Select
Return
End Sub
Private Sub ProcessTagsMove(SelectedItems As _
ObservableCollection(Of Object), DestItem As Object)
Select Case True
Case DestItem.GetType Is GetType(ProductModel)
For Each item As TagModel In SelectedItems
Dim product As ProductModel =
Products.Where(Function(l) l.product_id =
CType(DestItem, ProductModel).product_id).SingleOrDefault
Dim Tag As TagModel =
product.tags.Where(Function(t) t.Tag = item.Tag).SingleOrDefault
If Tag Is Nothing Then
If product.tags.Count > 10 Then
'-- too many
ProductsNoTagRoom.Add(product)
Else
'-- we have room...
product.tags.Add(item)
ProductsUpdated.Add(product)
End If
Else
'-- already set
ProductsSkipped.Add(product)
End If
Next
'-- Refresh
UpdateTagSumaryList(SelectedItems)
Case DestItem.GetType Is GetType(SummaryGroupModel)
Dim Destgroup As GroupModel = CType(DestItem, GroupModel)
For Each item As TagModel In SelectedItems
For Each product As ProductModel In Products
If (From t As TagModel In product.tags _
Where t.Tag = item.Tag).Count Then
If product.group.group_id = Destgroup.group_id Then
'-- already set
ProductsSkipped.Add(product)
Else
'-- Ok to change...
product.group = Destgroup
ProductsUpdated.Add(product)
End If
End If
Next
Next
'-- Refresh
UpdateGroupSumaryList(Destgroup)
Case Else
'-- Invalid ListBox - Shouldn't get here!
End Select
Return
End Sub
Private Sub ProcessGroupMove(SelectedItems As _
ObservableCollection(Of Object), DestItem As Object)
If DestItem.GetType Is GetType(ProductModel) Then
Dim srcGroup As SummaryGroupModel = _
CType(SelectedItems(0), SummaryGroupModel)
Dim product As ProductModel = _
Products.Where(Function(l) l.product_id = _
CType(DestItem, ProductModel).product_id).SingleOrDefault
If product.group.group_id = srcGroup.group_id Then
'-- already set
ProductsSkipped.Add(product)
Else
'-- Ok to change...
product.group = srcGroup
ProductsUpdated.Add(product)
End If
'-- Refresh
UpdateGroupSumaryList(SelectedItems(0))
End If
End Sub
示例项目没有对已经使用或超出数量限制的标签进行冲突或映射解析。我将把这项练习留给您。
不过,我在主窗体上添加了一个反馈控件,用于显示拖放操作的结果。这是更新 SummaryTagModel
和 SummaryGroupModel
集合以刷新标签和组列表框的代码。下面的代码只更新有更改的项,而不是更新整个列表。反馈控件绑定到这些集合,并反映任何更改。
'-- Tag Drop Management
ProductsNoTagRoom = New ObservableCollection(Of ProductModel)
ProductsUpdated = New ObservableCollection(Of ProductModel)
ProductsSkipped = New ObservableCollection(Of ProductModel)
...
Private Sub UpdateTagSumaryList(Items As ObservableCollection(Of Object))
Dim tmpTags As ObservableCollection(Of SummaryTagModel) = _
MockDataProvider.GetTags(Products)
'-- Only update changes
For Each tag As SummaryTagModel In Items
Dim destTag As SummaryTagModel = _
Tags.Where(Function(t) t.Tag = tag.Tag).SingleOrDefault
destTag.SetData(tmpTags.Where(Function(t) t.Tag = _
tag.Tag).SingleOrDefault)
Next
End Sub
Private Sub UpdateGroupSumaryList(Item As SummaryGroupModel)
'-- Only update change
Dim tmpGroups As ObservableCollection(Of SummaryGroupModel) = _
MockDataProvider.GetGroups(Products)
Dim destSection As SummaryGroupModel = Groups.Where(Function(ss) _
ss.group_id = Item.group_id).SingleOrDefault
destSection.SetData(tmpGroups.Where(Function(ss) ss.group_id = _
destSection.group_id).SingleOrDefault)
End Sub
ListDragDropHelper
类还可以支持列表框的重新排序和其他操作;但是,这些操作不包含在示例应用程序中。例如,列表框的重新排序非常简单 - HandleDrop
方法中的 SelectedItems
将是要移动的项目,DestItem
将是新位置。删除 SelectedItems
,然后将 destItem
插入到索引 x
中。要将 SelectedItems
移动到列表末尾,destItem
将为空。使用 .Add(x)
而不是 .InsertAt(x)
。
特别提及
DragDropToolTip
控件中使用的图标最初来自 CodePlex 上的 Silverlight Toolkit。
最后评论
这是我的第一次投稿,希望您觉得它很有用。
历史
- 2011 年 4 月 11 日 - 初始发布。