如何在不违反 MVVM 原则的情况下处理拖放?
Posted
技术标签:
【中文标题】如何在不违反 MVVM 原则的情况下处理拖放?【英文标题】:How to handle drag/drop without violating MVVM principals? 【发布时间】:2011-08-20 10:39:53 【问题描述】:目前我的 XAML 中有
<TabControl
AllowDrop="True"
PreviewDragOver="DragOver"
PreviewDrop="Drop" />
我的所有拖放代码都存在于我的 View 的代码隐藏中,而不是我的 ViewModel 中。
如何在 ViewModel 中处理拖放操作而不在 View 上添加任何依赖项?
【问题讨论】:
【参考方案1】:在各种博客文章中都有类似的库,例如 gong 和类似的 sn-ps。
但是,您不应该过于拘泥于完全没有代码隐藏。例如,这仍然是我书中的 MVVM:
void ButtonClicked(object sender, EventArgs e)
((MyViewModel) this.DataContext).DoSomething();
命令绑定可能是更好的选择,但逻辑肯定在视图模型中。使用拖放之类的东西,你想在哪里画线就更容易改变了。您可以在适当的时候让代码隐藏解释 Drag Args 并调用视图模型上的方法。
【讨论】:
如果你有一个小而静态的模型,这是可以的解决方案,但如果你需要松散耦合并使用依赖注入。【参考方案2】:这是我编写的一些代码,允许您在不违反 MVVM 的情况下将文件拖放到控件上。它可以很容易地修改为传递实际对象而不是文件。
/// <summary>
/// IFileDragDropTarget Interface
/// </summary>
public interface IFileDragDropTarget
void OnFileDrop(string[] filepaths);
/// <summary>
/// FileDragDropHelper
/// </summary>
public class FileDragDropHelper
public static bool GetIsFileDragDropEnabled(DependencyObject obj)
return (bool)obj.GetValue(IsFileDragDropEnabledProperty);
public static void SetIsFileDragDropEnabled(DependencyObject obj, bool value)
obj.SetValue(IsFileDragDropEnabledProperty, value);
public static bool GetFileDragDropTarget(DependencyObject obj)
return (bool)obj.GetValue(FileDragDropTargetProperty);
public static void SetFileDragDropTarget(DependencyObject obj, bool value)
obj.SetValue(FileDragDropTargetProperty, value);
public static readonly DependencyProperty IsFileDragDropEnabledProperty =
DependencyProperty.RegisterAttached("IsFileDragDropEnabled", typeof(bool), typeof(FileDragDropHelper), new PropertyMetadata(OnFileDragDropEnabled));
public static readonly DependencyProperty FileDragDropTargetProperty =
DependencyProperty.RegisterAttached("FileDragDropTarget", typeof(object), typeof(FileDragDropHelper), null);
private static void OnFileDragDropEnabled(DependencyObject d, DependencyPropertyChangedEventArgs e)
if (e.NewValue == e.OldValue) return;
var control = d as Control;
if (control != null) control.Drop += OnDrop;
private static void OnDrop(object _sender, DragEventArgs _dragEventArgs)
DependencyObject d = _sender as DependencyObject;
if (d == null) return;
Object target = d.GetValue(FileDragDropTargetProperty);
IFileDragDropTarget fileTarget = target as IFileDragDropTarget;
if (fileTarget != null)
if (_dragEventArgs.Data.GetDataPresent(DataFormats.FileDrop))
fileTarget.OnFileDrop((string[])_dragEventArgs.Data.GetData(DataFormats.FileDrop));
else
throw new Exception("FileDragDropTarget object must be of type IFileDragDropTarget");
用法:
<ScrollViewer AllowDrop="True" Background="Transparent" utility:FileDragDropHelper.IsFileDragDropEnabled="True" utility:FileDragDropHelper.FileDragDropTarget="Binding"/>
确保 DataContext 继承自 IFileDragDropTarget 并实现 OnFileDrop。
public class MyDataContext : ViewModelBase, IFileDragDropTarget
public void OnFileDrop(string[] filepaths)
//handle file drop in data context
【讨论】:
出色的工作!开箱即用,在 VS2017 中为我工作。 某些原因无法在<border>
上运行,有人知道为什么吗?
@Alfie 那是因为Border
没有从Control
继承,而OnFileDragDropEnabled
处理程序专门检查该类型。但是,拖放事件是从 UIElement
继承的,Border
确实 继承自 Border
。您可能会修改该方法以检查它,以便它包含更多内容。不过,我不确定是否还有其他影响需要考虑。
知道为什么我在依赖属性的 XAML 中收到“在类型中找不到可附加属性”错误吗?【参考方案3】:
这是一个比 Mustafa 的解决方案更通用、开箱即用且更简单的解决方案,只有一个 DependencyProperty
-
将此界面复制到您的项目中
public interface IFilesDropped
void OnFilesDropped(string[] files);
-
让您的 ViewModel 实现接口
public class SomeViewModel : IFilesDropped
public void OnFilesDropped(string[] files)
// Implement some logic here
-
在您的项目中复制此通用扩展
public class DropFilesBehaviorExtension
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled", typeof(bool), typeof(DropFilesBehaviorExtension), new FrameworkPropertyMetadata(default(bool), OnPropChanged)
BindsTwoWayByDefault = false,
);
private static void OnPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
if (!(d is FrameworkElement fe))
throw new InvalidOperationException();
if ((bool)e.NewValue)
fe.AllowDrop = true;
fe.Drop += OnDrop;
fe.PreviewDragOver += OnPreviewDragOver;
else
fe.AllowDrop = false;
fe.Drop -= OnDrop;
fe.PreviewDragOver -= OnPreviewDragOver;
private static void OnPreviewDragOver(object sender, DragEventArgs e)
// NOTE: PreviewDragOver subscription is required at least when FrameworkElement is a TextBox
// because it appears that TextBox by default prevent Drag on preview...
e.Effects = DragDropEffects.Move;
e.Handled = true;
private static void OnDrop(object sender, DragEventArgs e)
var dataContext = ((FrameworkElement)sender).DataContext;
if (!(dataContext is IFilesDropped filesDropped))
if (dataContext != null)
Trace.TraceError($"Binding error, 'dataContext.GetType().Name' doesn't implement 'nameof(IFilesDropped)'.");
return;
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
return;
if (e.Data.GetData(DataFormats.FileDrop) is string[] files)
filesDropped.OnFilesDropped(files);
public static void SetIsEnabled(DependencyObject element, bool value)
element.SetValue(IsEnabledProperty, value);
public static bool GetIsEnabled(DependencyObject element)
return (bool)element.GetValue(IsEnabledProperty);
-
为您选择的 UI 组件(此处为 TextBox)启用拖放文件行为
<TextBox ns:DropFilesBehaviorExtension.IsEnabled ="True" />
滴滴快乐!
【讨论】:
这是我最终使用的,效果很好。一个人从哪里开始能够自己做到这一点?我大部分时间都可以按照代码进行操作,但我永远无法自己想出这个。【参考方案4】:这只是为 VB 开发人员将 @Asheh 的答案移植到 VB.NET 的附加答案。
Imports System.Windows
Interface IFileDragDropTarget
Sub OnFileDrop(ByVal filepaths As String())
End Interface
Public Class FileDragDropHelper
Public Shared Function GetIsFileDragDropEnabled(ByVal obj As DependencyObject) As Boolean
Return CBool(obj.GetValue(IsFileDragDropEnabledProperty))
End Function
Public Shared Sub SetIsFileDragDropEnabled(ByVal obj As DependencyObject, ByVal value As Boolean)
obj.SetValue(IsFileDragDropEnabledProperty, value)
End Sub
Public Shared Function GetFileDragDropTarget(ByVal obj As DependencyObject) As Boolean
Return CBool(obj.GetValue(FileDragDropTargetProperty))
End Function
Public Shared Sub SetFileDragDropTarget(ByVal obj As DependencyObject, ByVal value As Boolean)
obj.SetValue(FileDragDropTargetProperty, value)
End Sub
Public Shared ReadOnly IsFileDragDropEnabledProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsFileDragDropEnabled", GetType(Boolean), GetType(FileDragDropHelper), New PropertyMetadata(AddressOf OnFileDragDropEnabled))
Public Shared ReadOnly FileDragDropTargetProperty As DependencyProperty = DependencyProperty.RegisterAttached("FileDragDropTarget", GetType(Object), GetType(FileDragDropHelper), Nothing)
Shared WithEvents control As Windows.Controls.Control
Private Shared Sub OnFileDragDropEnabled(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.NewValue = e.OldValue Then Return
control = TryCast(d, Windows.Controls.Control)
If control IsNot Nothing Then
AddHandler control.Drop, AddressOf OnDrop
End If
End Sub
Private Shared Sub OnDrop(ByVal _sender As Object, ByVal _dragEventArgs As DragEventArgs)
Dim d As DependencyObject = TryCast(_sender, DependencyObject)
If d Is Nothing Then Return
Dim target As Object = d.GetValue(FileDragDropTargetProperty)
Dim fileTarget As IFileDragDropTarget = TryCast(target, IFileDragDropTarget)
If fileTarget IsNot Nothing Then
If _dragEventArgs.Data.GetDataPresent(DataFormats.FileDrop) Then
fileTarget.OnFileDrop(CType(_dragEventArgs.Data.GetData(DataFormats.FileDrop), String()))
End If
Else
Throw New Exception("FileDragDropTarget object must be of type IFileDragDropTarget")
End If
End Sub
End Class
【讨论】:
【参考方案5】:这也可能对您有所帮助。附带的命令行为库允许您将任何事件转换为更符合 MVVM 框架的命令。
http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/
使用它非常容易。还救了我无数次的培根
希望对你有帮助
【讨论】:
以上是关于如何在不违反 MVVM 原则的情况下处理拖放?的主要内容,如果未能解决你的问题,请参考以下文章
如何在不实际拖放的情况下使用 jQuery UI Droppable 触发 Drop 事件?