プレリリース版のXamarin Formsを使う方法

前回のポストでプレリリース版「Xamarin.Forms 2.3.4.184-pre1」のPickerコントロールについて触れました。

Xamarin Studio や Visual Studio for Mac を利用して新規ソリューション(プロジェクト)を作成した場合、通常、Stable版のXamarin Formsプロジェクトが自動生成されます。

このプロジェクトに対して最新プレリリース版Xamarin Formsを適用する手順について説明します。
以下Xamarin Studioベースで画像キャプチャしていますが、Visual Studio for Macでも同様の手順です。
Visual Studio 2015(windows)における手順も後述します)

Xamarin Studio / Visual Studio for Macの場合

ソリューションの作成

Xamarin Srudio もしくは Visual Studio for Mac で、新規のXamarin Formsアプリケーション ソリューションを作成します。
ここではソリューション名を「PreExample1」としました。Prismも使わないシンプルなXamarin Formsソリューションとしています。

f:id:daigo-knowlbo:20170114154148p:plain

パッケージの追加

PCLプロジェクト(PreExample1)、Androidプロジェクト(PreExample1.Droid)、iOSプロジェクト(PreExample1.iOS)の各プロイジェクトの「パッケージ」に対してデフォルトでは、 stable版 の Xamarin.Forms パッケージへの参照が追加されています。
これをプレリリース版に置き換えましょう。
パッケージをマウス右ボタンクリックし「パッケージの追加」メニューを選択します。

f:id:daigo-knowlbo:20170114154211p:plain

「パッケージを追加」ウィンドウが表示されます。

f:id:daigo-knowlbo:20170114155725p:plain

左下の「プレリリース パッケージを表示する」にチェックを入れ、右上の検索ボックスに「xamarin forms」と入力します。
一覧に表示された「Xamarin.Forms」を選択し、右下のバージョン ドロップダウンリストから利用したいバージョンを選択します(ここでは 2.3.4.184-pre1を選択)。
「OK」ボタンをクリックするとパッケージの追加(更新)が行われます。

パッケージの更新完了後に、「パッケージ→Xamarin.Forms」をマウス右ボタンクリックすると、バージョンが更新されている事を確認することができます。

f:id:daigo-knowlbo:20170114160028p:plain

Android / iOSプロジェクトのパッケージも更新

上述で「PCLプロジェクト(PreExample1)」のパッケージが更新されました。
同様の手順で「Androidプロジェクト(PreExample1.Droid)・iOSプロジェクト(PreExample1.iOS)」のパッケージも最新のXamarin.Formsに更新しましょう。

Visual Studio 2015(Windows)の場合

Xamarin Formsプロジェクトを作成します。
ソリューションエクスプローラーからプロジェクトを選択→マウス右ボタン→NuGetパッケージの管理を選択します。

f:id:daigo-knowlbo:20170115122945p:plain

以下の「NuGetパッケージマネージャー」が表示されるので、Xamarin.Formsを選択→「プレリリースを含める」にチェック→「バージョン」から最新のプレリリース版を選択→更新ボタンクリック、操作を行います。

f:id:daigo-knowlbo:20170115122958p:plain

上記操作をiOS / Android / UWPの各プロジェクトにも行います。

最新版のXamarin Formsで遊べる!

さあ、最新プレリリース版の Xamarin Forms で遊びましょう!

PickerがBindableになるそうだ(Xamarin.Forms 2.3.4.184-pre1)

Xamarin Forms + MVVM開発において最も相性の悪いコントロールの代表である Picker がBindableになるそうです。
現在 Xamarin.Forms 2.3.4.184-pre1 にて実装が提供されています。

blog.xamarin.com

正式版への取り込みは以下のロードマップに記述されている通り「2.4.0 - February 2017」となるそうです。

forums.xamarin.com

そう、従来の(現状の)Pickerコントロールには、ListView.ItemsSourceのようなBindablePropertyが用意されていなかったんですよね。
あと、SelectedItemプロパティも無かったので、 SelectedIndexプロパティから選択オブジェクトを取得するという回りくどい実装が必要でした。

ということでPickerの派生クラスを自前で作ってBindablePropertyを用意するような事をする必要がありました。
以下のような感じで。

github.com

これがネイティブにサポートされる予定です(嬉しい)。

「Xamarin.Forms 2.3.4.184-pre1」のPickerを使ったサンプルを以下に置きました。

XamarinExamples/Prism/Control/UseBindablePicker at master · ryuichi111/XamarinExamples · GitHub

BindablePickerと呼んでいますが、実装自体は「Pickerコントロール」に 「ItemsSourceプロパティ / SelectedItemプロパティ」が追加される形でのアップデートになります。

Xamarin Forms(Prism)のシンプルサンプルをGithubにあげた

Xamarin Forms & Prismにおける超シンプルなサンプルをGithubにあげました。

github.com

サンプルの方針は以下です。

  • Xamarin Forms + Prismプロジェクトである。
  • 各種UIコントロールのプロパティはViewModelのプロパティとデータバインディングする。
  • 各種UIコントロールのイベントはCommand経由でViewModelのICommandにデータバインディングする。
  • 単一機能の超シンプルなサンプルを目指す。

基本的には「Buttonの使い方」とか「DatePickerの使い方」とか単一機能のシンプルなサンプルです。
内容的には、まだ、まったく足りていないので「1日1github」でソースを追加していこうと思っています(コメントとかも書いてないので・・・)。

現状は「いくつかのコントロールの使い方サンプル」「MasterDetailPageの使い方サンプル」のみ載せています。

ではー。

Prism(Xamarin Forms) における INotifyPropertyChanged

Xamarin Formsおいて ソース→ターゲット のデータバインディングでは、ソースオブジェクトに INotifyPropertyChangedインターフェイス を実装する必要があります(OneTimeモードは除く)。

この点について「素のXamarin Forms」と「Xamarin Forms With Prism」での実装方法を比較してみます。

また、Prism内部の実装を覗いて「ライブラリとして何をサポートしてくれているのか?」を見てみたいと思います。

こんなサンプルで説明します

以下のようなサンプルで説明を進めたいと思います。
実行画面は以下の通り。

f:id:daigo-knowlbo:20170109015009p:plain

サンプルの要点は以下の通りです。

  • R G Bの各値をソースとして画面上の各ラベルにデータバインドする
  • 「set White」/「set Black」/「set Yellow」のボタンをクリックするとソースの RGB値 がロジック上で変更される
  • ロジック上で変更された RGB値 は 即座に画面表示(ラベル)に反映される

素のXamarin Formsでは

ソースオブジェクトとして「MyColorクラス」を実装する事とします。
ソースオブジェクトは INotifyPropertyChangedインターフェイス を実装します。
ソース値変更時には PropertyChanged を呼び出します。

// MyColor.cs(ソースとなるオブジェクト)
using System;
using System.ComponentModel;

namespace Example1.Models
{
  public class MyColor : INotifyPropertyChanged
  {
    // INotifyPropertyChangedインターフェイスの実装
    public event PropertyChangedEventHandler PropertyChanged;

    // Fields
    private int red;
    private int green;
    private int blue;

