ドラッグしてドロップ on WPF

勢い良く手を上げたはよいけど何を書けばよくわからなくなったXAML Advent Calendar 19日目を執筆させていただきます、かーのと申します。よろしくお願いします。

みなさん、XAMLライフ、満喫していますか!

XAML Advent Calendar ですが、ぼくはWPF以外ほとんど触ったことがないので
おもにWPFのお話をしていきます!

今日はWPFにおけるドラッグアンドドロップの実装について、です!

ドラッグアンドドロップの難しさ

ドラッグアンドドロップ、アイコンを移動させてゴミ箱へ投げたり、
要素を移動して並び替えたり、用途は様々。

しかし、いざ実装しようとするとこれがなかなか曲者です。
移動の検知は?移動先は?ドロップの可否は?ドロップ時の処理は?

というわけで、ドラッグアンドドロップの仕組みを見ながら、せっかくのXAMLなので、今回は
「ビヘイビアベース」でのドラッグアンドドロップの実装をしたいと思います。
そっちの方がMVVMと親和性高いしね!

ドラッグアンドドロップの流れ

1. ドラッグアンドドロップの開始

まずは、ドラッグアンドドロップを開始する必要があります。
幸い、WPFには `DragDrop` クラスがいますので、あなたがすべきことは一つだけ、
DragDrop.DoDragDrop を呼び出すだけです。
ただし、マウスのドラッグ開始は自分で検知する必要があります。

2. ドロップの可否の判定

移動先はAllowDropプロパティがtrueになっている必要があります。また、DragOverイベントを適切にハンドルする必要があります。

3. ドロップ時の処理

ドロップの可否の判定でまずドロップが可能となっていなければなりません。
また、Dropイベントを適切にハンドルする必要があります。

これらを実装することでドラッグアンドドロップができるようになるわけですね!スゴイ!

Behaviorを用いて実装しよう!

Behaviorを使うことで、他のコントロールで処理を使いまわせるようになります!

というわけで、今回は以下のBehaviorを作ります。

DragStartBehavior – ドラッグ&ドロップを開始するためのBehavior
DragAcceptBehavior – ドラッグ&ドロップを受け付けるためのBehavior

この2つをコントロールに貼り付けることで、あっぱれ、作業は完了です!

DragStartBehavior

このビヘイビアがやるべき処理はただひとつ、ドラッグアンドドロップを開始させることです。

このBehaviorは2つのプロパティを持ちます。AllowedEffectsとDragDropDataです。

AllowedEffectsはこのデータがどんな操作を許容するかを示すフラグです。後述します。

DragDropDataはドラッグアンドドロップで渡されるデータです。
ViewModelからバインドされることを想定しています。

PreviewMouseDownの段階ではDragDropを呼んでいません。なぜなら、DragDropを呼んでしまうと
それ以降そのオブジェクトはマウスイベントを受信しなくなります。
つまり、クリックなどができなくなってしまうためです。

DragAcceptBehavior

ドラッグアンドドロップされたデータを検査し、処理するビヘイビアです。

このビヘイビアでは、ドラッグされたデータを確認し、それに応じた処理をする必要があります。
つまり、引数付きのイベントが必要です。と、いうわけで、次のような実装をしてみました。

このビヘイビアは、DragAcceptDescriptionというクラスがバインドできるようになっています。
ViewModelからDragAcceptDescriptionを供給し、バインドしてやることで、ViewModelから
ドラッグアンドドロップの制御が行えるというわけです。

ちなみに、DragAcceptDescriptionは以下のとおり:

DragEventArgsイベントを引数に取るイベントが2つ定義されてます。

ここまで来ればあとはXamlとViewModelをいじるだけです。
というわけで、リストを並び替えるサンプルを作ってみました。

screenshot

「にゃんぱすゲーム」です。ドラッグしてにゃんぱすーを完成させると勝ちです。

LivetとIx-Mainを使ってます。

メインウィンドウですが、同じようにgistをここに貼ると長いので、gistへのリンクを踏んでいってください→MainWindow.xaml

XAMLアドベントカレンダーながら、XAMLは最後の最後にちょろっと出てくるだけになってしまいました。
キーポイントはこの2つ、

<i:Interaction.Behaviors>
	<sample:DragAcceptBehavior Description="{Binding Description}" />
</i:Interaction.Behaviors>

<i:Interaction.Behaviors>
	<sample:DragStartBehavior AllowedEffects="Move" DragDropData="{Binding}" />
</i:Interaction.Behaviors>

です。

DragStartBehaviorでは、許されている操作に「移動」を指定しています。他にはコピーとかもあります。
そしてDragDropDataは{Binding}、バインドされている対象をそのまま指定してます。
一般的にはViewModelでしょう。今回は残念ながらstringです。手抜きです。

そしてViewModelはこちら。こちらもすみませんが、gistへ飛んでください→MainWindowViewModel.cs

コア部分は OnDragOver と OnDragDrop です。

private void OnDragOver(DragEventArgs args)
{
	if (args.AllowedEffects.HasFlag(DragDropEffects.Move) &&
			args.Data.GetDataPresent(typeof(String)))
	{
		args.Effects = DragDropEffects.Move;
	}
}

OnDragOverでは、DragEventArgsで渡ってきた情報を調査し、Stringなら受け入れます。
こうすることで、ファイルとか降ってきても弾けるわけです。

OnDragDropの説明はちょこっとだけ:

var fe = args.OriginalSource as FrameworkElement;
if (fe == null) return;
var target = fe.DataContext as string;
if (target == null) return;

「ドロップされた対象」はOriginalSourceで触れます。しかし、これはViewなので
バインドしているViewModelを取得するためにDataContextを取得しています。

と、まぁ、こんな感じで思ったより簡単にドラッグアンドドロップできるわけですね。

そしてこいつらはDragAcceptDescriptionのイベントからコールバックされてきます。
そしてDragAcceptDescriptionはViewでBindingされる、というわけです。

おつかれさまでした。

というわけで、こんな感じでよろしかったでしょうか、XAML Advent Calendar 19日目。
XAML要素があんまりなかったです。ごめんなさい。にゃんぱすー。

nyanpas

ソースコードとバイナリを置いておきます、よかったらごらんください。
バイナリ: にゃんぱす.zip
ソース一式: Karno.Xac.Sample.zip

そうそう、ちょっと頑張ればAdornerもつきます。

意外とさっくりBehavior作れますので、ぜひ活用してみてくださいね。

それでは、忘年会行ってきます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です