目的
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
独立させて作りたい場合は添付プロパティでやるって感じの感覚で使えば怖くない!