    // Properties
    public int Red {
      get {
        return this.red;
      }
      set {
        if (this.red != value) {
          this.red = value;
          OnPropertyChanged("Red");
        }
      }
    }

    public int Green {
      get {
        return this.green;
      }
      set {
        if (this.green != value) {
          this.green = value;
          OnPropertyChanged("Green");
        }
      }
    }

    public int Blue {
      get {
        return this.blue;
      }
      set {
        if (this.blue != value) {
          this.blue = value;
          OnPropertyChanged("Blue");
        }
      }
    }

    // プロパティ値の変更を通知します
    protected virtual void OnPropertyChanged(string propertyName)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}
  • PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    「?」はC#言語仕様で「nullでなければ呼び出す」、つまりPropertyChangedがnullでなければInvoke()を呼び出します。

ページの実装は以下の通りです。

// CustomColorPage.xaml(ページ定義)
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
  xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  x:Class="Example1.CustomColorPage">

  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <StackLayout Orientation="Horizontal">
      <Label Text="Red:"/>
      <Label Text="{Binding Red}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
      <Label Text="Green:"/>
      <Label Text="{Binding Green}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
      <Label Text="Blue:"/>
      <Label Text="{Binding Blue}" />
    </StackLayout>
    <StackLayout Orientation="Vertical">
      <Button Text="set White" Clicked="whiteButtonClicked"/>
      <Button Text="set Black" Clicked="blackButtonClicked"/>
      <Button Text="set Yellow" Clicked="yellowButtonClicked"/>
    </StackLayout>
  </StackLayout>
</ContentPage>

コードビハインドクラスで、ソースオブジェクト MyColor をインスタンス化して、BindingContextに設定する事でデータバインディングを行います。

// CustomColorPage.xaml.cs(コードビハインドクラス)
using System;
using Xamarin.Forms;

using Example1.Models;

namespace Example1
{
  public partial class CustomColorPage : ContentPage
  {
    // ソースオブジェクト
    public MyColor MyColor { get; } = new MyColor();

    // コンストラクタ
    public CustomColorPage()
    {
      InitializeComponent();

      this.BindingContext = this.MyColor;
    }

    // Whiteボタンクリックイベントハンドラ
    public void whiteButtonClicked(object sender, EventArgs e)
    {
      this.MyColor.Red = 255;
      this.MyColor.Green = 255;
      this.MyColor.Blue = 255;
    }

    // Blackボタンクリックイベントハンドラ
    public void blackButtonClicked(object sender, EventArgs e)
    {
      this.MyColor.Red = 0;
      this.MyColor.Green = 0;
      this.MyColor.Blue = 0;
    }

    // Yellowボタンクリックイベントハンドラ
    public void yellowButtonClicked(object sender, EventArgs e)
    {
      this.MyColor.Red = 255;
      this.MyColor.Green = 255;
      this.MyColor.Blue = 0;
    }
  }
}

Prismでは

ソースオブジェクトは BindableBaseクラス を継承します。
ソースオブジェクトはViewModelクラスとします。
ソース変更時には SetProperty() メソッドを呼び出します。

// MainPageViewModel.cs(ビューモデルクラス)
using Prism.Commands;
using Prism.Mvvm;
using System.Windows.Input;

namespace PrismExample1.ViewModels
{
  public class CustomColorPageViewModel : BindableBase
  {
    // Fields
    private int red;
    private int green;
    private int blue;

    // Properties(for DataBind)
    public int Red {
      get {
        return this.red;
      }
      set {
        this.SetProperty(ref this.red, value);
      }
    }

    public int Green {
      get {
        return this.green;
      }
      set {
        this.SetProperty(ref this.green, value);
      }
    }

    public int Blue {
      get {
        return this.blue;
      }
      set {
        this.SetProperty(ref this.blue, value);
      }
    }

    // コマンド
    public ICommand WhiteCommand { get; }

    public ICommand BlackCommand { get; }

    public ICommand YellowCommand { get; }

    // Constructor
    public CustomColorPageViewModel()
    {
      // ボタンクリックコマンド時のイベント処理
      this.WhiteCommand = new DelegateCommand(() =>
      {
        this.Red = 255;
        this.Green = 255;
        this.Blue = 255;
      });
    
      this.BlackCommand = new DelegateCommand(() =>
      {
        this.Red = 0;
        this.Green = 0;
        this.Blue = 0;
      });

      this.YellowCommand = new DelegateCommand(() =>
      {
        this.Red = 255;
        this.Green = 255;
        this.Blue = 0;
      });
    }
  }
}

ページ実装は以下の通りです。
ボタンクリック時の挙動は Command をCustomColorPageViewModeの各ICommandにデータバインディングします。

// CustomColorPage.xaml(ページ定義)
<?xml version="1.0" encoding="utf-8"?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms" 
  prism:ViewModelLocator.AutowireViewModel="True" 
  x:Class="PrismExample1.Views.CustomColorPage" 
  Title="MainPage">
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
    <StackLayout Orientation="Horizontal">
      <Label Text="Red:"/>
      <Label Text="{Binding Red}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
      <Label Text="Green:"/>
      <Label Text="{Binding Green}" />
    </StackLayout>
    <StackLayout Orientation="Horizontal">
      <Label Text="Blue:"/>
      <Label Text="{Binding Blue}" />
    </StackLayout>
    <StackLayout Orientation="Vertical">
      <Button Text="set White" Command="{Binding WhiteCommand}"/>
      <Button Text="set Black" Command="{Binding BlackCommand}"/>
      <Button Text="set Yellow" Command="{Binding YellowCommand}"/>
    </StackLayout>
  </StackLayout>
</ContentPage>

SetProperty()とは

Prism版ではソースオブジェクトのセッター内で、SetProperty()メソッドというものが使われています(素のXamarin Forms実装では「PropertyChangedEventHandler」を扱う部分)。
SetProperty()メソッドは、Prismライブラリ内の「Prism.Mvvm.BindableBaseクラス(Prismアセンブリ)」で実装されています。
Prismはオープンソースとして以下のGithubでソース一式が公開されています。

github.com

その中で BindableBaseクラス の実装は以下です。

Prism/BindableBase.cs at master · PrismLibrary/Prism · GitHub

2017/1/8時点の実装ソースを抜粋させていただくと以下となります(コメント文は除去しています)。

using System;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

namespace Prism.Mvvm
{
  /// <summary>
  /// Implementation of <see cref="INotifyPropertyChanged"/> to simplify models.
  /// </summary>
  public abstract class BindableBase : INotifyPropertyChanged
  {
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual bool SetProperty<T>(
      ref T storage,
      T value,
      [CallerMemberName] string propertyName = null)
    {
      if (object.Equals(storage, value)) return false;

      storage = value;
      this.OnPropertyChanged(propertyName);

      return true;
    }

