Xamarin.Forms の基本的なバインディング

Xamarin.Forms のデータ バインディングでは、通常は少なくとも一方がユーザー インターフェイス オブジェクトである 2 つのオブジェクト間において、プロパティのペアをリンクします。 これら 2 つのオブジェクトは、"ターゲット" および "ソース" と呼ばれます。

  • "ターゲット" は、データ バインディングが設定されている方のオブジェクト (およびプロパティ) です。
  • "ソース" は、データ バインディングによって参照されている方のオブジェクト (およびプロパティ) です。

この区別は少し混乱を招くことがあります。最も単純な場合は、データがソースからターゲットに流れます。これは、ソース プロパティの値からターゲット プロパティの値が設定されることを意味します。 ただし、場合によっては、データがターゲットからソースに、または双方向に流れることがあります。 混乱を避けるには、ターゲットは (データを受け取るのではなく提供する場合でも) 常に、データ バインディングが設定されている方のオブジェクトであることに留意します。

バインディング コンテキストを使用したバインド

通常はデータ バインディングをすべて XAML で指定しますが、コードでのデータ バインディングを確認することは有益です。 Basic Code Binding (基本的なコード バインディング) ページの内容は、LabelSlider を使用した XAML ファイルです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BasicCodeBindingPage"
             Title="Basic Code Binding">
    <StackLayout Padding="10, 0">
        <Label x:Name="label"
               Text="TEXT"
               FontSize="48"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

Slider は、0 から 360 までの範囲に設定されています。 このプログラムは、Slider を操作することによって Label を回転させることを意図したものです。

データ バインディングを使用しない場合は、SliderValueChanged イベントを、SliderValue プロパティにアクセスしてその値を LabelRotation プロパティに設定するイベント ハンドラーに設定します。 データ バインディングによって、そのジョブが自動化されます。イベント ハンドラーおよびイベント ハンドラー内のコードは不要になります。

BindableObject から派生した任意のクラスのインスタンスにバインディングを設定できます。これには、ElementVisualElementViewView の派生クラスが含まれます。 バインディングは、常にターゲット オブジェクトに設定されます。 そのバインディングによって、ソース オブジェクトが参照されます。 データ バインディングを設定するには、次に示すターゲット クラスの 2 つのメンバーを使用します。

  • BindingContext プロパティでは、ソース オブジェクトを指定します。
  • SetBinding メソッドでは、ターゲット プロパティとソース プロパティを指定します。

この例では、Label がバインディング ターゲットで、Slider がバインディング ソースです。 Slider ソースにおける変更が、Label テンプレートの回転に影響を与えます。 データはソースからターゲットに流れます。

BindableObject によって定義された SetBinding メソッドには、Binding クラスの派生元となる BindingBase 型の引数が使用されていますが、BindableObjectExtensions クラスによって定義された他の SetBinding メソッドがあります。 Basic Code Binding (基本的なコード バインディング) サンプルの分離コード ファイルでは、よりシンプルな、このクラスの SetBinding 拡張メソッドを使用しています。

public partial class BasicCodeBindingPage : ContentPage
{
    public BasicCodeBindingPage()
    {
        InitializeComponent();

        label.BindingContext = slider;
        label.SetBinding(Label.RotationProperty, "Value");
    }
}

Label オブジェクトはバインディング ターゲットです。したがって、このプロパティが設定されるオブジェクトであり、メソッドが呼び出される対象のオブジェクトです。 BindingContext プロパティでは、バインディング ソースである Slider が示されています。

SetBinding メソッドはバインディング ターゲットに対して呼び出されますが、ターゲット プロパティとソース プロパティの両方を指定しています。 ターゲット プロパティである Label.RotationProperty は、BindableProperty オブジェクトとして指定されています。 ソース プロパティは文字列として指定され、SliderValue プロパティを示しています。

SetBinding メソッドにより、データ バインディングの最も重要なルールの 1 つが明らかになります。

ターゲット プロパティは、バインド可能なプロパティによってサポートされている必要があります。

このルールは、ターゲット オブジェクトが、BindableObject から派生したクラスのインスタンスである必要があることを示しています。 バインド可能なオブジェクトとバインド可能なプロパティの概要については、「バインド可能なプロパティ」の記事を参照してください。

ソース プロパティにこのようなルールはなく、文字列として指定されます。 内部的には、実際のプロパティにアクセスするためにリフレクションが使用されます。 ただし、この特定のケースでは、Value プロパティはバインド可能なプロパティによってもサポートされています。

コードはいくらか簡素化することができます。バインド可能なプロパティである RotationProperty は、VisualElement によって定義され、LabelContentPage によって継承されるため、SetBinding 呼び出し内にクラス名は必要なくなります。

label.SetBinding(RotationProperty, "Value");

ただし、クラス名を含めておくと、ターゲット オブジェクトのよいリマインダーになります。

