データ バインディングの基礎

Browse sample. サンプルを参照する

.NET マルチプラットフォーム アプリ UI (.NET MAUI) データ バインディングを使用すると、2 つのオブジェクトのプロパティをリンクできるため、一方のオブジェクトを変更すると、もう一方のオブジェクトが変更されます。 これは非常に貴重なツールです。データ バインディングはコード内で完全に定義できますが、XAML にはショートカットがあり便利です。

データ バインディング

データ バインディングでは、ソースターゲットと呼ばれる 2 つのオブジェクトのプロパティを接続します。 コードでは、2 つの手順が必要です:

  1. ターゲット オブジェクトの BindingContext プロパティはソース オブジェクトに設定する必要があります。
  2. SetBinding メソッド (多くの場合、Binding と組み合わせて使用) を呼び出して、そのオブジェクトのプロパティをソース オブジェクトのプロパティにバインドする必要があります。

ターゲット プロパティはバインド可能なプロパティでなくてはなりません。つまり、ターゲット オブジェクトは BindableObject の派生元である必要があります。 Label のプロパティ (Text など) は、バインド可能なプロパティ TextProperty に関連付けられます。

XAML では、コードで必要なものと同じ 2 つの手順を実行する必要がありますが、SetBindingマークアップ拡張機能が Binding 呼び出しと Binding クラスの代わりに使用される点が異なります。 ただし、XAML でデータ バインディングを定義する場合、ターゲット オブジェクトの BindingContext を設定する方法は複数あります。 コードビハインド ファイルから設定される場合や、x:Static または StaticResource マークアップ拡張をする場合、プロパティ要素タグ BindingContext の内容として設定される場合があります。

ビューからビューへのバインド

データ バインディングを定義して、同じページ上の 2 つのビューのプロパティをリンクできます。 この場合は、x:Reference マークアップ拡張を使用してターゲット オブジェクトの BindingContext を設定します。

次の例には、 Slider と 2 つの Label ビューが含まれています。そのうちの 1 つは Slider 値によって回転され、もう 1 つは Slider 値を表示します。

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderBindingsPage"
             Title="Slider Bindings Page">
    <StackLayout>
        <Label Text="ROTATION"
               BindingContext="{x:Reference slider}"
               Rotation="{Binding Path=Value}"
               FontAttributes="Bold"
               FontSize="18"
               HorizontalOptions="Center"
               VerticalOptions="Center" />
        <Slider x:Name="slider"
                Maximum="360"
                VerticalOptions="Center" />
        <Label BindingContext="{x:Reference slider}"
               Text="{Binding Value, StringFormat='The angle is {0:F0} degrees'}"
               FontAttributes="Bold"
               FontSize="18"
               HorizontalOptions="Center"
               VerticalOptions="Center" />
    </StackLayout>
</ContentPage>

Slider には、x:Reference マークアップ拡張を使用して 2 つの Label ビューで参照する x:Name 属性が含まれています。 x:Reference バインディング拡張機能は 、参照される要素の名前に設定する、Name という名前のプロパティを定義します (この場合は slider)。 ただし、x:Reference マークアップ拡張を定義する ReferenceExtension クラスも NameContentProperty 属性を定義します。つまり、明示的には必要ないということです。

Binding マークアップ拡張機能自体は、BindingBindingBase クラスのような複数のプロパティを持つことができます。 BindingContentPropertyPath ですが、マークアップ拡張の "Path =" 部分は、そのパスが Binding マークアップ拡張の最初の項目であれば省略できます。

2 番目の Binding マークアップ拡張は、StringFormat プロパティを設定します。 .NET MAUI では、バインディングは暗黙的な型変換を実行しません。文字列以外のオブジェクトを文字列として表示する必要がある場合は、型コンバーターを指定するか、StringFormat を使用しなくてはなりません。

重要

書式設定文字列は、単一引用符で囲む必要があります。

バインディング モード

1 つのビューで、いくつかのプロパティにデータ バインディングを設定できます。 ただし、各ビューには 1 つの BindingContext しか含められないため、そのビューの複数のデータ バインディングは、同じオブジェクトのあらゆるプロパティを参照する必要があります。

