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】一瞬で実装できるドラッグ アンド ドロップのコード紹介
Windowsでお馴染みのドラッグ&ドロップは超便利なので、自作プログラムに組み込みたくなるものですが、イベント処理を記述しなければならないため、結構面倒です。他のサイトではドラッグ&ドロップの原理とか、イベント処理をゴリゴリ記述するような

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

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

コメント