エレガントは言い過ぎ&タイトル長い上&ダイマでごめんなさい。
KAMISHIBAI for Xamarin.FormsとXamarin.Forms.BehaviorsPackの合わせ技でこんな簡単にListViewで選択されて画面遷移してパラメーター渡しするのが簡単に書けますよという紹介です。
こんな感じのありがちなやつですが、これを至極簡単に書ける方法を紹介します。
サンプルの全コードはこちらのStylishListViewSampleをご覧ください。
さて、まず初期画面のViewModelをつぎのように記述します。
public class FruitsListPageViewModel : ViewModelBase { public IReadOnlyList<Fruit> Fruits { get; } = FruitsRepository.Fruits; public NavigationRequestCommand<Fruit> RequestDetail { get; } = new NavigationRequestCommand<Fruit>(); }
NavigationRequestCommandはICommandを実装したINavigationRequestの実装クラスです。
Command実行の受付と、画面遷移要求の発行を同時に行うやつです。
ユーザー操作から画面遷移までに、すべきことが少ない場合に使えます。(Actionをコンストラクタで登録して、簡単な処理を遷移前に実行する事も可能です。)
そしてXAMLをこんな感じで書きます。
<?xml version="1.0" encoding="utf-8" ?> <ContentPage ...> <ContentPage.BindingContext> <viewModels:FruitsListPageViewModel/> </ContentPage.BindingContext> <ContentPage.Behaviors> <mvvm:PushAsync Request="{Binding RequestDetail}" x:TypeArguments="views:FruitDetailPage" /> </ContentPage.Behaviors> <ListView ItemsSource="{Binding Fruits}"> <ListView.Behaviors> <behaviorsPack:SelectedItemBehavior Command="{Binding RequestDetail}"/> </ListView.Behaviors> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" TextColor="{Binding Color}"/> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
ListViewのBehaviorsに登録されているSelectedItemBehaviorをまず見てください。
<behaviorsPack:SelectedItemBehavior Command="{Binding RequestDetail}"/>
SelectedItemBehaviorは行が選択された時に、良い感じで選択されたアイテムを渡しつつCommandを実行してくれる自分的にはうれしい奴です(再選択問題なんかも対応してありますよ)。
ListViewで行が選択されたら、選択された行にBindされているFruitを引数に、RequestDetailのExecuteを呼び出してくれます。
つづいてPageのBehaviorsを見てください。
<mvvm:PushAsync Request="{Binding RequestDetail}" x:TypeArguments="views:FruitDetailPage" />
RequestDetailの要求を受けてFruitDetailPageにPushAsyncで遷移してね、と宣言されています。
これでListViewでいずれかが選択されたら、選択されたFruitを渡して画面遷移します。
あとは受け取る側を次のように実装します。
public class FruitDetailPageViewModel : ViewModelBase, IPageInitializeAware<Fruit> { public void OnInitialize(Fruit fruit) => Fruit = fruit;
OnInitializeメソッドのパラメーターとして型安全に受け取れます。受け取り後、このコードではFruitプロパティに値を設定しています。
良くないですか?
…ん?良くない?パラメーターはID渡しして、遷移先で値は取り直したいと?
OK、そのパターンを紹介しましょう。
初期画面のViewModelを次のように修正します。
// public NavigationRequestCommand<Fruit> RequestDetail { get; } // = new NavigationRequestCommand<Fruit>(); public NavigationRequestCommand<int> RequestDetail { get; } = new NavigationRequestCommand<int>();
型パラメーターをFruitからintに変更しただけです。
そして、XAMLのSelectedItemBehaviorでFruitのIdをCommandに渡すよう、次のように修正します。
before
<behaviorsPack:SelectedItemBehavior Command="{Binding RequestDetail}"/>
after
<!-- <behaviorsPack:SelectedItemBehavior Command="{Binding RequestDetail}" PropertyPath="Id"/>
最期に、受け取るところを受け取ったIdから値を再取得するように修正しましょう。
//public class FruitDetailPageViewModel // : ViewModelBase, IPageInitializeAware<Fruit> public class FruitDetailPageViewModel : ViewModelBase, IPageInitializeAware<int> { // public void OnInitialize(Fruit fruit) => Fruit = fruit; public void OnInitialize(int id) => Fruit = FruitsRepository.FindById(id);
これでOKです。
いや~、Xamarin.Forms.BehaviorsPackと紙芝居便利だな~(棒
おまけ
ところで、鋭い人は気が付いているかもしれませんが、これ画面遷移要求をViewModel通さなくても実装できちゃいますよね?
それがやっていい事かどうかは、判断に迷うところですが、できることはできます。
という事で、一応その方法も紹介しておきます。
<?xml version="1.0" encoding="utf-8" ?> <ContentPage ...> <ContentPage.BindingContext> <viewModels:FruitsListPageViewModel/> </ContentPage.BindingContext> <ContentPage.Behaviors> <mvvm:PushAsync x:TypeArguments="views:FruitDetailPage"> <mvvm:PushAsync.Request> <mvvm:NavigationRequestCommand x:TypeArguments="x:Int32" x:Name="NavigationRequestCommand"/> </mvvm:PushAsync.Request> </mvvm:PushAsync> </ContentPage.Behaviors> <ListView ItemsSource="{Binding Fruits}"> <ListView.Behaviors> <behaviorsPack:SelectedItemBehavior Command="{Binding Source={x:Reference NavigationRequestCommand}}" PropertyPath="Id"/> </ListView.Behaviors> <ListView.ItemTemplate> <DataTemplate> <TextCell Text="{Binding Name}" TextColor="{Binding Color}"/> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
PushAsyncビヘイビアへ設定するRequestをXAML上で名前付きで定義し
<mvvm:PushAsync x:TypeArguments="views:FruitDetailPage"> <mvvm:PushAsync.Request> <mvvm:NavigationRequestCommand x:TypeArguments="x:Int32" x:Name="NavigationRequestCommand"/> </mvvm:PushAsync.Request> </mvvm:PushAsync>
SelectedItemBehaviorでx:Referenceを利用してXAML上のNavigationRequestCommandを直接呼び出しています。
<behaviorsPack:SelectedItemBehavior Command="{Binding Source={x:Reference NavigationRequestCommand}}" PropertyPath="Id"/>
ViewModelではもともと何もしていないに等しいわけで、ViewModelを通さなくていい気もしますし、そもそもViewModelに分けない&再利用されないコードならそもそもコードビハインドにゴリゴリ書いちゃえよとも思えるし、でもViewModel通さないの?むむむ。。。 皆さんならどうしますか?
てとこで、今日はここまで!それではまた。