    protected virtual void OnPropertyChanged(
      [CallerMemberName]string propertyName = null)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(
      Expression<Func<T>> propertyExpression)
    {
      var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
      this.OnPropertyChanged(propertyName);
    }
  }
}

SetProperty()メソッドの引数は3つ。
第1引数は ref でプロパティの値を保持する変数(フィールド変数)を受け取ります。
第2引数は 変更後の値 を受け取ります。
第3引数は 変更が発生したプロパティ名を文字列で受け取ります。[CallerMemberName]属性が付けられ、デフォルト値としてnullが指定されています。CallerMemberName属性はC# 5で導入された属性で「呼び出し元のプロパティ名・メソッド名」が割り当てられます。つまり、ソースオブジェクトのプロパティセッターからSetProperry()を呼び出す場合、第3引数は省略しても暗黙的にプロパティ名が指定されます。

では、続けて SetProperty() の内部実装に目を移します。

if (object.Equals(storage, value)) return false;

現在値と変更値の比較を行い、変更がなければそのままリターンします。

storage = value;

値の変更をフィールド変数に代入しています。

this.OnPropertyChanged(propertyName);

OnPropertyChanged()メソッドの呼び出しを行なっています。

OnPropertyChanged()メソッドの実装は以下の通りです。

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

PropertyChangedは「event PropertyChangedEventHandler」として定義されたイベント変数です。
これは、素のXamarin Formsにおける実装と同様ですね。

まとめ

つまり・・・データバインディング周りの実装に関して、PrismのSetProperty()によって以下のような点がラップされ便利になっています。
* 値の変更チェックをラップしている
* PropertyChanged呼び出しをラップしている
* [CallerMemberName]によってプロパティ名を暗黙的に取得している

XAMLとコンパイルについて(Xamarin Forms)

あけましておめでとうございます。

今年も「自分の知識の整理」と「技術者としてのフェアの精神(ネットで教えてもらうだけでなく、自ら知り得た知識を共有する精神)」でブログを書いていこうと思います。

また、今年は勉強会とかにも積極的に顔を出してみようかな・・・とか思いつつ・・・

ということで、早速今回はXamarin Formsにおける小ネタを・・・

XAMLコンパイル

Xamarin Formsで開発を行う場合、UI定義はXAMLで行います。
UI定義以外のロジック(これはコードビハインドにベタ書きするものから、Prismなどのフレームワークを使ってOOに責務を分担したコードまで含めて)は C#言語 で記述します。
XAML と コードを記述し、ビルドメニューを選択するとコンパイル作業が行われ 各DLL が出力されます。このDLLはアセンブリと呼ばれる、.NETにおけるIL(Intermidiate Language)モジュールになります。この辺りは、.NETについてある程度精通している方ならご存知かと思います。C#実装は当然 IL に変換されます。
一方、XAMLはデフォルトでは「リソース」としてDLL内に埋め込まれます。そして実行時にXAML→ILへのコンパイルが行われます(補足として、実行する上では、最終的にはネイティブコードに変換)。

XamlCompilation属性

結論としては、「XamlCompilation属性」というものを用いるとXAMLをビルド時にコンパイルすることが可能になります。
以下、順を追って説明いたします。

コンパイル後のXAMLを確認する

ではまずは、デフォルトの「ビルド時にはXAMLコンパイルせず、実行時にXAMLコンパイルされる方式」について確認してみます。

①ソリューションの準備
以下のようなウィザードが生成した単純なXamarin Formsソリューションを用意します。

f:id:daigo-knowlbo:20170103115050p:plain

Xamarin FormsのPCLプロジェクトは「CompExample1」です。
CompExample1Page.xamlの定義は以下とします。Labelを2つ配置した単純なXAML定義です。

<?xml version="1.0" encoding="utf-8"?>
<ContentPage 
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
    xmlns:local="clr-namespace:CompExample1"
    x:Class="CompExample1.CompExample1Page">
  <Label Text="Welcome to Xamarin Forms!" 
       VerticalOptions="Center" 
       HorizontalOptions="Center" />
  <Label Text="test" />
</ContentPage>

②ビルド
ビルドを行います。
CompExample1/bin/Release/CompExample1.dll が出力されます。
これを ILSpy で逆コンパイルしてみましょう。
結果は以下です。
Resources配下に 対象XAMLがそのまま埋め込まれていることが確認できます。

f:id:daigo-knowlbo:20170103115528p:plain

また、フォームのコードビハインドコード内の InitializeComponent() 中で LoadFromXaml() によりXAMLリソースをロードしています。

f:id:daigo-knowlbo:20170103115548p:plain

XAMLを開発時にコンパイルする(XamlCompilation属性)

次に XamlCompilation属性 を使用してみます。
ソリューションは先程の CompExample1 を使用します。

①XamlCompilation属性の付加
XamlCompilation属性は「アセンブリ(アプリケーション)単位」もしくは「フォーム単位」で指定することができます。

アセンブリ単位の指定には、以下のように名前空間に対して「[assembly: XamlCompilation(XamlCompilationOptions.Compile)]」属性を付加します。

// アセンブリ単位に指定(App.xaml.cs)
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

[assembly: XamlCompilation(XamlCompilationOptions.Compile)]
namespace CompExample1
{
  public partial class App : Application
  {
    ...
  }
  ...
}

フォーム単位の指定には、以下のようにコードビハインドクラスに対して「[XamlCompilation(XamlCompilationOptions.Compile)]」属性を付加します。

// フォーム単位に指定(CompExample1Page.xaml)
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace CompExample1
{
  [XamlCompilation(XamlCompilationOptions.Compile)]
  public partial class CompExample1Page : ContentPage
  {
    public CompExample1Page()
    {
      InitializeComponent();
    }
  }
}

②ビルド
ビルドを行います。
先程と同様にCompExample1/bin/Release/CompExample1.dll を ILSpy で逆コンパイルしてみましょう。
結果は以下です。

f:id:daigo-knowlbo:20170103115607p:plain

Resourcesが無くなりました。
そして、コードビハインドクラスの InitializeComponent() 内にXAMLで定義したUIを構築するためのコードが追加されています。つまり、XAMLはコードに変換され、ILとなりDLLアセンブリに格納されました。

開発時コンパイルのメリット

XAMLを開発時にコンパイルすると以下のようなメリットがあります。

1) コンパイル時にXAML構文チェックが行われる

例えば以下のようにXAML構文上のエラーがあった場合、XamlCompilation属性を使用していないとビルド(コンパイル)は正常終了してしまいます。そして実行時にエラーとなります。
開発ビルド時に構文チェックが行われることで、開発生産性の向上が見込まれます。

... 省略
   <Label Content="test" />   ← WPFは Content でOKでしたが、Xamarin Formsでは Text ですね。
... 省略

2) 実行時のパフォーマンスが高くなる

XAMLコンパイルするタイミングが開発ビルド時になるので、実行時にフォームを表示する際のパフォーマンスが向上します。

Xamarin Formsのデータバインディングを整理する(2)

前回の続き・・・

前回の投稿では、Xamarin Formsにおけるデータバインディングの基本(?)について説明しました。それらは単一コントロール(ターゲット)へのデータバインディングでした。

ryuichi111std.hatenablog.com

今回は「コレクションデータ」(つまり繰り返し要素)のデータバインディングについて説明したいと思います。

コレクションデータのデータバインディング(ListView)

「コレクションデータのデータバインディング」という表現をすると、堅苦しく分かりにくいのですが、要するに具体的なコントロールとしては「ListViewコントロール」です。
つまり一覧表示形式です。
一覧表示を行う、またその一覧からユーザーに選択させる、といったUIはよく利用されるものであると思います。

