Livetでファイルドロップ

# 目的  
Livetを使ってWPFでファイルドロップする方法を示す。
一応LivetなのでMVVMというよりコードビハインドなしでやること。
livetの紹介
http://ugaya40.hateblo.jp/entry/Livet

Behaviorとは何？って人にはわからない内容です。
ViewModelはVMと省略して記載します。

# 結果
4つの方法で実装できた。
1.Behaviorに依存プロパティをつけVMの変数とバインド
2.Behaviorに依存プロパティをつけVMにはLivetCallMethodActionで通知
3.Behaviorに依存プロパティをつけVMにはIntertfaceを使う
4.添付プロパティをつけVMにはIntertfaceを使う

すべてDropされたファイル名をListBoxに表示するようにしています。
ListBoxのItemsSourceにObservableCollection<string>の変数をバインドしてます。


# 1.Behaviorに依存プロパティをつけVMの変数とバインド
## 簡単な説明
VMにドロップされたファイル(string[])の変数を作って
Behaviorのドロップされたファイル(string[])の依存プロパティとバインドさせる

## コードで説明
まずはBehavior
PreviewDragEnterとDropのイベントを購読して
ドロップされたファイルを受け取る&VMに渡す用の変数を依存プロパティで作る。(DropFilesProperty)


```csharp 
    class FileDropBehaviorA:Behavior<FrameworkElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.PreviewDragEnter += AssociatedObject_PreviewDragEnter;
            this.AssociatedObject.Drop += AssociatedObject_Drop;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.PreviewDragEnter -= AssociatedObject_PreviewDragEnter;
            this.AssociatedObject.Drop -= AssociatedObject_Drop;
        }

        private void AssociatedObject_PreviewDragEnter(object sender, DragEventArgs e)
        {
            //previewでの処理。おまじないw。
            e.Effects = DragDropEffects.All;
            e.Handled = true;
        }

        private void AssociatedObject_Drop(object sender, DragEventArgs e)
        {
            //こんな感じでdropされたファイル名が取得可能
            this.DropFiles = e.Data.GetData(DataFormats.FileDrop) as string[];
        }
                
        public string[] DropFiles
        {
            get { return (string[])this.GetValue(DropFilesProperty); }
            set { this.SetValue(DropFilesProperty, value); }
        }
        /// <summary>
        /// FrameworkPropertyMetadataOptions.BindsTwoWayByDefaultは付けなくても可
        /// ただしその場合は,xamlのbinding modeをtwowayをつける必要がある。
        /// FrameworkPropertyMetadataOptions.BindsTwoWayByDefaultをここでつけておくとその必要はない。
        /// </summary>
        public static readonly DependencyProperty DropFilesProperty =
        DependencyProperty.Register(nameof(DropFiles), typeof(string[]), typeof(FileDropBehaviorA), new FrameworkPropertyMetadata(default(string[]), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    }

``` 


続いてxaml
ポイントはDropFiles="{Binding DropFilesA1}" で
ここでVMのドロップされたファイルを保持する変数とバインドすること

``` csharp
<TabItem Header="FileDropA1">
    <i:Interaction.Behaviors>
        <v:FileDropBehaviorA DropFiles="{Binding DropFilesA1}" />
    </i:Interaction.Behaviors>
    <ListBox ItemsSource="{Binding ItemsA1}" />
</TabItem>
```

VMはドロップされたファイルを受け取るstring[]を持たせて
変更時にListにファイルを追加する感じに書く

```csharp

#region ItemsA変更通知プロパティ
private ObservableCollection<string> _ItemsA1;

public ObservableCollection<string> ItemsA1
{
    get
    { return _ItemsA1; }
    set
    { 
        if (_ItemsA1 == value)
            return;
        _ItemsA1 = value;
        RaisePropertyChanged();
    }
}
#endregion

#region DropFilesA1変更通知プロパティ
private string[] _DropFilesA1;

public string[] DropFilesA1
{
    get
    { return this._DropFilesA1; }
    set
    { 
        if (this._DropFilesA1 == value)
            return;
        this._DropFilesA1 = value;
        foreach (var item in value)
            this.ItemsA1.Add(item);
        this.RaisePropertyChanged();
    }
}
#endregion
```

# 2.Behaviorに依存プロパティをつけVMにはLivetCallMethodActionで通知
## 簡単な説明
Behavior自体は1と同じです。
Behaviorにドロップされたファイル(string[])の依存プロパティを持ち
LivetCallMethodActionでそのファイルをVMに通知する

## コードで説明
Behavior自体は1と同じなので省略

xaml
ポイントはl:LivetCallMethodActionのMethodParameterにドロップされたファイルを渡すこと。
MethodParameter="{Binding ElementName=FileDropBehaviorA, Path=DropFiles}"
↑を書くために<v:FileDropBehaviorA x:Name="FileDropBehaviorA" />と
名前を付ける


```csharp
<TabItem Header="FileDropA2">
    <i:Interaction.Behaviors>
        <v:FileDropBehaviorA x:Name="FileDropBehaviorA" />
    </i:Interaction.Behaviors>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Drop">
            <l:LivetCallMethodAction
                MethodName="DropFilesA2"
                MethodParameter="{Binding ElementName=FileDropBehaviorA, Path=DropFiles}"
                MethodTarget="{Binding}" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <ListBox ItemsSource="{Binding ItemsA2}" />
</TabItem>
```

VM
ItemsA2はObservableCollection<string>で1.のやり方と一緒なので省略
l:LivetCallMethodActionで定義した名前のpublic関数の引数に(string[] files)を入れる
xamlでBehaviorに追加したプロパティをbindする

```csharp
public void DropFilesA2(string[] files)
{
    foreach (var file in files)
        this.ItemsA2.Add(file);
}		
```

