.NET Multi-platform App UI (.NET MAUI) 数据绑定支持将两个对象的属性链接在一起,这样一个对象发生更改会引起另一个对象也发生更改。 数据绑定是非常有用的工具,可在代码中完整定义,同时 XAML 也提供了快捷方式和便利性。
数据绑定
数据绑定连接两个对象的属性,即源和目标。 在代码中,以下两个步骤是必需的:
- 目标对象的
BindingContext
属性必须设置为源对象, - 必须在目标对象上调用
SetBinding
方法(通常与Binding
类结合使用),将该对象的属性绑定到源对象的属性。
目标属性必须是可绑定属性,这意味着目标对象必须派生自 BindableObject。
Label 的一个属性(如 Text
)与可绑定属性 TextProperty
关联。
在 XAML 中,还必须执行代码中所需的两个相同步骤,但 Binding
标记扩展取代了 SetBinding
调用和 Binding
类。 但是,在 XAML 中定义数据绑定时,可通过多种方式设置目标对象的 BindingContext
。 有时通过代码隐藏文件设置,有时使用 StaticResource
或 x:Static
标记扩展设置,有时设置为 BindingContext
属性元素标记的内容。
注意
此页上的数据绑定表达式使用已编译的绑定。 有关已编译绑定的详细信息,请参阅 已编译绑定。
视图与视图绑定
可以定义数据绑定来链接同一页面上两个视图的属性。 在本例中,使用 BindingContext
标记扩展设置目标对象的 x:Reference
。
以下示例包含一个 Slider 和两个 Label 视图,其中一个视图按 Slider 值旋转,另一个视图显示 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"
x:DataType="Slider">
<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
属性,该属性由两个使用 Label 标记扩展的 x:Reference
视图引用。
x:Reference
绑定扩展定义一个名为 Name
的属性,用于设置所引用元素的名称,在本例中为 slider
。 但是,定义 ReferenceExtension
标记扩展的 x:Reference
类也为 ContentProperty
定义了 Name
属性,这意味着它不是显式必需的。
与 Binding
和 BindingBase
类一样,Binding
标记扩展本身也可以有多个属性。
ContentProperty
的 Binding
为 Path
,但如果路径是 Binding
标记扩展中的第一个项,则可以省略标记扩展的“Path=”部分。
第二个 Binding
标记扩展设置 StringFormat
属性。 在 .NET MAUI 中,绑定不会执行任何隐式类型转换,如果需要将非字符串对象显示为字符串,必须提供类型转换器或使用 StringFormat
。
重要说明
格式字符串必须置于单引号内。
绑定模式
单个视图可在其多个属性上创建数据绑定。 但每个视图只能有一个 BindingContext
,因此视图上的所有数据绑定须引用同一对象的属性。
解决此问题和其他问题的方法涉及到 Mode
属性,该属性设置为 BindingMode
枚举的成员:
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"
x:DataType="Label"
BindingContext="{x:Reference label}"
Grid.Row="1" Grid.Column="0"
Maximum="10"
Value="{Binding Scale, Mode=TwoWay}" />
<Label x:DataType="Slider"
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"
x:DataType="Label"
BindingContext="{x:Reference label}"
Grid.Row="2" Grid.Column="0"
Maximum="360"
Value="{Binding Rotation, Mode=OneWayToSource}" />
<Label x:DataType="Slider"
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"
x:DataType="Label"
BindingContext="{x:Reference label}"
Grid.Row="3" Grid.Column="0"
Maximum="360"
Value="{Binding RotationX, Mode=OneWayToSource}" />
<Label x:DataType="Slider"
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"
x:DataType="Label"
BindingContext="{x:Reference label}"
Grid.Row="4" Grid.Column="0"
Maximum="360"
Value="{Binding RotationY, Mode=OneWayToSource}" />
<Label x:DataType="Slider"
BindingContext="{x:Reference rotationYSlider}"
Text="{Binding Value, StringFormat='RotationY = {0:F0}'}"
Grid.Row="4" Grid.Column="1"
VerticalTextAlignment="Center" />
</Grid>
</ContentPage>
在此示例中,四个 Slider 视图分别用于控制 Scale
的 Rotate
、RotateX
、RotateY
和 Label 属性。 最初似乎 Label 的四个属性应为数据绑定目标,因为每个属性均由 Slider 设置。 但 BindingContext
的 Label 只能是一个对象,并且有四个不同的滑块。 因此,四个滑块中每个的 BindingContext
设置为 Label,并在滑块的 Value
属性上设置绑定。 通过使用 OneWayToSource
和 TwoWay
模式,这些 Value
属性可以设置源属性,即 Scale
的 Rotate
、RotateX
、RotateY
和 Label 属性。
三个 Slider 视图上的绑定为 OneWayToSource
,意味着 Slider 值导致其 BindingContext
的属性发生更改,即名为 Label 的 label
。 这三个 Slider 视图会导致 Rotate
的 RotateX
、RotateY
和 Label 属性发生更改:
但 Scale
属性的绑定是 TwoWay
。 这是因为 Scale
属性的默认值为 1,并且使用 TwoWay
绑定会导致 Slider 初始值设置为 1,而不是 0。 如果该绑定为 OneWayToSource
,那么 Scale
属性将从 Slider 默认值初始化为 0。 该 Label 元素不可见。
注意
VisualElement 类还具有 ScaleX
和 ScaleY
属性,它们分别在 x 轴和 y 轴上缩放 VisualElement。
绑定和集合
ListView 定义 ItemsSource
类型的 IEnumerable
属性,并显示该集合中的项。 这些项可以是任何类型的对象。 默认情况下,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
对象都有 Name
类型的 FriendlyName
和 string
属性、Color
类型的 Color 属性以及 Red
、Green
和 Blue
属性。 此外,NamedColor
静态构造函数将创建一个 IEnumerable<NamedColor>
集合,其中包含与 NamedColor
类中 Color 类型的字段对应的 Colors 对象,并将它分配给其公共静态 All
属性。
使用 NamedColor.All
标记扩展可以将静态 ItemsSource
属性设置为 ListView 的 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
:
要为项定义模板,ItemTemplate
应设置为引用 DataTemplate 的 ViewCell。
ViewCell 应定义一个或多个视图的布局来显示每个项:
<ListView ItemsSource="{x:Static local:NamedColor.All}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:NamedColor">
<ViewCell>
<Label Text="{Binding FriendlyName}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
注意
单元格和单元格子级的绑定源是 ListView.ItemsSource
集合。
在此示例中,Label 元素设置为 View 的 ViewCell 属性。 不需要 ViewCell.View
标记,因为 View 属性是 ViewCell 的内容属性。 此 XAML 显示每个 FriendlyName
对象的 NamedColor
属性:
可以展开项模板以显示详细信息和实际颜色:
<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 x:DataType="local:NamedColor">
<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 示例显示每个 Red
的 Green
、Blue
和 NamedColor
属性。 这些属性为 float
类型,范围为 0 到 1。 如果要显示十六进制值,则不能简单地以“X2”格式规范使用 StringFormat
。 这仅适用于整数,而且 float
值需要乘以 255。
可使用值转换器(也称为绑定转换器)解决此问题。 这是一个实现 IValueConverter 接口的类,这意味着它具有两个方法,分别名为 Convert
和 ConvertBack
。 当值从源传输到目标时,将调用 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" />
三个数据绑定引用此单一实例:
<Label Text="{Binding Red,
Converter={StaticResource intConverter},
ConverterParameter=255,
StringFormat='R={0:X2}'}" />
项模板显示颜色、其易记名称和 RGB 值:
ListView 可以处理基础数据中动态发生的更改,但前提是要采取某些步骤。 如果分配给 ItemsSource
的 ListView 属性的项集合在运行时发生更改,请对这些项使用 ObservableCollection<T> 类。
ObservableCollection<T> 实现 INotifyCollectionChanged
接口,ListView 会安装用于 CollectionChanged
事件的处理程序。
如果项自身的属性在运行时发生更改,则集合中的项需实现 INotifyPropertyChanged
接口,并使用 PropertyChanged
事件指示属性值的更改。
后续步骤
数据绑定提供了一种强大的机制,用于将一个页面内两个对象之间的属性链接在一起,也可将视觉对象和基础数据之间的属性链接在一起。 但是,当应用程序开始使用数据源时,常用的应用体系结构模式开始成为一种有用的范例。