shuhelohelo’s blog

Xamarin.Forms多めです.

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型のプロパティに対して、AddRemoveAtClearなどのメソッドで変更を加えると、それが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を指定します。 EventNameEventArgsParameterPathも、指定した文字列のイベントやプロパティを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つのメソッドの実装が強制されます。 そして、この順番に実行されます。

  1. OnNavigatingTo(遷移先:描画前)
  2. OnNavigatedFrom(遷移元)
  3. OnNavigatedTo(遷移先:描画後)

データの受け渡しがある画面遷移の場合、遷移先のOnNavigatingTo``OnNavigatedToでデータを受け取る。

他に、デバイスのホームボタンや戻るボタンが押されたときの動作は以下のとおりです。

ルートページ(例えばMainPage)でデバイスの戻るボタンが押されたとき

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. MainPageコンストラク
  2. OnNavigatingTo
  3. OnNavigatedTo
  4. App.OnInitialized

バイスのホームボタンが押されたとき

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. OnResume(ページ側)
  2. App.OnResume

アプリ起動時

  1. MainPageコンストラク
  2. OnNavigatingTo
  3. OnNavigatedTo
  4. App.OnInitialized

上スワイプでアプリ切替時(アプリ選択画面)

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. OnResume(ページ側)
  2. App.OnResume

電源ボタンを押したとき

  1. OnSleep(ページ側)
  2. App.OnSleep

復帰時

  1. OnResume(ページ側)
  2. App.OnResume

ナビゲーションバーの戻るボタン

  1. OnNavigatedFrom
  2. OnNavigatedTo

GobackAsyncメソッドで戻る場合

  1. OnNavigatingTo
  2. OnNavigatedFrom
  3. OnNavigatedTo

NavigateAsyncメソッドで遷移した場合

  1. 遷移先コンストラク
  2. OnNavigatingTo
  3. OnNavigatedFrom
  4. 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を使ってモバイルアプリケーションを開発するときの基本的な「こうしたいときはこう書く」を紹介してみました。

たくさんあるようなないような内容でしたが、個人的にはこれだけ知っていればそれなりのアプリケーションを作れると思います。

説明をもっとわかりやすく書けたらいいなとおもいつつ、今回のところはこのへんにしておきたいと思います。