ListViewコントロールを使う

では早速、ListViewコントロールをとりあえず使ってみたいと思います。
比較的シンプルな使い方のサンプルを紹介します。
で、その後で、個人的に気になる部分を掘り下げて探索していこうと思います。
以下のような画面のアプリを作成します。

f:id:daigo-knowlbo:20161229151613p:plain

フルーツクラス(Fruit)オブジェクトのコレクション(配列)を「ソース」とし、ListViewにデータバインディングして、名称(Name)とカロリー(Calory)をリスト表示します。

1.Fruitクラスの作成

データバインディングのソースとなるFruitクラスの実装は以下の通りです。
単純に 名称プロパティ(Nameプロパティ)とカロリープロパティ(Caloryプロパティ)のみを持つモデルクラスです。

[リスト1] fruit.cs
namespace FromExample1
{
  // フルーツクラス
  public class Fruit
  {
    // 名称を取得または設定します。
    public string Name { get; set; }

    // 100gあたりのカロリー数を取得または設定します。
    public int Calory { get; set; }
  }
}

2.フォームへのListViewの配置

ListViewコントロールを配置した、フォーム定義はの通りです。

[リスト2] FormExample1Form.xasml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  x:Class="FromExample1.FromExample1Page">
  <StackLayout Margin="0,20,0,0">
    <ListView x:Name="list1">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ViewCell>
            <StackLayout>
              <Label Text="{Binding Path=Name}" />
              <Label Text="{Binding Path=Calory, 
                            StringFormat='{0} kcal/100g'}" 
                     Margin="30,0,0,0"/>
            </StackLayout>
          </ViewCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>
</ContentPage>
  • ListView要素
    単純にStackLayoutの子要素として ListViewコントロール を配置しています。

  • <ListView.ItemTemplate>
    ListViewでは「データバインドされたソースコレクションオブジェクトの各要素を、繰り返し表示するためのアイテムテンプレート」を定義することが出来ます。これが ItemTemplateプロパティ になります。
    ItemTemplateは Xamarin.Forms.DataTemplateクラス型です。

  • DataTemplate
    配下に、リスト表示する各要素の具体的なビュー表現を定義します。
    サンプルでは「ViewCellクラス要素」を使用しています。ListViewコントロールのテンプレート定義には「Xamarin.Forms.Cell派生クラス」を使用する必要があります。

  • Binding構文
    ListViewアイテムテンプレートに対して2つのデータバインディングを指定しています。
    それぞれ Labelコントロール に対するものですが、1つ目は単純に「Nameプロパティ」(つまりFruit.Name)へのバインディングです。 2つ目は「Caloryプロパティ」(つまりFruit.Calory)へのバインディングです。加えて、StringFormat設定を指定しています。フォーマットの指定方式は、C#一般の {0} というプレースホルダを使用することができます。

【補足(+αなメモ)】
  • プロパティ構文
    Xaml構文の話になりますが、ListView要素配下の <ListView.ItemTemplate> は、「プロパティ構文」と呼ばれる記述法であり、「ListViewクラスのItemTemplateプロパティを、自分の子要素で定義する」という意味になります。
    ListView.ItemTemplateプロパティに設定する具象値は、直下の要素「DataTemplate要素オブジェクト」になります。

  • コンテンツプロパティ
    ViewCell要素直下にLabel要素が記述されています。これはXaml構文としては、「コンテンツプロパティ構文」と呼ばれるものになります。
    Xamarinヘルプで ViewCellクラス を確認すると、以下のようにクラス属性として「ContentProperty」が定義されています。Xaml定義上、明示的なプロパティ名を指定しなかった場合に、ContentProperty属性で指定したプロパティへの値設定と認識されます。

[Xamarin.Forms.ContentProperty("View")]
public class ViewCell : Cell

つまり、ViewCellクラス要素をXamlで定義する場合、以下の2つの定義は同じ意味を持ちます。

[リスト3] 
〜プロパティ名を暗黙的に指定〜
<ViewCell>
  <Label Text="{Binding Path=Name}" />
</ViewCell>

↑↓↑↓↑↓ 上と下は同意 ↑↓↑↓↑↓

〜プロパティ名を明示的に指定〜
<ViewCell>
  <ViewCell.View>
    <Label Text="{Binding Path=Name}" />
  </ViewCell.View>
</ViewCell>
  • BindinbgにおけるPath
    データバインディング構文「{Binding}」の中の Path= の記述は省略することが可能です。
    以下の2つの記述は同じ意味を持ちます。
    どちらの構文を使用するかは各プロジェクトで取り決めておくと良いでしょう。
[リスト4] 
<Label Text="{Binding Path=Name}" />

↑↓↑↓↑↓ 上と下は同意 ↑↓↑↓↑↓

<Label Text="{Binding Name}" />

3.データソースの作成とListViewへの割り当て

フォームのコードビハインドクラスでデータソースコレクションオブジェクトを作成し、ListViewコントロールバインドします。

[リスト5] FormExample1Form.xasml.cs
using System.Collections.Generic;
using Xamarin.Forms;

namespace FromExample1
{
  public partial class FromExample1Page : ContentPage
  {
    private List<Fruit> fruits;

    public FromExample1Page()
    {
      InitializeComponent();

      // ListViewにデータバインド
      this.InitializeDataSource();
      this.list1.ItemsSource = this.fruits;
    }

    // データソースオブジェクトを初期化します。
    private void InitializeDataSource()
    {
      this.fruits = new List<Fruit>();

      this.fruits.Add(new Fruit() { Name = "Apple", Calory = 54 }); 
      this.fruits.Add(new Fruit() { Name = "Strawberry", Calory = 34 });
      this.fruits.Add(new Fruit() { Name = "Peach", Calory = 40 });
      this.fruits.Add(new Fruit() { Name = "Pear", Calory = 43 });
      this.fruits.Add(new Fruit() { Name = "Grape", Calory = 59 });
    }
  }
}

ListViewコントロールの「ItemsSourceプロパティ」に対して、データバインディングを行う為のソースオブジェクトを設定します。
コレクション型データバインディングである為、ItemsSourceプロパティの型は「System.Collection.IEnumerable型」になります。

【補足(+αなメモ)】
  • 各コレクション要素の BindingContext
    サンプルにも示したように、ListViewは ItemsSourceプロパティ にデータソースオブジェクトを設定することで、各アイテム要素に対するデータバインディングが行われます。
    Labelコントロール等に対する単一データバインディングを行う場合、ターゲットとなる各コントロール要素の BindingContextプロパティ にソースオブジェクトを設定することで、データバインディングを実現しました。
    ではListViewにおいて、各アイテム要素へのデータバインディングはどのようになっているのでしょうか?
    ということで、ちょっと探索を・・・
    シンプルにListViewに対するデータバインディングを実装した後、デバッグ実行を行い、適当なところでブレークポイントを貼ります(Buttonを配置してClickイベントをハンドルした辺りとか)。で、ウォッチを使って「list -> TemplatedItems[0]」の値を確認します。サンプルでは ViewCell型であり、その BindingContextプロパティ には Fruitオブジェクト が設定されていることが分かります。