これと他の問題の解決策には、Mode プロパティが含まれます 。これは BindingMode 列挙型のメンバーに設定されます。

  • Default
  • OneWay — 値はソースからターゲットに転送されます
  • OneWayToSource — 値はターゲットからソースに転送されます
  • TwoWay — 値はソースとターゲットの間で両方向に転送されます
  • OneTime — データはソースからターゲットに移動しますが、 BindingContext が変更された場合のみとなります

次の例は、OneWayToSourceTwoWay の一般的な使い方の一つを示しています。

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.SliderTransformsPage"
             Padding="5"
             Title="Slider Transforms Page">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <!-- Scaled and rotated Label -->
        <Label x:Name="label"
               Text="TEXT"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <!-- Slider and identifying Label for Scale -->
        <Slider x:Name="scaleSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="1" Grid.Column="0"
                Maximum="10"
                Value="{Binding Scale, Mode=TwoWay}" />
        <Label BindingContext="{x:Reference scaleSlider}"
               Text="{Binding Value, StringFormat='Scale = {0:F1}'}"
               Grid.Row="1" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for Rotation -->
        <Slider x:Name="rotationSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="2" Grid.Column="0"
                Maximum="360"
                Value="{Binding Rotation, Mode=OneWayToSource}" />
        <Label BindingContext="{x:Reference rotationSlider}"
               Text="{Binding Value, StringFormat='Rotation = {0:F0}'}"
               Grid.Row="2" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationX -->
        <Slider x:Name="rotationXSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="3" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationX, Mode=OneWayToSource}" />
        <Label BindingContext="{x:Reference rotationXSlider}"
               Text="{Binding Value, StringFormat='RotationX = {0:F0}'}"
               Grid.Row="3" Grid.Column="1"
               VerticalTextAlignment="Center" />

        <!-- Slider and identifying Label for RotationY -->
        <Slider x:Name="rotationYSlider"
                BindingContext="{x:Reference label}"
                Grid.Row="4" Grid.Column="0"
                Maximum="360"
                Value="{Binding RotationY, Mode=OneWayToSource}" />
        <Label BindingContext="{x:Reference rotationYSlider}"
               Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
               Grid.Row="4" Grid.Column="1"
               VerticalTextAlignment="Center" />
    </Grid>
</ContentPage>

この例では、4 つのSliderビューは LabelRotateYScaleRotateXRotateプロパティを制御するよう意図されています。 最初は、 Label の 4 つのプロパティがそれぞれ Slider によって設定されているため、ターゲットのデータをバインドしているように見えます。 ただし、BindingContextLabel は 1 つのオブジェクトだけであり、4 つの異なるスライダーがあります。 そのため、 4 つの各スライダーの BindingContextLabel に設定され、バインドがスライダーの Value プロパティで設定されます。 OneWayToSourceValue を使用することで、これらの TwoWay プロパティはソース プロパティ (RotateYLabelScaleRotateRotateX プロパティ) を設定できます。

3 つの Slider ビューのバインドは OneWayToSource です。つまり、Slider 値によってその label のプロパティが変わります (Label という名前の BindingContext)。 これら 3 つの Slider ビューにより、LabelRotateXRotateRotateY プロパティが変更されます。

Reverse bindings.

しかし、 Scale プロパティのバインディングは TwoWay です。 これは、Scale プロパティの既定値が 1 で、Slider バインディングを使用すると、TwoWayの初期値が 0 ではなく 1 に設定されるためです。 そのバインディングが OneWayToSource の場合、 Scale プロパティは最初は Slider 既定値から 0 に設定されます。 Label は表示されません。

VisualElement クラスにも ScaleXScaleY プロパティがあり、それぞれ x 軸と y 軸で VisualElement を拡大縮小します。

バインディングとコレクション

ListView は、IEnumerable 型の ItemsSource プロパティを定義し、そのコレクションの項目を表示します。 これらの項目は、任意の型のオブジェクトにすることができます。 既定では、 ListView は各項目の ToString メソッドを使用してその項目を表示します。 これが必要なこともありますが、多くの場合、ToString はオブジェクトの完全修飾クラス名のみを返します。

