第2回:Xamarin.FormsとネイティブUI特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発(2/5 ページ)

» 2016年11月02日 05時00分 公開
[山本康彦BluewaterSoft/Microsoft MVP for Windows Development]

Xamarin.Formsでデータバインディング

 データバインディングも可能だ。「可能」というより、ListViewコントロールに不定個のデータを表示するような場合には、WPFなどと同様に必須となる技法である。また、本稿では説明しないが、コマンドのバインディングも使えば、コードビハインド(=ファイル名の末尾が「.xaml.cs」のファイル)からロジックを追い出せる。つまり、UIとロジックを分離できるのである。データバインディングは、規模の大きいアプリ開発にも必須の技術なのだ。

 Xamarin.Formsのデータバインディングは、WPFやWindowsストアアプリなどのものと基本的には同じだ。ただし、バインディングソース(=データの提供元)を簡単に書けるように改良されている。

 バインディングソースにするクラスは、WPFであれXamarin.Formsであれ、INotifyPropertyChangedインタフェース(System.ComponentModel名前空間)を実装しなければならない。しかしXamarin.Formsでは、それを実装したBindableObjectクラス(Xamarin.Forms名前空間)が用意されている*1。BindableObjectクラスを継承したクラスであれば、簡単にバインディングソースを記述できるのだ。

*1 BindableObjectクラスはWPFなどのDependencyObjectクラス(Windows.UI.Xaml名前空間)に相当する。ただし、DependencyObjectクラスはINotifyPropertyChangedインタフェースを実装していない。また、次のコードに登場するBindablePropertyクラスは、WPFなどのDependencyPropertyクラス(Windows.UI.Xaml名前空間)に相当するものだ。


 それでは、先ほどの時刻表示を、データバインディングで行うように改良してみよう。

 データを保持するクラスを新設するのが本来ではあるが、ここではあえてAppクラス(これはコードビハインドだ)に時刻データを持たせてみよう。AppクラスもBindableObjectクラスを継承しているので、このクラスにデータを持たせてデータバインディングを行うことが可能だ。これには「App.xaml.cs」ファイルを開き、次のコードのように記述する。

public partial class App : Application
{
  // NowTimeプロパティ
  public static readonly BindableProperty NowTimeProperty
    = BindableProperty.Create(
        nameof(NowTime),  // プロパティ名
        typeof(DateTimeOffset), // プロパティの型
        typeof(App),  // プロパティが定義されているクラス
        defaultValue: DateTimeOffset.Now, // プロパティの初期値(オプション)
        defaultBindingMode: BindingMode.OneWay  // モードの既定値(オプション)
      );
  public DateTimeOffset NowTime
  {
    get { return (DateTimeOffset)GetValue(NowTimeProperty); }
    set { SetValue(NowTimeProperty, value); }
  }

  // Currentプロパティを利用しやすくするため、Appを返す「Current」を追加
  public static App CurrentApp => Current as App; 

  ……省略……

バインディングソースを追加する(C#)
「App.xaml.cs」ファイルに太字の部分を追加した。
通常のプロパティ(NowTime)の他に、BindableProperty型のNowTimePropertyも追加する。BindablePropertyクラスのCreateメソッドの引数は、最初の3つだけが必須だ。
通常のプロパティのgetter/setterでは、親クラス(BindableObjectクラス)のGetValueメソッド/SetValueメソッドを呼び出す。SetValueメソッドを呼び出すと、プロパティの更新通知がバインディング対象に伝達される。もしもsetter内で他のプロパティも変更するのであれば、親クラス(BindableObjectクラス)のOnPropertyChangedメソッドも呼び出して、そのプロパティも変化したことを通知する必要がある。
なお、後のコードで楽をするために、CurrentAppプロパティも追加した。この書き方は「.NET TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0]」を参照。

