绑定模式
每个.NET Multi-platform App UI (.NET MAUI) 可绑定属性都有一个默认绑定模式,该模式是在创建可绑定属性时设置的,并且可从 BindableProperty 对象的 DefaultBindingMode
属性获得。 此默认绑定模式指示该属性是数据绑定目标时有效的模式。 大多数属性(如 Rotation
、Scale
和 Opacity
)的默认绑定模式都是 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 是目标。 绑定引用 Label 的 Opacity
属性,其默认值为 1。 因此,会从 Label 的初始 Opacity
值将 Slider 初始化为值 1。 如以下屏幕截图所示:
此外,Slider 将继续工作。 这是因为 Slider 的 Value
属性的默认绑定模式是 TwoWay
。 这意味着,Value
属性是数据绑定目标时,会从源设置目标,但也会从目标设置源。 这样就可以从初始 Opacity
值设置 Slider。
注意
可绑定属性不会发出属性更改信号,除非属性实际发生更改。 这样可以避免无限循环。
如果目标属性的默认绑定模式不适合特定的数据绑定,则可以通过将 Binding
的 Mode
属性(或 Binding
标记扩展的 Mode
属性)设置为 BindingMode
枚举的成员之一来替代:
Default
TwoWay
- 数据在源和目标之间双向传输OneWay
- 数据从源到目标单向传输OneWayToSource
- 数据从目标到源单向传输OneTime
- 数据从源到目标单向传输,但只有BindingContext
发生更改时才会传输
双向绑定
大多数可绑定属性的默认绑定模式都是 OneWay
,但某些属性的默认绑定模式为 TwoWay
,包括以下属性:
- DatePicker 的
Date
属性 - Editor、Entry、SearchBar 和 EntryCell 的
Text
属性 - ListView 的
IsRefreshing
属性 MultiPage
的SelectedItem
属性- Picker 的
SelectedIndex
和SelectedItem
属性 - Slider 和 Stepper 的
Value
属性 - Switch 的
IsToggled
属性 - SwitchCell 的
On
属性 - TimePicker 的
Time
属性
这些属性被定义为 TwoWay
,是因为数据绑定与模型-视图-视图模型 (MVVM) 模式一起使用时,viewmodel 类是数据绑定源,而视图(由 Slider 等视图组成)是数据绑定目标。 MVVM 绑定类似上述示例,因为你很可能希望页面上的每个视图都是使用 viewmodel 中的相应属性的值进行初始化,但视图中的更改也应该会影响 viewmodel 属性。
单向到源绑定
只读可绑定属性的默认绑定模式为 OneWayToSource
。 例如,ListView 的 SelectedItem
属性的默认绑定模式为 OneWayToSource
。 这是因为 SelectedItem
属性的绑定应导致设置绑定源。
单次绑定
只有在绑定上下文更改时,才会更新绑定模式为 OneTime
的目标属性。 对于这些目标属性的绑定,该模式简化了绑定基础结构,因为不必监视源属性中的更改。
许多属性(包括 Entry 的 IsTextPredictionEnabled
属性)都具有 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
类定义 Hue
、Saturation
、Luminosity
、Color
和 Name
属性。 当三个颜色分量中的任何一个更改值时,都将重新计算 Color
属性,并为所有四个属性引发 PropertyChanged
事件。 当 Color
属性更改时,NamedColor
类中的静态 GetNearestColorName
方法会获取最接近的命名颜色,并设置 Name
属性。
将 viewmodel 设置为绑定源时,绑定基础结构会将处理程序附加到 PropertyChanged
事件。 这样一来,绑定就可收到属性更改的通知,然后可以根据更改的值设置目标属性。 但是,当目标属性(或目标属性的 Binding
定义)的 BindingMode
为 OneTime
时,绑定基础结构不必在 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
属性,并将其设置为页面的 BindingContext
。 BoxView、Label 和三个 Slider 视图从 ContentPage 继承绑定上下文。 这些视图都是引用 viewmodel 中的源属性的绑定目标。 对于 BoxView 的 Color
属性和 Label 的 Text
属性,数据绑定为 OneWay
- 视图中的属性是根据 viewmodel 中的属性设置的。 但是,Slider 的 Value
属性使用 TwoWay
绑定模式。 这样就可以在 viewmodel 中设置每个 Slider,也可以在每个 Slider 中设置 viewmodel。
首次运行该示例时,可基于实例化 viewmodel 时设置的初始 Color
属性在 viewmodel 中设置 BoxView、Label 和三个 Slider 元素:
重写绑定模式
将 Binding
的 Mode
属性(或 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 类还定义了 ScaleX
和 ScaleY
属性,这些属性可以在水平和垂直方向上以不同方式缩放 VisualElement。
使用 TwoWay
绑定模式替代默认绑定模式的一个非常有用的应用场景需要用到 ListView 的 SelectedItem
属性。 默认绑定模式为 OneWayToSource
。 如果对 SelectedItem
属性设置数据绑定以引用 viewmodel 中的源属性,则会从 ListView 选择中设置该源属性。 但是,在某些情况下,可能还需要在 viewmodel 中初始化 ListView。
重要
默认绑定模式可能因控件而异,并在创建可绑定属性时设置。 其可从 BindableProperty 对象的 DefaultBindingMode
属性获取。