ListBoxのアイテムを追加/削除するときにアニメーションさせるためのコントロールを作ってみた

どこかの投稿サイトで、
ListBox コントロールに動的にアイテムを追加したとき、
アニメーションするようにしたいけどどうすればいいの?
みたいな質問が飛んでいて、
こうやったらできるんじゃないかな~と思っていたものを
実際に組んでみたので紹介しようと思います。

完成品はこちら。
test

ListBox コントロールなどの ItemsControl 派生のコントロールは、
指定されたアイテムを並べるとき、
コンテナと呼ばれるコントロールに各アイテムを入れて表示しています。
ここでは、そのコンテナをカスタムコントロールにして、
そのコントロール内でアニメーション処理させます。

というわけでカスタムコントロール AnimatedContainer クラスを作ります。

ほぼ自動生成されたコードのままですが、
Border コントロールの中に Button をひとつと、
指定されたコンテンツを表示させるための ContentPresenter を置いてあります。
非常に雑な置き方ですが、
このカスタムコントロールの配置はなんでもいいです。
というのは、実際の配置はこのカスタムコントロールを使う側で
Template を指定してユーザ側で指定してもらうからです。

さておき、
コードビハインドを見てみましょう。

いきなり長いコードですが、
ポイントはサイズ変更イベントハンドラの OnSizeChanged メソッドで
このコントロールが表示されるときにフェードインさせるアニメーションを実行しています。

また、PART_DeleteButton と名付けられたボタンがクリックされたタイミングで
このコントロールを非表示とするようにフェードアウトさせるアニメーションを実行しています。
さらに、フェードアウトのアニメーションが終了したら
コールバックとして DeletedCommand プロパティに指定されたコマンドを実行するようにしています。

ListBox コントロールにアイテムを追加/削除することを考えてみましょう。
追加するときはとにかく Add すればコンテナがひとつ増え、
始めに OnSizeChanged が呼ばれることになります。
では、逆に削除するときはどうでしょうか。
いきなり ItemsSource のコレクションから Remove などで削除してしまうと、
フェードアウトさせたいコンテナ自体がいきなり消えてしまいます。
したがって、コレクションから削除する前にフェードアウトのアニメーションを実行し、
そのアニメーションが完了してからコレクションから削除しなければいけません。
そんなわけで、削除するときは DeleteCommand プロパティを利用して
コレクションからアイテムを Remove すればいいのです。
また、そのようにできるように上記のコードでは、
DeleteCommand.Execute(this.DataContext) のように、
AnimatedContainer コントロールの DataContext を
パラメータとしてコマンドを実行しています。
これで処理を渡された方はどのアイテムを削除すれば良いかが判断できるようになっています。

そんなわけでこのカスタムコントロールを使う側の XAML と ViewModel です。

念のため ICommand インターフェースを実装した DelegateCommand クラスのソースも。

今回は ListBox コントロールを対象としましたが、
他の ItemsControl クラス派生のコントロール、
例えば TreeView とかDataGrid とかでも
応用が利くんじゃないかなぁと思っています。

アニメーションは凝ると楽しいけど、
実用的なものを考えようとすると難しいなぁ。
というわけで今日はこの辺で。