wpf 如何在不违反MVVM原则的情况下处理拖放操作?

8aqjt8rx  于 2023-01-06  发布在  其他
关注(0)|答案(6)|浏览(240)

目前我的XAML中有

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

我的所有拖放代码都存在于View的代码隐藏中,而不是ViewModel中。
如何在不添加任何视图依赖项的情况下处理ViewModel中的拖放操作?

qaxu7uf2

qaxu7uf21#

在各种博客文章中有一些库,如gong和类似的片段。
但是,您不应该过于执着于绝对没有代码隐藏。例如,在我的书中,这仍然是MVVM:

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

命令绑定可能是一个更好的选择,但逻辑肯定是在视图模型中。使用像拖放这样的东西,你想在哪里画线就更容易了。你可以让代码隐藏解释拖动参数,并在适当的时候调用视图模型上的方法。

hvvq6cgz

hvvq6cgz2#

下面是我写的一些代码,它允许你在不违反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
    }
}
2exbekwf

2exbekwf3#

这里有一个比Mustafa的解决方案更通用、更开箱即用、更简单的解决方案,它只有一个DependencyProperty
1.将此接口复制到项目中

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" />

快乐滴!

g6ll5ycj

g6ll5ycj4#

对于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
u1ehiz5o

u1ehiz5o5#

这可能对你有帮助。附加的命令行为库允许你将任何事件转换成一个更接近MVVM框架的命令。
http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/
使用这个非常容易,而且救了我无数次的命
希望这能帮上忙

ujv3wf0j

ujv3wf0j6#

基于@Kino101的回答,我创建了一个WinUI 3版本。它也可以作为gist提供。
第一个月

using Microsoft.UI.Xaml;
using Windows.ApplicationModel.DataTransfer;

namespace App1.Helpers;

public interface IFilesDropped
{
    void OnFilesDropped(string[] files);
}

public class DropFilesBehavior
{
    public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
        "IsEnabled", typeof(bool), typeof(DropFilesBehavior), PropertyMetadata.Create(default(bool), OnIsEnabledChanged));

    public static readonly DependencyProperty FileDropTargetProperty = DependencyProperty.RegisterAttached(
        "FileDropTarget", typeof(IFilesDropped), typeof(DropFilesBehavior), null);

    public static void SetIsEnabled(DependencyObject element, bool value)
    {
        element.SetValue(IsEnabledProperty, value);
    }

    public static bool GetIsEnabled(DependencyObject element)
    {
        return (bool)element.GetValue(IsEnabledProperty);
    }

    public static void SetFileDropTarget(DependencyObject obj, IFilesDropped value)
    {
        obj.SetValue(FileDropTargetProperty, value);
    }

    public static IFilesDropped GetFileDropTarget(DependencyObject obj)
    {
        return (IFilesDropped)obj.GetValue(FileDropTargetProperty);
    }

    private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var fe = d as FrameworkElement ?? throw new InvalidOperationException();
        
        if ((bool)e.NewValue)
        {
            fe.AllowDrop = true;
            fe.Drop += OnDrop;
            fe.DragOver += OnDragOver;
        }
        else
        {
            fe.AllowDrop = false;
            fe.Drop -= OnDrop;
            fe.DragOver -= OnDragOver;
        }
    }

    private static void OnDragOver(object sender, DragEventArgs e)
    {
        e.AcceptedOperation = DataPackageOperation.Move; // or Link/Copy
        e.Handled = true;
    }

    private static void OnDrop(object sender, DragEventArgs e)
    {
        var dobj = (DependencyObject)sender;
        var target = dobj.GetValue(FileDropTargetProperty);
        var filesDropped = target switch
        {
            IFilesDropped fd => fd,
            null => throw new InvalidOperationException("File drop target is not set."),
            _ => throw new InvalidOperationException($"Binding error, '{target.GetType().Name}' doesn't implement '{nameof(IFilesDropped)}'."),
        };

        if (filesDropped == null)
        {
            return;
        }

        var files = e.DataView.GetStorageItemsAsync().GetAwaiter().GetResult();
        if (files.Count == 0)
        {
            return;
        }

        filesDropped.OnFilesDropped(files.Select(f => f.Path).ToArray());
    }
}

视图模型中的用法,例如ViewModels/MainViewModel.cs

using App1.Helpers;

namespace App1.ViewModels;

public partial class MainViewModel : IFilesDropped
{
    public MainViewModel()
    {
    }

    public void OnFilesDropped(string[] files)
    {
        Debug.Print("---");
        foreach (var file in files)
        {
            Debug.Print($"{file}");
        }
    }
}

视图中的使用情况,例如Views/MainPage.xaml

<Page
    x:Class="App1.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:helpers="using:App1.Helpers" 
    Background="{ThemeResource SolidBackgroundFillColorBaseBrush}"
    mc:Ignorable="d">

    <Grid x:Name="ContentArea" helpers:DropFilesBehavior.IsEnabled="True" helpers:DropFilesBehavior.FileDropTarget="{x:Bind ViewModel}">
                
    </Grid>
</Page>

相关问题