none
MVVMでViewにUserControlのCollectionを表示させる方法 RRS feed

  • 質問

  • MVVMで、Canvas に任意数の UserControl を配置するアプリを作っています。
    具体的には、Canvas 上に画像ファイルをDropすると、その画像を表示する UserControl を生成して、Canvas.ChildrenにAddするイメージです。
    ImageオブジェクトではなくUserControlにしたのは、後々拡張するためです。現時点ではImageオブジェクトが一つ入っているだけのUserControlです。
    ViewModelを使わずに、View内部で完結させるテストアプリは実装できましたが、MVVMでの実装方法に悩んでます。

    下のような手順を考えています

    1. View内のCanvasパネルに画像ファイルをDrop → ViewModelに実装したCommand (AddImageCommand)を実行
    2. ViewModelのDropCommandで、画像を表示するUserControl用のViewModel(ImageUC_ViewModel)クラスのインスタンスを作成
    3. ImageUC_ViewModelのコレクション↓ に、生成したImageUC_ViewModelのインスタンスを追加
        ObservableCollection<ImageUC_ViewModel> ImageUC_ViewModelCollection
    4. 追加された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/

    なにかよい解決方法があれば教えて下さい。よろしくお願いします。

    2010年1月11日 4:50

回答

  • 対策その3が一番いいと思います。
    ListBoxのアイテムを選択する機能が邪魔なら、ListBoxの親のItemsControlを
    使うと、余計な機能がなくて使いやすいと思います。
    選択機能が欲しいならListBoxで、TemplateやStyle使ってやるのが素直だと思います。

    • 回答としてマーク NIM5 2010年1月16日 8:51
    2010年1月12日 0:57
  • 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
    2010年1月12日 8:31
    モデレータ

すべての返信

  • 対策その3が一番いいと思います。
    ListBoxのアイテムを選択する機能が邪魔なら、ListBoxの親のItemsControlを
    使うと、余計な機能がなくて使いやすいと思います。
    選択機能が欲しいならListBoxで、TemplateやStyle使ってやるのが素直だと思います。

    • 回答としてマーク NIM5 2010年1月16日 8:51
    2010年1月12日 0:57
  • 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
    2010年1月12日 8:31
    モデレータ
  • 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の機能を追加すると考えれば納得です。
    (気持ちの問題ですけど)

    ご指摘ありがとうございました。

    2010年1月16日 9:13