ただし、ListView コレクションの項目は、 テンプレート を使用して任意の方法で表示できます。これには、Cell から派生したクラスが含まれます。 テンプレートは、ListView のすべての項目に対して複製され、テンプレートで設定されているデータ バインディングが個々の複製に転送されます。 項目のカスタム セルは ViewCell クラスを使用して作成できます。

ListView は、NamedColor クラスを使用して、.NET MAUI で使用できるすべての名前付きカラーの一覧を 表示できます。

using System.Reflection;
using System.Text;

namespace XamlSamples
{
    public class NamedColor
    {
        public string Name { get; private set; }
        public string FriendlyName { get; private set; }
        public Color Color { get; private set; }

        // Expose the Color fields as properties
        public float Red => Color.Red;
        public float Green => Color.Green;
        public float Blue => Color.Blue;

        public static IEnumerable<NamedColor> All { get; private set; }

        static NamedColor()
        {
            List<NamedColor> all = new List<NamedColor>();
            StringBuilder stringBuilder = new StringBuilder();

            // Loop through the public static fields of the Color structure.
            foreach (FieldInfo fieldInfo in typeof(Colors).GetRuntimeFields())
            {
                if (fieldInfo.IsPublic &&
                    fieldInfo.IsStatic &&
                    fieldInfo.FieldType == typeof(Color))
                {
                    // Convert the name to a friendly name.
                    string name = fieldInfo.Name;
                    stringBuilder.Clear();
                    int index = 0;

                    foreach (char ch in name)
                    {
                        if (index != 0 && Char.IsUpper(ch))
                        {
                            stringBuilder.Append(' ');
                        }
                        stringBuilder.Append(ch);
                        index++;
                    }

                    // Instantiate a NamedColor object.
                    NamedColor namedColor = new NamedColor
                    {
                        Name = name,
                        FriendlyName = stringBuilder.ToString(),
                        Color = (Color)fieldInfo.GetValue(null)
                    };

                    // Add it to the collection.
                    all.Add(namedColor);
                }
            }
            all.TrimExcess();
            All = all;
        }
    }
}

NamedColorオブジェクトには、string 型の Name および FriendlyNameプロパティ、Color 型の Color プロパティ、および RedGreenBlue プロパティがあります。 さらに、NamedColor 静的コンストラクターは、Colors クラス内の型Colorのフィールドに対応する NamedColor オブジェクトを含む IEnumerable<NamedColor> コレクションを作成し、その公開用静的 All プロパティに割り当てます。

静的NamedColor.Allプロパティを ListViewItemsSource に設定するには、x:Static マークアップ拡張を使用します。

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples;assembly=XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">
    <ListView ItemsSource="{x:Static local:NamedColor.All}" />
</ContentPage>

その結果、項目が XamlSamples.NamedColor 型になります。

Binding to a collection.

項目のテンプレートを定義するには、ViewCellを参照する DataTemplateItemTemplate を設定する必要があります。 ViewCell は、各項目を表示する 1 つ以上のビューのレイアウトを定義する必要があります。

<ListView ItemsSource="{x:Static local:NamedColor.All}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
                <Label Text="{Binding FriendlyName}" />
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

セルとセルの子のバインディング ソースは、ListView.ItemsSource コレクションです 。

この例では、Label 要素は ViewCell プロパティの View に設定されています。 ViewCell.View タグは必要ありません。View プロパティは ViewCell の content プロパティであるためです。 この XAML は、各 NamedColor オブジェクトの FriendlyName プロパティを表示します。

Binding to a collection with a DataTemplate.