 上で作ったNowTimeプロパティ(バインディングソース)を画面のLabelコントロールにバインドするには、「MainPage.xaml」ファイルを開いて次のコードのように変更する。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             ……省略……
             BindingContext="{Binding Source={x:Static Application.Current}}"
             >
  <StackLayout VerticalOptions="Center">
    <!--<Label x:Name="Label1" Text="Hello, Xamarin!" TextColor="Aqua"
           FontSize="Large" FontAttributes="Bold, Italic"
           HorizontalOptions="Center" />-->
    <Label Text="{Binding NowTime, StringFormat='{0:HH:mm:ss}'}"
           TextColor="Aqua"
           FontSize="Large" FontAttributes="Bold, Italic"
           HorizontalOptions="Center" />
    <Button Text="Click me!" Clicked="Button_Clicked"
            FontSize="Medium" HorizontalOptions="Center" />
  </StackLayout>
</ContentPage>

NowTimeプロパティをLabelコントロールにバインドする(XAML)
まず、<ContentPage>タグにBindingContext属性を追加する(WPFなどのDataContextに相当)。これはApplicationクラス(実体はAppクラス)のインスタンスを、ContentPageコントロールのBindingContextプロパティに結び付けるという意味だ。
あるコントロールのBindingContextプロパティに設定されたオブジェクトは、その中に配置されたコントロールにも伝播する。ここでは、Appクラスのインスタンスが、LabelコントロールのBindingContextプロパティにも設定されていることになる。
次に、LabelコントロールのText属性をデータバインディングで書き直した。ここではBindingContextプロパティに設定されているAppオブジェクトのNowTimeプロパティをText属性に結び付けている。また、表示する際に、StringFormatで与えた書式を使うように指定している。必要なら、ここでバインディングのモードも指定する。ここでは先ほど既定値にしたBindingMode.OneWay(データの変化を画面に伝えるだけの一方向)のままでよいので、モード指定は省略した。

 最後に、ボタンをクリックしたときのイベントハンドラーでデータを書き換えるようにして完成だ(次のコード)。実行してみると、先ほどと同じように、ボタンをクリックしたときの時刻が表示されるはずだ。

private void Button_Clicked(object sender, EventArgs e)
{
  //Label1.Text = DateTimeOffset.Now.ToString("HH:mm:ss");
  App.CurrentApp.NowTime = DateTimeOffset.Now;
}

ボタンクリック時にバインディングソースを変更する(C#)
「MainPage.xaml.cs」ファイルを開き、太字の部分を書き換えた。
ここで先ほどAppクラスに追加しておいたCurrentAppプロパティが役に立つ。CurrentAppプロパティを追加しない場合は、ここのコードは「(App.Current as App).NowTime = ……」という形になる。1〜2箇所ならともかく、キャストするコードが多くなってきたら、筆者はこのようにして楽をすることにしている。
さて、これでアプリの動作は次のように変わる。
ボタンクリック→バインディングソース(AppクラスのNowTimeプロパティ)が変化→データバインディングにより画面表示が変化

Xamarin.Formsでタイマを使う

 せっかくなので、時刻表示をタイマで自動的に更新するようにして、時計アプリにしてみよう。ボタンは、タイマ動作のON/OFFに使うこととする。

 Xamarin.Formsで利用できるタイマは、Deviceクラス(Xamarin.Forms名前空間)のStartTimerメソッドだけのようである*2

 StartTimerメソッドには、タイマ割り込みごとに行いたい処理をラムダ式で与える。面白いのは、このラムダ式がtrueを返せばタイマ継続、falseを返すとタイマ終了となることだ。そのため、StopTimerというようなメソッドは用意されていない。

*2 Xamarin.Forms名前空間のタイマでは、Xamarin.Forms以外からも共通に使いたいライブラリでは使えない。そんなとき、筆者はRxのタイマを使う。


 タイマ処理をON/OFFするメソッドは、次のコードのように書ける。

// タイマ動作を継続させるフラグ
private bool m_ContinueFlag;

// タイマ動作をON/OFFするメソッド
public void StartStopTimer()
{
  if (m_ContinueFlag)
  {
    // タイマ処理を終了させる
    m_ContinueFlag = false;
  }
  else
  {
    // タイマ処理を開始する
    m_ContinueFlag = true;

    // タイマで時刻をセットする
    Device.StartTimer(TimeSpan.FromSeconds(1.0), () => {
      NowTime = DateTimeOffset.Now;
      return m_ContinueFlag;
    });
  }
}

タイマをON/OFFするメソッド(C#)
「App.xaml.cs」ファイルのAppクラスに、このメソッドを追加する。
このメソッドは、トグル動作である。タイマが動いていないときに呼び出すと、タイマを動かす。動いているときには、停止させる。
タイマが動いているときは、1秒ごとにNowTimeプロパティを書き換えている。

 ボタンのクリック時に上のメソッドを呼び出すようにしよう(次のコード)。アプリを起動してボタンをクリックするとタイマが動き出し、1秒ごとに時刻表示が更新されるようになる(もう一度ボタンをクリックするとタイマは止まる)。

private void Button_Clicked(object sender, EventArgs e)
{
  //App.CurrentApp.NowTime = DateTimeOffset.Now;
  App.CurrentApp.StartStopTimer();
}

ボタンクリックでタイマのON/OFFを切り替える(C#)
「MainPage.xaml.cs」ファイルを開き、太字の部分を書き換える。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。