f:id:daigo-knowlbo:20161229155252p:plain

各繰り返し要素のコントロールに対して、ListView内部でコレクションデータ内の該当するデータ項目が BindingContext に設定されているということであり、データバインディングの基本概念である「ターゲットのDataContextプロパティに、ソースオブジェクトを設定する」の動作が行われています。

以上が、最も基本的なListViewコントロールの使用方法です。

ListView と ItemsView< T > と Cell

前述のサンプルでは、コレクションの各要素を表示する為の DataTemplate定義 に「< ViewCell >」を使用しました。ViewCellの具体的な実装は「Xamarin.Forms.ViewCellクラス」になります。
ListViewコントロールでは、DataTemplate定義に「Xamarin.Forms.Cellクラスの派生クラス」を使用する必要があります(ViewCellクラスは、Cellクラスの派生クラスです)。これは、ListViewコントロールの仕様になります。また、Cellクラス自体は抽象クラスなので、そのまま利用することはできません。

なぜ Xamarin.Forms.Cell 派生クラスを指定する必要があるのか?

ビルドインのコントロールを使用する上では、まあ、そういう仕様だから、ということなのですが。
少しだけXamarin Formsの内部仕様に入り込みたいと思います。
ListViewコントロールクラスは、Xamarinヘルプを見れば分かる通り、ItemsView< T >クラスの派生クラスです。
具体的な定義は以下になります。

public class ListView : ItemsSource< Cell >

また、ItemsViewクラスは「テンプレートリストアイテムを含むビューの抽象基本クラス」です。
< T >は、データバインドされたソース要素を表示する為の型になります。
つまり、ListViewコントロールクラスは「ソースアイテムを表示するビジュアル要素として Cellクラス を使う仕様とした」ということです。また、Cellクラスは Elementクラス の派生クラスであり、Elementクラスはすべてのフォーム要素の基本クラスとなっているクラスになります。

Built-inのCell派生クラス達

ListViewコントロールの各アイテム要素表示には「Cell派生クラス」が利用できることが分かりました。
先程のサンプルでは ViewCellクラス を使用しましたが、これ以外に以下のビルドインCell派生クラスを使用することができます。

  • TextCell
  • ImageCell

※ ViewCellは、Viewクラス をコンテントプロパティとする、開発者が自由なレイアウトを構成する為のCellになります。
※ 開発者が定義したカスタムCellも利用可能です。

TextCellの使用方法

TextCellクラスは、リストアイテムを「Textプロパティ(表示テキスト)」と「Detailプロパティ(詳細テキスト)」によって表示します。
以下がXaml定義の例です。(データソースは前述のサンプルと同様にFruitコレクションを想定しています)

[リスト6]  TextCellの定義(xaml)
<ListView x:Name="list1">
  <ListView.ItemTemplate>
    <DataTemplate>
      <TextCell
            Text="{Binding Path=Name}" 
            Detail="{Binding Path=Calory, StringFormat='{0}kcal/100g'}"
            TextColor="Red"
            DetailColor="Green" />
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

Text(string) / Detail(string) / TextColor(Color) / DetailColor(Color) の各プロパティを指定することができます(それぞれの意味は省略・・・)。
1つ特徴として「TextCellは実行時にはネイティブコントロールを使用する為、パフォーマンスに優れている」という点があります(以下Xamarinヘルプより引用)。

TextCells are rendered as native controls at runtime, so performance is very good compared to a custom ViewCell.

そして、実行画面は以下。

f:id:daigo-knowlbo:20161229152046p:plain

ImageCellの使用方法

ImageCellクラスは、TextCellに加えて画像を表示する機能を持ちます。
早速サンプルですが、画像を表示するために Fruitクラス に「ImageSource型のImageプロパティ」を追加します。

[リスト7] Fruit.cs(Imageプロパティを追加)
using Xamarin.Forms;

namespace FromExample1
{
  public class Fruit
  {
    public string Name { get; set; }

    public int Calory { get; set; }
    // 画像プロパティを追加
    public ImageSource Image { get; set; }
  }
}

Xamlの定義は以下の通りです。先程のTextCellの使用とほぼ同様です。
テンプレート定義にImageCell要素を使用し、ImageSourceプロパティにはImageプロパティ(ソースオブジェクトである Fruitオブジェクト の Imageプロパティ)をバインド定義します。

[リスト8] ImageCellを使用するXamlフォーム
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  x:Class="FromExample1.UseImageCellPage">
  <StackLayout Margin="0,20,0,0">
    <ListView x:Name="list1">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ImageCell Text="{Binding Path=Name}" 
                 Detail="{Binding Path=Calory, StringFormat='{0}kcal/100g'}"
                 TextColor="Red"
                 DetailColor="Green"
                 ImageSource="{Binding Path=Image}"/>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </StackLayout>
</ContentPage>

コードビハインドクラスの定義は以下の通りです。
画像ファイル(.png)は、Xamarin Formsプロジェクトにリソースとして登録しています。

[リスト9] 
using System;
using System.Collections.Generic;

using Xamarin.Forms;

namespace FromExample1
{
  public partial class UseImageCellPage : ContentPage
  {
    private List<Fruit> fruits;

    public UseImageCellPage()
    {
      InitializeComponent();

      this.InitializeDataSource();
      this.list1.ItemsSource = this.fruits;
    }

    private void InitializeDataSource()
    {
      this.fruits = new List<Fruit>();


      this.fruits.Add(new Fruit() { 
        Name = "Apple", 
        Calory = 54, 
        Image = ImageSource.FromResource("FromExample1.Resources.apple.png") }); 
      this.fruits.Add(new Fruit() { 
        Name = "Strawberry", 
        Calory = 34,
        Image = ImageSource.FromResource("FromExample1.Resources.strawberry.png")});
      this.fruits.Add(new Fruit() { 
        Name = "Peach", 
        Calory = 40,
        Image = ImageSource.FromResource("FromExample1.Resources.peach.png")
      });
      this.fruits.Add(new Fruit() {
        Name = "Pear",
        Calory = 43,
        Image = ImageSource.FromResource("FromExample1.Resources.pear.png")
      });
      this.fruits.Add(new Fruit() { 
        Name = "Grape", 
        Calory = 59,
        Image = ImageSource.FromResource("FromExample1.Resources.grape.png")
      });
    }
  }
}
画像リソース

画像リソースについて若干の補足をしておきます。
上記サンプルでは画像(png形式)をプロジェクト内に「リソース」として埋め込み登録して利用しています。
つまりビルドにより生成されたアセンブリ(dll)に画像データがリソースとして埋め込んでいます。
サンプルのソリューション構成のキャプチャは以下の通りです。
Xamarin Formsプロジェクトに「Resources」フォルダを作成し、各画像ファイルを追加しています。

f:id:daigo-knowlbo:20161229152322p:plain

また追加した各画像ファイルのビルドアクションは「EmbeddedResource」を指定しています。

f:id:daigo-knowlbo:20161229152433p:plain