項目テンプレートを展開すると、詳細情報と実際の色を表示できます:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.ListViewDemoPage"
             Title="ListView Demo Page">
    <ContentPage.Resources>
        <x:Double x:Key="boxSize">50</x:Double>
        <x:Int32 x:Key="rowHeight">60</x:Int32>
        <local:FloatToIntConverter x:Key="intConverter" />
    </ContentPage.Resources>

    <ListView ItemsSource="{x:Static local:NamedColor.All}"
              RowHeight="{StaticResource rowHeight}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <StackLayout Padding="5, 5, 0, 5"
                                 Orientation="Horizontal"
                                 Spacing="15">
                        <BoxView WidthRequest="{StaticResource boxSize}"
                                 HeightRequest="{StaticResource boxSize}"
                                 Color="{Binding Color}" />
                        <StackLayout Padding="5, 0, 0, 0"
                                     VerticalOptions="Center">
                            <Label Text="{Binding FriendlyName}"
                                   FontAttributes="Bold"
                                   FontSize="14" />
                            <StackLayout Orientation="Horizontal"
                                         Spacing="0">
                                <Label Text="{Binding Red,
                                                      Converter={StaticResource intConverter},
                                                      ConverterParameter=255,
                                                      StringFormat='R={0:X2}'}" />                                
                                <Label Text="{Binding Green,
                                                      Converter={StaticResource intConverter},
                                                      ConverterParameter=255,
                                                      StringFormat=', G={0:X2}'}" />                                
                                <Label Text="{Binding Blue,
                                                      Converter={StaticResource intConverter},
                                                      ConverterParameter=255,
                                                      StringFormat=', B={0:X2}'}" />
                            </StackLayout>
                        </StackLayout>
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

バインディングの値コンバーター

前の XAML の例では、それぞれの NamedColorRedGreen、および Blue プロパティが表示されています。 これらのプロパティの型は float で、範囲は 0 ~ 1 です。 16 進数の値を表示する場合は、単に "X2" 書式指定で StringFormat を使用することはできません。 これは整数に対してのみ機能し、float 値に 255 を乗じる必要があります。

この問題は、値コンバーター(バインディング コンバーター とも呼ばれます) で解決できます。 これは IValueConverter インターフェイスを実装するクラスです。つまり、ConvertBackConvert という名前の 2 つのメソッドがあります。 Convert メソッドは、値がソースからターゲットに転送されると呼び出されます。 ConvertBackメソッドは、OneWayToSource または TwoWay バインディングでターゲットからソースへの転送に対して呼び出されます。

using System.Globalization;

namespace XamlSamples
{
    public class FloatToIntConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            float multiplier;

            if (!float.TryParse(parameter as string, out multiplier))
                multiplier = 1;

            return (int)Math.Round(multiplier * (float)value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            float divider;

            if (!float.TryParse(parameter as string, out divider))
                divider = 1;

            return ((float)(int)value) / divider;
        }
    }
}

この例では、バインディングがソースからターゲットへの一方向のみなので、ConvertBack メソッドは役割を果たしません。

バインディングは、Converter プロパティを使用してバインディング コンバーターを参照します。 バインディング コンバーターは、ConverterParameter プロパティで指定されたパラメーターを受け入れることもできます。 ある程度の汎用性のため、これは乗数の指定方法になっています。 バインディング コンバーターは、有効な float 値のコンバーター パラメーターをチェックします。

コンバーターはページのリソース ディクショナリでインスタンス化されるため、複数のバインド間で共有できます。

<local:FloatToIntConverter x:Key="intConverter" />

3 つのデータ バインディングで、この単一インスタンスが参照されます。

<Label Text="{Binding Red,
                      Converter={StaticResource intConverter},
                      ConverterParameter=255,
                      StringFormat='R={0:X2}'}" />

項目テンプレートは、色、フレンドリ名、および RGB 値を表示します。

Binding to a collection with a DataTemplate and a converter.

ListView は、基になるデータで動的に発生する変更を処理できますが、特定の手順を実行する必要があります。 項目のコレクションがランタイム中に ListView 変更の ItemsSource プロパティに割り当てられた場合は、これらの項目の ObservableCollection<T> クラスを使用します。 ObservableCollection<T>INotifyCollectionChanged インターフェイスを実装し、 ListViewCollectionChanged イベントのハンドラーをインストールします。

ランタイム中に項目自体のプロパティが変更された場合、コレクション内の項目は PropertyChanged イベントを使用してプロパティ値への INotifyPropertyChanged インターフェイスと信号の変更を実装する必要があります。

次のステップ

データ バインディングは、ページ内の 2 つのオブジェクト間、またはビジュアル オブジェクトと基になるデータの間でプロパティをリンクする強力なメカニズムを提供します。 しかし、アプリケーションがデータ ソースの操作を開始すると、一般的なアプリ アーキテクチャ パターンが便利なパラダイムとして出現し始めます。