数据绑定基础知识

浏览示例。 浏览示例

.NET Multi-platform App UI (.NET MAUI) 数据绑定支持将两个对象的属性链接在一起,这样一个对象发生更改会引起另一个对象也发生更改。 数据绑定是非常有用的工具,可在代码中完整定义,同时 XAML 也提供了快捷方式和便利性。

数据绑定

数据绑定连接两个对象的属性,即目标。 在代码中,以下两个步骤是必需的:

  1. 目标对象的 BindingContext 属性必须设置为源对象,
  2. 必须在目标对象上调用 SetBinding 方法(通常与 Binding 类结合使用),将该对象的属性绑定到源对象的属性。

目标属性必须是可绑定属性,这意味着目标对象必须派生自 BindableObjectLabel 的一个属性(如 Text)与可绑定属性 TextProperty 关联。

在 XAML 中,还必须执行代码中所需的两个相同步骤,但 Binding 标记扩展取代了 SetBinding 调用和 Binding 类。 但是,在 XAML 中定义数据绑定时,可通过多种方式设置目标对象的 BindingContext。 有时通过代码隐藏文件设置,有时使用 StaticResourcex: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 属性,这意味着它不是显式必需的。

BindingBindingBase 类一样,Binding 标记扩展本身也可以有多个属性。 ContentPropertyBindingPath,但如果路径是 Binding 标记扩展中的第一个项,则可以省略标记扩展的“Path=”部分。

第二个 Binding 标记扩展设置 StringFormat 属性。 在 .NET MAUI 中,绑定不会执行任何隐式类型转换,如果需要将非字符串对象显示为字符串,必须提供类型转换器或使用 StringFormat

重要说明

格式字符串必须置于单引号内。

绑定模式

单个视图可在其多个属性上创建数据绑定。 但每个视图只能有一个 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"
                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 视图分别用于控制 ScaleRotateRotateXRotateYLabel 属性。 最初似乎 Label 的四个属性应为数据绑定目标,因为每个属性均由 Slider 设置。 但 BindingContextLabel 只能是一个对象,并且有四个不同的滑块。 因此,四个滑块中每个的 BindingContext 设置为 Label,并在滑块的 Value 属性上设置绑定。 通过使用 OneWayToSourceTwoWay 模式,这些 Value 属性可以设置源属性,即 ScaleRotateRotateXRotateYLabel 属性。

三个 Slider 视图上的绑定为 OneWayToSource,意味着 Slider 值导致其 BindingContext 的属性发生更改,即名为 Labellabel。 这三个 Slider 视图会导致 RotateRotateXRotateYLabel 属性发生更改:

反向绑定。

Scale 属性的绑定是 TwoWay。 这是因为 Scale 属性的默认值为 1,并且使用 TwoWay 绑定会导致 Slider 初始值设置为 1,而不是 0。 如果该绑定为 OneWayToSource,那么 Scale 属性将从 Slider 默认值初始化为 0。 该 Label 元素不可见。

注意

VisualElement 类还具有 ScaleXScaleY 属性,它们分别在 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 类型的 FriendlyNamestring 属性、Color 类型的 Color 属性以及 RedGreenBlue 属性。 此外,NamedColor 静态构造函数将创建一个 IEnumerable<NamedColor> 集合,其中包含与 NamedColor 类中 Color 类型的字段对应的 Colors 对象,并将它分配给其公共静态 All 属性。

使用 NamedColor.All 标记扩展可以将静态 ItemsSource 属性设置为 ListViewx: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 应设置为引用 DataTemplateViewCellViewCell 应定义一个或多个视图的布局来显示每个项:

<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 元素设置为 ViewViewCell 属性。 不需要 ViewCell.View 标记,因为 View 属性是 ViewCell 的内容属性。 此 XAML 显示每个 FriendlyName 对象的 NamedColor 属性:

使用 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 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 示例显示每个 RedGreenBlueNamedColor 属性。 这些属性为 float 类型,范围为 0 到 1。 如果要显示十六进制值,则不能简单地以“X2”格式规范使用 StringFormat。 这仅适用于整数,而且 float 值需要乘以 255。

可使用值转换器(也称为绑定转换器)解决此问题。 这是一个实现 IValueConverter 接口的类,这意味着它具有两个方法,分别名为 ConvertConvertBack。 当值从源传输到目标时,将调用 Convert 方法。 在 ConvertBackOneWayToSource 绑定中从目标传输到源时将调用 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 值:

使用 DataTemplate 和转换器绑定到集合。

ListView 可以处理基础数据中动态发生的更改,但前提是要采取某些步骤。 如果分配给 ItemsSourceListView 属性的项集合在运行时发生更改,请对这些项使用 ObservableCollection<T> 类。 ObservableCollection<T> 实现 INotifyCollectionChanged 接口,ListView 会安装用于 CollectionChanged 事件的处理程序。

如果项自身的属性在运行时发生更改,则集合中的项需实现 INotifyPropertyChanged 接口,并使用 PropertyChanged 事件指示属性值的更改。

后续步骤

数据绑定提供了一种强大的机制,用于将一个页面内两个对象之间的属性链接在一起,也可将视觉对象和基础数据之间的属性链接在一起。 但是,当应用程序开始使用数据源时,常用的应用体系结构模式开始成为一种有用的范例。