C#でWPF学習中「ReactiveCommandを使ったドラックアンドドロップ」

C# コンピュータ
C#

実行環境

Windows10 2004
dotnet –version 5.0.104
Visual Studio Code
PowerShell 5.1

プロジェクトの作成

mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
dotnet add package Microsoft.Xaml.Behaviors.Wpf
dotnet add package ReactiveProperty.WPF
code .

ソースコード

ファイル名:MainWindow.xaml

<Window x:Class="WpfSample12DD.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="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors"
        xmlns:interactivity="clr-namespace:Reactive.Bindings.Interactivity;assembly=ReactiveProperty.WPF"
        xmlns:local="clr-namespace:WpfSample12DD"
        mc:Ignorable="d"
        Title="ドラッグアンドドロップ" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <x:Code>
                <![CDATA[
                    private void PreviewDragOrver(object sender, DragEventArgs e)
                    {
                        e.Effects = (e.Data.GetDataPresent(DataFormats.FileDrop)) ? DragDropEffects.Copy : DragDropEffects.None;
                        e.Handled = true;
                    }
                ]]>
            </x:Code>
            <Label Content="こちらにドラッグアンドドロップしてください。"
                AllowDrop="True"
                PreviewDragOver="PreviewDragOrver">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="PreviewDrop">
                        <interactivity:EventToReactiveCommand Command="{Binding PreviewDropCommand}">
                            <local:PreviewDropToDragFileConverter />
                        </interactivity:EventToReactiveCommand>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Label>
            <TextBox Text="{Binding Input.Value}"/>
        </StackPanel>
    </Grid>
</Window>

ファイル名:MainWindowViewModel.cs

using System.Diagnostics;
using System;
using System.Windows;
using Reactive.Bindings;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;

namespace WpfSample12DD
{
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ReactiveProperty<string> Input { get; private set; }

        public ReactiveCommand<DragFile> PreviewDropCommand { get; }

        public MainWindowViewModel()
        {
            Input = new ReactiveProperty<string>("");

            PreviewDropCommand = new ReactiveCommand<DragFile>();
            PreviewDropCommand.Subscribe(x => {
                Input.Value = x.Name;
            });

        }
    }
}

ファイル名:PreviewDropToDragFileConverter.cs

using Reactive.Bindings.Interactivity;
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Input;

namespace  WpfSample12DD
{
    public class PreviewDropToDragFileConverter : ReactiveConverter<DragEventArgs, DragFile>
    {
        protected override IObservable<DragFile> OnConvert(IObservable<DragEventArgs> source) => source
            .Where(e => e.Data.GetDataPresent(DataFormats.FileDrop))
            .Select(e => new DragFile {
                Name = ((string[])e.Data.GetData(DataFormats.FileDrop))[0],
            });
    }
}

ファイル名:DragFile.cs

using System;

namespace WpfSample12DD
{
    public class DragFile
    {
        public string Name { get; set; }
    }
}

ファイル名:MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 WpfSample12DD
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new MainWindowViewModel();
        }
    }
}

他のソースファイルに変更なし。

説明

「こちらにドラッグアンドドロップしてください。」と書いてあるラベル部分にエクスプローラーなどからファイルをドラッグアンドドロップします。そうするテキストボックスにファイルのパスが表示されます。

ドラックアンドドロップで処理を行うためにどうしてもコントロールのイベントを捕まえる必要がありました。探したところWindowsFormsの様にコードビハインドで処理する方法は、比較的早い段階で見つけることが出来ました。
【WPF】一瞬で実装できるドラッグ アンド ドロップのコード紹介 | 初学者DIYプログラミング入門
Windowsでお馴染みのドラッグ&ドロップは超便利なので、自作プログラムに組み込みたくなるものですが、イベント処理を記述しなければならないため、結構面倒です。 他のサイトではドラッグ&ドロップの原理とか、イベント処理をゴリゴリ記述するよう

ドラックアンドドロップであればコードビハインドでも良いかと思いましたが、MVVMで何とかならないかとさらに探したところReactiveCommandにめぐり逢いました。
MVVM でイベント引数の値を ViewModel のコマンドに渡す方法 - かずきのBlog@hatena
こちらを見て、そういえばさらっと書いてるだけだったなぁと思ったので…。 elf-mission.net イベント引数を ViewModel で使いたい マウス系イベントや選択系イベントは、イベント引数にしか入ってない値とかもあったりして使い...

ドラックアンドドロップのイベントの引数DragEventArgsをDragFileというファイル名だけを持たせるクラスに変換しています。
この変換処理を挟まない場合DragEventArgsが引き渡されるようです。
変換後ViewModel側のPreviewDropCommandが実行されます。
ドロップ部分はこれで良いのですが、ドロップを受け入れる際、受け入れ可能のアイコンに変更する処理をさせるのが定番です。
こちらもReactiveCommandで処理しようと思い、色々試しましたが、私には無理でした。この部分はコードビハインドで実装しました。Viewの中で完結する処理だったのでXAMLに埋め込んむことで良しとします。

コメント