さて始まりました放浪軍師のXamarin.Formsによるアプリ開発。Unityやれよ。
画面遷移時に受け取った Model を ReactiveProperty でバインドしたい
簡単にできると思ったのですが中々に苦戦したので備忘録として残しておきます。
環境
Xamarin.Forms 4.4.0.991640
ReactiveProperty 6.2.0
Prism.Unity.Forms 7.2.0.1422
navigationService.NavigateAsync() の基本
Prism には画面遷移する際に便利なnavigationService.NavigateAsync()
というメソッドが存在します。ただ単に遷移する際は ViewModel にこう書きます。
await navigationService.NavigateAsync("View"); //Viewは遷移先Viewの名前
そして第二引数に NavigationParameters
型を与える事によって遷移先にパラメータを与える事ができます。
var parameters = new NavigationParameters { { "Model", model } }; //Model をパラメータにして await navigationService.NavigateAsync("View", parameters); //第二引数にすると遷移先に渡せる
遷移先の ViewModel では OnNavigatedTo()
の引数から渡された値を取り出すことができます。
public override void OnNavigatedTo(INavigationParameters parameters) { if (parameters == null) return; //nullを確認 if (parameters.ContainsKey("Model")) Model = (Model)parameters["Model"]; //あれば取り出す }
ちなみにNavigationParameters
はDictionary
みたいな感じで複数のパラメータを扱えます。便利だね!
受け取った Model を ReactiveProperty でバインド
さてここからが本題。この引き渡された Model クラスを ReactiveProperty でバインドさせるにはどうしましょうか?率直に書けばこんな感じだと思います。
こんな Model だったとして
public class Model : BindableBase { public string Title { get => title; set => SetProperty(ref title, value); } private string title; }
ViewModel でこうやってみる。
public class ViewModel : ViewModelBase { private Model Model { get; set; } public ReactiveProperty<string> Title { get; } public ViewModel(INavigationService navigationService, Model model) : base(navigationService) { this.Model = model; Title = Model.ObserveProperty(x => x.Title).ToReactiveProperty().AddTo(this.Disposable); } public override void OnNavigatedTo(INavigationParameters parameters) { if (parameters == null) return; if (parameters.ContainsKey("Model")) Model = (Model)parameters["Model"]; } }
…が、ダメでした。View はうんともスンとも言いません。いけると思ったんですがね。で、この件についていろいろと探していると以下記事にて、その解決策が示されていました。
今のコードに適用するとこんな感じです。
public class ViewModel : ViewModelBase { private Model Model { get; set; } public ReactiveProperty<string> Title { get; set; } //←setter追加 public ViewModel(INavigationService navigationService, Model model) : base(navigationService) { this.Model = model; //Title = Model.ObserveProperty(x => x.Title).ToReactiveProperty().AddTo(this.Disposable); //←こっちに書かずに } public override void OnNavigatedTo(INavigationParameters parameters) { if (parameters == null) return; if (parameters.ContainsKey("Model")) Model = (Model)parameters["Model"]; Title = Model.ObserveProperty(x => x.Title).ToReactiveProperty().AddTo(this.Disposable); //←こっちに書いて this.RaisePropertyChanged(null); //←Viewにプロパティの変更を通知 } }
ただこのやり方がどうにもしっくりこない。折角の ReactiveProperty なのに変更の通知を記述しなければならないし、ReactiveProperty は基本コンストラクタで書きたいよね…。ということで考えてみたのですが、突如閃きました。圧倒的閃き!
解決策
まず Model 内Replacement()
というデータを丸ごと差し替える為のメソッドを作成するじゃろ?
public class Model : BindableBase { public string Title { get => title; set => SetProperty(ref title, value); } private string title; //↓丸ごと差し替えるメソッドを作成 public void Replacement(Model p) { Title = p.Title; } }
そしてOnNavigatedTo()
にて渡されたパラメータを、今作成したReplacement()
にそのまま渡す。
public class ViewModel : ViewModelBase { private Model Model { get; } //←setter不要 public ReactiveProperty<string> Title { get; } //←setter不要 public ViewModel(INavigationService navigationService, Model model) : base(navigationService) { this.Model = model; Title = Model.ObserveProperty(x => x.Title).ToReactiveProperty().AddTo(this.Disposable); } public override void OnNavigatedTo(INavigationParameters parameters) { if (parameters == null) return; if (parameters.ContainsKey("Model")) Model.Replacement((Model)parameters["Model"]); //パラメータをそのまま渡す } }
これで Replacement()
内で値が書き換わるので、ReactiveProperty により Model → ViewModel → View と伝搬するという訳です。なにが圧倒的閃きだ。
いやぁ解ってしまえば何てことありませんが、思いつくまでが中々に骨が折れましたね。解決まで大体3時間ぐらいかかりました。うーん、私もまだまだだなぁ…