Slider を操作すると、それに応じて Label が回転します。

Basic Code Binding

Basic Xaml Binding (基本的な XAML バインディング) ページは、すべてのデータ バインディングを XAML で定義している点を除き、Basic Code Binding (基本的なコード バインディング) と同じです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BasicXamlBindingPage"
             Title="Basic XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               BindingContext="{x:Reference Name=slider}"
               Rotation="{Binding Path=Value}" />

        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

コードの場合と同様、ターゲット オブジェクトである Label にデータ バインディングを設定しています。 2 つの XAML マークアップ拡張が使用されています。 それらは、中かっこの区切り記号によって、すぐに見分けがつきます。

  • x:Reference マークアップ拡張は、slider という名前の Slider であるソース オブジェクトを参照するために必要です。
  • Binding マークアップ拡張によって、LabelRotation プロパティが、SliderValue プロパティにリンクされます。

XAML マークアップ拡張の詳細については、「XAML マークアップ拡張」の記事を参照してください。 x:Reference マークアップ拡張は、ReferenceExtension クラスによってサポートされています。Binding は、BindingExtension クラスによってサポートされています。 XML 名前空間のプレフィックスが示すように、x:Reference は XAML 2009 仕様の一部です。一方、Binding は Xamarin.Forms の一部です。 中かっこ内に引用符が使用されていないことに注目してください。

BindingContext を設定するとき、x:Reference マークアップ拡張を忘れがちです。 次のように、誤ってプロパティをバインディング ソースの名前にじかに設定することがよくあります。

BindingContext="slider"

しかし、これは正しくありません。 そのマークアップでは、BindingContext プロパティが、"slider" というスペルの英字の string オブジェクトに設定されます。

ソース プロパティが BindingExtensionPath プロパティを使用して指定されていることに注意してください。これは、Binding クラスの Path プロパティに対応します。

Basic XAML Binding (基本的な XAML バインディング) ページに示されているマークアップは、簡素化することができます。x:ReferenceBinding などの XAML マークアップ拡張には、コンテンツ プロパティ属性を定義することができます。これは、XAML マークアップ拡張に対してプロパティ名を使用する必要がないことを意味します。 Name プロパティは x:Reference のコンテンツ プロパティであり、Path プロパティは Binding のコンテンツ プロパティです。つまり、これらは式から省略できます。

<Label Text="TEXT"
       FontSize="80"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand"
       BindingContext="{x:Reference slider}"
       Rotation="{Binding Value}" />

バインディング コンテキストを使用しないバインド

BindingContext プロパティは、データ バインディングの重要なコンポーネントですが、常に必要なわけではありません。 代わりに SetBinding 呼び出しまたは Binding マークアップ拡張においてソース オブジェクトを指定できます。

これは、Alternative Code Binding サンプルに示されています。 XAML ファイルは、LabelScale プロパティを制御するための Slider が定義されている点を除き、Basic Code Binding (基本的なコード バインディング) サンプルと同様です。 その理由のため、Slider は -2 から 2 までの範囲に設定されています。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.AlternativeCodeBindingPage"
             Title="Alternative Code Binding">
    <StackLayout Padding="10, 0">
        <Label x:Name="label"
               Text="TEXT"
               FontSize="40"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Slider x:Name="slider"
                Minimum="-2"
                Maximum="2"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

分離コード ファイルでは、BindableObject によって定義された SetBinding メソッドを使用してバインディングを設定します。 引数は、Binding クラスのコンストラクターです。

public partial class AlternativeCodeBindingPage : ContentPage
{
    public AlternativeCodeBindingPage()
    {
        InitializeComponent();

        label.SetBinding(Label.ScaleProperty, new Binding("Value", source: slider));
    }
}

Binding コンストラクターには 6 つのパラメーターがあるため、名前付き引数を使用して source パラメーターが指定されています。 その引数は slider オブジェクトです。

このプログラムを実行すると、少々驚くかもしれません。

Alternative Code Binding

左側の iOS 画面は、ページが最初に表示されたときに画面がどのように見えるかを示しています。 Label はどこでしょうか。

問題は、Slider が初期値の 0 になっていることです。 このため、LabelScale プロパティも 0 に設定され、既定値の 1 がオーバーライドされています。 この結果、最初は Label が表示されません。 Android のスクリーンショットが示すように、Slider を操作して Label が再び表示されるようにすることができますが、最初に表示されないのは不安を与えます。

次の記事では、Scale プロパティの既定値から Slider を初期化することによる、この問題の回避方法について説明しています。

Note

VisualElement クラスでは、ScaleX プロパティと ScaleY プロパティも定義しています。これにより、VisualElement を縦方向と横方向に別々にスケールできます。

