LoginSignup
3
3

More than 5 years have passed since last update.

Livetでファイルドロップ

Last updated at Posted at 2017-02-11

目的

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の変数をバインドしてます。

1.Behaviorに依存プロパティをつけVMの変数とバインド

簡単な説明

VMにドロップされたファイル(string[])の変数を作って
Behaviorのドロップされたファイル(string[])の依存プロパティとバインドさせる

コードで説明

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

    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のドロップされたファイルを保持する変数とバインドすること

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

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


#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}"
↑を書くためにと
名前を付ける

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

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

3.Behaviorに依存プロパティをつけVMにはIntertfaceを使う

簡単な説明

ファイルドロップされたときの処理用のInterfaceを作り
それを依存プロパティでもったBehaviorを作る

コードで説明

まずはinterface

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

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

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}"がポイント

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

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

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.のと一緒

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内でイベントの購読開始
でもこれってイベント購読解除はどこに書くんだ?

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"がポイント
添付プロパティなので中に書く

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

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

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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3