はじめに
前回記事でPrismのDIを試した
その際、インターフェイスを登録し忘れて実態クラスをViewModel
の引数に渡していた
その時の様子は以下
public class MainWindowViewModel : BindableBase
{
IPerson person;
public MainWindowViewModel(Person person)
{
this.person = person;
}
// ViewModelのコンストラクターはこれだけ。引数なしのコンストラクターは存在しない。
}
てっきりDIコンテナーにインターフェイスと実装を登録しないと動かない、エラーが出るものかと重いっていたが、正常に画面が立ち上がった
どういう経緯なのか調べる
ViewModelLocator
View
とViewModel
を紐づけるのがViewModelLocator
の役割
ViewModelLocator - Prism Library
prism:ViewModelLocator.AutoWireViewModel="True"
をWindow
もしくはUserControl
タグ内に書くことで、その機能が有効になる
(参考):公式HPを訳しているQiita記事
ViewModelLocatorをつかわない場合
比較のために、Prismの機能をつかわない場合を載せる
方法1: XAMLでViewModelを指定する
<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.