上図プロパティウィンドウは、ソリューションツリーから対象pngファイルを選択し、マウス右ボタンメニュー「プロパティ」で表示されます(Xamarin StudioでもVisual Studio For Macでもほぼ同様のUI)。
プロパティウィンドウの中に「リソースID」という項目があります。このIDによりプログラムから対象リソースを読み込むことができます。
つまり、以下のコードで「リソースIDが FromExample1.Resources.apple.png のリソースをImageSourceとして読み込む」ことができるのです。

[リスト10] 
ImageSource.FromResource("FromExample1.Resources.apple.png")

で、ImageCellを使用したサンプルの実行画面は以下です。

f:id:daigo-knowlbo:20161229152220p:plain

  • SwitchCell と EntryCell
    あと「SwitchCell」と「EntryCell」というCell派生クラスがあるのですが、これはTableView用に用意されたものなのでここでは省略。

コードでテンプレートを定義する

ここまでXaml構文を利用してListViewへのデータバインディング・テンプレート定義を行なってきましたが、これらは全て コードで実装することができます。
一般的にはXAMLで定義可能な箇所は、あえてコードではなくXAMLで定義することの方が、可読性なメンテナンス性が高いと思われます。
リスト2 / 5 をコードで実装すると以下のようになります。

[リスト11]
using System.Collections.Generic;
using Xamarin.Forms;

namespace FromExample1
{
  public partial class FromExample1Page : ContentPage
  {
    private List<Fruit> fruits;

    public FromExample1Page()
    {
      InitializeComponent();

      // テンプレートを初期化
      this.InitializeTemplate();

      // ListViewにデータバインド
      this.InitializeDataSource();
      this.list1.ItemsSource = this.fruits;
    }

    // テンプレートを初期化します。
    private void InitializeTemplate()
    {
      DataTemplate dataTemplate = new DataTemplate(() => {
        StackLayout stackLayout = new StackLayout();
          
        Label nameLabel = new Label();
        nameLabel.SetBinding(
          Label.TextProperty, 
          "Name", 
          BindingMode.OneWay, 
          null, 
          null);
        Label caloryLabel = new Label() { Margin = new Thickness(30, 0, 0, 0) };
        caloryLabel.SetBinding(
          Label.TextProperty, 
          "Calory", 
          BindingMode.OneWay, 
          null, 
          "{0}kcal/100g");
        stackLayout.Children.Add(nameLabel);
        stackLayout.Children.Add(caloryLabel);

        return new ViewCell() { View = stackLayout };
      });
      this.list1.ItemTemplate = dataTemplate;
    }

    // データソースオブジェクトを初期化します。
    private void InitializeDataSource()
    {
      this.fruits = new List<Fruit>();

      this.fruits.Add(new Fruit() { Name = "Apple", Calory = 54 }); 
      this.fruits.Add(new Fruit() { Name = "Strawberry", Calory = 34 });
      this.fruits.Add(new Fruit() { Name = "Peach", Calory = 40 });
      this.fruits.Add(new Fruit() { Name = "Pear", Calory = 43 });
      this.fruits.Add(new Fruit() { Name = "Grape", Calory = 59 });
    }
  }
}

DataTemplateをインスタンス化する際のコンストラクタ引数に「テンプレートを作成する為の Func< object >匿名関数」を渡します。
DataTemplateコンストラクタ引数Func< T >自体のジェネリック型は object ですが、ListViewが扱える 項目表示要素は Cell派生クラス である為、これに従います(Cell派生クラス以外をreturnするとコンパイルは通りが実行時エラーとなります)。

まとめ

ListViewはよく使われるメジャーなUIなのですが、こうしてまとめようと思うと何気に多くの技術要素が詰まっているように思いました。
HeaderTemplate とか FooterTemplate についても本投稿では触れていませんし、アイテム選択時の「ItemSelectedイベント」についても触れていません。
同様の記事はたくさんあると思いますが、これを読んで頂いた皆さんにとって、1つでもヒントになるようなことがあれば幸いかと。

Xamarin Formsのデータバインディングを整理する(1)

昨今の開発言語やフレームワークにおいて「データバインディング」は非常に重要で有益な技術となっています。
Xamarin Formsにおいても データバインディング がサポートされており、これは WPF / Silverlight などから引き継がれた技術になります(といっても、詳細についてはWPF / Silverlight / Xamarinでは実装内容・実装レベルが異なりますが・・・WPFではDependencyPropertyであったものが Xamarin FormsではBindablePropertyであったり・・・)。

データバインディングは、Xamarin Formsアプリを開発するすべてのエンジニアが利用する技術だと思います。
そして、手軽に利用することもできるし、結構深い技術であるとも思っています。
自分自身の知識の整理も含めて、これから何回かに渡って「Xamarin Formsのデータバインディング」について書いていこうと思います。

※あくまでブログであり、書籍とかじゃないから体系的なまとめはあまり気にしないで、基本+自分の興味の赴くままの技術探求をしていこうかと思います。

データバインディングの基本(ソース と ターゲット)

概念として、データバインディングにおける「元データのオブジェクト側」=「ソース」、「データバインドされる側」 = 「ターゲット」と呼びます。
データバインディングを行う上では、「ソースオブジェクト」と「ターゲットオブジェクト」の2つを結びつける必要があります。その具体的な方法は以下になります。

  • ターゲットオブジェクトの「BindingContextプロパティ」に「ソースオブジェクト」を設定する

シンプルなデータバインディング

早速、以下にシンプルなデータバインディングの例を示します。
ソースは「Personオブジェクト」、ターゲットはフォームに配置した「Labelコントロール」です。
Personは単純なPOCOです。
PersonオブジェクトのNameプロパティの値を、LabelコントロールのTextプロパティにデータバインドします。

Personクラスの定義

ソースとなるPersonクラスの定義です。

// リスト1 person.cs
namespace Example1
{
  public class Person
  {
    public string Name { get; set; }
  }
}

フォームの定義

ターゲットとするLabelコントロールを配置したフォームの定義です。

// リスト2 Example1Page.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  x:Class="Example1.Example1Page">
  <StackLayout Margin="0,20,0,0">
    <Label x:Name="Label1" Text="{Binding Path=Name}" />
  </StackLayout>
</ContentPage>

LabelコントロールのTextプロパティに対してデータバインドを行なっています。
「 {Binding Path=Name} 」がXAMLにおけるバインディング構文になります。
ターゲットオブジェクトに対するソースオブジェクトの設定は、この後に紹介するフォームのコードビハインドクラス上で行なっています。
ここでは、以下の2点を {Bindng} 構文によって定義しています。

  • ターゲットオブジェクトであるLabelの「Textプロパティ」に対してデータバインドを行う
  • Path=Nameによって、ソースオブジェクトのNameプロパティ値をバインドすることとする(つまりPersonオブジェクトのNameプロパティ値)

ソースとターゲットの結び付け定義

ソースとなるPersonオブジェクトを作成して、ターゲットとなるLabelコントロールのBindingContextプロパティに設定します。

// リスト3 Example1Page.xaml.cs
using Xamarin.Forms;

namespace Example1
{
  public partial class Example1Page : ContentPage
  {
    private Person person;

