【WPF Prism】 ViewModelLocatorがViewにViewModelを紐づけるまで


はじめに

前回記事でPrismのDIを試した

その際、インターフェイスを登録し忘れて実態クラスをViewModelの引数に渡していた

その時の様子は以下

public class MainWindowViewModel : BindableBase
{
    IPerson person;

    public MainWindowViewModel(Person person)
    {
        this.person = person;
    }
    // ViewModelのコンストラクターはこれだけ。引数なしのコンストラクターは存在しない。
}

てっきりDIコンテナーにインターフェイスと実装を登録しないと動かない、エラーが出るものかと重いっていたが、正常に画面が立ち上がった

どういう経緯なのか調べる

ViewModelLocator

ViewViewModelを紐づけるのがViewModelLocatorの役割
ViewModelLocator - Prism Library

prism:ViewModelLocator.AutoWireViewModel="True"WindowもしくはUserControlタグ内に書くことで、その機能が有効になる

(参考):公式HPを訳しているQiita記事

ViewModelLocatorをつかわない場合

比較のために、Prismの機能をつかわない場合を載せる

方法1: XAMLでViewModelを指定する

MainWindowView.xaml
<Window.DataContext>
    <vm:MainWindowViewModel />
</Window.DataContext>

ただし、今回のようにコンストラクターに引数が必要な場合(ViewModel(Person person))はエラーがでるため使えない

(引数なしのコンストラクターを別途用意すれば使えなくはないが・・)

方法2: コードビハインドでViewModelを指定する

引数がある場合は、コードビハインドで指定する

public MainWindow()
{
    InitializeComponent();

    this.DataContext = new MainWindoViewModels(new Person()); // ←
}

【本題】ViewModelLocatorがViewModelをバインドするまで

PrsimのViewModelLocator.AutoWireViewModelについて調べる

調べるといっても、GitHubからコードをおとして関数をたどっていくだけの作業

ViewModelLocatorクラス

// 1.XAML添付プロパティ prism:ViewModelLocator.AutoWireViewModel
DependencyProperty AutoWireViewModelProperty = DependencyProperty.RegisterAttached(
    "AutoWireViewModel",        // 登録する依存関係プロパティの名前
    typeof(bool?),              // プロパティの型
    typeof(ViewModelLocator),   // 依存関係プロパティを登録する所有者型
    new PropertyMetadata(
        defaultValue: null,     // 依存関係プロパティの既定値
        // プロパティの有効値が変更されるときにプロパティ システムによって必ず呼び出されるハンドラー
        propertyChangedCallback: AutoWireViewModelChanged)
        );

// 2. 添付プロパティが変更されたら呼ばれるハンドラー
AutoWireViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    // EventArgs e から添付プロパティ値を取得 -> True なら、プロバイダのメソッドを呼ぶ
    // d: プロパティの値が変更されたビュー
    // Bind: view.DataContext = viewModel
    ViewModelLocationProvider.AutoWireViewModelChanged(d, Bind);
    // 呼び出し元と名前が同じでややこしい
}

// 3. プロバイダ側で呼ばれるコールバック関数
// DataContextをビューにセットする
Bind(object view, object viewModel)

(参考):WPF4.5入門 その45 「添付プロパティ」 - かずきのBlog@hatena

ViewModelLocationProviderクラス

ビューのAutoWireViewModelプロパティが変更されたらメソッドが呼ばれる

ViewModelLocatorのルール(convention)にしたがって、***View***ViewModelのマッピングがなされる

// AutoWireViewModelが変更されたら呼ばれる
// 引数1: プロパティが変更されたビュー(Dependency Object)
// 引数2: Bind(view, viewmodel)コールバック関数
void AutoWireViewModelChanged(object view, Action<object, object> setDataContextCallback)
{
    // 登録されているビューモデルがあれば、それを使う
    object viewModel = GetViewModelForView(view);

    // 登録されたビューモデルがみつからなかったら、ルールに従ってビューモデルを探す
    if (viewModel == null)
    {
        // (略)

        viewModel = _defaultViewModelFactoryWithViewParameter != null ? 
        _defaultViewModelFactoryWithViewParameter(view, viewModelType) 
        : _defaultViewModelFactory(viewModelType);
    }

    setDataContextCallback(view, viewModel); // view.DataContext = viewModel
}
Factory 実装 結果
_defaultViewModelFactoryWithViewParameter(view, viewModelType) Prism.Ioc.ContainerLocator.Container.Resolve(viewModelType) OK
_defaultViewModelFactory(viewModelType) System.Activator.CreateInstance(viewModelType) System.MissingMethodException: 'No parameterless constructor defined for type 'FullApp.ViewModels.MainWindowViewModel'.'

パラメーター付きのViewModelだとエラーがでるのは、XAMLで指定する場合と似ている

Viewのコードビハインド

コードビハインドで同じことをやる

public MainWindow()
{
    InitializeComponent();

    var viewModelType = typeof(ViewModels.MainWindowViewModel);
    var viewModel = Prism.Ioc.ContainerLocator.Container.Resolve(viewModelType);
    
    this.DataContext = viewModel;
}

UnityContainer.Resolve()

次に調べるべきはIUnityContainer.Resolve()

もう力が尽きたので、インターフェイスのサマリーだけ読んだ

Unityは型が登録されているかどうかをチェックし、登録されていれば登録情報を使用してオブジェクトを作成する。
型が登録されていない場合、リフレクションを使用して型に関する情報を取得し、リフレクションデータを使用して型をインスタンス化および初期化するためのパイプラインを作成する。

MainWindowViewModelは登録されていないので、リフレクションによって再帰的にインスタンスが生成されたと推測される

おわりに

PrismのViewModelLocatorがどのようにViewModelのインスタンスを生成するか追っていった

最後はUnityContainerライブラリのResolveメソッドの実装によっている
・・というところまでたどって力尽きた

(おまけ)UnityContainerの開発が終了していた

コミュニティがこのプロジェクトに対する興味を完全に失ったので、このプロジェクトはもうメンテナンスしません。今後リリース予定もありません。

Due to a complete lack of interest from the community to support this project, it has been archived and will no longer be maintained. There will be no more releases of this library.