绑定模式

Browse sample.浏览示例

每个.NET Multi-platform App UI (.NET MAUI) 可绑定属性都有一个默认绑定模式,该模式是在创建可绑定属性时设置的,并且可从 BindableProperty 对象的 DefaultBindingMode 属性获得。 此默认绑定模式指示该属性是数据绑定目标时有效的模式。 大多数属性(如 RotationScaleOpacity)的默认绑定模式都是 OneWay。 如果这些属性是数据绑定目标,则从源设置目标属性。

下列示例展示了在 Slider 上定义的数据绑定:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DataBindingDemos.ReverseBindingPage"
             Title="Reverse Binding">
    <StackLayout Padding="10, 0">
        <Label x:Name="label"
               Text="TEXT"
               FontSize="80"
               HorizontalOptions="Center"
               VerticalOptions="Center" />
        <Slider x:Name="slider"
                VerticalOptions="Center"
                Value="{Binding Source={x:Reference label},
                                Path=Opacity}" />
    </StackLayout>
</ContentPage>

在本例中,Label 是数据绑定源,Slider 是目标。 绑定引用 LabelOpacity 属性,其默认值为 1。 因此,会从 Label 的初始 Opacity 值将 Slider 初始化为值 1。 如以下屏幕截图所示:

Reverse binding.

此外,Slider 将继续工作。 这是因为 SliderValue 属性的默认绑定模式是 TwoWay。 这意味着,Value 属性是数据绑定目标时,会从源设置目标,但也会从目标设置源。 这样就可以从初始 Opacity 值设置 Slider

注意

可绑定属性不会发出属性更改信号,除非属性实际发生更改。 这样可以避免无限循环。

如果目标属性的默认绑定模式不适合特定的数据绑定,则可以通过将 BindingMode 属性(或 Binding 标记扩展的 Mode 属性)设置为 BindingMode 枚举的成员之一来替代:

  • Default
  • TwoWay - 数据在源和目标之间双向传输
  • OneWay - 数据从源到目标单向传输
  • OneWayToSource - 数据从目标到源单向传输
  • OneTime - 数据从源到目标单向传输,但只有 BindingContext 发生更改时才会传输

双向绑定

大多数可绑定属性的默认绑定模式都是 OneWay,但某些属性的默认绑定模式为 TwoWay,包括以下属性:

这些属性被定义为 TwoWay,是因为数据绑定与模型-视图-视图模型 (MVVM) 模式一起使用时,viewmodel 类是数据绑定源,而视图(由 Slider 等视图组成)是数据绑定目标。 MVVM 绑定类似上述示例,因为你很可能希望页面上的每个视图都是使用 viewmodel 中的相应属性的值进行初始化,但视图中的更改也应该会影响 viewmodel 属性。

单向到源绑定

只读可绑定属性的默认绑定模式为 OneWayToSource。 例如,ListViewSelectedItem 属性的默认绑定模式为 OneWayToSource。 这是因为 SelectedItem 属性的绑定应导致设置绑定源。

单次绑定

只有在绑定上下文更改时,才会更新绑定模式为 OneTime 的目标属性。 对于这些目标属性的绑定,该模式简化了绑定基础结构,因为不必监视源属性中的更改。

许多属性(包括 EntryIsTextPredictionEnabled 属性)都具有 OneTime 的默认绑定模式。

viewmodel 和属性更改通知

在数据绑定中使用某个 viewmodel 时,该 viewmodel 就是数据绑定源。 viewmodel 不会定义可绑定属性,但它会实现一种通知机制,允许在属性值更改时通知绑定基础结构。 此通知机制是 INotifyPropertyChanged 接口,该接口定义名为 PropertyChanged 的单个事件。 实现此接口的类通常会在其公共属性之一更改值时触发该事件。 如果属性从不更改,则不需要引发该事件。 INotifyPropertyChanged 接口还可以由 BindableObject 实现,只要可绑定属性更改值,就会引发 PropertyChanged 事件。

在以下示例中,数据绑定允许使用三个表示色调、饱和度和亮度的 Slider 元素来选择颜色:

public class HslColorViewModel : INotifyPropertyChanged
{
    Color color;
    string name;
    float hue;
    float saturation;
    float luminosity;

    public event PropertyChangedEventHandler PropertyChanged;

    public float Hue
    {
        get
        {
            return hue;
        }
        set
        {
            if (hue != value)
            {
                Color = Color.FromHsla(value, saturation, luminosity);
            }
        }
    }

    public float Saturation
    {
        get
        {
            return saturation;
        }
        set
        {
            if (saturation != value)
            {
                Color = Color.FromHsla(hue, value, luminosity);
            }
        }        
    }

