データ バインディングの基礎
.NET Multi-platform App UI (.NET MAUI) データ バインディングを使用すると、2 つのオブジェクトのプロパティをリンクできるため、一方のオブジェクトを変更すると、もう一方のオブジェクトが変更されます。 これはとても役立つツールです。データ バインディングはコード内で完全に定義でき、XAML はショートカットと利便性を提供します。
データ バインディング
データ バインディングでは、ソースとターゲットと呼ばれる 2 つのオブジェクトのプロパティを接続します。 コードでは、次の 2 つの手順が必要です。
- ターゲット オブジェクトの
BindingContext
プロパティは、ソース オブジェクトに設定する必要があります。 SetBinding
メソッド (多くの場合、Binding
クラスと組み合わせて使用されます) は、ターゲット オブジェクトで呼び出して、そのオブジェクトのプロパティをソース オブジェクトのプロパティにバインドする必要があります。
ターゲット プロパティはバインド可能なプロパティである必要があります。つまり、ターゲット オブジェクトは BindableObject から派生する必要があります。 Text
などの Label のプロパティは、バインド可能なプロパティ TextProperty
に関連付けられています。
XAML では、SetBinding
呼び出しと Binding
クラスの代わりに Binding
マークアップ拡張を使用することを除いて、コードで必要なのと同じ 2 つの手順を実行する必要があります。 ただし、XAML でデータ バインディングを定義する場合、ターゲット オブジェクトの BindingContext
を設定する方法は複数あります。 分離コードファイルから設定される場合もあれば、StaticResource
や x:Static
のマークアップ拡張子を使って設定される場合や、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:Name
属性を含み、x:Reference
マークアップ拡張を使用して、2 つの Label ビューによって参照されます。 x:Reference
バインディング拡張は、参照される要素の名前、この場合は slider
に設定する Name
というプロパティを定義します。 ただし、x:Reference
マークアップ拡張を定義している ReferenceExtension
クラスは、Name
の ContentProperty
属性も定義しています。これは、明示的に要求されていないことを意味します。
Binding
マークアップ拡張自体は、BindingBase
や Binding
クラスと同様に、いくつかのプロパティを持つことができます。 Binding
の ContentProperty
は Path
ですが、パスが Binding
マークアップ拡張の最初の項目である場合、マークアップ拡張機能の「Path=」部分は省略できます。
2 番目の Binding
マークアップ拡張機能は、StringFormat
プロパティを設定します。 .NET MAUI では、バインディングは暗黙的な型変換を実行しません。文字列以外のオブジェクトを文字列として表示する必要がある場合は、型コンバーターを指定するか、StringFormat
を使用する必要があります。
重要
書式設定文字列は、単一引用符で囲む必要があります。
バインディング モード
1 つのビューで、いくつかのプロパティにデータ バインディングを設定できます。 ただし、各ビューに含められるのは 1 つの BindingContext
のみのため、そのビュー上の複数のデータ バインディングは、同じオブジェクトのすべてのプロパティを参照する必要があります。
この問題やその他の問題を解決するには、BindingMode
列挙型の要素に設定される Mode
プロパティを使用します。
Default
OneWay
- 値はソースからターゲットに転送されますOneWayToSource
- 値はターゲットからソースに転送されます。TwoWay
- 値はソースとターゲットの間で双方向に転送されますOneTime
- データは、ソースからターゲットに移動しますが、BindingContext
が変更された場合のみです
次の例は、OneWayToSource
および TwoWay
のバインディング モードの一般的な使い方を示しています。
<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 ビューが Label の Scale
、Rotate
、RotateX
、RotateY
プロパティを制御しようとしています。 最初は、Label のこれら 4 つのプロパティは、それぞれ Slider によって設定されているので、データ バインディングのターゲットであるように思えます。 しかし、Label の BindingContext
は 1 つのオブジェクトにしかなりえず、4 つの異なるスライダーがあります。 そのため、4 つのスライダーの BindingContext
はそれぞれ Label に設定され、バインディングはスライダーの Value
プロパティに設定されます。 OneWayToSource
と TwoWay
モードを使用することにより、これらの Value
プロパティは、Label の Scale
、Rotate
、RotateX
、RotateY
プロパティであるソース プロパティを設定できます。
Slider ビューの 3 つのバインディングは OneWayToSource
で、Slider の値が BindingContext
のプロパティ (label
という名前の Label) の変更を引き起こすことを意味します。 これら 3 つの Slider ビューによって、Label の Rotate
、RotateX
、RotateY
プロパティが変更されます。
ただし、Scale
プロパティのバインディングは TwoWay
です。 これは、Scale
プロパティの初期値が 1 であり、TwoWay
バインディングを使用すると Slider の初期値が 0 ではなく 1 に設定されるためです。 このバインディングが OneWayToSource
である場合、Scale
プロパティの初期値は Slider の初期値から 0 に設定されます。 Labelは表示されません。
Note
VisualElement クラスには、ScaleX
プロパティと ScaleY
プロパティもあり、それぞれ 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
プロパティ、さらに、Red
プロパティ、Green
プロパティ、Blue
プロパティがあります。 さらに、NamedColor
静的コンストラクターは、Colors クラス内の Color 型のフィールドに対応する NamedColor
オブジェクトを含む IEnumerable<NamedColor>
コレクションを作成し、そのパブリック静的 All
プロパティに割り当てます。
静的 NamedColor.All
プロパティを ListView の ItemsSource
に設定するには、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
型であることを示します。
項目のテンプレートを定義するには、ViewCell を参照する DataTemplate に ItemTemplate
を設定する必要があります。 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 のコンテンツ プロパティであるためです。 この XAML は、各 NamedColor
オブジェクトの FriendlyName
プロパティを表示します。
項目テンプレートを展開すると、詳細情報と実際の色を表示できます。
<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 の例では、それぞれの NamedColor
が有する個々の Red
プロパティ、Green
プロパティ、Blue
プロパティが表示されます。 これらのプロパティは float
型で、範囲は 0 から 1 です。 16 進数の値を表示する場合は、単純に "X2" 書式指定で StringFormat
を使用することはできません。 この書式設定は整数に対してのみ機能するのに加え、float
値に 255 を乗算する必要があります。
この問題は、値コンバーター (バインディング コンバーター) で解決できます。 これは IValueConverter インターフェイスを実装するクラスなので、Convert
と ConvertBack
という 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;
}
}
}
注
バインディングはソースからターゲットへの 1 つの方法に過ぎないため、この例では ConvertBack
メソッドは役割を果たしません。
バインディングは、Converter
プロパティを使用してバインディング コンバーターを参照します。 バインディング コンバーターは、ConverterParameter
プロパティで指定されたパラメーターを受け入れることもできます。 ある程度の汎用性のために、これは乗数の指定方法です。 バインディング コンバーターは、有効な float
値のコンバーター パラメーターをチェックします。
コンバーターはページのリソース ディクショナリでインスタンス化されるため、複数のバインド間で共有できます。
<local:FloatToIntConverter x:Key="intConverter" />
3 つのデータ バインディングで、この単一インスタンスが参照されます。
<Label Text="{Binding Red,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
項目テンプレートは、色、フレンドリ名、RGB 値を表示します。
ListView は、基になるデータで動的に発生する変更を処理できますが、特定の手順を実行する場合にのみ実行できます。 実行時に ListView 変更の ItemsSource
プロパティに割り当てられた項目のコレクションの場合は、これらの項目の ObservableCollection<T> クラスを使用します。 ObservableCollection<T> は INotifyCollectionChanged
インターフェイスを実装し、ListView は CollectionChanged
イベントのハンドラーをインストールします。
実行時に項目自体のプロパティが変更された場合、コレクション内の項目は INotifyPropertyChanged
インターフェイスを実装し、PropertyChanged
イベントを使用してプロパティ値の変更を通知する必要があります。
次のステップ
データ バインディングは、ページ内の 2 つのオブジェクト間、またはビジュアル オブジェクトと基になるデータの間でプロパティをリンクするための強力なメカニズムを提供します。 しかし、アプリケーションがデータ ソースの操作を開始すると、一般的なアプリ アーキテクチャ パターンが便利なパラダイムとして出現し始めます。
.NET MAUI