前回 作成した Xamarin.Forms 用 ViewModelLocator をさっそく使ってみます
いつものように Xamarin.Forms の PCL プロジェクトを作成したら、前回作成した Xamarin.Forms 用 PCL をビルドしてできた Prism.Mvvm.Xamarin.dll を含む 3 つの DLL の参照を追加します
最終的に Nuget に登録するかもしれませんが、とりあえず InfrastructureAsemblies フォルダに配置して参照しています
そのあとかんたんな Page と ViewModel を追加します
<?xml version="1.0" encoding="utf-8" ?> <local:AwareContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:PrismXamarin.Views;assembly=PrismXamarin" xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=Prism.Mvvm.Xamarin" prismmvvm:ViewModelLocator.AutoWireViewModel="true" x:Class="PrismXamarin.Views.TopPage"> <Label Text="{Binding Message}" VerticalOptions="Center" HorizontalOptions="Center" /> </local:AwareContentPage>
これはトップ画面の XAML で、XAML に ViewModel を生成・参照するコードが記述されていないのがポイント
/// <summary> /// トップ画面 /// </summary> public partial class TopPage : AwareContentPage { /// <summary> /// コンストラクタ /// </summary> public TopPage() { this.InitializeComponent(); } }
中身はからっぽ
/// <summary> /// Prism 対応 ContentPage /// </summary> public class AwareContentPage : ContentPage, IView { #region IView /// <summary> /// Bindable DataContext /// </summary> public object DataContext { get { return this.BindingContext; } set { this.BindingContext = value; } } #endregion //IView }
AwareContentPage は ContentPage に IView のインタフェースを実装するためだけのクラスです
/// <summary> /// トップ画面の ViewModel /// </summary> public class TopPageViewModel : ViewModelBase { #region Message プロパティ /// <summary> /// Message /// </summary> private string message = string.Empty; /// <summary> /// Message の取得と設定 /// </summary> public string Message { get { return this.message; } set { this.SetProperty<string>(ref this.message, value); } } #endregion //Message プロパティ /// <summary> /// コンストラクタ /// </summary> public TopPageViewModel() { this.message = "Hello! Prism MVVM!"; } }
ViewModel はお決まりのアレ
あとは下準備を App.cs に追加!
/// <summary> /// 共通 アプリケーションクラス /// </summary> public class App { /// <summary> /// コンストラクタ /// </summary> static App() { // ViewModel をインスタンス化するデフォルトメソッドを指定します ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Resolve(type)); // View から対応する ViewModel を取得するロジックを設定します ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType => { var viewName = viewType.FullName; viewName = viewName.Replace(".Views.", ".ViewModels."); var viewAssemblyName = typeof(ViewModelBase).GetTypeInfo().Assembly.FullName; var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName); return Type.GetType(viewModelName); }); } /// <summary> /// メイン画面を取得します /// </summary> /// <returns>メイン画面</returns> public static Page GetMainPage() { return new TopPage(); } /// <summary> /// 指定したクラスをインスタンス化します /// </summary> /// <param name="type">クラスの型</param> /// <returns>インスタンス化した指定クラス</returns> protected static object Resolve(Type type) { return Activator.CreateInstance(type); } }
Prism for Windows Runtime のように ViewModelLocator の初期化処理を App クラスに追加しました
// View から対応する ViewModel を取得するロジックを設定します ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType => { var viewName = viewType.FullName; viewName = viewName.Replace(".Views.", ".ViewModels."); var viewAssemblyName = typeof(ViewModelBase).GetTypeInfo().Assembly.FullName; var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName); return Type.GetType(viewModelName); });
この部分は View と ViewModel をひもづける処理を記述しています
~View というクラス名の View だったら、~ViewModel をひもづけるということをしています
さて、これで動くはずなので実行してみると
Prism.Mvvm.Xamarin.dll が見つからないと怒られました
そこで、Xamarin.iOS、Xamarin.WinPhone のプロジェクトにも Prism.Mvvm.Xamarin.dll の参照を追加して再実行
それでもだめ!あれ~?っと思い Windows Phone の方を実行してみると
こっちは問題なし・・・自動で ViewModel がバインドされて画面にメッセージが表示されました
なんで iOS はだめなのか・・・いろいろ考えたあげく思いついたのが、Xamarin.Forms の XAML は XAML であって XAML ではないということです
どういうことかというと、ViewModelLocator を TopPage.xaml 内でしか参照していないので、アプリケーションのバイナリに含まれなくなったのではないかと予想
そのため、App.cs のコンストラクタを次のように書き換えてみました
/// <summary> /// コンストラクタ /// </summary> static App() { // Prism.Mvvm.Xamarin アセンブリを読み込み可能にするおまじない var autoWired = ViewModelLocator.AutoWireViewModelProperty.DefaultValue; // ViewModel をインスタンス化するデフォルトメソッドを指定します ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Resolve(type)); // View から対応する ViewModel を取得するロジックを設定します ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(viewType => { var viewName = viewType.FullName; viewName = viewName.Replace(".Views.", ".ViewModels."); var viewAssemblyName = typeof(ViewModelBase).GetTypeInfo().Assembly.FullName; var viewModelName = string.Format(CultureInfo.InvariantCulture, "{0}ViewModel, {1}", viewName, viewAssemblyName); return Type.GetType(viewModelName); }); }
ViewModelLocator を C# コード側から参照するおまじないを追加しただけ!
この状態で実行すると
動いた~!・・・やっぱり Xamarin.Forms の XAML は曲者ですね;