Alternative XAML Binding (代替 XAML バインディング) ページには、同等のバインディングがすべて XAML で示されます。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.AlternativeXamlBindingPage"
             Title="Alternative XAML Binding">
    <StackLayout Padding="10, 0">
        <Label Text="TEXT"
               FontSize="40"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"
               Scale="{Binding Source={x:Reference slider},
                               Path=Value}" />

        <Slider x:Name="slider"
                Minimum="-2"
                Maximum="2"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

これで、Binding マークアップ拡張には、コンマで区切った SourcePath という 2 つのプロパティが設定されました。 好みにより、これらは同じ行で使用することもできます。

Scale="{Binding Source={x:Reference slider}, Path=Value}" />

Source プロパティは、埋め込み x:Reference マークアップ拡張に設定されています。それ以外の場合、BindingContext を設定するのと同じ構文になります。 中かっこ内に引用符が使用されていないこと、および 2 つのプロパティをコンマで区切る必要があることに注意してください。

Binding マークアップ拡張のコンテンツ プロパティは Path ですが、そのマークアップ拡張の Path= の部分は、式の中で最初のプロパティである場合にのみ省略できます。 Path= の部分を省略するには、2 つのプロパティを入れ替える必要があります。

Scale="{Binding Value, Source={x:Reference slider}}" />

XAML マークアップ拡張は、通常は中かっこで区切りますが、オブジェクト要素として表すこともできます。

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand">
    <Label.Scale>
        <Binding Source="{x:Reference slider}"
                 Path="Value" />
    </Label.Scale>
</Label>

これで、Source プロパティと Path プロパティは、標準の XAML 属性になりました。値は引用符で囲んで使用し、属性はコンマで区切りません。 x:Reference マークアップ拡張は、オブジェクト要素にすることもできます。

<Label Text="TEXT"
       FontSize="40"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand">
    <Label.Scale>
        <Binding Path="Value">
            <Binding.Source>
                <x:Reference Name="slider" />
            </Binding.Source>
        </Binding>
    </Label.Scale>
</Label>

この構文は一般的ではありませんが、複合オブジェクトを使用する場合に必要になることがあります。

これまでに示した例では、BindingBindingContext プロパティと Source プロパティを x:Reference マークアップ拡張に設定して、ページ上の別のビューを参照しています。 これら 2 つのプロパティは、Object 型であり、ソースのバインドに適したプロパティを含む任意のオブジェクトに設定することができます。

この後の記事では、BindingContext または Source プロパティを x:Static マークアップ拡張に設定して静的プロパティまたはフィールドの値を参照したり、StaticResource マークアップ拡張に設定して、リソース ディクショナリに格納されているオブジェクトを参照したりできること、あるいは、一般的には ViewModel のインスタンス (しかし常にそうとは限らない) であるオブジェクトに直接設定できることがわかります。

BindingSource プロパティと Path プロパティによってバインディング コンテキストが定義されるように BindingContext プロパティを Binding オブジェクトに設定することもできます。

バインディング コンテキストの継承

この記事では、Binding オブジェクトの BindingContext プロパティまたは Source プロパティを使用してソース オブジェクトを指定できることを説明しました。 両方が設定されている場合、BindingSource プロパティが BindingContext よりも優先されます。

BindingContext プロパティには非常に重要な特性があります。

BindingContext プロパティの設定は、ビジュアル ツリーを介して継承されます。

これから説明するように、これはバインディング式を簡素化するために非常に便利であり、場合によっては (特に Model-View-ViewModel (MVVM) のシナリオにおいては)、不可欠です。

Binding Context Inheritance (バインディング コンテキストの継承) サンプルは、バインディング コンテキストの継承を示す簡単なデモです。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.BindingContextInheritancePage"
             Title="BindingContext Inheritance">
    <StackLayout Padding="10">

        <StackLayout VerticalOptions="FillAndExpand"
                     BindingContext="{x:Reference slider}">

            <Label Text="TEXT"
                   FontSize="80"
                   HorizontalOptions="Center"
                   VerticalOptions="EndAndExpand"
                   Rotation="{Binding Value}" />

            <BoxView Color="#800000FF"
                     WidthRequest="180"
                     HeightRequest="40"
                     HorizontalOptions="Center"
                     VerticalOptions="StartAndExpand"
                     Rotation="{Binding Value}" />
        </StackLayout>

        <Slider x:Name="slider"
                Maximum="360" />

    </StackLayout>
</ContentPage>

StackLayoutBindingContext プロパティは、slider オブジェクトに設定されています。 このバインディング コンテキストは、LabelBoxView の両方によって継承され、これらは両方とも、Rotation プロパティが SliderValue に設定されています。

バインディング コンテキストの継承

次の記事では、ターゲット オブジェクトとソース オブジェクトの間のデータ フローがバインディング モードによってどのように変わるかについて説明します。

他の Xamarin ビデオは、Channel 9 および YouTube でご覧いただけます。