How to handle drag/drop without violating MVVM principals?

前端 未结 5 660
时光说笑
时光说笑 2020-12-24 07:40

Currently I have in my XAML


All of

相关标签:
5条回答
  • 2020-12-24 07:55

    There are libraries for this such as gong and similar snippets on various blog articles.

    However, you shouldn't get too hung up on having absolutely no code-behind. For example, this is still MVVM in my book:

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

    A command binding might be a better choice, but the logic is definitely in the viewmodel. With something like Drag and Drop, it's more variable where you want to draw the line. You can have code-behind interpret the Drag Args and call methods on the viewmodel when appropriate.

    0 讨论(0)
  • 2020-12-24 08:01

    Here is a solution a bit more generic, out-of-the-box and easier than Mustafa's one, with a single DependencyProperty

    1. Copy this interface in your projet and
    public interface IFilesDropped
    {
        void OnFilesDropped(string[] files);
    }
    
    1. Make you ViewModel implement the interface
    public class SomeViewModel : IFilesDropped
    {
        public void OnFilesDropped(string[] files)
        {
            // Implement some logic here
        }
    }
    
    1. Copy this generic extension in your project
    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. Enable the Drop files behavior to the UI components of you choice (here a TextBox)
    <TextBox ns:DropFilesBehaviorExtension.IsEnabled ="True" />
    

    Happy drops !

    0 讨论(0)
  • 2020-12-24 08:10

    This is just an additional answer that ports @Asheh's answer's to VB.NET for VB developers.

    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
    
    0 讨论(0)
  • 2020-12-24 08:11

    Here is some code I wrote that allows you to drag and drop files onto a control without violating MVVM. It could easily be modified to pass the actual object instead of a file.

    /// <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");
            }
        }
    }
    

    Usage:

    <ScrollViewer AllowDrop="True" Background="Transparent" utility:FileDragDropHelper.IsFileDragDropEnabled="True" utility:FileDragDropHelper.FileDragDropTarget="{Binding}"/>
    

    Ensure the DataContext inherits from IFileDragDropTarget and implements the OnFileDrop.

    public class MyDataContext : ViewModelBase, IFileDragDropTarget
    {
        public void OnFileDrop(string[] filepaths)
        {
            //handle file drop in data context
        }
    }
    
    0 讨论(0)
  • 2020-12-24 08:14

    This might also be of some help to you. The attached command behavior library allows you to convert any event(s) into a command which will more closely adhere to the MVVM framework.

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

    Using this is extremely easy. And has saved my bacon numerous times

    Hope this helps

    0 讨论(0)
提交回复
热议问题