WPF 拖放行为





5.00/5 (5投票s)
WPF 拖放行为
我的一个同事在以一种适合 MVVM 模式的方式实现拖放功能时遇到了一些麻烦。一旦你对各种模式有了一定的了解,这其实很简单,但如果你不了解,那绝对是个噩梦。
MVVM 的一个基本原则是,你永远不应该在你的代码隐藏中编写代码,所有代码都应该被封装起来,并且在 XAML 中是可绑定的,以实现结果。任何处理过拖放的人都会突然发现他们的代码隐藏被用于处理拖放的代码所覆盖,并且在处理多个控件时会成倍增加。
我浏览了网络寻找信息,找到了很多,最终选择了 Microsoft 的 Bea Stollnitz 的一个例子。在她 的 文章 中,我找到了一个用 C# 编写的关于拖放行为的最佳和最直观的例子之一。
我不会深入研究她所有的代码,她已经可以下载了,只是想说它很好,而且正是我想要的,即使存在她描述的限制。
可用的功能允许你将数据从一个 ItemsControl
拖动到另一个相同数据类型或在其自身内部重新排序。它提供了一个用于拖动项目的浮动模板和一个用于放置位置的视觉提示。
我想增强它以允许其他场景,同时保持尽可能多的功能。
优点
- 拖动功能
- 拖动模板 – 很好!
- 逻辑封装
缺点
- 无法控制放置行为
我的版本允许你在运行时注入附加功能。调整后的类图显示了关系,但我们实际上只使用了 DragDropBehaviour
类。
你仍然可以使用标准选项
<DockPanel>
<Label DockPanel.Dock="Top" Content="Checkout" />
<ListBox hlb:DragDropBehaviour.IsDragSource="true"
hlb:DragDropBehaviour.IsDropTarget="true"
hlb:DragDropBehaviour.DragTemplate="{StaticResource MyTemplate}"
ItemsSource="{Binding Items}"
MinWidth="100"
MinHeight="100"
AllowDrop="True"
SelectionMode="Multiple">
</ListBox>
</DockPanel>
但我添加了另一个可绑定的选项 DropProcessor
,它允许你覆盖默认的 DropProcessor
以实现你想要的任何功能。
<ListBox hlb:DragDropBehaviour.DropProcessor="{Binding DropProcessor}"
hlb:DragDropBehaviour.IsDragSource="true"
hlb:DragDropBehaviour.IsDropTarget="true"
hlb:DragDropBehaviour.DragTemplate="{StaticResource moo}"
ItemsSource="{Binding Items}"
MinWidth="100"
MinHeight="100">
在这个例子中,我创建了一个名为“Nutters R’ Us”的小型枪支商店,你可以在这里购买武器和弹药。你可以看到有一个武器区,一个弹药区和一个你选择的购买区。
我只在结账区添加了一个自定义的 DropProcessor
,它仅在你放置“OrdinanceViewModel
”类型的项目时适用。
Public Class CheckoutDropProcessor
Inherits DropProcessor
Public Overrides Function GetDropAdorner(ByVal behaviour As DragDropBehaviour, _
ByVal adornerLayer As System.Windows.Documents.AdornerLayer) As DropAdorner
If TypeOf behaviour.TargetItemContainer.DataContext Is WeaponViewModel Then
If TypeOf behaviour.SourceItemContainer.DataContext Is OrdnanceViewModel Then
Return New OrdnanceToWeaponDropAdorner(behaviour, adornerLayer)
End If
End If
Return MyBase.GetDropAdorner(behaviour, adornerLayer)
End Function
Public Overrides Function IsDropAllowed(ByVal behaviour As DragDropBehaviour, _
ByVal draggedItem As Object) As Boolean
If Not behaviour.SourceItemContainer Is Nothing _
AndAlso TypeOf behaviour.SourceItemContainer.DataContext Is OrdnanceViewModel Then
If Not behaviour.TargetItemContainer Is Nothing _
AndAlso TypeOf behaviour.TargetItemContainer.DataContext Is WeaponViewModel Then
Return True
End If
Return False
End If
Return MyBase.IsDropAllowed(behaviour, draggedItem)
End Function
Public Overrides Sub Drop(ByVal behaviour As DragDropBehaviour, _
ByVal draggedItem As Object, ByVal dropEffect As System.Windows.DragDropEffects)
If Not behaviour.TargetItemContainer Is Nothing _
AndAlso TypeOf behaviour.TargetItemContainer.DataContext Is WeaponViewModel Then
If TypeOf behaviour.SourceItemContainer.DataContext Is OrdnanceViewModel Then
CType(behaviour.TargetItemContainer.DataContext, WeaponViewModel)_
.AddOrdinance(CType(behaviour.SourceItemContainer.DataContext, OrdnanceViewModel))
Dim indexRemoved As Integer = -1
If ((dropEffect And DragDropEffects.Move) <> DragDropEffects.None) Then
indexRemoved = Utilities.RemoveItemFromItemsControl_
(behaviour.SourceItemsControl, draggedItem)
End If
If (((indexRemoved <> -1) AndAlso (behaviour.SourceItemsControl _
Is behaviour.TargetItemsControl)) AndAlso _
(indexRemoved < behaviour.InsertionIndex)) Then
behaviour.InsertionIndex -= 1
End If
Exit Sub
End If
End If
MyBase.Drop(behaviour, draggedItem, dropEffect)
End Sub
End Class
这个类继承自基类“DropProcessor
”,它提供了与原始文章相同的功能,但我已经
重写了几个方法。第一个,“GetDropAdorner
”测试以确保你将一个 OrdinanceViewModel
放置到 WeaponViewModel
上,并提供一个不同的和自定义的 DropAdorner
,它没有提供可爱的插入视觉效果,而是将一个“IsDropTarget
”属性应用到 ListBoxItem
,以允许模板控制视觉效果。IsAllowedDrop
也测试了这种情况,Drop
方法也是如此。在所有情况下,它们只是在测试 Drop
的一个特殊情况,并调用基类的 方法。
演示应用程序的图表有点大,但你可以看到我仍然在 MVVM 方面有多糟糕,虽然我通过这个演示学到了很多东西,但我仍然很想共享 ViewModel
……但这是一个坏习惯。
我重点介绍了两个主要类,我们已经讨论了 CheckoutDropProcessor
。这让你能够灵活地增强你的放置场景,而无需你的所有开发人员深入研究行为的内部,从而让他们有充足的时间来完成真正的工作,即构建一些有用的东西。
我已经把这个 放在 Codeplex 上了,并且 源代码 和 二进制文件 都是可用的。