    public Example1Page()
    {
      InitializeComponent();
      
      // Personオブジェクト作成
      this.person = new Person();
      person.Name = "ryuichi daigo";

      // ターゲットのラベルコントロールにソースを設定
      this.Label1.BindingContext = person;
    }
  }
}

実行結果画面は以下の通りです。

f:id:daigo-knowlbo:20161225103551p:plain

BindingContextプロパティ

前述のように「ソースとターゲットの結び付け」は、「ターゲットオブジェクトのBindingContextプロパティにソースオブジェクトを設定する」事で行います。
BindingContextプロパティをXamarinヘルプで確認すると、「Xamain.Forms.BindableObjectクラスで定義されたObject型のプロパティ 」であることが分かります。
つまり、データバインディングにおけるターゲットとなりうるオブジェクトとは、BindableObjectを継承したクラスであると言う事が出来ます。
そして、Labelなどの多くのUIコントロールクラスは Viewクラス を継承しています。さらにViewクラスから継承関係を辿ると、以下の図のようなクラス継承関係となっています。

f:id:daigo-knowlbo:20161225174653p:plain

このことから、Xamarin Formsにおける殆どのUIコントロールが、データバインディングにおける「ターゲット」となる事ができると言う事、及びその意味が分かります。

BindableProperty

ターゲットオブジェクトに対してソースオブジェクトをデータバインドする事が分かりました。
ただ、上述の例でも示したように、ターゲット自体ではなくターゲットオブジェクトのプロパティ(LabelコントロールクラスのTextプロパティ)に対して、ソースオブジェクトのプロパティ(PersonクラスのNameプロパティ)をデータバインドします。
これはごく当たり前の自然なお話ですね。
ただし、ここにも技術的仕組み上の あるルール が存在します。
ターゲットオブジェクトの中で「データバインドの対象とする事ができるプロパティ」とは、「BindableProperty」と呼ばれる特殊なプロパティである必要があります。
Labelクラスのヘルプページを参照すると、Textプロパティの項目には以下のような記述があるはずです(https://developer.xamarin.com/api/type/Xamarin.Forms.Label/)。

String. Gets or sets the text for the Label. This is a bindable property.

そしてBindablePropertyは少し特殊な定義の仕方をする必要があり、例えばstring型のTextプロパティをBindablePropertyとして定義する場合、以下のような実装を行うことになります。

// リスト4 BindablePropertyの実装方法(カスタムコントロール実装時などに使う)
public static readonly BindableProperty TextProperty =
  BindableProperty.Create("Text", typeof(string), typeof(Example1Page), default(string), 
                          propertyChanged: (bindable, oldValue, newValue) =>
                          ((Example1Page)bindable).Text = (string)newValue);    
public string Text
{
  get { return (string)GetValue(TextProperty); }
  set { SetValue(TextProperty, value); }
}

そう、以下のような単純なプロパティ定義では、データバインド可能なプロパティにはなりません。

// リスト5 こんな定義ではデータバインド可能なプロパティとはならない・・・
public string Text { get; set; }

この辺りの詳細は、カスタムコントロールを実装する際に必要になります。
ひとまず、提供されているコントロールはこのような実装を行なっている、そしてBindablePropertyがデータバインド可能なプロパティである、と理解しておけばまずは良いのではないかと思います。

バインディングの方向

データバインディングとは「ターゲットオブジェクトとソースオブジェクト」の結び付けです。
そして、バインディングには方向の概念があります。
以下の3つの方向の方式があります。これは、Xamarin.Forms.BindingMode列挙体で定義されています(WPFと異なりOneTimeという設定がなくなっています)。

  • OneWay
    「ソース → ターゲット」の単方向に対してデータバインディングが行われます。
    プログラムロジック上でソースとなるモデルオブジェクトの値を設定・変更し、そのソース値をターゲット(UIコントロール等)にバインドする事で画面表示を切り替えるような用途が想定されます。

  • OneWayToSource
    「ターゲット → ソース」の単方向に対してデータバインディングが行われます。
    UI上の入力項目(例えばEntryコントロール)をターゲットとし、画面上で入力された値をソースオブジェクトにバインディングさせるような用途が考えられます。

  • TwoWay
    「ターゲット ←→ ソース」の両方向に対してデータバインディングが行われます。
    プログラムロジック上でのソースオブジェクトの変更をターゲットであるUIに反映させる、また、逆にターゲットであるUI上での値変更をソースオブジェクトに反映させる、という相互でのデータバインディングが行われます。

3つのバインディングモードを使用したサンプル

3つのバインディングモードの動作の違いを確認できるサンプルプログラムを作成します。

ソースオブジェクト

データバインディングのソースオブジェクトとして ValueModelクラス を用意します。
単純に Value1 / Value2 / Value3 というstring型のプロパティを3つ持ちます。
いくつか補足が必要な実装が含まれていますが、ソース下部で説明を行います。

// リスト6 ValueModel.cs
using System;
using System.ComponentModel;

namespace Example2
{
  /// <summary>
  /// ソースオブジェクトとして利用するオブジェクト
  /// </summary>
  public class ValueModel : INotifyPropertyChanged
  {
    /// <summary>
    /// INotifyPropertyChangedインターフェイスの実装
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    // データを保持するフィールド
    private string value1;
    private string value2;
    private string value3;

    // Value1プロパティ
    public string Value1
    {
      get
      {
        return this.value1;
      }
      set
      {
        if (this.value1 != value)
        {
          this.value1 = value;
          OnPropertyChanged("Value1");
        }
      }
    }

    // Value2プロパティ
    public string Value2
    {
      get
      {
        return this.value2;
      }
      set
      {
        if (this.value2 != value)
        {
          this.value2 = value;
          OnPropertyChanged("Value2");
        }
      }
    }

    // Value3プロパティ
    public string Value3
    {
      get
      {
        return this.value3;
      }
      set
      {
        if (this.value3 != value)
        {
          this.value3 = value;
          OnPropertyChanged("Value3");
        }
      }
    }

    // プロパティ値の変更を通知します
    protected virtual void OnPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this,
          new PropertyChangedEventArgs(propertyName));
      }
    }
  }
}

上記 ValueModelクラス の実装に関して「INotifyPropertyChangedインターフェイスの実装」について補足説明を行う必要があります。
INotifyPropertyChangedインターフェイスとは、その名称の通り「プロパティの変更を通知する」インターフェイスです。
INotifyPropertyChangedインターフェイスを実装する事で、「ソースオブジェクトのプロパティ値に変更があった際に、ターゲットオブジェクトへの通知」を行う事ができます。
つまり、OneWay / TwoWayモードのデータバインディングにおいてソースオブジェクトの変更がリアルタイムにターゲットに伝達されます。

フォームコントロール(ターゲット)の定義

フォーム上に3つの Entryコントロール を配置します。それぞれ Value1へのOneWayモードバインディング / Value2へのOneWayToSourceモードバインディング / Value3へのTwoWayモードバインディング とします。
また、3つのボタンを用意し、それぞれ、クリックされたらプログラム側で Value1 / Value2 / VAlue3 の値を変更します。各ソース値の変更が、ターゲット(Entryコントロール)に反映される様子を確認します。
もう1つ、「show data」ボタンを配置します。これはクリックすると、Value1 / Value2 / Value3 の値をアラートダイアログで表示します。これにより、ターゲット(Entryコントロール)からソースの値への変更の反映を確認します。

