“Windows 8.1 におけるストア ビジネスアプリの開発”(MVP Community Camp 2014)DEMO解説 #3:ページ間のナビゲーション
皆様、こんにちは!ちょっと間が空いてしまいましたが、デモ解説の #3、ページ間のナビゲーションについてご紹介します。まずは今回も冒頭はスライドシェアのリンクですね。
<Windows 8.1 におけるストア ビジネスアプリの設計と開発>
Windows 8.1 におけるストア ビジネスアプリの設計と開発 from Shotaro Suzuki
Windows 8.1 と Prism を使ったストアアプリのページナビゲーションについて
要は、NavigationService の使い方を、アプリに実装するということですが、今回は英国の同僚が公開しているアプリをもとに実装したもので、アプリとしてはそれなりに体裁の整ったものになっていますので、このままご紹介していきます。
セッションでは、ナビゲーションサービスの説明として、下記の2つのコード断片をご紹介しました。
アプリのスタートアップ時のナビゲーション(App.xaml.cs)
1: protected override Task OnLaunchApplication(LaunchActivatedEventArgs args)
2: {
3: this.NavigationService.Navigate("Search", null);
4:
5: return (Task.FromResult<object>(null));
6: }
検索ボタンに割り当てられたコマンドによるナビゲーション(SearchPageViewModel.cs)
1: void OnSearchCommand()
2: {
3: this._navigationService.Navigate("SearchResults", this.SearchTerm);
4: }
あとは前の2回の解説を見ればわかりますが、今回は少し詳細にアプリにつき、解説してあります。
まず、ソリューションファイルはこちらからダウンロードしてください(セッションでもご紹介した通り実アプリではなくあくまでサンプルです)。起動して実行する方は、Flickr (Yahoo!の画像共有サービスですね)の ID で API Key を取ってくることを忘れずに!
セッションでご紹介したデモアプリは、下記の通りの構成でした。3画面があり、Flickr から、写真を検索して、検索結果を表示して、個別の写真詳細を表示するアプリです。
検索画面はこんな感じですね。背景画像は MSN で公開されている壁紙を使用しました。
ちなみにこの検索ワードは、私が愛用している E.Guitar と同じモデル、Ibanez RG 550 Genesis Collection です。検索結果はこんな感じです。
そして、写真を1枚タッチすると、詳細はこんな感じです。
加えてアプリバーを下からスワイプすると、ユーザー画像ライブラリに保存できる、というボタンがあります。
このアプリは、IoC(Inversion of Control )コンテナとして、”NuGet パッケージの管理”から、.NET 4.5 用に最適化されている AutoFac というものを入れてあります。これは便利で、最近ストアアプリでの利用がどうやら?推奨されているようです。
※ Prism のテンプレートに、IoC コンテナとして、Unityを使っているものも含まれていますが、それと似たようなものですね。Prism テンプレートはこちらから。インストールは簡単です。その後、Visual Studio を再起動してください。
こちらが AutoFac がダウンロードできるサイトです。NuGet からもインストールできます。
Inversion of Control とは、Dependency Injection(依存性の注入) と同義です。すなわち、自分で作成したコンポーネントから Framework を呼び出すのではなく、Framework からコンポーネントを呼び出してくれます。依存性の注入というのは(いつも思うのですが)分かりにくい日本語です。要は、設定ファイルにオブジェクト間の連携を記述しておけば、あるオブジェクトに対して使うオブジェクトがセットされ、そのオブジェクトの利用者は自らインスタンス生成をしなくて良い、ということになるわけです。
さて、ここではView は3つあります。先述した通り、SearchPage、SearchResultsPage、そして、PhotoDetailsPage です。
また、他に2つのサービスがあり、Windows 8/8.1 固有のものです。1つは、写真をピクチャライブラリに保存するもので、もう1つは、ナビゲーションサービスの特性を抽象化して各 ViewModel から使えるようにするためのものです。
Autofac コンテナーを準備し、Abstractions ライブラリフォルダに入っているインタフェースにより抽象化されたサービスの実装をこれを使って行います。ViewModel をインスタンス化し、それらを View の DataContext にバインディングします。
ここでは、(ベースクラスに加えて) 4 つの ViewModel を準備し、3つの Views をサポートします。
1. SearchPageViewModel
2. SearchResultsPageViewModel および SearchResultViewModel (この ViewModel は、各々のView の中に表示される GridView にバインドされます)
3. PhotoDetailsPageViewModel
もう一つ、Model クラスがあります。これが、FlickrSearchItem です。これは、Flickr から取ってきたデータをデシリアライズしたデータを表示するものです。コードとしては、FlickR の API を使って検索し、結果を取得して、Model クラスのインスタンスを List に入れて返します。
このコードは (ポータブル HttpClient ライブラリの利用により) 、極めて可搬性に優れています。 したがって、すぐに抽象化をする必要はないのですが、抽象化した方が良いと思われます。これに依存性を持つクラスに注入できるからです。
また、このライブラリの中に ICommand があり、SimpleCommand クラスの中にあります。このクラスは、ViewModelBase と呼ばれる ViewModel のベースクラスで、 INotifyPropertyChanged を実装しています。
これらのインタフェースは、ナビゲーションサービスや写真の保存サービスを抽象化するもので、それら特定のサービス作成に伴い実装されます。
※ この辺りまでは、MVVM、Commanding、Binding、Abstraction、IoC、などを知っていることが前提になっていますが、ここではこれ以上個別に詳述はしません。もし「これらを初めて聞いた」、という場合は、この Prism for Windows Runtime のドキュメントやリンク先の URL 等にも書かれていますので、そちらを参照してくださいませ。
スクラッチでアプリを作成してPrism for Windows Runtime を追加
さてここからアプリを作成します。前回のアプリ同様、Visual Studio 2013 で、最初にテンプレートから、無印新規アプリを選択し生成、その上で、NuGet からオンラインで、Prism.StoreApps パッケージを追加します。その後、アセットフォルダに画像を配置します。MSN等の壁紙からダウンロードする等して適切な大きさに加工したうえで、物理フォルダーにコピーします。その後、マニフェストファイルを開いて適切に設定します(例:アイコン用、スプラッシュスクリーンの画像用、等々)。
できるだけシンプルにしたいので、一つのプロジェクトの中に、必要なフォルダーを名前を付けて作成します。
できあがったところで、今回は必要ないので、MainPage.xaml(と分離コード)を削除します。
まずは、App クラスのベースクラスを変更します。Visual Studio 無印新規アプリテンプレートの標準の、Application から、Prism の MvvmAppBase に変更します。
次に、ViewModel フォルダーに移動します。オリジナルの ViewModelBase ベースクラスを作り、それを Prism のオリジナル ViewModel クラスに置換します。同時に、FlickrService コードは、そのコードを抽象化するインターフェースを使用して作成したもので、それが一つの ViewModel を構成しています。すなわち当該 ViewModel は、実装それ自体に依存するよりは、(少なくとも構築時には)当該インターフェースに依存したインターフェースに依存します。もう一つの ViewModel に関する注意点は、ナビゲーションサービスに依存するものであったということです。このナビゲーションサービスは、独自に実装したもので、Prism for Windows Runtime が提供する INavigationService に依存するものです。そして、2つの ViewModel において、ViewModel.OnNavigatedTo メソッドをオーバーライドし、ナビゲーション操作により受け渡されるパラメーターを受け取れるようにします。
Prism の NavigationService を使う場合には、シンプルに下記のように書きます。
1: void OnSearchCommand()
2: {
3: this._navigationService.Navigate("SearchResults", this.SearchTerm);
4: }
ここで、 “SearchResults” は抽象化されたトークンで、SearchResultsPage ページへ遷移するのに使われます。
ここでオリジナルの SimpleCommand クラスを使い、これにより、 Prism の DelegateCommand を置換します。ほぼ同じように動作します。
そして、ViewModel と DataContext の値の設定をおこなうためにコードを書きます。ついで、ページのベースクラスを Prism の VisualStateAwarePage に変更します。最終的に XAML の中で 各 View が宣言的に作成され、Prism の ViewModelLocator.AutoWireVIewModel プロパティを True に設定することになります。これで全部のパーツがつながることになります。
1.アプリを起動すると、SearchPage の View に遷移します(NavigationService )。
2. Navigation で使われるのは、 “Search” のような文字列(論理名)です。これは、正確に SearchPage のようなページ (View) 名に解決されます。
3. View は ViewModel を生成し、そして ViewModel は、それが依存するサービスの名前解決をします。
これれらとは別に、App クラスを、これらに合致するような形に変更しておく必要があります。上記の 1.で、OnLaunchApplication メソッドをオーバーライドしておく必要があります。
1: protected override Task OnLaunchApplication(LaunchActivatedEventArgs args)
2: {
3: this.NavigationService.Navigate("Search", null);
4:
5: return (Task.FromResult<object>(null));
6: }
そして、2.において、GetPageType メソッドをオーバーライドします。これは、デフォルトの実装が、例えばページトークンが “Main” のようなものだと、その名前を元にそれを “MainPage” タイプに名前解決しようとするためです。この理由は、この “MainPage” クラスの名前空間が、現在実行されているアセンブリの名前空間となることが通常期待されるからです。しかし、このアプリの View はそのように名前付けされていませんので、オーバーライドした訳です。
1: protected override Type GetPageType(string pageToken)
2: {
3: string baseName = "MvvmStart.Views.{0}Page";
4: string typeName = string.Format(baseName, pageToken);
5: Type type = Type.GetType(typeName);
6: return (type);
7: }
そして、最後のポイント上の 3. で、Autofac コンテナーを使い、 OnInitialize メソッドをオーバーライドしています。
1: protected override void OnInitialize(IActivatedEventArgs args)
2: {
3: base.OnInitialize(args);
4:
5: ContainerBuilder builder = new ContainerBuilder();
6:
7: builder.
8: RegisterInstance<INavigationService>(this.NavigationService).
9: As<INavigationService>();
10:
11: builder.RegisterType<FlickrService>().As<IFlickrService>();
12: builder.RegisterType<PhotoSavingService>().As<IPhotoSavingService>();
13:
14: builder.RegisterType<SearchPageViewModel>().AsSelf();
15: builder.RegisterType<SearchResultsPageViewModel>().AsSelf();
16: builder.RegisterType<PhotoDetailsPageViewModel>().AsSelf();
17: builder.RegisterType<SearchResultViewModel>().AsSelf();
18:
19: this._container = builder.Build();
20: }
Autofac に詳しい訳ではないので、この使い方で良いかはわかりません。しかし、これにより、基本的にナビゲーションサービス、Flickr サービス、写真保存サービスが登録されます。そして、ViewModel が、それぞれの依存するサービスを解決した形のコンテナーから生成されるのです。
そこで、これらのコンテナー群を使うために、Resolve() メソッドをオーバーライドしておきます。
1: protected override object Resolve(Type type)
2: {
3: return (this._container.Resolve(type));
4: }
以上です。
次に、SearchResultsPageViewModel を作成します。これは、SearchResultViewModel のコレクションを持っています。この SearchResultViewModel は、コンテナーからのSearchResultViewModel 作成の仕方や、その依存性の解決の仕方を保持しています。AutoFac が全体的にはうまくやってくれますので、SearchResultsPageViewModel のコンストラクターはこのようになります。
1: public SearchResultsPageViewModel(
2: INavigationService navigationService,
3: IFlickrService flickrService,
4: Func<SearchResultViewModel> resultViewModelFactory)
5: {
6: this._navigationService = navigationService;
7: this._flickrService = flickrService;
8: this._resultViewModelFactory = resultViewModelFactory;
9: }
以上です。
次は、#4として、データアクセス・認証、疎結合コンポーネント間通信、 の箇所を纏めて取り上げ、ついで、#5 として、ユーザー入力の検証(バリデーション)のところを取り上げて、このセッションの解説を終了したいと思います。
それではまた!
鈴木章太郎