WPF/MVVM/C#/Prism5.0 ViewModelLocationProvider ~ViewとViewModelを自動で関連付け~


WPFでMVVMは難しい

残念なことに、WPFでMVVMパターンを適用する際には、.NET標準だけ使うとなると、綺麗でわかりやすく保守が容易なコードが書けません。
書けないような基盤しかないのです。
なので、PrismなどのMVVM基盤ライブラリが必要となります。
https://msdn.microsoft.com/ja-jp/library/gg406140.aspx

他にも様々なライブラリが公開されていますが、MS謹製ということで今回はPrismを利用しようと思います。
Prismを利用することで得られるメリットを公開します。
以下の予定です。

  1. BindableBase/DelegateCommand ~ViewModelの基盤~
  2. ErrorsContaier ~便利なエラー通知~
  3. ViewModelLocationProvider ~ViewとViewModelを自動で関連付け~
  4. Regionってなんなのさ ~Viewの配置をお手軽に~
  5. IModuleとUnity ~UIでDI~
  6. DIPパターンの恩恵 ~MSBuildで並列ビルド~

本稿は上記3の記事になります。

※Visual Studio 2013 Community Editionで実験しています。

ViewModelLocationProvider

これまでやってきたサンプルでは、ViewとViewModelの紐付けは以下のようになっています。

CalcView.xaml.cs
using Microsoft.Practices.Prism.Mvvm;
using System.Windows;

namespace KStore.Calc._2
{   
    public partial class CalcView : Window
    {
        public CalcView()
        {
            InitializeComponent();
            this.DataContext = new CalcViewModel(); // <-- ここで紐づけ
        }
    }
}

この
this .DataContext = new CalcViewModel();

を自動でやってくれるというちょっとオーバースペックな機能がPrismにはあります。
いや、オーバースペックではなく、今後必要になるのですが、まだあまり必要性は感じられません。
とにかく、やってみます。

CalcView.xamlに以下の2行を追加します。

CalcView.xaml
<Window x :Class="KStore.Calc._2.CalcView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local ="clr-namespace:KStore.Calc._2"
        mc:Ignorable ="d"
        Title="Calc" Height ="350" Width="525"
        xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Mvvm;assembly=Microsoft.Practices.Prism.Mvvm.Desktop"
        prism:ViewModelLocator.AutoWireViewModel ="True"
        >
</Window>

このままビルドすると、ビルドは通りますが、以下のように怒られます。

Your views must implement IView

実行してみると以下の例外。

言われたとおりに、CalcViewにIViewを実装。これはほとんどマーカーインタフェースですね。
DataContextにViewModelを紐付けする行も削除しました。

CalcView.xaml.cs
using Microsoft.Practices.Prism.Mvvm;
using System.Windows;

namespace KStore.Calc._3
{   
    public partial class CalcView : Window, IView // <-- このインターフェースを追加
    {
        public CalcView()
        {
            InitializeComponent();
        }
    }
}

で、実行してみると・・・

バインドされていない。。。
何故か。

ViewModelLocationProviderのコードを見ると、
Viewの名前に加えて、"ViewModel"が必要みたいです。
今は、
CalcView -> CalcViewModelにしていたのですが、
CalcView -> CalcViewViewModel でないとダメみたいです。

うーん。Viewの名前は末尾に"View"にしないほうが良さそうですね。

気を取り直して、CalcViewViewModel にしてみて、実行します。

バインドされました!
これを使うには、ViewModelの命名規則を必ず
Viewの名前 + "ViewModel"とする必要があります。

上記はXAMLのみで完結する記載でしたが、XAMLで記載せず、コードビハインドで以下のように書いてもOKです。

CalcView.xaml.cs
using Microsoft.Practices.Prism.Mvvm;
using System.Windows;

namespace KStore.Calc._3
{   
    public partial class CalcView : Window, IView
    {
        public CalcView()
        {
            InitializeComponent();
            ViewModelLocationProvider.AutoWireViewModelChanged(this);
        }
    }
}

今回はあまり長くならずに済みました。

まとめ

ViewModelLocationProviderを使うには、Viewの名前とViewModelの名前に規則性を持たせる必要があります。
Viewの名前に "ViewModel"を末尾に加えます。
例:
CalcView -> CalcViewViewModel

Viewの名前は末尾に"View"をつけないほうが良さそう。