Prismを使ってXamarin.Formsアプリを作るときのメモ[引越記事]
はじめに
モバイルに限らずアプリケーションの開発というと難しく感じられるかもしれませんが、シンプルなアプリケーションであれば「こうしたいときはこう書く」みたいな基本的なパターンを知っておくだけで作ることができます。
それはVisual Studioのような統合開発環境の進歩や様々なライブラリ、フレームワークの充実、言語自体の進歩のおかげだったりします。
しっかりしたアプリケーションを作るためには専門的な知識が沢山必要だったりするのでしょうが、せっかくこんなにアプリケーション開発のハードルが下がってきたのだから、要点だけ押さえてアプリケーションの開発を楽しんでみていいんじゃないかな、と思います。
この記事では「イチから」というわけにはいきませんが、Xamarin.FormsとPrismというアプリケーション開発のフレームワークを使った、「こうしたいときはこう書く」を紹介したいと思います。
モバイルアプリの典型的な動作
モバイルアプリの動作は非常に大雑把に言って、「画面をタッチしたら画面が変わる」とか「画面をタッチしたら情報が更新される」とかです。
となると、イベント処理(ボタンを押す)、画面遷移(画面が変わる)、データバインディング(情報の更新)の基本的な書き方を知っていればシンプルなアプリケーションが作れる、と思います。
そこで、この記事では以下の5つの項目について書きます。
- データバインディング
- イベント処理
- 画面遷移
- データの受け渡しを伴う画面遷移
- 状態によるボタンの有効無効
前提
- Windows 10
- Visual Studio 2017
- Prism Template Pack インストール済み
- Prism Blank App(Xamarin.Forms)でプロジェクト作成済み
1.データバインディング
バインディングしたいプロパティはpublic
にします。
そしてsetter
内でSetProperty
メソッドを使います。
このメソッドがプロパティの値に変化があったときにView側に通知して表示が変化します。
private string _title; public string Title { get => _title; set => SetProperty(ref _title, value); }
Title="{Binding Title}"
プロパティが持つプロパティをバインディングしたい場合
View側で目的のプロパティまでのパスを「.」でつなげていきます。
以下の例は自作のGeolocaionクラスで、このクラスはLocation
プロパティを持っていて、そのLocation
プロパティはLatitude
プロパティを持っています。
Geolocation
型のプロパティのプロパティであるLatitude
をバインディングしたい場合は、Text="{Binding Path=Geolocation.Location.Latitude}"
とします。
Geolocation
private Geolocation _geolocation; public Geolocation Geolocation { get => _geolocation; set => SetProperty(ref _geolocation, value); }
<Label Text="{Binding Path=Geolocation.Location.Latitude}" FontSize="15" HorizontalTextAlignment="End" HorizontalOptions="FillAndExpand"/>
配列やListなどのCollectionをバインディングするとき
ObservableCollection
を使います。
以下のようにObservableCollection
型のプロパティを用意し、それをリスト形式の表示を行うUI要素(XamarinであればListView、WPFならListBoxなど)にバインディングします。
public ObservableCollection<LocationInformation> Targets { get; set; } = new ObservableCollection<LocationInformation>();
<ListView AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1" HasUnevenRows="True" ItemsSource="{Binding Path=Targets}"> //以下略
このObservableCollection
型のプロパティに対して、Add
やRemoveAt
やClear
などのメソッドで変更を加えると、それがView側に反映されます。
2.イベント処理
コードビハインドのxaml.cs
ファイルにイベントハンドラを書くこともできますが、ここではViewModel側にイベントハンドラを書く方法を紹介します。
Buttonが押されたとき
<Button Text="text" Command="{Binding Path=MyCommand}" />
public Command MyCommand => new Command(() => { //ボタンが押されたときの処理を書く });
Buttonが押されたとき、文字列をパラメータとして渡す
CommandParameter
に指定した文字列がViewModel側のCommandが呼び出されるときに引数として渡される。
以下の例ではView側で遷移先のViewを文字列で指定できるようにしている。
<Button Text="text" Command="{Binding Path=MyCommand}" CommandParameter="SecondPageView"/>
public Command<string> MyCommand => new Command<string>(name => { _navigationService.NavigateAsync(name); });
Button以外のUI要素がタップされた場合
Buttonの場合は押されたときにCommand
プロパティにバインディングしたCommandが実行されますが、Button以外のUI要素がタップされたときはGestureRecgnaizers
プロパティを使います。
Labelがタップされた場合
<Label Text="{Binding Path=TargetInfo.Name}" FontSize="40" HorizontalOptions="FillAndExpand" HorizontalTextAlignment="Center"> <Label.GestureRecognizers> <TapGestureRecognizer Command="{Binding Path=NavigateCommand}" CommandParameter="SelectTargetPage"/> </Label.GestureRecognizers> </Label>
ListViewでアイテムがタップされた場合
EventToCommandBehavior
を使います。
そのUI要素の任意のイベントを拾いたい場合に使います。
EventName
には拾いたいイベント名を指定します。
Command
にはCommand型のプロパティを指定します。この例では選択されたアイテムのデータを引数として渡したいので、プロパティの定義ではCommand<T>
クラスを使います。
EventArgsParameterPath
プロパティにはListViewが持つプロパティのうち、Commandに渡したいプロパティの名前を指定します。この例では選択されたアイテムが格納されるSelectedItem
を指定します。
EventName
もEventArgsParameterPath
も、指定した文字列のイベントやプロパティをPrismが探して使用してくれます。
<ListView.Behaviors> <behaviors:EventToCommandBehavior EventName="ItemSelected" Command="{Binding Path=ItemSelectedCommand}" EventArgsParameterPath="SelectedItem"/> </ListView.Behaviors>
public Command<LocationInformation> ItemSelectedCommand => new Command<LocationInformation>(targetInfo => { //アイテムが選択されたときの処理を書く。 //引数targetInfo(名前は何でも良い)には選択されたアイテムのデータが入っている。 //この例では`LocationInformation`型のコレクションをListViewにバインディングしているので、targetInfoは`LocationInformation`型のデータです。 });
3.画面遷移
上の例で出てきたとおり。
NavigateAsync
メソッドで指定したViewに遷移できる。
_navigationService.NavigateAsync("遷移先のView名");
じゃあこの_navigationService
はどこからきたのかというと、そのViewModelのコンストラクタの引数として渡されてくるので、それをprivateなフィールドに入れてViewModel内で使えるようにしている。
_navigationService
をViewModelのコンストラクタに渡すのはPrismがDIコンテナを使ってやってくれる。
private INavigationService _navigationService; public SelectTargetPageViewModel(INavigationService navigationService) { _navigationService = navigationService; }
データの受け渡しのある画面遷移
NavigateAsync
には受け渡したいデータを指定できるOverrideがあるので、それを使います。
NavigationParameters
はDictionaryと同じようにキーと値のペアで渡したいデータ(任意のオブジェクト)を格納していきます。
そして、NavigateAsync
メソッドで遷移先のView名とともにNavigationParameters
型のオブジェクトを渡します。
var parameters = new NavigationParameters { { "キー名", targetInfo}, }; _navigationService.NavigateAsync(nameof(TargetDetailPage), parameters);
受け取る側のViewModelでは以下のようにしてデータを受け取ります。
OnNavigatingTo
(またはOnNavigatedTo
)メソッドで受け取ります。
引数parametersに遷移先でNavigateAsync
メソッドに渡したNavigationParameters
オブジェクトが入ります。
それをキー名で取り出してas
で型変換して利用します。
public void OnNavigatingTo(INavigationParameters parameters) { var targetInfo = parameters[nameof(LocationInformation)] as LocationInformation; if (targetInfo == null) { this.Title = "New"; return; } this.TargetInfo = targetInfo; this.Title = "Edit"; }
4.画面遷移時の処理の流れ
画面遷移するときに遷移元と遷移先で後始末や準備、遷移元からのデータの受取などをしたい場合は、遷移元と遷移先でViewModelクラスにINavigationAware
インタフェースを継承して遷移時に実行されるメソッドを実装する。
INavigationAware
は以下の3つのメソッドの実装が強制されます。
そして、この順番に実行されます。
- OnNavigatingTo(遷移先:描画前)
- OnNavigatedFrom(遷移元)
- OnNavigatedTo(遷移先:描画後)
データの受け渡しがある画面遷移の場合、遷移先のOnNavigatingTo``OnNavigatedTo
でデータを受け取る。
他に、デバイスのホームボタンや戻るボタンが押されたときの動作は以下のとおりです。
ルートページ(例えばMainPage)でデバイスの戻るボタンが押されたとき
- OnSleep(ページ側)
- App.OnSleep
復帰時
- MainPageコンストラクタ
- OnNavigatingTo
- OnNavigatedTo
- App.OnInitialized
デバイスのホームボタンが押されたとき
- OnSleep(ページ側)
- App.OnSleep
復帰時
- OnResume(ページ側)
- App.OnResume
アプリ起動時
- MainPageコンストラクタ
- OnNavigatingTo
- OnNavigatedTo
- App.OnInitialized
上スワイプでアプリ切替時(アプリ選択画面)
- OnSleep(ページ側)
- App.OnSleep
復帰時
- OnResume(ページ側)
- App.OnResume
電源ボタンを押したとき
- OnSleep(ページ側)
- App.OnSleep
復帰時
- OnResume(ページ側)
- App.OnResume
ナビゲーションバーの戻るボタン
- OnNavigatedFrom
- OnNavigatedTo
GobackAsyncメソッドで戻る場合
- OnNavigatingTo
- OnNavigatedFrom
- OnNavigatedTo
NavigateAsyncメソッドで遷移した場合
- 遷移先コンストラクタ
- OnNavigatingTo
- OnNavigatedFrom
- OnNavigatedTo
5.状態によるボタンの有効、無効
DelegateCommand
を使います。
public ICommand MyCommand => new DelegateCommand(() => { //実行したい処理 }, () => { //Buttonの有効、無効を判定したい条件 //例えば、何かしらかのUI要素にバインディングしている値が特定の値かどうか }). ObservesProperty(() => /*有効無効の判定に使いたいプロパティ*/);
例えば、Entry(WPFでいうTextBox)コントロールにName
プロパティをバインディングしていて、そのEntryに入力がなければButtonは無効、入力があれば有効、というような動作をさせたい場合は以下のようにする。
public ICommand MyCommand => new DelegateCommand(() => { //実行したい処理 }, () => { //Buttonの有効、無効を判定したい条件 return string.IsNullOrWhiteSpace(this.Name); }). ObservesProperty(() => this.Name);
複数のプロパティを条件としたい場合、例えば2つのEntryコントロールの両方に入力がある場合だけButtonを有効にする場合は以下のようにする。
public ICommand MyCommand => new DelegateCommand(() => { //実行したい処理 }, () => { //Buttonの有効、無効を判定したい条件 return string.IsNullOrWhiteSpace(this.Name) && string.IsNullOrWhiteSpace(this.Age); }) .ObservesProperty(() => this.Name) .ObservesProperty(() => this.Age);
おわりに
ざっくり大雑把にXamarin.FormsとPrismを使ってモバイルアプリケーションを開発するときの基本的な「こうしたいときはこう書く」を紹介してみました。
たくさんあるようなないような内容でしたが、個人的にはこれだけ知っていればそれなりのアプリケーションを作れると思います。
説明をもっとわかりやすく書けたらいいなとおもいつつ、今回のところはこのへんにしておきたいと思います。