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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (3投票s)

2011年4月11日

CPOL

9分钟阅读

viewsIcon

55275

downloadIcon

1008

本文档重点介绍为 Silverlight 开发一个兼容 MVVM 的列表框到列表框的拖放辅助程序。

Sample Image

目录

引言

本文档重点介绍为 Silverlight 开发一个兼容 MVVM 的列表框到列表框的拖放辅助程序。市面上已经有很多关于 MVVM 模式及其优点的优秀文章,我们不会再让大家感到困惑。

背景

我们在 Silverlight LOB 应用程序中需要一个高度灵活的拖放库,该库需要:

  1. 符合 MVVM 模式
  2. 支持 SingleMultipleExtended 列表框选择模式
  3. 最少的编码要求
  4. 拖动悬停工具提示,支持 Blend 模板
  5. 低 CPU 使用率
  6. 轻量级
  7. 免费

环顾四周,我们没有找到符合以上标准的解决方案。那些接近的要么功能过多(过于臃肿),要么不支持 MVVM(仅限代码后置)。

我们为该解决方案使用了 .NET 4.0,但将其转换为 .NET 3.5 并不需要太多改动。

先决条件

我们本想让此解决方案不依赖任何第三方框架;但是,我们需要一个针对列表框控件的 MouseMove 事件的包装器。

因此,该解决方案使用了 MVVM 框架 - Laurent Bugnion (GalaSoft) 的 MVVM Light。我们使用 EventToCommandRelayCommand 将 XAML 连接到 ListDragDropHelper 类。

我敢肯定每个人都有自己喜欢的框架;但在这篇文章中,我们希望将重点放在解决方案上。不过,要进行更改非常容易,只需要调整 ListDragDropHelper 类中的一个属性,即名为 ChildMoveCommandRelayCommand 属性,以及 XAML 代码中每个列表框的 MouseMove 事件,以替换为您喜欢的 MVVM 框架的相应部分。

项目设计

为了确保代码符合 MVVM 模式的要求,即使这是一个小型示例应用程序,将代码分解为单独的项目模块也是一个好策略:

  • SL MVVM DragDropHelper 示例 - 包含视图的主应用程序
  • Models - 所有数据模型
  • ViewModels - 所有视图模型和视图模型定位器
  • Services - 模拟数据服务
  • Helpers - 支持类
  • ListDragDropSL - 拖放服务和 DragDropToolTip 控件

下面是包含的示例项目的类图。该示例项目演示了如何使用 ListDragDropHelper 类单独或批量(扩展多选)重新标记和分组产品。

ListDragDropHelper 类概述

在构建 ListDragDropHelper 类时,我们希望尽量减少连接视图 <=> ViewModel <=> ListDragDropHelper 的工作量。因此,我们将过程简化为以下步骤:

  1. DragDropToolTip 控件放置在包含所有将参与拖放操作的列表框的父 Panel 控件(Grid/Canvas/等)上,并为其命名;
  2. 在 ViewModel 中为每个可以发起拖放操作的列表框声明一个 ListDragDropHelper 属性;
  3. 为每个列表框的 MouseMove 事件添加一个触发器;
  4. 在 ViewModel 中编写悬停和放置处理程序;
  5. 样式化 DragDropToolTip 控件。

我们本可以将 DragDropToolTip 控件完全在 ListDragDropHelper 类中生成;但是,我们将其用作一个标记,以便快速识别列表框的 Panel 控件,用于拖放操作。优点是,这使得我们可以在 Visual Studio XAML 编辑器或 Expression Blend 中轻松地为控件设置样式。

ListDragDropHelper 类的工作原理

我们选择该类的关键部分来突出显示其核心功能。要查看完整代码,请下载本文档顶部提供的解决方案。所有源代码都已完全注释,易于理解。

在列表框 MouseMove 事件启动时,ListDragDropHelper 类将执行以下操作:

  1. 检查鼠标按钮是否按下;
  2. 根据 DragDropToolTip 控件的定位来确定 XAML 结构;
  3. 定位 DragDropToolTip 控件的 Z-Index(适用于所有面板类型);
  4. 连接父控件(UserControl/Navigation Page)的 MouseMoveLeftMouseButtonUp 事件;

为什么我们不使用列表框的 LeftMouseButtonDown 事件?这个事件从未触发。我们看到一些解决方案使用了 SelectedItemGotFocus 事件,但这些事件存在问题。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

父宿主控件的 MouseMoveLeftMouseButtonUp 事件将管理拖放操作直至完成。在拖放操作期间,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>

示例应用程序

包含的示例代码演示了如何为列表框同时使用 SingleExtended 选择模式。我们使用了三个列表框:Products、Tags 和 Groups。一个产品可以有多个标签,并且只有一个组。拖放操作允许以下规则:

  1. 多个产品可以被选中并拖放到单个标签或组上。
  2. 多个标签可以被拖放到单个产品或组上 - 通过组链接将一组标签应用到单个/多个产品上。
  3. 将单个组拖放到单个产品上。

这是 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

示例项目没有对已经使用或超出数量限制的标签进行冲突或映射解析。我将把这项练习留给您。

不过,我在主窗体上添加了一个反馈控件,用于显示拖放操作的结果。这是更新 SummaryTagModelSummaryGroupModel 集合以刷新标签和组列表框的代码。下面的代码只更新有更改的项,而不是更新整个列表。反馈控件绑定到这些集合,并反映任何更改。

'-- 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 日 - 初始发布。
© . All rights reserved.