第2回:Xamarin.FormsとネイティブUI:特集:Xamarin+Visual Studioで始めるiOS/Android/UWPアプリ開発(2/5 ページ)
Xamarinでは画面を作成するのにXamarin.FormsとネイティブUIの2つの方法がある。それぞれの方法で簡単なアプリを作ってみよう。
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;
……省略……
「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>
まず、<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;
}
「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;
});
}
}
「App.xaml.cs」ファイルのAppクラスに、このメソッドを追加する。
このメソッドは、トグル動作である。タイマが動いていないときに呼び出すと、タイマを動かす。動いているときには、停止させる。
タイマが動いているときは、1秒ごとにNowTimeプロパティを書き換えている。
ボタンのクリック時に上のメソッドを呼び出すようにしよう(次のコード)。アプリを起動してボタンをクリックするとタイマが動き出し、1秒ごとに時刻表示が更新されるようになる(もう一度ボタンをクリックするとタイマは止まる)。
private void Button_Clicked(object sender, EventArgs e)
{
//App.CurrentApp.NowTime = DateTimeOffset.Now;
App.CurrentApp.StartStopTimer();
}
「MainPage.xaml.cs」ファイルを開き、太字の部分を書き換える。
Copyright© Digital Advantage Corp. All Rights Reserved.