Livetがもたらす 自然で高速な WPF MVVMアプリケーション

TechParty 2011 in Tokyo
RIA アーキテクチャ研究会
尾上 雅則
Agenda
 0.自己紹介
 1.5分で振り返るMVVMパターン
 2.Livetの紹介
 3.Livetとメモリリーク
 4.Livetライブラリのそのほかの機能
 おまけ.LivetとフルMVVM
 5.まとめとお願い
0.自己紹介
 尾上 雅則(おのうえ まさのり)
 27歳 フリーランス
 MVVMer。他にWindowsForms/ASP.NET/SQL
Server/Windows Serverなど
 Blog - the sea of fertility(http://ugaya40.net)
 Twitter - @ugaya40
MVVMパターンが何故WPF/Silverlightに必要なのかの復習
1.5分で振り返るMVVMパターン
WPF/Silverlightのコレクションコントロール
 WPF/Silverlightのコレクションコントロールは仮想
化が徹底されている。
 ListBox/ListView
1.5分で振り返るMVVMパターン
WPF/Silverlightのコレクションコントロール
 WPF/Silverlightのコレクションコントロールは仮想
化が徹底されている。
 TreeView
展開するまで
やはり子のインスタンス
が無い!
1.5分で振り返るMVVMパターン
WPF/Silverlightのコレクションコントロール
 WPF/Silverlightのコレクションコントロールは仮想
化が徹底されている。
 ListBox/ListViewその2
1.5分で振り返るMVVMパターン
WPF/Silverlightのコレクションコントロール
 じゃあどうする?
 → データバインドで全て解決
各項目に対応するような
表示用のデータバインド用オブジェクト
があれば全てデータバインドで解決でき
る
(チェック状態などもboolとして持つ)
むしろ他のアプローチじゃ解決できない!
1.5分で振り返るMVVMパターン
WPF/Silverlightの特徴
 WPF/Silverlightでは、データバインドがC#コード
より優遇されている。
 DataTemplateなどは、C#コードからじゃフル機能
使えないとMSDNに明記がある。
 コレクションコントロールなどは、ことごとくデー
タバインドが無ければ詰みやすい設計となっている。
1.5分で振り返るMVVMパターン
WPF/Silverlightの特徴
 なのでアプリケーションを適切に責務分割しようと
すると、、、、
呼出
ビジネス
ドメイン
(アプリの機能)
プレゼン
(アプリの外観)
イベント
1.5分で振り返るMVVMパターン
WPF/Silverlightアプリの責務分割
 なのでアプリケーションを適切に責務分割しようと
すると、、、、
外観
呼出
Data
Binding
画面
画面を
レンダリングする
ための情報
ビジネス
ドメイン
(アプリの機能)
イベント
1.5分で振り返るMVVMパターン
MVVMパターン
 この形がMVVMパターンです。見ての通りなんら特
別なパターンではありません。
 ViewとViewModelでプレゼンロジックを、Modelで
アプリのドメインロジックを分離するのが最大の目
的
依存
Data
Binding
View
ViewModel
Model
イベント
各責務を見ていきましょう
1.5分で振り返るMVVMパターン
View
 View
 UIの外観と構造を定義。
 XAMLで書くのが基本
 ViewModelと併せてプレゼンテーションロジック担当
1.5分で振り返るMVVMパターン
ViewModel
 ViewModel
 Viewのデータバインディングの為のデータストア。
 入力値検証。
 Modelからの通知の受信と入力の橋渡し
 WPFの場合は大抵画面と対応するTree構造になる
 コレクションコントロールの子も普通ViewModelを持
ちます。
1.5分で振り返るMVVMパターン
Model
 Model
 アプリケーションの機能そのもの
 ステートフル

Webみたいにリクエスト毎に使い捨てじゃないので当然ス
テートフル(プロパティ変更通知を持つ)
 ただのデータの入れ物ではない

データの入れ物だとするとデータを詰めるのは
ViewModel?。それはそもそもMVVMの目的に反する。プ
レゼンとドメインが分離しない。
1.5分で振り返るMVVMパターン
MVVMパターン
 このセクションで話したことは私の以前の資料の
ReWriteというかコピペです。
 かなり省略して話したので、疑問のある方などは以
下の資料を参照ください。
MVVMパターンとは?
わんくま同盟東京勉強会 #60 セッション資料
The sea of fertility
http://ugaya40.net/mvvm/what_is_mvvm.html
Livet
2.Livetの紹介
Livet
 Livet
 WPF4のための「国産」MVVMインフラストラクチャ
 ライセンスはzlib/libpng
 →通常の使用であれば、クレジットなど必要ない
 →ソース改変使用する場合はクレジットの明記が必要
Livet WPF4 MVVM インフラストラクチャ
Project Home :
Source Code :
http://ugaya40.net/livet
https://bitbucket.org/ugaya40/livet/
2.Livetの紹介
Livetの特徴 – 簡単な導入
 Setup.exe一発導入(新規・バージョンアップ共)
 Visual Studio 2010 Professional以上なら拡張機能
マネージャによる一発導入が可能
 Visual Studio Express 2010 C#/Visual Basic対応
2.Livetの紹介
Livetの特徴 – Visual Studio統合機能
 各種プロジェクト・アイテムテンプレート・コード
スニペット
 共にC#/Visual Basic双方提供
2.Livetの紹介
Livetの特徴 – Blendable
 LivetのView用全機能は、ExpressionBlend4のGUI
から、一つの例外もなく記述可能になっています。
 直接XAMLを触りたくなければ触らずに済みます。
Blendが本来サポートして
いない書式も設定できます
2.Livetの紹介
Livetの特徴 – メインライブラリ(1)
 LivetのMVVMライブラリ部分は
 Codebehind付MVVM

ViewとViewModel間の通信は極力データバインディ
ングにするものの、シンプルなデータバインディング
で対応できないものはCodebehindで実装
 FullMVVM

ViewとViewModel間の通信は全てDataBindingで行
える形式
の双方に対応できるように設計してあります。
2.Livetの紹介
Livetの特徴 – メインライブラリ(2)
 MVVMライブラリは本来何が問題領域でしょうか?
 DataBindingの補完

本来は.NET Framework/Silverlightの領域。バインドできないプロ
パティ・ダイアログなのどの揮発性の現象
 WPF/SLの記述冗長性の排除

本来はC# or DSLの問題。依存関係プロパティもプロパティ変更通知
も現在のC#とは記述の相性が悪い。
MVVM ≠ DataBinding という認識なので
Livetは「DataBindingの補完」を最優先して設計しています。
 選択肢がいくつかある場合、常にWPFの思想と機構に準じます。
2.Livetの紹介
開発体制
去年の10月からしばらく僕一人で作っていましたが
Ver0.96から複数人での開発MLを設けての体制に移行していま
す。
• @yfakariyaさん
• @azyobuzinさん
• @ufcppさん
• @aetos382さん
• @okazukiさん
• @ugaya40
この上なく心強い面子です。
2.Livetの紹介
現状と予定
現在 ver 0.98。次に0.99をリリースし、その次 1.0をもっ
て英語化し、Codeplexにて公開して世界のライブラリと
競ってみる予定です。
MS系技術にしては珍しくMVVMライブラリは激戦区。
だからこそ国産ライブラリが今後の標準に影響を与える可能
性だってあります。僕らは一応そこを目指しています。
Prism,MVVM Light Toolkit,
Simple MVVM Toolkit,Cariburn,NoMVVM,
NotifyPropertyWeaver…
その他世界には名の知れたMVVMライブラリが数多くあります。
この問題がもっとも大きいのです
3.Livetライブラリの紹介
ViewModelHelper/Notificator
リッチクライアントでは、Webと異なり各責務のライ
フサイクルを考慮する必要がある。
これはMVVMに関係なく、リッチクライアントの責務
分割そのものにまつわる宿命。
リッチクライアントはステートフルだから。
責務のライフサイクルを考慮しないとメモリリークの原
因となります
まずWPFでの責務間ライフサイクルを紹介します。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
 ViewとViewModelのライフサイクルが完全に一致す
るパターン
Data
Binding
検索結果画面
ViewModel
検索結果画面
どちらも使い捨てのインスタンスです。ライフサイクル
が一致しているので問題はおこりません。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
 ViewよりViewModelの寿命が長いパターン
テスト1ViewModel
テスト2ViewModel
ここら辺の
Viewは勝手
に消える
Data
Binding
・
・
・
でも
ViewModelは
ある
テスト99ViewModel
テスト100ViewModel
3.Livetライブラリの紹介
ViewModelHelper/Notificator
 ViewがViewModelを取り換えるパターン
一覧
仮想化モードRecycleだと、
コンテナが再利用される
(つまりコンテナの
ViewModelが変わる)
明細
明細画面は一覧で別の
アイテムが選択される
ごとにViewModelが
変わる
3.Livetライブラリの紹介
ViewModelHelper/Notificator
以上のようにWPFではViewとViewModelのライフサ
イクルを開発者が明示的に制御する事が難しくなってい
ます。
Data
Binding
View
ViewModel
でもDataBindingなら問題ありません。
リソースリークが発生しない仕組があります。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
以上のようにWPFではViewとViewModelのライフサイ
クルを開発者が明示的に制御する事が難しくなっていま
す。
呼出
View
ViewModel
イベント
しかしDataBindingで処理できない所を
この形で処理した場合は注意が必要です。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
メモリリークが発生する可能性があります。
呼出
View
ViewModel
イベント
しかしDataBindingで処理できない所を
この形で処理した場合は注意が必要です。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
そもそもメモリリークは
イベント
受信側A
public class イベント受信側A
{
ButtonA.Click += Clicked;
イベント
発行元
ButtonA
private void Clicked(object
sender, RoutedEventArgs e)
{
//処理
Click
イベント公開
3.Livetライブラリの紹介
ViewModelHelper/Notificator
そもそもメモリリークは
イベント
受信側A
public class イベント受信側A
{
実は発行元(Button A)が
ButtonA.Click += Clicked;
受信側AのClickedメソッ
ドの参照を保持している
private void Clicked(object
sender, RoutedEventArgs e)
{
//処理
イベント
発行元
ButtonA
Click
イベント公開
3.Livetライブラリの紹介
ViewModelHelper/Notificator
そもそもメモリリークは
イベント
受信側A
public class イベント受信側A
{
イベントの登録を解除しないで、
ButtonA.Click += Clicked;
受信側が勝手に消滅しようとし
ても発行側に参照されているの
private void Clicked(object
で消えられない!
sender,
RoutedEventArgs e)
{
//処理
イベント
発行元
ButtonA
Click
イベント公開
3.Livetライブラリの紹介
ViewModelHelper/Notificator
つまりイベント由来メモリリークは、
イベントハンドラを明示的に解除しないと、
イベント受信側がイベント発行側から参照されっぱなし
で起こる問題です。
これは、開発者側からするとイベント解除すべきタイミ
ングの判断しにくいWPFでは大きな問題となります。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
ViewとViewModelの関係に焦点を絞って言えば、
 ViewModelがイベントを発行
 Viewが受信する
ので、
Viewの寿命がViewModelより短いパターンで問題に
なってきます。
そしてそれがWPFのコレクションコントロールの基本
的な動作です。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
Livetでは、この問題に対応するため、
DataBinding機構と同じ仕組みを介する機能を提供して
います。
それがViewModelHelper/Notificatorです。
DataBindingと同じで、内部的にはWeakEventパター
ンが使用されています。イベントによるイベントハンド
ラの参照を参照として認識させないための機構です。
ViewModelHelper/Notificatorを使用すれば、ハン
ドラを解除しなくても一応リークはしません。
3.Livetライブラリの紹介
Notificator
 Notificator
 Livetでの汎用WeakEvent機構です。
 イベント発行側ではコードスニペット : lnevを使用して、普通の
CLRイベントとNotificatorを定義します。
 コードスニペット : lnevを使用すると、



普通のCLRイベント
Notificatorオブジェクト
CLRイベントとNotificatorをRaise(発火)させるメソッド
 の3つがregionで囲まれた状態で生成されます。
3.Livetライブラリの紹介
Notificator
 Notificator
 受信側ではNoticatorHelperを使って、受信ハンドラを
登録します。
 もちろん、ハンドラの解除も似たような構文で用意して
あります。
3.Livetライブラリの紹介
ViewModelHelper
 ViewModelHelper
 LivetでModelからのイベントをWeakEventで受信する
専用の機構です。
 以下の4つの機能を備えています。




Modelからのプロパティ変更通知ハンドラの登録・解除
Modelからのコレクション変更通知ハンドラの登録・解除
ModelからのNotificatorオブジェクトを介したイベントのハ
ンドラの登録・解除
Modelの変更通知コレクションと同期する、読み取り専用変
更通知コレクションの作成
3.Livetライブラリの紹介
ViewModelHelper
 ViewModelHelper
 プロパティ変更通知ハンドラの受信
 Modelの変更通知コレクションと同期する、読み取り専用変更通知
コレクションの作成
3.Livetライブラリの紹介
ViewModelHelper/Notificator
しかし、今さらですが、
WeakEventパターンは何も考えなくても全ての問題を
解決できる銀の弾丸ではありません。
WeakEventパターンでは不測の発火という問題が発生
しえます。
ハンドラを明示的に解除しなくて良いのだから、ハンド
ラの解除はGCまかせになります。
開発者はもうハンドラが反応しないつもりでいても、
GCが回収するまではハンドラが反応するのです。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
しかし、LivetのWeakEvent実装の元となったWPFの
DataBinding機構は不測の発火に対する対処をデザイン
束縛で解決しています。Livetでも同様の対処をする事
で不測の発火による問題を防ぐことができます。
 基本的にViewModelHelper/Notificatorを責務間通信
では使用する
 ハンドラを解除できるタイミングが明らかな時はちゃ
んと解除をする
3.Livetライブラリの紹介
ViewModelHelper/Notificator
しかし、LivetのWeakEvent実装の元となったWPFの
DataBinding機構は不測の発火に対する対処をデザイン束縛
で解決しています。Livetでも同様の対処をする事で不測の
発火による問題を防ぐことができます。
 間にユーザー操作を介さずに、イベント発行元の更新を目
的とするハンドラは書かない(発行元に副作用を及ぼすハ
ンドラは書かない)
 ユーザー操作を挟まないで発行元のイベントを起点に発行元
を更新するのであれば、そもそもすべての処理を発行元でし
ていれば良いのです。
 この3点を守れば、問題のあるシナリオはないはずです。
3.Livetライブラリの紹介
ViewModelHelper/Notificator
 ViewModelHelper/Notificatorはコードビハインド付
MVVMであろうが、フルMVVMであろうが、そもそも
MVVMでなかろうが、WPF/SLで責務分割を考えた時
に必ず発生する問題に対するソリューションです。
 以下のデザイン原則を守る事で、
ViewModelHelper/NotificatorはWPF/SLでのイベン
ト由来メモリリークに対する銀の弾丸になります。
 ハンドラの解除が出来る時は明示的にする
 ユーザー操作を介さず、発行元を更新するハンドラを書
かない。
4.Livetライブラリの紹介
そのほかの機能 – バインディングサポート
 Livet.Behaviors.ControlBinding.OneWay名前空間
には、
すべての標準WPFコントロールの、
標準ではバインドできないプロパティを一定のルールで
抽出し、疑似的に単方向バインドを可能にするビヘイビ
アとアクションが含まれています。
Livetアセンブリの7割の容量を占める機能です。
(自動生成です)
4.Livetライブラリの紹介
そのほかの機能 – バインディングサポート
・TextBoxのSelectedTextは本来バインドできない
4.Livetライブラリの紹介
ViewModelCommand/ListenerCommand
 Livetにおいて、ViewModelがViewに公開するユー
ザー操作の事です。コマンドはあくまでもユーザー操
作を介して呼び出されるものです。
 操作と、操作の実行可否状態をラップする事が出来ま
す。
 コマンドはただのメソッドと違い「ユーザー操作」で
あるとの意味づけを行えます。
 悪しきデザインにならないためにも、私自身はコード
ビハインド付MVVMでも意味を明示するために、コマ
ンドを使用します。
3.Livetライブラリの紹介
ViewModelCommand/ListenerCommand
 ViewModelCommand
 ViewModelがViewに公開する操作とその実行可否オブ
ジェクトです。
 CanExecuteが引数を取らないのが他のライブラリとの
違い
 ListenerCommand
 ViewModelCommandに、Viewからの情報を受け取れ
るようにしたものです。
 これもユーザー操作を介さないで呼び出さないというデ
ザインルールで、様々なトラブルを防ぎつつ運用できま
す。
4.Livetライブラリの紹介
コマンドとバインディングサポート まとめ
 バインディングサポート・
ViewModelhelper/Notificatorは、コードビハインド
付MVVMだろうと、フルMVVMだろうと共通して扱え
る機構です。
 コマンドはお好みで。
 以降はコードビハインドを書かない「フルMVVM」の
ための機能の紹介になります。
やはりこれがMVVMの花
おまけ.LivetとフルMVVM
フルMVVMとは?
 コントロールの拡張手段
 カスタムコントロール
 ビヘイビア
 XAMLベース状態遷移手段
 トリガー
 アクション
 XAMLベースメッセージング機構
を駆使して、コードビハインド無しのWPFアプリケー
ション開発を行うものです。
おまけ.LivetとフルMVVM
フルMVVMとは?
 コードビハインドがないため、ViewはXAMLと各拡張
要素だけになります。各拡張要素がメインのViewと
独立する事は、Viewの各拡張要素がドメインに結合
して不適切な粒度になるのを防ぎやすくなります。
 フルMVVM用の拡張要素は、C#で作成し、実際に画
面に組み込む時には全てXAMLで定義可能なため、
ExpressionBlendなどのWYSWIGなエディタの支援
をフルに受けられるようになります。これは理論上デ
ザイナとプログラマの分担開発を可能にします。
 コードビハインドがないため、ViewとViewModel
の責務を各開発者が逸脱しにくくなります。
おまけ.LivetとフルMVVM
フルMVVMとは?
 LivetのフルMVVM用の機能は、もっとも開発に時間
をかけた部分ではありますが、基本から説明している
時間がないので、元となった形式であるPrismとの違
いをメインに駆け足でご紹介します。
おまけ.LivetとフルMVVM
メッセージシステム
 文字列キーベース Messenger
View
ViewModel
おまけ.LivetとフルMVVM
メッセージシステム
 View定義メッセージ
6.まとめ
 Livetは、「純国産」のMVVMインフラです。
 WPFの思想に則りながら、バインディング機能の補完
を中心に機能を拡張していっています。
 コードビハインド不使用にこだわらない設計を心がけ
ます。
 ユーザーさんが増えてから、フィードバックをいただ
くようになり、最近の進化は僕が一人でこそこそと開
発していたころと比べると目覚ましいものがあります。
6.まとめ
 ぜひ使ってみてください。
 使っていただけないとLivetは育ちません。
 現時点でももっとも使いやすいWPFインフラになって
いると思っています。
 よろしくお願いいたします。