    public float Luminosity
    {
        get
        {
            return luminosity;
        }
        set
        {
            if (luminosity != value)
            {
                Color = Color.FromHsla(hue, saturation, value);
            }
        }
    }

    public Color Color
    {
        get
        {
            return color;
        }
        set
        {
            if (color != value)
            {
                color = value;
                hue = color.GetHue();
                saturation = color.GetSaturation();
                luminosity = color.GetLuminosity();
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Hue"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Saturation"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Luminosity"));
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Color"));

                Name = NamedColor.GetNearestColorName(color);
            }
        }
    }

    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
            }
        }
    }
}

在此示例中,HslColorViewModel 类定义 HueSaturationLuminosityColorName 属性。 当三个颜色分量中的任何一个更改值时,都将重新计算 Color 属性,并为所有四个属性引发 PropertyChanged 事件。 当 Color 属性更改时,NamedColor 类中的静态 GetNearestColorName 方法会获取最接近的命名颜色,并设置 Name 属性。

将 viewmodel 设置为绑定源时,绑定基础结构会将处理程序附加到 PropertyChanged 事件。 这样一来,绑定就可收到属性更改的通知,然后可以根据更改的值设置目标属性。 但是,当目标属性(或目标属性的 Binding 定义)的 BindingModeOneTime 时,绑定基础结构不必在 PropertyChanged 事件上附加处理程序。 目标属性仅在 BindingContext 更改时更新,而不是在源属性本身更改时更新。

以下 XAML 使用 HslColorViewModel

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.SimpleColorSelectorPage">
    <ContentPage.BindingContext>
        <local:HslColorViewModel Color="MediumTurquoise" />
    </ContentPage.BindingContext>

    <ContentPage.Resources>
        <Style TargetType="Slider">
            <Setter Property="VerticalOptions" Value="CenterAndExpand" />
        </Style>
    </ContentPage.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <BoxView Color="{Binding Color}"
                 Grid.Row="0" />
        <StackLayout Grid.Row="1"
                     Margin="10, 0">
            <Label Text="{Binding Name}"
                   HorizontalTextAlignment="Center" />
            <Slider Value="{Binding Hue}" />
            <Slider Value="{Binding Saturation}" />
            <Slider Value="{Binding Luminosity}" />
        </StackLayout>
    </Grid>
</ContentPage>

在此示例中,HslColorViewModel 被实例化,并设置了 Color 属性,并将其设置为页面的 BindingContextBoxViewLabel 和三个 Slider 视图从 ContentPage 继承绑定上下文。 这些视图都是引用 viewmodel 中的源属性的绑定目标。 对于 BoxViewColor 属性和 LabelText 属性,数据绑定为 OneWay - 视图中的属性是根据 viewmodel 中的属性设置的。 但是,SliderValue 属性使用 TwoWay 绑定模式。 这样就可以在 viewmodel 中设置每个 Slider,也可以在每个 Slider 中设置 viewmodel。

首次运行该示例时,可基于实例化 viewmodel 时设置的初始 Color 属性在 viewmodel 中设置 BoxViewLabel 和三个 Slider 元素:

Simple color selector.

操作滑块时,BoxViewLabel 会相应更新。

重写绑定模式

BindingMode 属性(或 Binding 标记扩展的 Mode 属性)设置为 BindingMode 枚举的其中一个成员,即可替代目标属性的绑定模式。

但是,设置 Mode 属性并不总是能生成预期结果。 例如,在以下示例中,将 Mode 属性设置为 TwoWay 无法获得预期结果:

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

在此示例中,期望获得的结果是,Slider 将被初始化为 Scale 属性的初始值(即 1),但这并没有发生。 初始化 TwoWay 绑定时,首先从源设置目标,这意味着会将 Scale 属性设置为 Slider 默认值 0。 对 Slider 设置 TwoWay 绑定时,最初会从源设置 Slider

或者,可以将绑定模式设置为 OneWayToSource

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

现在,Slider 被初始化为 1(Scale 的默认值),但操作 Slider 并不会影响 Scale 属性。

注意

VisualElement 类还定义了 ScaleXScaleY 属性,这些属性可以在水平和垂直方向上以不同方式缩放 VisualElement

使用 TwoWay 绑定模式替代默认绑定模式的一个非常有用的应用场景需要用到 ListViewSelectedItem 属性。 默认绑定模式为 OneWayToSource。 如果对 SelectedItem 属性设置数据绑定以引用 viewmodel 中的源属性,则会从 ListView 选择中设置该源属性。 但是,在某些情况下,可能还需要在 viewmodel 中初始化 ListView

重要

默认绑定模式可能因控件而异,并在创建可绑定属性时设置。 其可从 BindableProperty 对象的 DefaultBindingMode 属性获取。