# 3.Behaviorに依存プロパティをつけVMにはIntertfaceを使う
## 簡単な説明
ファイルドロップされたときの処理用のInterfaceを作り
それを依存プロパティでもったBehaviorを作る

## コードで説明
まずはinterface

```csharp
public interface IFileDropB
{
    void Drop(string[] files);
}
```

Behavior
ポイントはFileDropHandlerPropertyを作り
Dropされたときに実行するようにすること
this.FileDropHandler?.Drop(e.Data.GetData(DataFormats.FileDrop) as string[]);
これはxamlでbindされていないときはnullで実行されない

```csharp
class FileDropBehaviorB : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.PreviewDragEnter += AssociatedObject_PreviewDragEnter;
        this.AssociatedObject.Drop += AssociatedObject_Drop;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.PreviewDragEnter -= AssociatedObject_PreviewDragEnter;
        this.AssociatedObject.Drop -= AssociatedObject_Drop;
    }

    private void AssociatedObject_PreviewDragEnter(object sender, DragEventArgs e)
    {
        e.Effects = DragDropEffects.All;
        e.Handled = true;
    }

    private void AssociatedObject_Drop(object sender, DragEventArgs e)
    {
        //ここでinterfaceの関数を実行する。xamlでbindされていないときはnullで実行されない
        this.FileDropHandler?.Drop(e.Data.GetData(DataFormats.FileDrop) as string[]);
    }

    public IFileDropB FileDropHandler
    {
        get { return (IFileDropB)this.GetValue(FileDropHandlerProperty); }
        set { this.SetValue(FileDropHandlerProperty, value); }
    }

    // Using a DependencyProperty as the backing store for FileDropHandler.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty FileDropHandlerProperty =
    DependencyProperty.Register(nameof(FileDropHandler), typeof(IFileDropB), typeof(FileDropBehaviorB));
}
```

続いてxaml
FileDropHandler="{Binding}"がポイント

```csharp
<TabItem Header="FileDropB">
    <i:Interaction.Behaviors>
        <v:FileDropBehaviorB FileDropHandler="{Binding}" />
    </i:Interaction.Behaviors>
    <ListBox ItemsSource="{Binding ItemsB}" />
</TabItem>

```

最後にVM
VMに作ったinterfaceを継承させる
ドロップされた時の関数を実装する
ItemsBはObservableCollection<string>で1.のやり方と一緒なので省略

```csharp
public class MainWindowViewModel : ViewModel,Views.IFileDropB

void IFileDropB.Drop(string[] files)
{
    foreach (var file in files)
        this.ItemsB.Add(file);
}	
```

# 4.添付プロパティをつけVMにはIntertfaceを使う
## 簡単な説明
3.の添付プロパティ版

## コードで説明
まずはinterface内容は3.のと一緒

```csharp
public interface IFileDropC
{
    void Drop(string[] files);
}
```

Behavior
ポイントはFileDropHandlerPropertyを作り
Dropされたときに実行するようにすること
GetFileDropHandler(sender as UIElement)?.Drop(e.Data.GetData(DataFormats.FileDrop) as string[]);
これはxamlでbindされていないときはnullで実行されない
イベントの購読自体は依存プロパティ作成時にコールバックを追加してそこの中でやること。
つまりOnFileDropHandlerChanged内でイベントの購読開始
でもこれってイベント購読解除はどこに書くんだ？

```csharp
class FileDropBehaviorC//behaivorの継承要らない
{
    public static IFileDropC GetFileDropHandler(UIElement target)
    {
        return (IFileDropC)target.GetValue(FileDropHandlerProperty);
    }

    public static void SetFileDropHandler(UIElement target, IFileDropC value)
    {
        target.SetValue(FileDropHandlerProperty, value);
    }

    /// <summary>
    /// コールバックでイベントの購読の定義
    /// </summary>
    public static readonly DependencyProperty FileDropHandlerProperty =
    DependencyProperty.RegisterAttached("FileDropHandler", typeof(IFileDropC), typeof(FileDropBehaviorC), new PropertyMetadata(OnFileDropHandlerChanged));

    private static void OnFileDropHandlerChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var element = obj as UIElement;
        if (element != null)
        {
            //この場合、イベントの購読解除はどこでやるんだ？
            element.PreviewDragOver += Element_PreviewDragOver;
            element.Drop += Element_Drop;
        }
    }
    private static void Element_PreviewDragOver(object sender, DragEventArgs e)
    {
        e.Effects = DragDropEffects.All;
        e.Handled = true;
    }

    private static void Element_Drop(object sender, DragEventArgs e)
    {
        GetFileDropHandler(sender as UIElement)?.Drop(e.Data.GetData(DataFormats.FileDrop) as string[]);
    }
}
```

続いてxaml
 v:FileDropBehaviorC.FileDropHandler="{Binding}" Header="FileDropC"がポイント
添付プロパティなので中に書く

```csharp
<TabItem 
    v:FileDropBehaviorC.FileDropHandler="{Binding}" Header="FileDropC">
    <ListBox ItemsSource="{Binding ItemsC}" />
</TabItem>

```

最後にVM
ここは3.と一緒

```csharp
public class MainWindowViewModel : ViewModel,Views.IFileDropC

void IFileDropC.Drop(string[] files)
{
    foreach (var file in files)
        this.ItemsC.Add(file);
}	
```


# 最後に
依存プロパティと添付プロパティとかわけわからんだとイミフだと思うけど
Behaviorに変数をバインドさせるには依存プロパティが必要。ほかのプロパティではNG
独立させて作りたい場合は添付プロパティでやるって感じの感覚で使えば怖くない！

一応ソースは↓
https://github.com/kskhsn/BehaviorSample