前回は,Prismでの画面遷移時にパラメータを渡す方法を確認しました。パラメータはstringのKeyとobject型の任意の値をセットにして渡しましたが,Keyのstringがリテラル文字の為,これもお互いのViewModelでの意思の疎通がとれていないと,片側だけ名称を変更してしまって実行時エラーになる可能性があります。これもnameofを使ってコンパイルエラーにしたいところです。しかし,今回はクラス名ではなく任意のパラメータ名の為そのままではnameofは使えません。今回はConditionクラスというものを作って,Conditionクラスとnameofを組み合わせて,パラメータのリテラル文字問題を対応していきます。
Conditionsフォルダーの作成
BlancApp1プロジェクトに「Conditions」というフォルダーを作ります。その中に「PageBCondition」というクラスを作ります。PageBConditionというのは,PageBのパラメータ用のクラスという意味です。Conditionの前の文字は書くViewの名前を使うことにしますが,これはローカルルールの為,チーム内で話し合って決定すればよいことです。今回はこのようなルールで実装します。
PageBConditionの記述
using System; using System.Collections.Generic; using System.Text; namespace BlankApp1.Conditions { internal sealed class PageBCondition { public PageBCondition(string title) { Title = title; } public string Title { get; } } }
PageBConditionにはPageBに必要な引数をプロパティにして記述します。引数が2つの場合はPageBConditionのプロパティも2つにします。今回はTitleプロパティ1つだけでいいので,Titleプロパティだけを記述します。コンストラクタには必ずタイトルの文字を指定させるようにします。これは完全コンストラクタパターンといって,そのクラスで必要な値をコンストラクタですべて指定させることで,クラスのインスタンスが不完全な状態を作らない実装パターンです。こうしておけば,PageBConditionのインスタンスが生成された後に,Titleの値が設定されていないという状態が発生しなくなり,バグの混入が防げます。アクセス修飾子が「internal」なのは,プロジェクト内でしか使用しないためで,sealedを記述しているのは,継承させる予定がないからです。必要な限り,クラスにはsealedを付けたほうが予定外に継承されることが防げるため,バグの混入が防げます。
MainPageViewModelの変更
private void PageBShow() { var param = new NavigationParameters { {"title","XXXX" } }; NavigationService.NavigateAsync(nameof(PageBView), param); }
MainPageViewModelの問題の部分です。Keyであるstringの「”title”」が問題の部分です。この部分を次のように変更します。
private void PageBShow() { var param = new NavigationParameters { {nameof(PageBCondition),new PageBCondition("XXXX") }, }; NavigationService.NavigateAsync(nameof(PageBView), param); }
Keyの部分はPageBConditionとし,さらにそれをnameofにすることで,PageBConditionの名称変更にも追従してコンパイルエラーになるようにしています。
PageBViewModelの変更
続いて引数を受け取る側のPageBViewModelも変更します。
using BlankApp1.Conditions; using Prism.Commands; using Prism.Mvvm; using Prism.Navigation; using System; using System.Collections.Generic; using System.Linq; namespace BlankApp1.ViewModels { public class PageBViewModel : ViewModelBase { public PageBViewModel(INavigationService navigationService) : base(navigationService) { } public override void OnNavigatingTo(INavigationParameters parameters) { var val = parameters[nameof(PageBCondition)] as PageBCondition; if(val == null) { throw new ArgumentException(nameof(PageBCondition)); } Title = val.Title; } } }
MainPageViewModel側からは引数のKeyはPageBConditionで渡ってくるのでparametersに対してはparameters[nameof(PageBCondition)]と書いてアクセスします。さらにその中にPageBConditionのインスタンスが入っているため「as PageBCondition」で変換しており,変換に失敗したらNullになるため,その場合はArgumentExceptionにしています。これは正しく実装していれば起きないエラーなので,テスト工程で取り除けるレベルのバグがある場合のみこのExceptionが発生するという事になります。
最後に変換後のPageBConditionのインスタンのプロパティを使用することで,引数を受け取ることができます。「Title = val.Title;」とすることで,タイトルの設定ができます。
引数が2つ以上のときはPageBConditionのプロパティを増やすことで対応できるため,引数がどれだけ必要でもMainPageViewModelとPageBViewModelとの間の引数渡しはPageBConditionを1つのままで対応できます。
実行
念のため実行してみても正しく引数が渡っていることが確認できます。