トップ回答者
MVVMでViewにUserControlのCollectionを表示させる方法

質問
-
MVVMで、Canvas に任意数の UserControl を配置するアプリを作っています。
具体的には、Canvas 上に画像ファイルをDropすると、その画像を表示する UserControl を生成して、Canvas.ChildrenにAddするイメージです。
ImageオブジェクトではなくUserControlにしたのは、後々拡張するためです。現時点ではImageオブジェクトが一つ入っているだけのUserControlです。
ViewModelを使わずに、View内部で完結させるテストアプリは実装できましたが、MVVMでの実装方法に悩んでます。下のような手順を考えています
- View内のCanvasパネルに画像ファイルをDrop → ViewModelに実装したCommand (AddImageCommand)を実行
- ViewModelのDropCommandで、画像を表示するUserControl用のViewModel(ImageUC_ViewModel)クラスのインスタンスを作成
- ImageUC_ViewModelのコレクション↓ に、生成したImageUC_ViewModelのインスタンスを追加
ObservableCollection<ImageUC_ViewModel> ImageUC_ViewModelCollection - 追加されたImageUC_ViewModel を表示するUserControlのインスタンスを生成し、CanvasのChildrenに追加(Add)する
わからないのは、以下の点です。
a.UserControlのViewModelコレクションと、CanvasのChildrenを関連づける方法
b.ViewModelのコレクションに追加されたときに、UserControlのインスタンスを作成してCanvas.Childrenに追加する方法
c.CanvasのDropイベントが発生したときに、ViewModelのCommandを発生させる方法(本題ではないですけど)
イベントハンドラ(ビハインドコード)でViewModelのCommandを直接呼び出せばできないことはないのですが、XAMLで記述する方法はありますか?ListBoxなら、ListBox.ItemsにViewModelのCollectionをDataBindingさせて、ItemsTemplateにUserControlを表示させれば一発なんですけど、同じようなことはCanvasパネルに対しても可能でしょうか?
ViewModelのコレクションと、Canvas.Children をDataBindingさせて、CanvasのなんらかのTemplateを使ってUserControlを生成できればと思ったんですけど、ChildrenはReadOnlyPropertyということでbindingできませんでした。
一応対策を考えてみました。
(対策その1):ViewModelにUserControlのコレクションを持たせてそれをCanvasに表示する
ViewModelがUserControlのインスタンスを持っていいのか?という疑問もありますが、そこはよしとするにしても、
ViewModelのUserControlCollection と CanvasのChildrenの同期をどうやってとるのかわかりません(対策その2):ListBoxのItemsのようなプロパティを持つカスタムCanvasクラスを作る
こちらも、カスタムCanvas内の ItemsプロパティとChildrenの同期をとる方法がわかりません。(対策その3):ListBoxのItemsPanelTemplateをCanvasに変えて、ListBoxをCanvasのように使う
参考→<http://d.hatena.ne.jp/Yamaki/20071011/1192091886>
一番簡単だとは思うんですけど、腑に落ちない・・・
ListBoxに余計な機能がたくさんあって、項目を選択させたときの表示方法とかも全部Templateで直さないといけない。
でもほかの方法がなければ検討します。フォーラム内を検索すると下記のような記事(CollectionView)が見つかり、関係がありそうな気がするのですがどう使ってよいのかわかりませんでした。
http://social.msdn.microsoft.com/Forums/ja-JP/wpffaqja/thread/1fdb1a7b-2211-41c8-8c62-bdec6ee37a33/
なにかよい解決方法があれば教えて下さい。よろしくお願いします。
回答
-
MVVMは疎結合を保つためにバインドを利用しますから、直接バインドができないのであればViewとViewModelの両方を知っているヘルパークラスを作成し、ViewModelにおけるデータコレクションの変化でViewのCanvasを描画するとうまくいくかもしれません。未検証です。
しかし、こんなことはせずにa_かずき_さんが書かれているようにItemsControlを利用するのが最初の選択肢としては素直だと思います。
コードの参考例がありましたので載せておきます。
WPF: Is it possible to bind a Canvas’s Children property in XAML?
http://stackoverflow.com/questions/889825/wpf-is-it-possible-to-bind-a-canvass-children-property-in-xaml
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/- 回答としてマーク NIM5 2010年1月16日 8:51
すべての返信
-
MVVMは疎結合を保つためにバインドを利用しますから、直接バインドができないのであればViewとViewModelの両方を知っているヘルパークラスを作成し、ViewModelにおけるデータコレクションの変化でViewのCanvasを描画するとうまくいくかもしれません。未検証です。
しかし、こんなことはせずにa_かずき_さんが書かれているようにItemsControlを利用するのが最初の選択肢としては素直だと思います。
コードの参考例がありましたので載せておきます。
WPF: Is it possible to bind a Canvas’s Children property in XAML?
http://stackoverflow.com/questions/889825/wpf-is-it-possible-to-bind-a-canvass-children-property-in-xaml
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/- 回答としてマーク NIM5 2010年1月16日 8:51
-
a_かずき_ さん、trapemiyaさん、回答ありがとうございます。
自分のアプリに組み込んでみて、なんとか思った通りの機能になりました。trapemiyaさんが指摘された ↓ の6を参考にして、これまで作ってたCanvasをそっくり ItemsPanelTemplate に持って行くだけで、ほぼ同じ動作になりました。
http://stackoverflow.com/questions/889825/wpf-is-it-possible-to-bind-a-canvass-children-property-in-xaml
Canvasの代わりに ItemsPanel を使うというより、Canvas に ItemsPanelの機能を追加すると考えれば納得です。
(気持ちの問題ですけど)ご指摘ありがとうございました。