如何在不违反MVVM主体的情况下处理拖放操作?

时间:2011-05-06 19:31:40

标签: .net wpf mvvm drag-and-drop

目前我的XAML

<TabControl  
    AllowDrop="True"
    PreviewDragOver="DragOver"
    PreviewDrop="Drop" />

我的所有拖放代码都存在于我的View的代码隐藏中,而不是在我的ViewModel中。

如何在ViewModel中处理拖放操作而不在View上添加任何依赖项?

5 个答案:

答案 0 :(得分:18)

有各种类型的库,例如gong以及各种博客文章中的类似代码段。

但是,你绝不应该完全没有代码隐藏。例如,我的书中仍然是MVVM:

void ButtonClicked(object sender, EventArgs e)
{
    ((MyViewModel) this.DataContext).DoSomething();
}

命令绑定可能是更好的选择,但逻辑肯定在viewmodel中。使用Drag和Drop之类的东西,你想要绘制线条的位置更加多变。您可以在适当的时候使用代码隐藏解释Drag Args并在viewmodel上调用方法。

答案 1 :(得分:12)

以下是我编写的一些代码,它允许您在不违反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
    }
}

答案 2 :(得分:0)

这也可能对你有所帮助。附加的命令行为库允许您将任何事件转换为更紧密地遵守MVVM框架的命令。

http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

使用它非常容易。并多次保存我的培根

希望这有帮助

答案 3 :(得分:0)

对于VB开发人员来说,这只是一个额外的答案,即@asheh对VB.NET的回答。

$router->get('/route-name', 'myController@myMethodName');

答案 4 :(得分:0)

这里有一个解决方案,它比Mustafa的解决方案更为通用,开箱即用,并且更简单,只需一个DependencyProperty

  1. 将此界面复制到您的projet中,然后
public interface IFilesDropped
{
    void OnFilesDropped(string[] files);
}
  1. 让您的ViewModel实现接口
public class SomeViewModel : IFilesDropped
{
    public void OnFilesDropped(string[] files)
    {
        // Implement some logic here
    }
}
  1. 将此通用扩展名复制到您的项目中
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);
    }
}
  1. 对您选择的UI组件(此处为TextBox)启用“拖放文件”行为
<TextBox ns:DropFilesBehaviorExtension.IsEnabled ="True" />

快乐滴!