// リスト7 Example2Page.xaml
<?xml version="1.0" encoding="utf-8"?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms" 
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
  x:Class="Example2.Example2Page">

  <StackLayout x:Name="stack1" Margin="0,20,0,0">
    <Label Text="OneWay(ソース→ターゲット)" />
    <Entry x:Name="entry1" Text="{Binding Path=Value1,Mode=OneWay}"/>

    <Label Text="OneWayToSource(ターゲット→ソース)" />
    <Entry x:Name="entry2" Text="{Binding Path=Value2,Mode=OneWayToSource}"/>

    <Label Text="TwoWay(ソース←→ターゲット)" />
    <Entry x:Name="entry3" Text="{Binding Path=Value3,Mode=TwoWay}"/>

    <Button Clicked="onClickedModifyValue1" Text="ソースのValue1値を変更"/>
    <Button Clicked="onClickedModifyValue2" Text="ソースのValue2値を変更"/>
    <Button Clicked="onClickedModifyValue3" Text="ソースのValue3値を変更"/>

    <Button Clicked="onClickedShowData" Text="show data"/>
  </StackLayout>
</ContentPage>

以下がフォームのコードビハインドクラスになります。
ソースオブジェクトは適当な値で生成しています。
stack1(StackLayoutコントロール)の BindingContextプロパティ にソースオブジェクトを設定しています。
実際にデータバインディングを行うのは「StackLayoutの子コントロールである 3つのEntryコントロール」です。しかし、親であるStackLayoutコントロールにソースオブジェクトを設定しています。これは「BindingContextによるデータソースのバインド情報は、子要素に継承される」というデータバインディングの特徴を生かしています。

// リスト8 Example2Page.xaml.cs
using Xamarin.Forms;

namespace Example2
{
  public partial class Example2Page : ContentPage
  {
    // ソースオブジェクト
    private ValueModel valueModel;

    public Example2Page()
    {
      InitializeComponent();

      // ソースオブジェクト(Valuemodel)を生成します
      this.valueModel = new ValueModel(){
        Value1 = "default value 1",
        Value2 = "default value 2", 
        Value3 = "default value 3" };

      // データバインディングのターゲットオブジェクトとソースオブジェクトを紐付けます
      this.stack1.BindingContext = this.valueModel;
    }

    public void onClickedModifyValue1(object sender, System.EventArgs e)
    {
      // Value1データ値をプログラムロジックで変更します
      this.valueModel.Value1 = "Modify1:" + System.DateTime.Now.ToString("yyyy/MM/dd");
    }

    public void onClickedModifyValue2(object sender, System.EventArgs e)
    {
      // Value2データ値をプログラムロジックで変更します
      this.valueModel.Value2 = "Modify2:" + System.DateTime.Now.ToString("yyyy/MM/dd");
    }

    public void onClickedModifyValue3(object sender, System.EventArgs e)
    {
      // Value3データ値をプログラムロジックで変更します
      this.valueModel.Value3 = "Modify3:" + System.DateTime.Now.ToString("yyyy/MM/dd");
    }

    public async void onClickedShowData(object sender, System.EventArgs e)
    {
      // ソースオブジェクトの値をポップアップ表示します
      await DisplayAlert(
        "info", 
        string.Format("Value1: {0} /" +
                      "Value2: {1} /" +
                      "Value3: {2}", 
                      this.valueModel.Value1, 
                      this.valueModel.Value2, 
                      this.valueModel.Value3),
        "close");
    }
  }
}

実行してみる

ここではiOSシミュレータで実行します。
①実行
実行直後の画面が以下です。1つ目のEntry(OneWay) および 3つ目のEntry(TwoWay)にはソースの値が反映されています。2つ目のEntryはOneWayToSourceモードである為、ソース→ターゲットへのデータバインディングは行われません。

f:id:daigo-knowlbo:20161225115342p:plain

②次に、3つのEntryにUI上から「変更」の文字を追記します。

f:id:daigo-knowlbo:20161225115403p:plain

③ここで、「show data」ボタンをクリックし、ソースオブジェクトの値を確認します。
Value2 / Value3にはEntryコントロールUI上での「変更」文字の追記が反映されました。この2つはそれぞれ、OneWayToSource / TwoWayモードでデータバインディングされている為、「ターゲット→ソース」へのデータ反映が行われました。

f:id:daigo-knowlbo:20161225115128p:plain

④次に「ソースのValue1値を変更」「ソースのValue2値を変更」「ソースのValue3値を変更」ボタンをクリックします。
これにより、プログラム上で Value1 / Value2 / Value3 の値が変更されます。
以下がその結果画面です。1つ目、3つ目のEntryコントロールの値が変更されました。OneWay / TwoWayモードデータバインディングである為、「ソース→ターゲット」へのデータ変更反映が行われました。
1つ重要な点は、ソース→ターゲットの変更反映を行う為に、ソースオブジェクト(ここではValueModelクラス)に対してINotifyPropertyChangedインターフェイス の実装が必要であるということです。

f:id:daigo-knowlbo:20161225115158p:plain

⑤最後にもう1度「show data」ボタンをクリックします。
④での操作により、ターゲットであるEntryコントロールへのデータ変更の反映の有無に関わらず、ソースオブジェクトの値自体は変更されていることを確認することができます。

f:id:daigo-knowlbo:20161225115221p:plain

コードでデータバインディングを定義

実際には多くの場合、XAMLのデータバインド構文を利用することになりますが、同じことをコードから行う事が出来ます。
先程のリスト1 / リスト2を、コードによるデータバインディング定義で書き換えると次のようになります。
Xamlへの {Binding Path=Name} の書き換えになります)

// リスト8 「リスト2 Example1Page.xaml」をコードで書き換えると
this.Label1.BindingContext = person;
this.Label1.SetBinding(
    Label.TextProperty, 
    "Name", 
    BindingMode.OneWay, 
    null, 
    null);

SetBinding()メソッドは「BindableObjectの拡張メソッド」として定義されています。
BindableObjectとは、つまりデータバインディングのターゲットとなりうる(多くの)UIコントロールが該当します。
Label1の SetBinding() メソッドを呼び出します。
第1引数は、バインディング対象の BindableProperty を指定します。前述でバインド可能なプロパティは「通常のプロパティではなくBindablePropertyという特殊なプロパティである」と説明しました。Xaml定義上では「暗黙的にTextプロパティへの {Binding} 定義」を行いますが、コード上では「明示的にLabel.TextPropertyというBindableProperty型」を指定します。
第2引数は、Pathの指定になります。ソースはオブジェクト Person の Nameプロパティ がバインド対象なので「"Name"」となります。

まとめ & つづく・・・

ということで、Xamarin Formsにおけるデータバインディングの基本をまとめてみました。
基本といっても、まだまだ情報不足な部分もありますが、それでも結構長くなってしまったので、本投稿ではここまでとします。
今後のポストではコレクションコントロールへのデータバインディングとかにも触れていこうと思っています。

【追記】続きはこちら↓↓↓

ryuichi111std.hatenablog.com