绑定值转换器
.NET Multi-platform App UI (.NET MAUI) 数据绑定通常将数据从源属性传输到目标属性,在某些情况下则从目标属性传输到源属性。 当源和目标属性都是同一类型,或当一个类型可以隐式转换为另一种类型时,这类传递都是非常简单的。 如果不是这种情况,则必须执行类型转换。
在字符串格式设置一文中,你已了解如何使用数据绑定的 StringFormat
属性将任何类型转换为字符串。 对于其他类型的转换,需要在类中编写一些专门的代码以实现 IValueConverter 接口。 实现 IValueConverter 的类被称为“值转换器”,但它们通常也被称为“绑定转换器”或“绑定值转换器”。
绑定值转换器
假设想要定义源属性为类型 int
但目标属性为 bool
的数据绑定。 想要此数据绑定在整数源等于 0 时生成 false
值,在其他情况则生成 true
。 可以使用实现 IValueConverter 接口的类来达成此目的:
public class IntToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value != 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}
然后,将此类的实例设置为 Binding
类的 Converter
属性或 Binding
标记扩展的 Converter
属性。 此类成为数据绑定的一部分。
当数据在 OneWay
或 TwoWay
绑定中由源移动到目标时,将调用 Convert
方法。 value
是来自数据绑定源的对象或值。 该方法必须返回数据绑定目标类型的值。 此处所示的方法将 value
参数强制转换为 int
,然后将其与 0 比较并得到 bool
返回值。
当数据在 TwoWay
或 OneWayToSource
绑定中由目标移动到源时,将调用 ConvertBack
方法。 ConvertBack
执行相反的转换:它假定 value
参数是来自目标的 bool
,然后将其转换为源的 int
返回值。
注意
如果数据绑定还包括 StringFormat
设置,则在将结果格式化为字符串之前调用值转换器。
以下示例演示如何在数据绑定中使用此值转换器:
<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.EnableButtonsPage"
Title="Enable Buttons">
<ContentPage.Resources>
<local:IntToBoolConverter x:Key="intToBool" />
</ContentPage.Resources>
<StackLayout Padding="10, 0">
<Entry x:Name="entry1"
Text=""
Placeholder="enter search term"
VerticalOptions="Center" />
<Button x:DataType="Entry"
Text="Search"
HorizontalOptions="Center"
VerticalOptions="Center"
IsEnabled="{Binding Source={x:Reference entry1},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
<Entry x:Name="entry2"
Text=""
Placeholder="enter destination"
VerticalOptions="Center" />
<Button x:DataType="Entry"
Text="Submit"
HorizontalOptions="Center"
VerticalOptions="Center"
IsEnabled="{Binding Source={x:Reference entry2},
Path=Text.Length,
Converter={StaticResource intToBool}}" />
</StackLayout>
</ContentPage>
在此示例中,在页面的资源字典中实例化 IntToBoolConverter
。 然后,使用 StaticResource
标记扩展引用它,以在两个数据绑定中设置 Converter
属性。 在页面上的多个数据绑定间共享数据转换器是很常见的: 如果在应用程序的多个页面中使用了值转换器,则可以在应用程序级资源字典中将其实例化。
此示例演示 Button 根据用户键入 Entry 视图中的文本执行操作时的常见需求。 将每个 Entry 的 Text
属性初始化为空字符串,因为默认情况下 Text
属性为 null
,在这种情况下,数据绑定将不起作用。 如果用户没有在 Entry 中键入任何内容,则应禁用 Button。 每个 Button 都包含其 IsEnabled
属性的数据绑定。 数据绑定源是相应 Entry 的 Text
属性的 Length
属性。 如果 Length
属性不是 0,则值转换器返回 true
并启用 Button:
注意
如果知道值转换器将仅用于 OneWay
绑定,则 ConvertBack
方法只能返回 null
。
上面显示的 Convert
方法假定 value
参数的类型为 int
,并且返回值的类型必须为 bool
。 同样,ConvertBack
方法假设 value
参数的类型为 bool
,返回值为 int
。 如果不是这种情况,将发生运行时异常。
可以将值转换器编写得更加通用化且可以接受多种不同类型的数据。 Convert
和 ConvertBack
方法可以使用带有 value
参数的 as
或 is
运算符,或者可以在该参数上调用 GetType
以确定其类型,然后再执行适当的操作。 每个方法的返回值的预期类型由 targetType
参数给出。 有时,值转换器与不同目标类型的数据绑定一起使用。 在这种情况下,值转换器可以使用 targetType
参数为正确的类型执行转换。
如果正在执行对于不同区域性会有所不同的转换,请使用 culture
参数实现此目的。
绑定转换器属性
值转换器类可以具有属性和泛型参数。 以下值转换器针对目标将源中的 bool
转换为类型为 T
的对象:
public class BoolToObjectConverter<T> : IValueConverter
{
public T TrueObject { get; set; }
public T FalseObject { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? TrueObject : FalseObject;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((T)value).Equals(TrueObject);
}
}
以下示例演示如何使用此转换器来显示 Switch 视图的值。 虽然在资源字典中将值转换器实例化为资源很常见,但此页面演示了另一种替代方法。 此处的每个值转换器都在 Binding.Converter
属性元素标记之间实例化。 x:TypeArguments
指示泛型参数,并将 TrueObject
和 FalseObject
都设置为该类型的对象:
<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.SwitchIndicatorsPage"
Title="Switch Indicators">
<ContentPage.Resources>
<Style TargetType="Label">
<Setter Property="FontSize" Value="18" />
<Setter Property="VerticalOptions" Value="Center" />
</Style>
<Style TargetType="Switch">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
</ContentPage.Resources>
<StackLayout Padding="10, 0">
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label Text="Subscribe?" />
<Switch x:Name="switch1" />
<Label>
<Label.Text>
<Binding x:DataType="Switch"
Source="{x:Reference switch1}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="x:String"
TrueObject="Of course!"
FalseObject="No way!" />
</Binding.Converter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label Text="Allow popups?" />
<Switch x:Name="switch2" />
<Label>
<Label.Text>
<Binding x:DataType="Switch"
Source="{x:Reference switch2}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="x:String"
TrueObject="Yes"
FalseObject="No" />
</Binding.Converter>
</Binding>
</Label.Text>
<Label.TextColor>
<Binding x:DataType="Switch"
Source="{x:Reference switch2}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="Color"
TrueObject="Green"
FalseObject="Red" />
</Binding.Converter>
</Binding>
</Label.TextColor>
</Label>
</StackLayout>
<StackLayout Orientation="Horizontal"
VerticalOptions="Center">
<Label Text="Learn more?" />
<Switch x:Name="switch3" />
<Label FontSize="18"
VerticalOptions="Center">
<Label.Style>
<Binding x:DataType="Switch"
Source="{x:Reference switch3}"
Path="IsToggled">
<Binding.Converter>
<local:BoolToObjectConverter x:TypeArguments="Style">
<local:BoolToObjectConverter.TrueObject>
<Style TargetType="Label">
<Setter Property="Text" Value="Indubitably!" />
<Setter Property="FontAttributes" Value="Italic, Bold" />
<Setter Property="TextColor" Value="Green" />
</Style>
</local:BoolToObjectConverter.TrueObject>
<local:BoolToObjectConverter.FalseObject>
<Style TargetType="Label">
<Setter Property="Text" Value="Maybe later" />
<Setter Property="FontAttributes" Value="None" />
<Setter Property="TextColor" Value="Red" />
</Style>
</local:BoolToObjectConverter.FalseObject>
</local:BoolToObjectConverter>
</Binding.Converter>
</Binding>
</Label.Style>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
在此示例中,在三个 Switch 和 Label 对中的最后一个中,将泛型参数设置为 Style,并为 TrueObject
和 FalseObject
的值提供整个 Style 对象。 这些替代了资源字典中设置的 Label 的隐式样式,因此该样式中的属性被显式分配给 Label。 切换 Switch 会导致相应的 Label 对此更改作出反应:
注意
还可以使用触发器基于其他视图在用户界面中实现更改。 有关详细信息,请参阅触发器。
绑定转换器参数
Binding
类定义 ConverterParameter
属性,Binding
标记扩展也定义 ConverterParameter
属性。 如果设置此属性,则该值将作为 parameter
参传递到 Convert
和 ConvertBack
方法。 即使值转换器实例在多个数据绑定之间共享,ConverterParameter
也可能不同,执行的转换也会有所不同。
使用颜色选择程序可以演示 ConverterParameter
属性的用法。 以下示例显示 RgbColorViewModel
,它有三个用于构造 Color 值且类型为 float
的属性,分别名为 Red
、Green
和 Blue
:
public class RgbColorViewModel : INotifyPropertyChanged
{
Color color;
string name;
public event PropertyChangedEventHandler PropertyChanged;
public float Red
{
get { return color.Red; }
set
{
if (color.Red != value)
{
Color = new Color(value, color.Green, color.Blue);
}
}
}
public float Green
{
get { return color.Green; }
set
{
if (color.Green != value)
{
Color = new Color(color.Red, value, color.Blue);
}
}
}
public float Blue
{
get { return color.Blue; }
set
{
if (color.Blue != value)
{
Color = new Color(color.Red, color.Green, value);
}
}
}
public Color Color
{
get { return color; }
set
{
if (color != value)
{
color = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Red"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Green"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Blue"));
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"));
}
}
}
}
Red
、Green
和 Blue
属性值介于 0 和 1 之间。 但是,你可能更愿意将组件显示为两位数的十六进制值。 若要在 XAML 中将这些显示为十六进制值,它们必须乘以 255,转换为整数,然后使用 StringFormat
属性中的“X2”规范格式化。 乘以 255 并转换为整数可以由值转换器执行。 若要尽可能提高值转换器的通用性,可以使用 ConverterParameter
属性指定乘法因数,这意味着它将作为 parameter
参数进入 Convert
和 ConvertBack
方法:
public class FloatToIntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)Math.Round((float)value * GetParameter(parameter));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value / GetParameter(parameter);
}
double GetParameter(object parameter)
{
if (parameter is float)
return (float)parameter;
else if (parameter is int)
return (int)parameter;
else if (parameter is string)
return float.Parse((string)parameter);
return 1;
}
}
在此示例中,Convert
方法从 float
转换为 int
,同时乘以 parameter
值。 ConvertBack
方法将整数 value
参数除以 parameter
,并返回 float
结果。
parameter
参数的类型可能会有所不同,具体取决于是在 XAML 中还是在代码中定义数据绑定。 如果 Binding
的 ConverterParameter
属性是在代码中设置的,则很可能设置为数字值:
binding.ConverterParameter = 255;
ConverterParameter
属性的类型为 Object
,因此 C# 编译器将文字 255 解释为整数,并将该属性设置为该值。
但是,在 XAML 中,ConverterParameter
可能设置为如下所示:
<Label Text="{Binding Red,
Converter={StaticResource doubleToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />
虽然 255 看起来像数字,但由于 ConverterParameter
的类型为 Object
,因此 XAML 分析器将 255 视为字符串。 为此,值转换器包括一个单独的 GetParameter
方法,用于处理类型为 float
、int
或 string
的 parameter
的情况。
以下 XAML 示例在其资源字典中实例化 FloatToIntConverter
:
<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.RgbColorSelectorPage"
Title="RGB Color Selector"
x:DataType="local:RgbColorViewModel">
<ContentPage.BindingContext>
<local:RgbColorViewModel Color="Gray" />
</ContentPage.BindingContext>
<ContentPage.Resources>
<Style TargetType="Slider">
<Setter Property="VerticalOptions" Value="Center" />
</Style>
<Style TargetType="Label">
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<local:FloatToIntConverter x:Key="floatToInt" />
</ContentPage.Resources>
<StackLayout Margin="20">
<BoxView Color="{Binding Color}"
HeightRequest="100"
WidthRequest="100"
HorizontalOptions="Center" />
<StackLayout Margin="10, 0">
<Label Text="{Binding Name}" />
<Slider Value="{Binding Red}" />
<Label Text="{Binding Red,
Converter={StaticResource floatToInt},
ConverterParameter=255,
StringFormat='Red = {0:X2}'}" />
<Slider Value="{Binding Green}" />
<Label Text="{Binding Green,
Converter={StaticResource floatToInt},
ConverterParameter=255,
StringFormat='Green = {0:X2}'}" />
<Slider Value="{Binding Blue}" />
<Label>
<Label.Text>
<Binding Path="Blue"
StringFormat="Blue = {0:X2}"
Converter="{StaticResource floatToInt}">
<Binding.ConverterParameter>
<x:Single>255</x:Single>
</Binding.ConverterParameter>
</Binding>
</Label.Text>
</Label>
</StackLayout>
</StackLayout>
</ContentPage>
Red
和 Green
属性的值是使用 Binding
标记扩展显示的。 但是,Blue
属性实例化 Binding
类,以演示如何将显式 float
值设置为 ConverterParameter
属性: