Based on @Kino101's answer, I created a WinUI 3 version. It is also available as a gist.
Helpers/DropFilesBehaviour.cs
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());
}
}
Usage in your ViewModel, e.g. 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}");
}
}
}
Usage in your view, e.g. 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>