WPFビヘイビア:FileDropBehavior.cs – ファイルのドラックアンドドロップのビヘイビア

コンピュータ

ファイル名:Behaviors\FileDropBehavior.cs

using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Input;

namespace Maywork.WPF.Behaviors;

public class FileDropBehavior : Behavior<UIElement>
{
    public static readonly DependencyProperty DropCommandProperty =
        DependencyProperty.Register(
            nameof(DropCommand),
            typeof(ICommand),
            typeof(FileDropBehavior),
            new PropertyMetadata(null));

    public ICommand? DropCommand
    {
        get => (ICommand?)GetValue(DropCommandProperty);
        set => SetValue(DropCommandProperty, value);
    }

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.AllowDrop = true;
        AssociatedObject.PreviewDragOver += OnDragOver;
        AssociatedObject.PreviewDrop += OnDrop;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.PreviewDragOver -= OnDragOver;
        AssociatedObject.PreviewDrop -= OnDrop;

        base.OnDetaching();
    }

    private void OnDragOver(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            e.Effects = DragDropEffects.Copy;
        }
        else
        {
            e.Effects = DragDropEffects.None;
        }

        e.Handled = true;
    }

    private void OnDrop(object sender, DragEventArgs e)
    {
        if (!e.Data.GetDataPresent(DataFormats.FileDrop))
            return;

        var files = (string[])e.Data.GetData(DataFormats.FileDrop);

        if (DropCommand?.CanExecute(files) == true)
        {
            DropCommand.Execute(files);
        }

        e.Handled = true;
    }
}

/*
// 使い方
 // ファイルのドラックアンドドロップのビヘイビア

// パッケージ導入
dotnet add package Microsoft.Xaml.Behaviors.Wpf


// 使用例


// XAML
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:behavior="clr-namespace:Maywork.WPF.Behaviors"

<Grid Background="LightGray">

    <i:Interaction.Behaviors>
        <behavior:FileDropBehavior DropCommand="{Binding DropFilesCommand}" />
    </i:Interaction.Behaviors>

    <TextBlock
        Text="ここにファイルをドロップ"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"/>
</Grid>


// ViewModel
public RelayCommand DropFilesCommand { get; }

public MainViewModel()
{
    DropFilesCommand = new RelayCommand(
        execute: param =>
        {
            var files = param as string[];
            if (files == null) return;

            foreach (var file in files)
            {
                MessageBox.Show(file);
            }
        });
}


*/
Download

利用例(クリックで展開)

ファイル名:App.xaml

<Application x:Class="ViewModelBaseDemo.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:ViewModelBaseDemo"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>

ファイル名:App.xaml.cs

using System.Configuration;
using System.Data;
using System.Windows;

namespace ViewModelBaseDemo;

/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}


ファイル名:Helpers\RelayCommand.cs

using System.Windows.Input;

namespace Maywork.WPF.Helpers;

public sealed class RelayCommand : ICommand
{
    private readonly Action<object?> _execute;
    private readonly Func<object?, bool>? _canExecute;

    public RelayCommand(
        Action<object?> execute,
        Func<object?, bool>? canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object? parameter)
        => _canExecute?.Invoke(parameter) ?? true;

    public void Execute(object? parameter)
        => _execute(parameter);

    public event EventHandler? CanExecuteChanged;

    public void RaiseCanExecuteChanged()
        => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

/*
// 使い方
// ICommand の簡易実装クラス。
// execute : 実行時に呼ばれる処理
// canExecute : 実行可否を判定する処理(省略時は常に true)
// RaiseCanExecuteChanged() を呼ぶことでボタン状態を更新可能。

// 使用例

public RelayCommand SubmitCommand { get; }

public MainViewModel()
{
    SubmitCommand = new RelayCommand(
        execute: _ => MessageBox.Show($"Title: {Title}"),
        canExecute: _ => !IsEmpty
    );
}
 
 */

ファイル名:Helpers\ViewModelBase.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Maywork.WPF.Helpers;


public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    protected bool SetProperty<T>(
        ref T field,
        T value,
        Action<T>? onChanged = null,
        [CallerMemberName] string? propertyName = null)
    {
        if (Equals(field, value))
            return false;

        field = value;

        onChanged?.Invoke(value);

        OnPropertyChanged(propertyName);

        return true;
    }
}

/*
// 使い方

// INotifyPropertyChanged の実装を簡略化する基底クラス。
// SetProperty() は値変更時のみ通知を行い、
// onChanged デリゲートで追加処理を記述できます。

// 使用例

using Maywork.WPF.Helpers;

namespace ViewModelBaseDemo;

internal class MainViewModel : ViewModelBase
{
    private string _title = "";

    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }
}

 */

ファイル名:MainViewModel.cs

using Maywork.WPF.Helpers;
using System.Windows;

namespace ViewModelBaseDemo;

public class MainViewModel : ViewModelBase
{
    public RelayCommand DropFilesCommand { get; }

    public MainViewModel()
    {
        DropFilesCommand = new RelayCommand(
            execute: param =>
            {
                var files = param as string[];
                if (files == null) return;

                foreach (var file in files)
                {
                    MessageBox.Show(file);
                }
            });
    }

}

ファイル名:MainWindow.xaml

<Window x:Class="ViewModelBaseDemo.MainWindow"
        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:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:ViewModelBaseDemo"
        xmlns:behavior="clr-namespace:Maywork.WPF.Behaviors"
        mc:Ignorable="d"
        Title="FileDropBehavior Demo" Height="450" Width="800">
    <Grid Background="LightGray">

        <i:Interaction.Behaviors>
            <behavior:FileDropBehavior DropCommand="{Binding DropFilesCommand}" />
        </i:Interaction.Behaviors>

        <TextBlock Text="ここにファイルをドロップ"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"/>
    </Grid>
</Window>

ファイル名:MainWindow.xaml.cs

using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ViewModelBaseDemo;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var vm = new MainViewModel();
        this.DataContext = vm;
    }
}

ファイル名:ViewModelBaseDemo.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net10.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
  </ItemGroup>

</Project>

コメント